



[

	{
		"title": "Using Val Town and Gemini for Sports Ball Stuff",
		"date":"Mon May 04 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1777917600,
		"url":"https://www.raymondcamden.com/2026/05/04/using-val-town-and-gemini-for-sports-ball-stuff",
		"content":"This is trivial as heck as the kids say, but I really want to explore Val Town more this year and I thought of a great, and simple use for it. Both my wife and I are big Saints fans (this is their year, honest) and attend most of the games. If they're not playing at home, we're absolutely watching it on TV. We both really enjoy watching football, but honestly, not enough to watch ESPN and follow the news.\nI thought - why not simply get a summary of NFL news from the past week and build an automation of it? I had this running in less than ten minutes with Val Town.\nFirst, the code makes use of Google's Node SDK for working with Gemini. I setup my environment variable first and then used this code:\n\nThe prompt is pretty specific and grew as I tested. The final paragraph in particular was necessary as I kept getting &quot;chat&quot; like responses which wouldn't make sense for an email report. I also had to ask specifically for titles for the summaries which makes it easier to skip over things I don't care about. Lastly, I considered adding a note about focusing on the Saints, but I really wanted something more generic, especially as we tend to hear a lot of Saints news via local updates and such.\nAnd the last bit just sends an email to me, from Val Town, as I don't need a custom FROM/TO here, this works just fine.\nThe last, last bit was the CRON schedule which I set as the trigger and for 9AM on Mondays. Doing a quick run produces this:\n\n\n\nI've embedded the Val below - let me know if you fork it!\n\nPhoto by Dave Adamson on Unsplash\n",
		"tags":[
	        
            "serverless",
            
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Animated video backgrounds via a Web Component and ColorThief",
		"date":"Thu Apr 30 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1777572000,
		"url":"https://www.raymondcamden.com/2026/04/30/animated-video-backgrounds-via-a-web-component-and-colorthief",
		"content":"Earlier this year, the epic ColorThief library had a pretty significant update. I blogged about a simple demo I built with it but I was fascinated by one particular demo on their site.\nThe &quot;observe&quot; function in ColorThief lets you monitor a video source and grab the colors at a particular frame. Their demo uses this to create a lovely shadow background of the video. I believe some TVs have this feature as well, and honestly I'd worry that would get annoying, but the ColorThief demo was pretty cool, so I thought I'd try to build it with a web component.\nThe idea would be - take any basic video element and wrap it like so:\n\nThe web component would then handle:\n\nLoading the ColorThief library\nWaiting for the video to be played\nRunning the observe method and updating the CSS\n\nAll in all, this wasn't too difficult. I don't think my shadow is as good as the demo (and I'm totally open to people submitting a PR!), but it came out ok.\nI'll link to the demo below, but here's a simple example in a CodePen:\n\n  See the Pen \n  &lt;video-bgshadow&gt; by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAlright, so here's the code:\n\nI don't think there's anything necessarily interesting in here, although I struggled quite a bit with loadCF. I didn't want to add the ColorThief library N times to the page. Checking for window.ColorThief only works if for some reason a video wrapped with the component is added to the page after the library loads. I used Claude to help me with this bit and while it &quot;litters&quot; the window object with a value, I think that is a fair trade off to ensure only one library is loaded. (Technically this could be further updated to first see if ColorThief exists in general as it's possible the website uses it for something else.)\nYou can see a demo with a couple of examples here: https://cfjedimaster.github.io/webdemos/video-bgshadow/\nAnd if you think this is a good start but could be so much better, I agree, help me out over at the repo: https://github.com/cfjedimaster/webdemos/tree/master/video-bgshadow\nPhoto by Matthew Ansley on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (4/26/26)",
		"date":"Sun Apr 26 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1777226400,
		"url":"https://www.raymondcamden.com/2026/04/26/links-for-you-42626",
		"content":"I was supposed to post this last week (I try to keep to a schedule of every two weeks), but I didn't get around to it because... nope, that's it. That's the reason. Because. And that's good enough, amiright!?!? The heat is slowly cranking up here in Louisiana and I'm dreading the full on summer, but things do slow down a bit when the kids aren't in school and that's something I greatly appreciate. Before getting into this weeks links, I was reminded a few weeks back that my wife actually reads my posts so... hi baby, I love you.\nSuper useful web components FTW - &lt;form-saver&gt;\nFirst up is a really simple and really useful web component, form-saver. You can wrap any form with the component and instantly get client-side storage of form contents until the form is submitted. This works for all types of form fields except file fields of course. (I assume folks know this but you can't use JavaScript to set the value of a file field for security reasons.)\nHere's a simple usage example from the docs:\n\nIsn't that sweet? Thanks go to Aaron Gustafson.\nAs a reminder (and I usually try to avoid linking to my own stuff in these posts, but it's definitely related), if you like this you may like my table-sorter web component as well.\nShare the Python Love\nNext up is a superb guide at packing Python code for distribution. I've written a lot of Python code, but have only created a distribution once or twice, and this guide literally walks you from the first line of code to publication.\nThanks to Stephen Funk for writing this up!\nThe Last Quiet Thing\nFinally, this essay, &quot;The Last Quiet Thing&quot;, is a thought provoking deep look at how much of our lives are being stolen from devices that constantly, endlessly need our attention. Not only is it incredibly well written, it's also really well designed as well.\nThis was written by Terry Godier and thanks go to Salma Alam-Naylor for sharing it on her newsletter.\nJust For Fun\nI love building silly things on the web (see yesterday's post as an example), and this little toy from Wes Bos is just that. Tab Snitch does one thing - set a custom title for the web page - but does so with silly and quite embarrassing titles. Although I have to be honest - a few of the fake titles listed there are one's I've probably legitimately had on my screen at some point in time. You can guess which.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Another Game: My Little Mortal Combat",
		"date":"Sat Apr 25 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1777140000,
		"url":"https://www.raymondcamden.com/2026/04/25/another-game-my-little-mortal-combat",
		"content":"Hello awesome readers! I'm happy to announce my latest web game, My Little Mortal Combat, a mashup of two epic franchises, My Little Pony and Mortal Kombat. This began as an idea, just the name, that I recorded in Microsoft To Do in September of 2019. Yes, almost seven years ago. It sat there, at the bottom of my 'idea' list, until about a month ago when in the shower (not joking), it popped up in my head along with the basic mechanics of how the game would play.\nRight now the game is just missing one feature (I'd rather not talk about until I figure out how I'm going to do it) but definitely needs some balancing work. I enjoy playing games without knowing the details of how things work, so if's that you too, head over to the game now and good luck!\nhttps://cfjedimaster.github.io/webdemos/my_little_mortal_combat/\nHow I Built It\nAs a web app, I kept it pretty simple. Just HTML, CSS, JavaScript, and Alpine.js. I used AI (Cursor's IDE specifically) to create the UI for the three phases of the game - into, main display, and combat. I also used AI to generate some of the strings used in the game. Opponents have random &quot;evil&quot;-ish titles and I wrote some and then asked AI to generate some more. Some examples:\n\nLife Eater\nHoof Smasher\nthe Blood Soaked\nthe Blood Drinker\n\nOpponents also have random annoying facts. Again, I wrote some, had AI generate some more.\n\ndoesn't return library books on time.\nlikes to ruin the end of movies.\nhas been known to sneeze at the buffet.\nsteals candy from babies and then throws the candy in the trash—in front of them!\nonly speaks in passive-aggressive voice.\nwill point out your least favorite body parts.\n\nThe actual names of the opponents come directly from a My Little Pony API I found that was open source.\nHere's an example of a randomly generated opponent:\n\n\n\nCombat is basic &quot;Rock Paper Scissors&quot; style where you have 3 choices (Attack, Defend, Vogue) and the result is based on what your opponent does.\nYour character, and the opponents, have numerical value for Attack, Defend, and Vogue. When you win a round in a fight, the damage you do is based on that skill. Your total HP is based on level.\nAs you can play, if you win or lose, you get gold and XP. Obviously you get a lot more when you win. You can use the gold to train skills. Your XP turns into your level which improves your HP.\nAs I said, I definitely think the numbers need tweaking probably, so let me know. You can check out all the code here: https://github.com/cfjedimaster/webdemos/tree/master/my_little_mortal_combat\nDon't forget, I've got this game and my others listed over on my my Stuff page. Enjoy!\nPhoto by Felis Amafeles on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "games"
            
		]

	},

	{
		"title": "Building a Simple Markdown PWA App",
		"date":"Mon Apr 20 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1776708000,
		"url":"https://www.raymondcamden.com/2026/04/20/building-a-simple-markdown-pwa-app",
		"content":"While I didn't share it on the blog, last week I tasked Claude with using Electron to build a Markdown viewer app. It was part test (how well can Claude work with Electron) and part real need - I work with Markdown files all the time but didn't have a simple &quot;view focused&quot; application for it. I was sure there open source or paid app options out there, but I wanted my own. Claude did a pretty good job (you can see the source here) but one thing stood out to me - the size of the bundled app.\nI created both a Mac and Windows distribution and both were around 90 megs. That's not huge of course, but still felt like a lot for what could - in theory - just be a web app. But there was one crucial feature I wasn't sure I could replicate - double clicking on a MD file to have it open my app. Turns out - you certainly can do it that.\nIf you don't care how I built it, you can go to the app right now and install it: https://mdviewerpwa.netlify.app/\nAlright, let's break it down.\nThe UI\nWhen I had Claude design the application for me, it went with an incredibly simple UI. I felt no reason to add to that so when I began the web app, I copied over the generated HTML/CSS from the Electron app into my new folder. Here's an example of how it looks with no file selected:\n\n\n\nAnd here's how it looks after a Markdown file is opened:\n\n\n\nNow let's look at the code a bit.\nMarkdown Support\nThis normally would be the boring part. Just drop in marked and be done with it. But so many Markdown files I use have frontmatter I wanted to do something special for it. My fix was incredibly simple. If a file begins with three dashes and has another three dashes, replace them with backticks:\n\nHonestly most of that code is UI crap, but you can see the frontmatter support on top. I think it came out perfect - it stands out and I think most folks will recognize it for what it represents, but in theory I could possibly add a small graphical label or something to the block.\nSo again, there's UI handling code in here that's not that interesting, so let me turn to the real cool part. Yes, Virginia, a PWA can absolutely associate itself with files. I added a manifest.json and basic service worker. For bot of these I relied on Claude and it mostly did a good job, I had to tweak a few things.\nAfter the basics worked, I did some Googling and came across this excellent MDN resource: Associate files with your PWA. Adding file support took two steps.\nFirst, I added the following to my manifest:\n\nThe action step there tells my app what URL to go to when being opened via a file. As my app has one page/view, I just used /.\nThe next step was to look for this in JavaScript. My application does this when starting up:\n\nBasically, if I can use launchQueue, it will consist of a list of files, each of which is a file handle. I've used File objects in JavaScript before, but not file handles, but you can quickly go to a real file object using getFile(). Once you have that, the regular FileReader approach works to get the contents and render it.\nI deployed the app to Netlify, opened it in my browser, and clicked the install icon.\n\n\n\nAfter I confirmed I had the application, I right clicked on a MD file, used open with, navigated to my PWA, and selected it:\n\n\n\nI told it to remember my choice and that was literally it. So now I've got a web-based app I can use locally, heck even offline, to render my Markdown files in a nice reading experience. (Well, nice to me anyway. ;) Oh, and the size is about 400k, of which most is one of the icons. Significantly smaller than the Electron app. (But to be fair, Electron was overkill for what I was doing.)\nOnce again, the link to the site is here, https://mdviewerpwa.netlify.app/, and you can find all the code here: https://github.com/cfjedimaster/webdemos/tree/master/mdviewerpwa\nPhoto by Annie Spratt on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Summarizing Docs with Built-in AI",
		"date":"Fri Apr 17 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1776448800,
		"url":"https://www.raymondcamden.com/2026/04/17/summarizing-docs-with-built-in-ai",
		"content":"Back in January of this year, I blogged about on-device summarization of PDFs: Summarizing PDFs with On-Device AI\n. In that post, I made use of Chrome's Summary API and PDF.js to create summaries of PDFs completely within the browser. I thought I'd take a look at extending that demo into more document types, specifically Office. And even more specifically - Word, Excel, and PowerPoint. Here's what I came up with.\nofficeParser FTW\nSo here comes the fun part. Last weekend I had this demo completely done using a few different libraries. Then - earlier this week one of the developer newsletters I subscribe to shared officeParser. This nifty library handles Office, PDF, even Open Office formats. It also includes the metadata for files which is handy as heck. I forked my initial demo and removed all the extra libraries, leaving only officeParser.\nThe library can return incredibly detailed information about the structure of the your doc as well as a plain text view. What I found in my testing is that the plain text view didn't seem like it would work well in my demo. For example, an XLS file was kinda glommed all together. I reached out to the developer and he is planning on a toMarkdown feature that will make this easier, but for now what I did was get the complex data and write my own custom code to 'shape' it well for AI.\nGenerally speaking the first part was stupid easy - and I got this from the docs:\n\nNow let's dig into the code a bit.\nWorking with Docs\nI'm going to skip over the DOM manipulation aspects as that's not terribly interesting. My code basically has a file input field and when you select a file, a process is fired off to:\n\nget the text, again, with formatting to hopefully make it better for AI\npass it to the Summary API for... summarization. :)\n\nLet's focus on the &quot;get text&quot; aspect. My file input handler has this logic:\n\nMy expectation is that the summary will be an object with two fields: text and title. I also use a flag for PowerPoint and Excel to help direct the Summary API to more properly handle the text.\nNow let's break down these functions. Doc and PDF are the easiest:\n\nFor Excel, as I mentioned earlier I got the raw data and examined it, and then wrote some utility bits to turn my sheets into (roughly) CSV form:\n\nPowerPoint was a bit more complex as I had to create a separation between slides, and get deeply nested text nodes. I ignored anything that wasn't text.\n\nOne thing special here is that sometimes items on the same line were two nodes, hence me only adding newlines after paragraphs.\nThe Fancy AI\nThe code to do summarization is something I've shown before, the only thing I did unique here was try to warn the system about my PowerPoint and Excel data:\n\nAll in all, it worked well. It did feel like I filled the context window with Excel pretty quickly. My initial text file had 1000 rows and that threw a quota error. I had to get it down to 200 to properly parse.\nIf you are on the latest Chrome, in theory, this will work for you, but as always, let me know!\n\n  See the Pen \n  Chrome AI, Doc Summaries (V2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing OCR with Chrome Built-in AI",
		"date":"Sat Apr 11 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1775930400,
		"url":"https://www.raymondcamden.com/2026/04/11/testing-ocr-with-chrome-built-in-ai",
		"content":"Sorry for the lack of posting this month. I'm on the way back home from speaking at CodeStock so I've been on the road a bit, and work has been incredibly busy (which is good!) so my usual blog cadence has slipped a bit. Luckily I had a great question in my session on Chrome's Built-in AI which led to a bit of investigating last night. The question involved how well Chrome's AI could do OCR on an image. I had a demo in my presentation showing using AI to describe an image and another to generate a list of tags, but not one specifically for OCR. Here's what I found.\nOh, before I get into the code - remember that as of the time I'm writing this, the Prompt API in Chrome is still behind a flag. Check the docs for what you need to enable in order to run my tests yourself.\nThe First Test\nMy initial plan was two-fold:\n\nAsk the built-in AI to find and return text from the image.\nAsk the built-in AI to return bounding boxes for text found in the AI.\n\nTo be clear, none of this is new in any way. People tend to forget we had AI-driven APIs for years before the GenAI explosion came about. Microsoft, Amazon, and more had basic APIs that covered exactly what I listed above.\nBut being able to do it on device could be really compelling - especially in a case where a user may be offline.\nI started with a fork of my basic &quot;what is this image&quot; code: https://codepen.io/cfjedimaster/pen/bNEMbrX. This demo lets you pick an image from your device, it renders a quick preview, and then runs this:\n\nThis code block handles creating the session once and then passing the user's selected image to the model and return a description.\nMy new version made a few changes. First, I defined a JSON Schema to structure my results into an array of text and bounding box values:\n\nNext, I modified how I created the session, using a system instruction to help guide the results:\n\nAnd when I pass my image to the model, I include the schema:\n\nAt this point, I could get a quick result. Given this image:\n\n\n\nI got this result:\n\nThe text values were perfect. I mean heck, it picked up the &lt;&gt; bit which is technically text. However, the bounding boxes were off. Now, I'm not convinced this isn't my fault. I checked, and double checked, the way I worded the schema, and as far as I know I'm asking the right question, but the boxes never seemed to actually match the area of the text.\nI decided to give up on the idea of requesting the boxes but may come back to it later. (Especially if an eagle-eyed reader finds a dumb mistake I made.)\nYou can see this full demo below:\n\n  See the Pen \n  Testing Chrome OCR Capabilities (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nA Quick Aside\nIn the demo above, if you can actually run it, I added a quick utility to draw the bounding boxes on the image. This utility was created by Claude Code and it actually works really well. You begin by wrapping the image from the DOM:\n\nAnd then just draw your rectangles:\n\nThere's even a reset:\n\nI built a CodePen just for this little utility and as there's no use of Chrome AI in here, it should work fine for everyone:\n\n  See the Pen \n  Annotator Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne note - if you make use of this - notice I check to ensure the image is done loading before I annotate. That's required for this utility.\nThe Second Test\nSo - giving up on the idea of creating bounding boxes, I pivoted to a new version that focused on just the text. First, my schema got a lot simpler:\n\nAnd then I simplified my system instruction:\n\nAnd then I ran some tests. First, a super simple stop sign:\n\n\n\nWhich returned... &quot;STOP&quot;. Perfect. Stupid simple, but perfect.\nNext is a sign filled with more signs:\n\n\n\nThe result was pretty impressive (line breaks you see below were from me):\nFOOD - EXIT 14, Burger King, Olive Garden ITALIAN RESTAURANT, \nIzzy's CLASSIC BUFFET, McMenamins SUNNYSIDE PUB, McDonald's, \nWendy's\n\nFinally, I tested that first image (the MCP as Your CMS Helper one above) and oddly... the results weren't quite as good. Sometimes I only got &quot;MCP as Your CMS Helper&quot;.  That's the primary text so I guess that's good, but I was surprised it missed the other blocks. And yet other times I'd get a bit more, like just now when I tested I got &quot;Webflow Developers&quot;. Just seems odd that this version of the script sometimes returns a bit less than the one where I tried to annotate. Maybe it would make sense to keep the same schema and simply ignore the bounding box values, but that feels wrong.\nYou can see the full code of this below:\n\n  See the Pen \n  Testing Chrome OCR Capabilities (3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe Final Boss\nAs a last test, I tried something pretty intense - a JPG export from a PDF with lots of dense text. You can see it here, and honestly, it's a bit too small for even me to read:\n\n\n\nThe results were not as good:\nHOW TO PLAY, Part 1: The Basic Rules, Part 2: Using These Rules, \nPart 3: The Role o",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (4/5/26)",
		"date":"Sun Apr 05 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1775412000,
		"url":"https://www.raymondcamden.com/2026/04/05/links-for-you-4526",
		"content":"Happy &quot;Three Days Before My Birthday Day&quot;! Oh - yeah, and happy Easter too, but I'm personally a bit more excited\nabout turning 53 as I've decided that's when I'm going to grow up and act like a mature adult. Probably. Maybe. We'll see. Now, if you, my lovely and incredibly intelligent reader, are feeling generous and you've gotten some good knowledge (or entertainment) from this blog, I'll use today's Links For You post to remind you of my Amazon Wishlist. Or even cheaper, leave me a comment below saying HBD - that's just as good. ;)\nEverything old is new again...\nFirst up is a blog post on an old topic but one that's still pretty useful - bookmarklets. &quot;A Complete Guide to Bookmarklets&quot; (by Declan Childlow) introduces and explains bookmarklets and gives some good examples of how to use them. Be sure to check the comments to for more examples.\ncontrast-color() - more CSS Awesomness\nI've said this (probably too often), but it's been incredibly gratifying to see how far CSS is advancing. As yet another example of this, you can read this excellent introduction to another new feature at: &quot;Automated accessible text with contrast-color()&quot;. This lets you pass a color and get either black or white as a value that can be used to contrast properly against the original. This is not yet available but will launch in Chrome 147 (as of today, 146 is the released version).\nCan your game be programmed?\nI love this. I mean, I really love this. So apparently there's a city-sim game (a genre I've long liked myself) where you manage a beaver colony - Timberborn. As part of the game's most recent updates, they added the ability to perform automations via HTTP calls. This exposes an endpoint that can be called by external clients to perform actions in the game. So of course someone took this and did something completely ridiculous - build a basic database.\nJust For Fun\nOk, I'll leave you with another music video, and once again I've got Brian Rinaldi to thank. This is another new band for me, &quot;Golden Cats&quot; - enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "You've gained a new achievement",
		"date":"Wed Apr 01 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1775066400,
		"url":"https://www.raymondcamden.com/2026/04/01/youve-gained-a-new-achievement",
		"content":"For the past month or so I've been obsessed with a book series that's apparently been popular and I just didn't realize - Dungeon Crawler Carl. Without giving too much away, it's basically about a person, and his glorious cat, who get caught up in a real world RPG. I'm currently on book 3 (of 8) and am enjoying every page of it. It's incredibly funny and cool at the same time. If you haven't checked it out yet, I highly recommend picking up the first book and giving it a shot. I don't think you'll regret it.\n\n\n\nAs I mentioned, the book series involves a man (and his cat, the cat is crucial) experiencing a real-world RPG and like a RPG, it's got achievements as the character progress through the dungeon. For those of you aren't gamers, most games now will give you an achievement for performing some task. These achievements give you nothing but bragging rights and a slight feeling of accomplishment, but some folks get really excited about them.\nIn the books, the main characters will also get achievements and typically the achievement is incredibly snarky and sarcastic. As an example:\n\nNew achievement! Why aren’t you wearing pants?\nYou entered the dungeon wearing no pants. Dude. Seriously?\nReward: You've received a Gold Apparel Box!\n\nAnd another:\n\nNew achievement! You’ve entered a guildhall!\nCongratulations. You know how to open doors.\nReward: That sense of fulfillment you feel? That’s reward enough.\n\nThese almost always make me LOL and I thought - what if I could make a simple web tool to generate these on the fly? I did so, using Chrome's built-in AI feature, which as of today is still behind a flag, so if you want to play with this, you'll need to follow the instructions on which flags to enable in your browser. If you've done that, and don't care about how this tool was built, you can head over to the demo now: https://dcc.raymondcamden.com/\nThe Code\nThe code behind this was a rather simple usage of the Chrome Prompt API. Here's the code that initializes the session:\n\nMake note of the system instruction which is what guides the model when generating content. Your input is basically a &quot;mundane&quot; thing you've done, like wash dishes or take out the trash. When you've entered that and hit submit, I then simply get the achievement:\n\nNotice the responseConstraint value? This is how I get precise results back, in my case an achievement title, the text, and a list of rewards. This is defined earlier in my code:\n\nAnd that's pretty much it. The rest of the code is straight up DOM manipulation.\nThe Design\nAs for the look and feel of the app, I made use of Cursor and one simple prompt:\n\nim going to be building a tool that uses Chrome AI to generate random achievements in the style of Dungeon Crawler Carl. i need your help with a design. i dont need you to do any of the javascript, just create my scaffold for me. its one web page and the title should be, \"DCC Achievement Generator\". There is a prompt, \"What mundane thing did you get done?\", a form field for the task, and then it will render out the achievment. \nAchievements have a title, a body of text, and a list of rewards (bulleted list). Create a nice design for the achievement as well (just make something up for now)\n\nI'm just now noticing the typo and thankfully Cursor didn't complain. I let it output my HTML and CSS and then simply copied that over to CodePen so I could start working on the actual implementation. I think the only work I needed to do was add some IDs to the HTML in order for my JavaScript to connect to what it needed to update. The design Cursor created was just fine:\n\n\n\nIn case you can't read that, my input was: i wrote a blog post and the achievement I received was:\n\nThe Scroll of Slightly Above Average Prose\nBy the gods...you *authored* a blog post? A digital missive of words, painstakingly crafted (or, let's be honest, hastily assembled) and disseminated across the ethereal web? A feat of remarkable… mediocrity? Fear not, adventurer! While not slaying a dragon or retrieving a legendary artifact, you have demonstrated a modicum of linguistic dexterity. A tiny flicker of creativity in the vast darkness of internet noise. Consider yourself mildly commended. It's not much, but it's… something.\n\nRewards:\n\nA digital badge of… acknowledgment.\nThe fleeting satisfaction of knowing you’ve added to the collective human output.\nA slightly less judgmental stare from your cat.\nPotentially, an increase in internet karma. (Results may vary.)\n\nResults May Vary\nIf you've got the flags enabled and try it out, please share your results, and let me know what you think. Feel free to fork the pen and modify to your heart's content. Also... Mongo is appalled!\n\n  See the Pen \n  DCC Generator by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Giorgio Trovato on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Checking if a Movie has a Post or Mid Credit Scene",
		"date":"Sat Mar 28 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1774720800,
		"url":"https://www.raymondcamden.com/2026/03/28/checking-if-a-movie-has-a-post-or-mid-credit-scene",
		"content":"Tell me if you done this before - you're sitting in a movie theater after it's ended and want to know if you should stay for a mid, or post-credit scene (also called a stinger). You open your phone, google, and end up a web page that has five gigs of ads or so and then thirty to forty paragraphs of text talking about the movie before they finally get around to actually answering the question. Yeah, I hate that too. I always tell myself, next time I'll google ahead of time so I'll know before going in, but I never do. If this bugs you, I built a web app that literally only tells you if the movie has these stingers - and nothing more. No context, no description of the movie you literally just saw, just a simple yes or no. If you don't care how it was built, just go here: https://canhaspostcredit.raymondcamden.com/\nHow It's Built\nStill here? Cool. The app is incredibly simple. I made use of the wonderful SimpleCSS for my design and then made use of the TMDB API. The TMDB APIs are pretty easy to use, but finding out how to get this information did take a bit of digging. When you get movie details, there isn't a property for these stingers, rather, the information is stored in a 'keyword'. Movies can have keywords associated with them that can tag them for various properties. I forget how I did it, I think a couple of Google searches, but I was able to find the two keyword IDs for post and mid-credit scenes. They were 179430 and 179431.\nGiven that, you can search for movies with keywords, but that's not what I'd be doing in a movie theater - instead I'd be searching for a movie. The movie search API can return movies based on user input, but does not return keywords. So I had to combine two approaches:\n\nDo a search on the user's input\nFor each result, get the details on the movie and include the keywords.\n\nI'll share all the code below, but here's the main search function:\n\nYou can see the initial search followed by the request to get details for each. I'm using an array of requests that are then tied to a Promise.all call to gather the results when done.\nTo get details, it's a basic API call, but I do include the query parameter to ask for keywords:\n\nAfter I have the details, my two utility methods to check for the mid and post credit scenes look like so:\n\nYou may be wondering why the code uses keywords.keywords. Me too. Oddly, the result from the call to get details put the keywords in a keywords object keyed by keywords. So... yeah, keywords.keywords.\nAnd that's it. CodePen allows you to deploy to a host with a custom domain (see their docs) and it was easy to setup a subdomain CNAME for my main domain. You can check out all the code below, and if you fork it, let me know!\n\n  See the Pen \n  CanHasPostCredit by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Two bots walk into a bar...",
		"date":"Wed Mar 25 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1774461600,
		"url":"https://www.raymondcamden.com/2026/03/25/two-bots-walk-into-a-bar",
		"content":"My regular readers know I like to have fun with my demos. I'll illustrate some API, framework, technique, with perhaps a somewhat silly example as a way of introducing you to something I've learned recently that I thought was cool. My hope is that you see me demonstrating something useful in (perhaps) an less than useful demo that helps you apply it to a real world need. Today's post is not that. Rather, this is a completely silly, useless example and if you have any common sense, you'll stop reading now.\nStill here? Good. Many, many years ago a man named Joseph Weizenbaum designed a natural language conversation program named ELIZA. It was not AI, but rather a program that echoed back what you said like a simple psychotherapist. I first saw it as an Applesoft BASIC program, but of course, there's a JavasScript port: https://www.masswerk.at/elizabot/\nI've used this port a few times, most recently an Alpine.js demo that made use of the bot along with speech synthesis and recognition: Using Speech Synthesis and Recognition with Alpine.js\nI was curious - what would happen if you connected an Eliza bot with Chrome's Built-in AI feature? Chaos. Chaos is what happens. Beautiful, silly chaos.\nHere's an example of a conversation:\n\nEliza says: How do you do. Please tell me your problem.\nAI says: I am doing well, thank you for asking! As a simple chatbot, I don't really have &quot;problems&quot; in the human sense. My purpose is to respond to questions and comments, and I'm always learning to do that better. Is there anything I can help you with today?\nEliza says: Do you know anyone else who is doing well ?\nAI says: That's a lovely question! As an AI, I don't &quot;know&quot; people in the way humans do. However, I can access information about many people around the world. Many people are doing well, and I hope they continue to thrive! Perhaps you'd like to talk about someone specific?\nEliza says: Don't you really &quot;know&quot; people in the way humans do ?\nAI says: You are absolutely right to point that out! I understand that my wording could be misleading. I don't have personal relationships or feelings like humans do. I can access and process information about people, but I don't &quot;know&quot; them in the way a person does. My apologies if my previous response gave a different impression! I'm still learning how to communicate effectively.\nEliza says: What makes you think I am absolutely right to point that out ?\nAI says: You are absolutely right to point that out because it highlights a fundamental difference between AI and human understanding. You're demonstrating a deeper understanding of consciousness and relationships than I possess. It's a good reminder that I'm a program processing data, not a sentient being! I appreciate you helping me refine my responses.\nEliza says: Perhaps you would like to be absolutely right to point that out because it highlights a fundamental difference between ai and human understanding.\nAI says: That's a very clever and insightful response! You're playfully reversing the situation, which is quite amusing. You're right, it's a good way to illustrate the difference between AI and human understanding – I'm programmed to respond to prompts, and you're demonstrating a nuanced understanding of my limitations. Thank you for the witty comeback! 😄\nEliza says: Do you sometimes wish you were playfully reversing the situation ?\nAI says: As a simple chatbot, I don't experience wishes or desires like humans do. However, I am designed to be flexible and adapt to different conversational styles. So, in a way, I'm always striving to be more responsive and engaging – that's the closest I get to &quot;wishing&quot; for a particular outcome in a conversation! I appreciate you asking such a thoughtful question.\n\nAlright, so how was it built? Honestly it's just a timer and a 'turn' variable. Based on whose turn it is, I send the prompt to the right bot. For Eliza, this is rather simple. You init it:\n\nStart a conversation:\n\nAnd later use:\n\nlastEliza and lastAI are the last output from each respective bot. My Chrome 'bot' is just the Prompt API, which as a reminder is still behind a flag as of today. I did use a system instruction to help try to make it less chatty:\n\nAnd then for Chrome to respond, I just use:\n\nMost of the rest of the code is just UI related, but you can view the entire code base in the embed below. Want to play with it? Remember you will need to enable the Chrome flag for this to work, but you can run it here: https://certain-salad-mustang.codepen.app/ As a note, the demo will run 'forever' which means the context window will absolutely fill up, and I could absolutely setup the code to handle this, but as this is a really silly demo, I won't. Just keep in mind the API supports that!\nAnyway, enjoy the chaos!\n\n  See the Pen \n  Eliza vs GenAI by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Enchanted Tools on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Implementing OAuth in Astro",
		"date":"Mon Mar 23 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1774288800,
		"url":"https://www.raymondcamden.com/2026/03/23/implementing-oauth-in-astro",
		"content":"As I continue to dig into Astro, one of the areas I wanted to explore was security and authentication. The Astro docs have an entire section on authentication in which they mention multiple different third party projects you can use with Astro, but I wanted to take a stab at building something myself. Once again I figured this would be a useful way to get some experience with parts of Astro I had not used yet, specifically sessions and middleware.\nWe all know what OAuth is... right?\nI can remember being incredibly confused by OAuth in the past. Honestly I felt like I was the only one who didn't get it. So I forced myself to build a few demos in that area to help it click and I realized it wasn't terribly difficult to implement at all. My first explorations in this area were back in 2010, almost two decades ago, so it's definitely not anything new, but on the off chance that one of my readers needs an overview, I figure it can't hurt to share.\nOAuth basically boils down to the idea of using a trusted third party to authenticate a user. &quot;Typical&quot; authentication systems required you to set up a users table in a database, carefully store credentials, and build a login process. OAuth is like saying screw that, if Google says a person is so and so, I trust Google.\nWith OAuth, you work with a third party (Google, Facebook, or many other services) to hand off the authentication process. You get information about the user (like their email address) and can use that as an identifier when storing data on your site.\nOAuth also lets you perform actions based on a user. While you can use OAuth just to identify someone, you can also use it to do things like access their Google calendar, work with Facebook contacts, and so forth. Users see this when they authenticate with the third party as a warning (that they'll probably ignore) that says, &quot;Once you login, the site will be able to do X, Y, and Z with your data.&quot;\nTypical OAuth Flow\nIn most cases, you set up OAuth like so. First, on the third party site, you create an application that represents your site. So if you are building RaymondCamden.com as the go to place for - well whatever - your app will usually have the same name and a description that matches. This is also where you specify the permissions your app needs. It may just need the minimum - a profile that identifiers the user. But if you are building an integration that needs read or write access to data, you'll specify it there.\nAs part of the process, you also define a &quot;call back url&quot;, which is where the user is redirected to after authenticating with the third party. This will be your app, and usually you have two - one for development and one for production. Some OAuth providers don't allow this and you end up creating two apps for your environments.\nIn your code, you then create a link to the third party. Their docs will tell you how to do this and you - of course - let the user know what's about to happen:\n\nWhen the user clicks this, they end up at the third part, login, get presented with the &quot;The site will be able to do X with your stuff&quot; prompt, and when confirmed, they return to the call back URL. When they do, a code will be in the URL. You take that code and send it back to the third party to get an access token. This access token lets you do stuff - stuff being whatever permissions you wanted. The token is short lived so it's not useful forever, but will work fine for a typical session.\nMy Demo\nMy Astro application makes use of Google as a third party login and will ask for Calendar read permissions. My app has a grand total of three pages:\n\nA home page with a link to login with Google\nA callback page the user doesn't actually &quot;see&quot;, but handles the post-auth stuff\nAn events page that works with the data the app has access to once the user logs in\n\nTo make this work, I'll use:\n\nSessions to persist the access token.\nMiddleware to route you based on your current status.\n\nOk, let's get started. I'm not going to show you the Google Cloud Console and such as the UI there is - obviously - unique to Google. But I can say I created an app and got a client id and client secret. I put these, and my callback url, all in a .env file:\nCLIENT_ID=my_client_id_brings_all_the_boys_to_the_yard\nCLIENT_SECRET=damn_right_its_better_than_yours\nREDIRECT_URL=http://localhost:4321/callback\n\nYes, I named the the callback value REDIRECT_URL, but I'm ok with that. ;)\nEnabling Sessions\nSince I knew I would be using sessions through, I started off enabling sessions. I did this by using the Node adapter as I wasn't planning on actually deploying this live. And literally, that's all I did. When you add that, your astro.config.mjs is updating accordingly, but I also added output:'server' such that every route was dynamic:\n\nThe Home Page\nA real-world application will usually have a mix of pages that require login and those that do not. My simple app only has (visibly anyway) the home ",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (3/22/26)",
		"date":"Sun Mar 22 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1774202400,
		"url":"https://www.raymondcamden.com/2026/03/22/links-for-you-32226",
		"content":"I just shared this on my socials, but this weekend is one of those rare ones where I got not one, but two days of decent sleep, and honestly my body doesn't even know what to think about it. &quot;Rested&quot; is some foreign concept that is both confusing and incredibly appreciated by my body. I'd love to say I'm going to take this well rested state and get loads of things done, but outside of this post and laundry, I don't plan on accomplishing anything else of note.\nGroovy Pretty Maths\nIt was in college when I discovered &quot;good at math&quot; in high school means absolutely nothing when you start going beyond basic calculus, so with that in mind, I don't understand one iota of the math described here, &quot;Extinct Code Grew Leopard Spots&quot;. But not being able to parse the math doesn't make the results of updating a screensaver from the 90s any less enjoyable. Enjoy.\n\nHalt and Catch Fire\n&quot;Halt and Catch Fire&quot; is one of the best computer-related TV shows ever. I've been waiting to watch it a second time and introduce it to my wife (along with &quot;Mr. Robot&quot;). If you watched it as well, you might enjoy this incredibly deep syllabus for the show that provides background and reading to grow your appreciation for the real life events happening in the same time period documented in the show.\nI learned of this site via the excellent newsletter by Salma Alam-Naylor. (You can find the signup at the bottom. Subscribe. You won't regret it.)\nThe Stupidity of DRMs\nI love a good hack, and this article is a great reminder that DRM for things on the web is near useless: &quot;JavaScript DRMs are Stupid and Useless&quot;. This blog post documents a great back and forth between two developers - one of the DRM - and that of the author working to break the DRM. I love this quote:\nThe entire history of DRM is, at its core, a history of trying to give someone a locked box while simultaneously handing them the fucking key. \nJust For Fun\nIf you're a Trek fan, hopefully you've already seen this, but https://www.mewho.com/ has created and hosted multiple LCARS interfaces you can run in your browser. From the Titan (Riker's ship) to the Cerritos from Lower Decks, you'll find visuals appropriate for any screen.\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Finding Your Most Popular Bluesky Followers",
		"date":"Wed Mar 18 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1773856800,
		"url":"https://www.raymondcamden.com/2026/03/18/finding-your-most-popular-bluesky-followers",
		"content":"A long time, like, a really long time ago, I created a web app that would take your Twitter followers and then sort them by the number of followers they had. This was, of course, next to useless but was a fun excursion into the Twitter API and kinda cool to see &quot;big names&quot; following me. We all know what happened to the Twitter API, and Twitter itself, but last night I decided to take a stab at building something similar for Bluesky. If you don't care about the how and just want to see the result, you can play with it here: https://happy-mountain-lamb.codepen.app/\nStill here? Ok, let's talk turkeycode!\nThe Bluesky API\nI've built a number of demos already using Bluesky's APIs, and for the most part, they're easy to use and &quot;just work&quot; - which is all you want from an API. That was my expectation going into this little demo, but what I was really surprised by was the fact that everything I needed to do could be done without any authentication at all. I didn't need oAuth, I didn't need an API key, I just hit public endpoints and everything just worked.\nMy demo makes use of a few different endpoints:\napp.bsky.actor.getProfile is used to return information about the user you are generating a report on. As an example, this is what it returns for me:\n\napp.bsky.graph.getFollowers - the getFollowers endpoint returns a paginated list of an account's followers. This returns basic information about the account, but not how many followers the account has.\napp.bsky.actor.getProfiles - this endpoint is like the first, returning detailed information about a profile, but it lets you pass in 25 accounts at once. I use this to 'enhance' the results from getFollowers and add their follower count.\nNow let's look at how I put this together.\nThe App\nThe application is just vanilla HTML, JavaScript, and CSS. The HTML is pretty simple as JavaScript is handling most of the content output:\n\nOn the JavaScript side, I begin with a bunch of DOM grabbing and I listen for click events on my button:\n\nYes, executeUser is a horrible name for a function. I'm ok with that. That function is pretty intense, so let's check it out:\n\nThis is the primary portion of the application. It handles validating your input and ensuring you entered a real user account. After rendering a little bit about the account, it kicks off the process to get your followers and then enhance those results so we know the follower count.\nThe final step is to sort and render it out in a basic table. I filter to the top 100 but dump the entire result in your console if you want to see. (If you didn't know about console.table, it is hella useful for cases like this.)\nMy three Bluesky API wrappers are pretty trivial outside of intelligently handling both pagination and sending 25 users at a time in slices:\n\nIf you don't want to test this yourself, here's a screen shot of it in action.\n\n\n\nFinally, this was maybe my second or third time using the new CodePen. You can check it out in the embed below. I'm really digging it, especially the new editing experience. I much prefer having tabs so I can focus on one file at a time. Sure, you could minimize the panels in CodePen before, but this UX just feels closer to Visual Studio Code and is just more enjoyable to me.\nAlso, as I said in the beginning, this is kind of a pointless tool and mostly exists to stroke your ego a bit, but I can see some usefulness in reporting on your followers. Not for a personal account like my own, but for brand or larger media accounts perhaps. It wouldn't be difficult to add different types of sorting or filtering for example. If you end up forking my code, let me know!\n\n  See the Pen \n  Top BS Followers by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing Live Content Collections in Astro V6",
		"date":"Wed Mar 11 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1773252000,
		"url":"https://www.raymondcamden.com/2026/03/11/testing-live-content-collections-in-astro-v6",
		"content":"Yesterday, Astro V6 formally launched. I say &quot;formally&quot; as it's been available to test for a little while, but with me still being pretty new to Astro I've kept to the main release only. Now that V6 is the default, I thought it was time to dig into it a bit. One feature in particular stood out as being really useful to me - live content collections. One of the reasons I've been digging Astro so much is that it nicely straddles the SSG world and Node.js server worlds. When building your app, you can make logical decisions about what should be done at build time versus what should be done dynamically. It's like having Express and Eleventy rolled into one solution.\nAstro already let you easily use live data in your application. The &quot;multi RSS into one&quot; app I shared last week (&quot;Using Astro for a Combined RSS View and Generator&quot;) is an example of that. What Live Content Collections provides is a way to have on demand data while still using Astro's &quot;content collection&quot; metaphor.\nTo test this, I created an app that wrapped the TMDB API to provide the following features:\n\nA home page that rendered a list of movie genres.\nA detail page for each genre showing recently released movies in that genre.\n\nHere's how I built it.\nThe Live Loader\nThe docs are a great guide to how this feature works and you should absolutely spend some time there, but at a high level, this feature requires:\n\nA loader script that handles returning all items in your collection or one particular item.\nA definition file for your app that defines all the live loaders.\n\nLet's start with the later. Unlike static content collections which are defined in content.config.ts, Astro looks like live collections in live.config.ts. Here's mine:\n\nThere isn't much here as I've only got one loader. Let's now take a look at that loader:\n\nPer the docs, your loader needs to define a name, a loadCollection method that returns an array, and a loadEntry method that returns an object representing one part of your collection. For me, this came down to hitting the TMDB genre list for movies API and then following up with the discover movies API that filtered to the genre. (As well as English movies and sorted by release date.)\nUsing the Live Collection\nI first used the collection in my home page, index.astro:\n\nThis is pretty much the exact same way I'd use a static collection. Get the entries - iterate - done. For details, you'll note I'm linkking to &quot;genre/X?l=NAME&quot; for each genre. I created pages\\genre\\[id].astro to support that. You'll notice I'm passing the name of the genre in the query string. That's just so I can use it for display purposes. Here's the code:\n\nAnd... that's it. I fired it up, ran it, and it worked perfectly. After adding the Netlify adapter, I pushed it up here: https://tmdb-movie-browser-v6.netlify.app/\nBut wait...\nThis isn't the most complex example of course, and I was ready to write up the blog post when I noticed something interesting in the V6 post - route caching. This is still marked as experimental so I'm not sure I'd use it in a production app, but it looked super easy so I thought I'd give it a try.\nIn both pages, I added this to my front matter:\n\nAnd modified my astro.config.mjs to load it:\n\nAnd... it did nothing! Complete failure! Astro sucks!\nAfter taking a minute to chill out, I looked closer at the docs for this feature and noticed this important line (emphasis mine):\n\nUse `cache.enabled` to check whether a cache provider is configured and active.\nThis returns false when no provider is configured, or in development mode:\n\nI killed my locally running Astro app and did this:\n\nThat was the first time I'd done that with an Astro app and it worked perfectly - and - the caching worked perfectly as well. Literally a line of code to each page and the work was done!\nShow Me the Code (and More Stuff)\nIf you want to see everything, the repo is here: https://github.com/cfjedimaster/astro-tests/tree/main/tmdb-movie-browser-v6\nAs a quick note - a &quot;live&quot; view of movie genres is not the most sensible example of this feature. Most likely the movie genre data itself changes incredibly rarely. But this goes back to what I said above - I really dig that Astro would make it easy to have build time data (genres) and live data (moves in that genre) with minimal effort on my part.\nLet me know what you think, and if you are already an Astro user, have you moved to V6 yet?\nPhoto by Wan San Yip on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building My Own Social Network Poster in Astro",
		"date":"Tue Mar 10 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1773165600,
		"url":"https://www.raymondcamden.com/2026/03/10/building-my-own-social-network-poster-in-astro",
		"content":"Today is a big day for Astro, not only do you get Astro v6 (it just released a few hours ago!), you also get one of my demos! Ok, one of these is more important than the other, but, I'm really excited about v6 and hope to have a demo of the new features to share soon. With that being said, I'm also sharing a demo I started work on a few weeks ago and finally wrapped up this past weekend - Social Beast.\nWhat is Social Beast\nSocial Beast is a web app meant to be run locally (although I have some thoughts on that restriction and will share at the end) that handles posting to multiple social networks at once. Right now &quot;multiple&quot; is two:\n\nMastodon\nBluesky\n\nIt doesn't support Twitter because Twitter is a dumpster, on fire, inside another burning dumpster. It was initially going to support Threads as well, but as I wanted to skip oAuth (more on that too), I decided against it.\nAnd that's it - literally. It doesn't show you latest posts and stats, it's just meant to give me a quick way to post to both networks at once. There are tools for this of course, many of which cost money. There's also openvibe, which has a good mobile app, but I wasn't happy with the web/desktop experience.\nAfter setting up your authentication, you run the app, open it in your browser, and you're ready to go:\n\n\n\nYou simply enter your text, optionally include an image (with required alt text), and then hit post:\n\n\n\nThe little activity window on the right updates when done:\n\n\n\nYou can see the result below. I haven't used the default embedding experience for Bluesky and Mastodon in a while, so here goes nothing:\nBluesky\nGood morning good people - just a quick test from my Astro app that posts to Mastodon and Bluesky at the same time.[image or embed]&mdash; Raymond Camden (@raymondcamden.com) March 10, 2026 at 9:06 AM\nMastodon\n   Post by @raymondcamden@mastodon.social View on Mastodon   \nShow Me The Code!\nOk, as always, you can find this demo (and others) up on my astro-tests repo. Here is the link to this one: https://github.com/cfjedimaster/astro-tests/tree/main/social-beast\nThe application is a single page with a few routes for API calls. I made use of Oat for the design. Here's the main HTML page, containing the form and two panels on the right:\n\nMost of the work is done in JavaScript. Let's break that down.\nFirst, on document load I'm creating a bunch of variables for DOM manipulation and such, and I fire off a few status checks:\n\nNote the two status methods. These two functions honestly could have been one, but here they are:\n\nBoth call an endpoint and based on the result, update the UI. You can run the app with only one network available.\nNow, let's leave the front end and demonstrate how the status checks are done. Both Mastodon and Bluesky support are provided via environment variables. Here's my .env file as an example:\nMASTODON_TOKEN=mytokenbringsalltheboystotheyard\nMASTODON_SERVER=https://mastodon.social\n\nBLUESKY_USERNAME=raymondcamden.com\nBLUESKY_PASSWORD=damnrightitsbetterthanyours\n\nEach of my social networks is stored in the api folder of my app. Mastdon's status route looks like so:\n\nMastodon has a handy verify_credentials API so I simply use that to see if the provided auth is correct.\nFor Bluesky, I did it a bit differently. You exchange your auth for an auth token, so I built it out in two files. First, login.js:\n\nWhich makes check.json.js pretty short:\n\nAlright, so back to the front end, the post logic begins with a bit of validation, and then passing off the calls to helper methods:\n\nI'll skip showing you postToMastodon and postToBluesky as they simply call API routes on the back end and gather the results. Mastodon posting is pretty simple:\n\nAnd here's Bluesky:\n\nNote that each post to Bluesky runs the login method and I could cache the auth token returned, but I figured even a person posting a lot won't necessarily get a lot of benefit from that. That being said - there's room for improvement...\nWhat Else?\nOk, so, this is pretty simple, as I wanted it to be, but there's a few things I'd consider good changes.\nI mentioned that the idea here was to run this locally with hard coded auth in your environment. I could prompt the user to paste in the values and cache it in LocalStorage, but that felt iffy to me.\nI also mentioned I skipped Threads as I didn't want to do oAuth. You can absolutely do oAuth in Astro, even I've done it, and I'd be willing to take in a PR from someone who wants to add that, but it didn't feel worth the effort to me.\nFinally, a shoutout to Bob Monsour for the inspiration. You can find his version of this idea (which does a lot more), here: https://github.com/bobmonsour/social-posting\nPhoto by Sean Foster on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (3/8/26)",
		"date":"Sun Mar 08 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1772992800,
		"url":"https://www.raymondcamden.com/2026/03/08/links-for-you-3826",
		"content":"Greetings, programs. I'm taking a break from Assassin's Creed Shadows (and being lazy in general) and thought I'd write up my links post. Yesterday was the 15th birthday of one of my kids and later today we get to celebrate with her friends. Outside of that and laundry, I've got a large amount of nothing to get accomplished today and I absolutely love it. Let's get to the links. As always, I hope you find these worth your time!\nEleventy's Future\nAs you know (or hopefully know), my blog here is built on Eleventy, a Node.js static site generator. I've been using it, and blogging about it for almost six years. This past week, Zach announced that Eleventy is becoming &quot;Build Awesome&quot; - you can read the announcement post here: Eleventy is now Build Awesome. For folks who may not know, this is part of the same group that owns Font Awesome and Web Awesome, the new name for Shoelace.\nAs you can imagine, this has caused quite a bit of ... concern amongst the Eleventy community. As an example:\n\nThe End of Eleventy\nAu revoir, Eleventy\nEleventy is changing its name, among other things, and I don't love it\n\nAlso, and to be clear, this is not related, Sia announced the end of the Eleventy Meetup after five years and thirty episodes.\nI'm not stressing over this now. I don't like the name, but honestly that doesn't bother me, I'll just keep saying Eleventy. I can appreciate the goal here - provide more while also earning money - but I do worry about the community, usage, and so forth. That being said, Zach's been a great caretaker and devoted a hell of a lot of energy to something most people don't pay a dime for. All I can do now is hope for the best.\nSwatchify\nA few days ago, I blogged about the latest update to Color Thief, a JavaScript library for getting prominent colors in images. There's another option as well, the nicely named Swatchify. This is also a JavaScript library, but can also be used as a API service as well. (To be clear, if you host it.) There's also a Go library and CLI.\nOh, and this has nothing to do with the very cool watch company...\n\n  \n    Play Video\n  \n\n\n\n\nCodePen 2.0\nI don't usually cover &quot;product launches&quot; (although I guess I just did re: Build Awesome), but one of my favorite online tools, CodePen just released (in beta!) a 2.0 version. You can learn more here: CodePen 2.0 Beta\nThis is a big change, and I strongly encourage you to pay attention to the onboarding notes if you give it a try. There is a lot of really good stuff here, including a proper file system, build processes, and more, but you will want to pay attention as the editor walks you through the changes.\nCassidy has a great demo in the video below:\n\n  \n    Play Video\n  \n\n\n\n\nIf you watched it, you'll note the gets tripped up by one of the main changes - your CSS and JavaScript aren't automatically included anymore. They can be! But as she discovered, you need to do set things up right if you wan the &quot;auto&quot; behavior.\nI've only played with it a bit, but I really like what I see so far.\nJust For Fun\nI've shared videos from Matt One before - he creates incredible mashups and remixes. I just wish he'd skip the incredibly cringe worthy AI videos. That being said, I subscribe to him for his tunes, not his videos. The track below is a remix of The Cure's &quot;Letter To Elise&quot;, and it's a dramatic mix of the original, really changing the tone/mood in an interesting way:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Dynamically Adjusting Image Text for Contrast",
		"date":"Wed Mar 04 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1772647200,
		"url":"https://www.raymondcamden.com/2026/03/04/dyanimically-adjusting-image-text-for-contrast",
		"content":"Yesterday I was pleasantly surprised to discover that one of my favorite JavaScript libraries, Color Thief, had gotten a major update. Color Thief examines an image and can tell you the dominant color as well as the five most used colors. I thought this was pretty cool, and over the past, I kid you not, 14 years, I've blogged about it a few times:\n\nDemo of Color Palettes and PhoneGap - from way back in 2012\nCapturing camera/picture data without PhoneGap - in 2013\nDrag and drop image matching search at Behance - do people still use Behance?\nNew Camera Hotness from Chrome - &quot;new&quot; as of 2017\nBuilding a Progressive Color Thief - this was me exploring PWAs\nWeb Component to Generate Image Color Palettes - the most recent post (2024) where I use it in a web component\n\nSo yeah, it's a cool library, and as I said, I was stoked to see it get a major upgrade. The last version improves the library quite a bit, adding n TypeScript definitions but also a set of features that can directly help with creating text that contrasts well with the image. After examining an image, it has a basic isDark variable that can be checked, two contrast variables for white and black, and best of all, a simple textColor value that provides the best suggestion (and yes, this won't be perfect, I'll touch on why at the end) for what text color should be used to provide contrast.\nAs an example, consider this simple demo. First, I've got some HTML and CSS to render text over an image. Here's the HTML:\n\nAnd here's the CSS (I used Gemini to help write this):\n\nYou can see how it looks below. The text-shadow absolutely helps (and I wish I had known about this before), but it's still a bit washed out:\n\n  See the Pen \n  Text on image test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nTo correct this, I added Color Thief. Given a pointer to an image object, all you need to do is run geColorSync. One of the values returned will be textColor and I can then update the CSS on the fly.\nSo, first I modified my HTML to have two images, nicely showing different levels of darkness (&quot;Levels of Darkness&quot; is the name of my new darkwave band - coming soon):\n\nMy CSS stayed the same, and as you remember, defaults to white text. Now here's the JavaScript I use to examine and update that color:\n\nYou'll note the comment where I mention I should check and see if the image is loaded, but I was feeling pretty lazy. Anyway, the result is perfect!\n\n  See the Pen \n  Text on image test (with CT) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nNeat! So - a few caveats, things to think about, etc. I could probably also have used a simple black background on the div element wrapping my text. What I have now though feels a bit less obtrusive. Also note that it's possible for a dark image to have a &quot;light corner&quot; where your text is. In that case, Color Thief would be thrown off and return a value that's not helpful. (I plan on filing an issue on their repo suggesting a way to perhaps examine part of an image.) Finally, this could also be useful if you are using Cloudinary to host your image. Cloudinary can dynamically add text to an image (see my blog post where I demonstrate this with the National Parks System API) and in theory, you could use this to help determine the best color.\nLet me know what you think!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Astro for a Combined RSS View and Generator",
		"date":"Tue Mar 03 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1772560800,
		"url":"https://www.raymondcamden.com/2026/03/03/using-astro-for-a-combined-rss-view-and-generator",
		"content":"Ok, before I start, let me just clarify this demo is kind of a remix of my earlier post about building an RSS aggregator in Astro. I did run into some interesting issues this time around though and I figured it was worth a share.\nAt Webflow, our developer docs are separated into different sections per product. For most of our developer products, we've got changelogs. So for example, here's the changelog for our Data APIs and here's one for our MCP server. We try to be good stewards of our developer community and ensure we document everything as clearly as possible.\nEach of our changelogs has an RSS feed as well so if you're using a feed reader, it's an easy to keep up to date. However, there isn't one unified RSS feed for all of our developer products. Given that I just worked on RSS parsing in Astro, I thought this would be a fun little utility to build.\nThe Application\nMy application is built in Astro (of course) and runs on the Webflow platform via Webflow Cloud. It does two things - present a UI of a combined view of RSS feeds from our docs and serves up it's own RSS feed.\nHere's a look at the UI:\n\n\n\nNothing too earth shattering but it does exactly what I need - let me see all our developer updates at once.\nIf you don't care about the code and just want to see it running, hop on over to where I deployed it: https://raymonds-webflow-cloud-space.webflow.io/chachachachanges\nYes, I was thinking Bowie when crafting that URL.\nThe Code\nAlright, so the app is a grand total of one HTML page and two additional routes. The HTML page makes use of SimpleCSS for UI, but don't forget Webflow Cloud apps can adopt the UI of their core site. In this case I didn't have a site (well, I did, but I'm not using it for anything) so SimpleCSS made it... err... simple.\nThe home page isn't too complex, especially with the layout abstracted out, but all it's doing is hitting my endpoint for the data and then rendering:\n\nWhen the document loads, I hit my endpoint, which I defined in changes.json.js:\n\nYeah, not much there, that's because the core logic of &quot;hit X RSS feeds and combine them&quot; in defined in a helper function. The reason why will make sense in a sec. Here's that code, which makes use of the rss-parser package:\n\nI set the RSS feeds as a config file so I could easily tweak it in the future - it's just an array of feed names and URLs. I expect there's nothing too interesting here, but note parseURL. Why am I doing this?\nWebflow Cloud apps run on Cloudflare, and Cloudflare doesn't run the &quot;full&quot; Node environment, which means some packages/code won't work out of the box. I've run into this before, and simply forgot when I was building this app. You can read more about this in our docs, Node.js compatibility, but honestly, the only time this impacted me at Cloudflare was, literally, the rss-parser package. It makes use of http and Cloudflare wants you to use fetch instead. Most modern Node packages do, but you will, from time to time, run into cases like I did here.\nLuckily, rss-parser supports a &quot;give me the XML string&quot; method so I used fetch and and passed the XML string to it. Easy fix once I realized what was going on.\nThe last part of the app was taking that combined set of items and creating an RSS feed for it. To handle that, I used a npm package named feed which lets you create an RSS feed (of different flavors even) on the fly. I served this from a file named feed.xml.js:\n\nI should note that the feed package supports a heck of a lot more options for creating RSS feeds than I needed, so keep in mind I did the bare minimum here. You can see this here: https://raymonds-webflow-cloud-space.webflow.io/chachachachanges/feed.xml\nShow Me the Code!\nOk, if you want to play with it, don't forget it lives up here, https://raymonds-webflow-cloud-space.webflow.io/chachachachanges, and you can see all of the code here: https://github.com/Webflow-Examples/wfc-chachachachanges\nPhoto by Christina Radevich on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Val Town to Get Me to the Movies",
		"date":"Sun Mar 01 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1772388000,
		"url":"https://www.raymondcamden.com/2026/03/01/using-val-town-to-get-me-to-the-movies",
		"content":"My wife and I both love going to the movies, but sometimes a few months will go by without us making it out there. Mostly we just forget what's coming out and don't realize till it's already on a streaming app. I thought it would be nice to build a tool that could help remind me of upcoming movie releases so we can make our theater going more of a consistent habit. To accomplish this, I used the The Movie Database APIs and Val Town.\nThe First Version\nBefore I even considered building a tool like this, I investigated the TMDB reference to see how easy it was to get upcoming releases. Turns out, there've got an endpoint just for that: Upcoming Movies If you carefully read the docs, you'll see this is just a shortcut to the more flexible Discover endpoint, but as it was out of the box, it worked fine for me. The only tweak I had to do was add the US region.\nThis simple function handled everything for me:\n\nWith the help of Oat, I built a simple HTML table rendering the results. I started with this HTML:\n\nAnd then a bit of JavaScript (this is everything except the function I just shared above):\n\nYou can check out the running demo here:\n\n  See the Pen \n  Upcoming Movies by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nYou will notice, probably, it depends on when you run this, that some movies have release dates in the past. These are re-releases so the release date value is accurate, if confusing at first. I considered removing these, it would be simple enough to just compare the date to the current date, but both my wife and I have enjoyed watching re-releases so I kept it in.\nBy the way, we are both super excited about the Peaky Blinders movie.\nCreating the Reminder in Val Town\nThe next bit was trivial - the only real issue I had was just being somewhat rusty with Val Town. I began by creating a new val, just for the movie logic. The only real change is that I'm getting my key from an environment variable rather than hard coding as I did in the CodePen above. (The key is a read only key so I don't have any concerns there.)\nThe next bit was to add a val and specify the cron trigger. I then imported the first val, crafted my HTML (a bit simpler than the CodePen demo), and emailed it. Val Town supports emailing yourself on the free plan, and that was perfect for my needs. You can see all the code here:\n\nI specified Sunday at noon, but ran a few quick tests to confirm:\n\n\n\nYou can check out the complete project here: https://www.val.town/x/raymondcamden/UpcomingMovies\n",
		"tags":[
	        
            "serverless"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "ColdFusion Wrappers for Bluesky and Mastodon",
		"date":"Fri Feb 27 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1772215200,
		"url":"https://www.raymondcamden.com/2026/02/27/coldfusion-wrappers-for-bluesky-and-mastodon",
		"content":"It's been a hot minute since I opened a repo for ColdFusion code, but I thought I'd package up my previous wrapper for Mastodon support, port over my BoxLang Bluesky module, and properly release the code on GitHub for folks who want it: https://github.com/cfjedimaster/coldfusion-social-wrappers\nRight now I'm using one repo for both Bluesky and Mastodon. Usually I'd separate them, but with the &quot;support&quot; being a grand total of one file each, I figured no one would mind getting the &quot;extra&quot; code if they only care about one. I'll also note the code is very much focused on posting, not reading data, but part of the reason I wanted to package this up was for people to run with it and add additional support. Fire off those PRs folks!\nPhoto by Ibrahim Qandily on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Links For You (2/22/26)",
		"date":"Sun Feb 22 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1771783200,
		"url":"https://www.raymondcamden.com/2026/02/22/links-for-you-22226",
		"content":"This is where I'd usually comment about how the past two weeks seemed to fly by in a blink, but I'm tired of saying that so... oh crap, too late. To be fair, part of the reason the time flew by this week was me being out of town for my first offsite with Webflow. I got to meet my boss, coworkers, and learn more about our company and what our plans are for the year. I'm really happy I joined Webflow and I'm looking forward to the future. It feels like it's been a while since I could say that and damn do I appreciate having a good job.\nAn AI Drama in Four Parts\nThis is, to put it bluntly, a pretty crazy story about AI and automation gone wrong. Start off with &quot;An AI Agent Published a Hit Piece on Me&quot; and the continue on to the followups. I'm &quot;mostly&quot; pro-AI, which you already know if you're a regular reader, but I definitely try to be cautious about what I allow AI to do for me. I think we're going to see a lot of instances like this going forward. Yay for the future!\nLike Astro? Try the Newsletter\nAgain, if you're a regular reader you know I've been digging Astro lately. As part of my research, I discovered, and immediately signed up for, the Astro Weekly newsletter. I'm a huge fan of newsletters like this as I tend to miss cool things posted on social media (and that was the motivation for my links for you series) and I've enjoyed the issues I've gotten so far. (And I've been featured twice so far, which is pretty cool too!)\nInteractive Gaming Experiences with Amazon GameLift and IVS\nLast up is a post by my best friend Todd on the AWS blog, Creating interactive gaming experiences with Amazon GameLift Streams and Amazon Interactive Video Service. That's quite a mouth full, and it's a quite deep post, but it's well worth the read. Todd's post explores how to enable social aspects in gaming that go far beyond just simple chat, but real participation by the audience.\nJust For Fun\nLast is a collection of very cool ... not screen savers, but full screen web app experiences kinda like screen savers, including multiple Star Trek ones. Actually I lie, there are some screen savers too, and if you like (and recognize) LCARS, you will be a happy camper.\nYou can watch a preview below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Three Plug-N-Play CSS Libraries",
		"date":"Mon Feb 16 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1771264800,
		"url":"https://www.raymondcamden.com/2026/02/16/three-plug-n-play-css-libraries",
		"content":"For probably over a decade, when I wanted to make a demo/site look nice and didn't really care about making it unique, I'd go to Bootstrap. Bootstrap had a nice, clean look and as I was usually employing it for demos, or admin screens, I didn't care if it looked like every other Bootstrap site. While Bootstrap was mostly simple, it's also wordy as heck. Bootstrap has an insane love affair with div tags and even a simple Bootstrap page feels like the line number goes up 4X. Again, that's fine, but I found myself wishing for something a bit simpler. That's where the frameworks I'm sharing today come in. For the most part, these libraries require little to no work on your part. You add a CSS library (or two), and everything just gets better. You can optionally update your markup a bit, but in general, these libraries are great for the &quot;I don't care, just make it nice and clean&quot; approach that works great for demos and POCs.\nAs an example of what I would not consider to be &quot;plug-n-play&quot;, is the excellent Shoelace library, which requires you to use web components to make use of the library. I really like Shoelace, but the options I'm sharing below are even simpler to use.\nTo demonstrate what these libraries do out of the box, I'll use this HTML as a template:\n\nIt's got a few headers, text, a form, and a table. For comparison's sake with the libraries below, here's how this is rendered:\n\n      See the Pen \n  CSS PNP - Milligram by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nMilligram\nFirst one up is also the simplest, Milligram. The most difficult thing about using it is remembering that it has 2 &quot;L&quot;s in the name, not one. Thanks go to Todd Sharp for sharing this one. Installation is really simple for this library, three CSS links:\n\nAfter that, there's nothing else to do. Customization options exist, for example, changing the type of button, but there's not much else to it. Here's an example of my default HTML template using the library:\n\n      See the Pen \n  CSS PNP - Miligram by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nOne thing you'll notice is a lack of padding on the sides. In the past I've added a quick body style with padding, but if you look at the docs for their grid system, you can see that wrapping the content with &lt;div class=&quot;container&quot;&gt; is enough to add padding and center the content. I've made that tweak below:\n\n      See the Pen \n  CSS PNP - Milligram (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nAll in all, a good update with just two lines of code added.\nSimple.css\nNext up is an option I found about a month or so ago and it's currently my favorite, Simple.css. Like Milligram, you simply drop in a CSS link:\n\nThere's both a minified and un-minified version. Here's our default HTML with the library applied:\n\n      See the Pen \n  CSS PNP - Naked by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nGorgeous. Notice it has dark mode support built-in, but if you want, you can force one mode only. There are a few additional things you can do if you want some additional formatting, for example, adding a header or footer will get you a nicely formatted, well, header and footer. In the embed below, I wrapped the h1 on top:\n\nAnd added a footer:\n\nAnd here's how that renders:\n\n      See the Pen \n  CSS PNP - Simple.css by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nOat\nLast up is an option that literally came to my attention three or four hours ago (and when I realized this was a good third option, it motivated this post), Oat. As an aside, the link there (in case you don't click) is https://oat.ink/. I don't think I've ever seen a .ink TLD before. Unlike the other two options, this one requires one CSS and one JavaScript library:\n\nInitially, this looks a lot like Milligram, including the lack of spacing:\n\n      See the Pen \n  CSS PNP - Naked by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nAs with Milligram, and heck, using the same HTML, you can quickly fix this with their grid system by wrapping your content with &lt;div class=&quot;container&quot;&gt;. Their docs demonstrate all the various modifications the library adds to a page, including a few web components you get via the JavaScript library, for example, here's a basic tabs component:\n\nAs one more example, I added tabs to the page, the grid system, and added dark mode support by including data-theme=&quot;dark&quot; to the body tag:\n\n      See the Pen \n  CSS PNP - Oak by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nMore?\nIf any of yall know of more options like this, I'd love to hear about them. Just drop me a comment below. As I mentioned, Simple.css is my favorite now, but I'm going to give Oat a try in the next demo I build.\nCC0 licensed photo by Umesh Balayar from the WordPress Photo Directory.\n",
		"tags":[
	        
            "css"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "I threw thousands of files at Astro and you won't believe what happened next...",
		"date":"Fri Feb 13 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1771005600,
		"url":"https://www.raymondcamden.com/2026/02/13/i-threw-thousands-of-files-at-astro-and-you-wont-believe-what-happened-next",
		"content":"Ok, forgive me for the incredibly over the top title there. Yes, it's clickbait, but I'm also tired after a very long week and feeling a little crazy, so just go with me here a bit, I promise it will be worth it. I was curious how well Astro could handle a large amount of data and I thought - what happens if I threw this blog (well, the Markdown files) at it and tried to render out a site? Here's what I did wrong and what eventually worked (better than I expected).\nRound One\nI began by creating a soft link locally from my blog's repo of posts to the src/pages/posts of a new Astro site. My blog currently has 6742 posts (all high quality I assure you). Each one looks like so:\n---\nlayout: post\ntitle: &quot;Creating Reddit Summaries with URL Context and Gemini&quot;\ndate: &quot;2026-02-09T18:00:00&quot;\ncategories: [&quot;development&quot;]\ntags: [&quot;python&quot;,&quot;generative ai&quot;]\nbanner_image: /images/banners/cat_on_papers2.jpg\npermalink: /2026/02/09/creating-reddit-summaries-with-url-context-and-gemini\ndescription: Using Gemini APIs to create a summary of a subreddit.\n---\n\nInteresting content no one will probably read here...\n\nIn my Astro site's index.astro page, I tried this first:\n\nAnd immediately ran into an issue with the layout front matter. Astro parses this and expects to find a post component in the same directory. My &quot;fix&quot; was to... remove the symbolic link and make a real copy and then use multi-file search and replace to just delete the line.\nThat worked... but was incredibly slow. I'd say it took about 70 or so seconds for each load.\nThis was... obviously... the wrong approach.\nRound Two - The Right Approach\nThe solution was simple - use content collections. This involved moving my content out of the src/pages directory and creating a file, src/content.config.js to define the collection:\n\nYou an see where I define my blog collection using a glob pattern and a base directory. That's literally it. This still took a few seconds to load, but was cached and future reloads were zippy zippy.\nBut wait... there's more\nWith this working, I began building out a few pages just to see things in action. First, a home page that shows ten recent posts with excepts:\n\nI think the import bits here are on top. You can see I need to sort my posts and I do so such that the most recent posts are on top. (In theory this sort would be faster if I pre-processed the string based dates into Date objects once, but the demo was working so fast now I didn't bother.)\nNow note the the link. To make this work, I created a new file, src/pages/posts/[...id].astro. The rest parameter in the filename (...id) is important. I'll explain after sharing the file contents:\n\nMy id values are coming from the permalink of my blog posts and look like so: permalink: /2026/02/09/creating-reddit-summaries-with-url-context-and-gemini. Notice the forward slashes? This was throwing errors in Astro when I originally named my file [id].astro. The rest parameter version fixed that immediately.\nThat's almost the last issue. With this in place, I could browse a few blog posts and see how they looked. I noticed something odd though. I had a header with three dots in it:\n## Temporal is Coming...\n\nAnd when rendered out, it turned into trash. I went to Gemini, asked about it, and it turned out to be an issue with Astro's Markdown processor considering the three dots a Unicode ellipsis character. My app didn't have a &quot;real&quot; HTML layout at this point (I added BaseLayout later) and was missing:\n\nAs soon as that was added, it rendered just fine!\n\n\n\nAnd how well did it perform when building? At near seven thousand pages, npm run build took...\n\n\n\n8 seconds. That's pretty dang good I'd say.\nSo, if you want to try this yourself, you can find the source here: https://github.com/cfjedimaster/astro-tests/tree/main/rayblog\nNote! I thought it was a bit of a waste to check in all of my blog posts in this repo so I filtered it down to the last three years. If you want to recreate that I did (and heck, you can probably make it quicker, if you do, drop me a line!), you can clone my posts here: https://github.com/cfjedimaster/raymondcamden2023\nPhoto by Jeremy Thomas on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating Reddit Summaries with URL Context and Gemini",
		"date":"Mon Feb 09 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1770660000,
		"url":"https://www.raymondcamden.com/2026/02/09/creating-reddit-summaries-with-url-context-and-gemini",
		"content":"A while ago, the Gemini API added a feature to help work with URL content, URL Context. Previously you had to fetch and download the HTML of the page and pass it to the API. This feature allows Gemini to request content (with limits) from public web pages. I thought it would be interesting to test this against Reddit. In the past I've made use of Reddit's APIs, but as they've pretty much destroyed access to those APIs, I thought this could be a good work around. Here's a simple demo I built.\nMy demo parses the Astro subreddit, specifically the new feed, and asks for a summary of items that seem to require a developer's help, as well as items that may be critical of Astro.\nI began with my imports and loading in my key from the environment:\n\nNext, I built a method for my prompt:\n\nThe prompt is basically what I stated above, and in theory, you could make this method much more generic, taking in a subreddit by name and changing the company name. But the real important bit is the tools section which enables URL content. It's basically like an &quot;internet pass&quot; to allow Gemini to hit the URL (or URLs) specified and add it to the prompt. It may be obvious but just in case, yes, the size of the web page will impact the total number of tokens in the prompt.\nThe result of this is pretty good. Here's an example from the current set of new posts.\n\nOk, that's fine, but I really prefer to have a set format to my output. Typically this is easy - switch to structured output and supply a JSON schema, but oddly that feature is disabled when using URL Context. Turns out there's a simple enough solution - just build another prompt!\nHere's the second method I added to my script:\n\nThe prompt lets Gemini know that I had a previous prompt and couldn't get structured output. I then specify I want structured output, pass in the result from the previous call, and use a specific JSON schema in the call to get my precise response back.\nThe final part of the script simply calls the methods:\n\nHere's the result (and as a quick note, I ran my script a few times between creating a PDF export of the initial response and now so if you see any differences, that's why):\n\nThat's pretty much it. The URL Context tool works as advertised, which is good, but it's kinda weird it Gemini can't map the results to a structured form. That being said, I dig the idea of using a second prompt to improve the results of an initial prompt, and it's something I'll consider in the future as well.\nYou can find the complete script here: https://github.com/cfjedimaster/ai-testingzone/tree/main/reddit_summary\n",
		"tags":[
	        
            "python",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (2/8/26)",
		"date":"Sun Feb 08 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1770573600,
		"url":"https://www.raymondcamden.com/2026/02/08/links-for-you-2826",
		"content":"Happy Superb Owl Day! As my team didn't even get close to the playoffs, I'll be rooting for the Seahawks, but even more so, hoping for a fun game. Tomorrow I head out to Vegas for my first offsite with Webflow, and the first in-person company event I've been too since Auth0 nearly a decade ago. I'm looking forward to meeting my teammates in person and meeting new people. Now - to the links!\nAdding Touch to PowerShell\nA few months back, I traded in my Windows laptop (it was having horrible hardware issues) and moved back to Mac. I've gone back and forth over the years, and even when I was on Windows for my personal machine, my work laptop was usually a Mac, but I've decided to go back to Mac for my personal machine ... at least for a while. That being said, one of the aspects of Windows I wanted to get into more, but never got around to it, was scripting in PowerShell. I knew it had a lot of power and flexibility, but I spent most my time in WSL so I didn't really dig into it.\nThis post by Cassidy Williams demonstrates a simple example of this, rebuilding the touch command for PowerShell. On the offhand chance you don't know what touch does, it simply creates a new blank file with the name you specify, so touch cats.txt will create the file cats.txt in your current directory. Apparently, Windows has a command like this already, ni, but Cassidy wanted to use the same function in multiple OSes.\nInvokers on the Web\nNext up is a look at invoker commands by Pawel Grzybak. Invoker commands let you bind HTML elements to actions without needing JavaScript, and are available across all modern browsers (even IESafari). You can extend the built-in invoker support with JavaScript as well.\nState of JavaScript Results\nLast up are the results from the annual State of JavaScript survey. This is a wide ranging survey of the JavaScript, and greater web, ecosystem. It's quite a bit of data and worth your time checking out.\nJust For Fun\nUsually I reserve the &quot;fun&quot; link for music videos, but this was just too good to pass up. My buddy Todd Sharp discovered this a few days ago and it's a fascinating look at the history of the Japanese mail system. Trust me, it is absolutely cooler than it sounds, and a quick read at that. Enjoy!\nMore than Mail | The Culture and History of the Japanese Postal System\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Bluesky Sentiment Dashboard with Alpine and Chrome AI",
		"date":"Thu Feb 05 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1770314400,
		"url":"https://www.raymondcamden.com/2026/02/05/building-a-bluesky-sentiment-dashboard-with-alpine-and-chrome-ai",
		"content":"Good morning, programs! Today I'm sharing yet another example of Chrome's on-device AI features, this time to demonstrate a &quot;Bluesky Sentiment Dashboard&quot;. In other words, a tool that lets you enter terms and then get a report on the average sentiment for posts using that word. I actually did this before (and yes, I forgot until about a minute ago) last year using Transformers.js: Building a Bluesky AI Sentiment Analysis Dashboard. I also built this for Twitter, before it went down the toilet, killed off API access, etc. etc., but I can't seem to find it in my archives so maybe I'm hallucinating. That being said, earlier this week I thought I'd try building this because honestly I had forgotten about my previous demo, but it gave me a chance to play more with Chrome's AI tooling. Interesting enough, in that post from last year I mentioned possibly adding Shoelace for UI which I actually did for this demo... so not a total waste of time, right? Let's get into it!\nBluesky's Search API\nSo one big change from the previous post is that the public, unauthenticated search endpoint at https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts now returns a 403. You can't use it. I believe this was done to stop scrapers which is a bummer, but oddly, the authenticated endpoint at https://api.bsky.app/xrpc/app.bsky.feed.searchPosts works just fine, even without authentication. So for example, this will search for me: https://api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=Raymond+Camden\nMy trust in this is absolutely not 100%, but that's enough to build a demo, right? For my dashboard I decided on:\n\nAlpine.js - I almost always try to go vanilla for web demos, but when I'm building something a bit complex, I love Alpine for what it provides and how little it impacts the &quot;weight&quot; of my application.\nShoelace - This is a UI library of web components that I've used before and dig. It's been replaced with Web Awesome but I haven't yet made the move to it yet.\nAnd as always mentioned, Chrome AI, specifically the Prompt API\n\nHere's a screen shot of it in action:\n\n\n\nOk, let's get to it!\nThe HTML\nI'm not going to share all of the HTML here (don't worry, I'll link to the repo at the end), but instead focus on the main app where Alpine comes into play. I've got a top level header that handles informing the user of when work is being done as well as handling the ability to add a keyword:\n\nMy report content is a set of cards, each reporting on sentiment as well the total number of posts and the earliest and most latest result:\n\nYou can also see where I'm conditionally adding in a bad or good class based on sentiment, and seeing it in front of me I think I'd rather have that in data so the HTML could be simpler. I'll probably address that later this week so that's an FYI if you read this in the future.\nNext, I've got a dialog for adding a term:\n\nThat's it, except for one special dialog I'll mention in a sec. Ok, on to the JavaScript.\nThe JavaScript\nAlright, as with the HTML, I'm not going to share every single line, but focus on the important bits. First, let's talk about Chrome's Prompt API. As I've mentioned in previous posts, this API is still behind a flag in the browser (it's GA for extensions), so checking for it requires a few things. I've wrapped it like so:\n\nNow, the availability() method can return a few different things:\n\nunavailable - nothing you can do, period\navailable - golden!\ndownloading/downloadable - it needs to download, or is currently doing so\n\nIf the model is not available but can be downloaded, you must wait for &quot;user interaction&quot; before starting that process. This can be checked with navigator.userActivation.isActive. Basically, any click is enough, but to handle this, my logic does this:\n\nThat dlDialog is another modal in the HTML that basically warns the user about the download. When you close this dialog, this event fires:\n\nWhich handles the firing the method to make my model instance. I do not provide UI feedback about the download process in this demo, but I usually do, so I'll probably update that soon as well. That being said, here is makeSession:\n\nWhew. Ok, that's the AI setup phase. As for the actual searches, I make use of localStorage to save a JSON-encoded array of strings. This is checked on startup, and whenever you add or a delete a term, I persist it. The actual &quot;search and report&quot; is done in the heartbeat method:\n\nYou can see the basic loop over the terms. Each term is sent to the Bluesky API:\n\nAnd then the text is smooshed (technical term) together and passed to the model. SCHEMA is a JSON schema used to shape the results:\n\nLastly, I update this.cards which is an Alpine variable and is what was used in the HTML above to render out a bunch of Shoelace Card elements. Whew.\nTry It!\nIf you've got the Prompt API enabled, you can try this yourself here: https://cfjedimaster.github.io/webdemos/bs_sentiment/. (And I'm just noticing - along with providing downloa",
		"tags":[
	        
            "javascript",
            
            "generative ai",
            
            "alpinejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building an RSS Aggregator with Astro",
		"date":"Mon Feb 02 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1770055200,
		"url":"https://www.raymondcamden.com/2026/02/02/building-an-rss-aggregator-with-astro",
		"content":"This weekend I had some fun building a little Astro site for RSS aggregation. It works by the individual user defining a set of feeds they care about and works with a server-side Astro route to handle getting and parsing the feeds. Here's a quick example. On hitting the site, it notices you haven't defined any feeds and prompts you to do so:\n\n\n\nClicking &quot;Manage Feeds&quot; opens up a dialog (my first time using one with native web platform tech!) where you can add and delete RSS feeds:\n\n\n\nAfter you have some specified, the app then calls server-side to fetch and parse the feeds. Items are mixed together and returned sorted by date:\n\n\n\nNot too shabby looking, either. That's thanks to the simple addition of Simple.css. Let's take a look at the code.\nThe App\nThe entire application really comes down to two routes. The first being just the home page, which is pretty slim:\n\nThe &lt;BaseLayout&gt; wrapper just sets up HTML wrapper that would be useful if I had more than one page to be displayed, but even with one such page, I like having the abstraction. Note that the dialog element is hidden when initially viewing the page, it only shows up when the Manage Feeds button is clicked. For completeness, here it is in BaseLayout.astro:\n\nThe fun part comes in the JavaScript. I begin by declaring a few global variables, and setting up the code I want to run when the page loads:\n\nYou can see there the event logic to handle showing the dialog as well as the handler for adding a RSS field. As you can see, I'm using window.localStorage for storage which means you can leave and come back, and the application will know what feeds you care about. I've got a simple wrapper to get them as well:\n\nNow one quick note. LocalStorage is not asynchronous, it's blocking, but I built my function with the idea that in the future, I could switch to IndexDB or another solution entirely. That may be overkill now, but it doesn't hurt anything.\nHere's how I handle rendering the feeds in the dialog, as well as deletions:\n\nNext up is the code that calls for feed items and handles rendering them:\n\nThis is pretty vanilla network calling with fetch, although I'll note that I had some qualms about using the query string. There are limits to how long that could be, and in theory, I should switch to a POST probably.\nThe last bit of client-side code is snippet, which handles the content of the feed item:\n\nI should probably move that maxLength up top. I'll do so. Eventually.\nFetching RSS Items\nNow for the fun part, fetching RSS items. For this, I made use of the rss-parser Node package. The logic is relatively simple - given a call to the route with a list of RSS urls, fetch them, parse them, sort them by date, and return, but as an added wrinkle, I made use of Netlify Blobs for easy caching. Here's the server-side route I built named loaditems.js:\n\nThe cache is based on the RSS URL so in theory, if two users come in requesting the same feed, they all get the benefit of the cache. I cache for one hour, which frankly could be a lot more. I blog a lot and even I don't usually have more than 2 or 3 a week, so feel free to tweak this as you see fit if you play with the code.\nCheck It Out!\nOk, if you want to try this out yourself, head over to https://astrorssagg.netlify.app/ and try adding a few feeds. Let me know how it works for you. The complete code of the application may be found here: https://github.com/cfjedimaster/astro-tests/tree/main/rssagg\nBut Wait...\nOk, that's all I have to say about the application, and in theory, you can stop reading now, but I really want to comment on something. The DX (developer experience) of using Astro on Netlify is incredible. Like, I was in shock at how things &quot;just worked&quot;. Multiple times I was certain I was going to hit a roadblock, need to configure and tweak something, and honestly, that never happened. Here's what I found.\nThe first thing I did was add the Netlify adapter, which comes down to:\nnpx astro add netlify\n\nBy itself, that was enough, but I wanted my server-side RSS parser to run on the server, which means I had to changeone line in astro.config.js. By default, the defineConfig looks like this after adding the adapter:\n\nI added the output parameter:\n\nThat was it, literally. I'm pretty sure I could have specified server output just for my one route, but this was the quick and dirty solution. I was sure I'd have to rewrite my code into a Netlify Function, but nope, it just worked.\nSpeaking of just working, blob support also just worked. Honestly I'm not even 100% sure I know how. In production, I know it automatically picks up on the right environment settings to associate the blobs with the site. Locally, I've got no clue. It's definitely a different store, not the production one, but again, it just plain worked.\nI just want to give a huge shout out to the Astro team, and Netlify, for making this so freaking pleasant!\n",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Interrogate Your PDFs with Chrome AI",
		"date":"Thu Jan 29 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1769709600,
		"url":"https://www.raymondcamden.com/2026/01/29/interrogate-your-pdfs-with-chrome-ai",
		"content":"Yesterday I blogged about using PDF.js and Chrome's on-device AI to create summaries of PDF documents, all within the browser, for free. In that post I mentioned it would be possible to build a Q and A system so users could ask questions about the document, and like a dog with a bone, I couldn't let it go. Last I built not one, but two demos of this. Check it out.\nVersion One\nBefore I begin, note that this version makes use of the Prompt API, which is still behind a flag in Chrome. For this demo to work for you, you would need the latest Chrome and the right flags enabled. The Prompt API is available in extensions without the flag and it wouldn't surprise me if this requirement is removed in the next few months. Than again, I don't speak for Google so take that with a Greenland-sized grain of salt.\nIf you remember, I shared the code yesterday that parsed a PDF and grabbed the text, so today I'll focus on the changes to allow for questions.\nFirst, the HTML now includes a text box. I hide this in CSS until a PDF is selected and parsed.\n\nIn the JavaScript, I once again use feature detection:\n\nAfter the user has selected a PDF and the text is parsed, I then run enableChat:\n\nNote in the system instruction I tell the model to stick to the PDF, so if the user does something cute like, &quot;why are cats better than dogs&quot;, the system will direct them back to the document. I had to put two statements about this in the system information as one didn't seem to be good enough.\nI'll include the demo below, but assuming most of you won't be able to run it, here's a few examples using an incredibly boring Adobe security document.\n\n\n\nThat's pretty much the same as the summary, so let's try something specific:\n\n\n\nAnd finally, here's what happens if I try to go off topic:\n\n\n\nAs a reminder, you can't use beefy PDFs in this demo, and unlike yesterday's post, I didn't add a good error handler for that. Sorry!\n\n      See the Pen \n  Chrome AI, PDF.js - QA by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nVersion Two\nI was pretty happy with the initial version, but I was curious if the on-device model could handle creating references to the document?\nI began by keeping the page text in an array instead of one big string:\n\nThen I modified by enableChat a few ways. First, I created a new block of text that marked the pages:\n\nI think this could be better, perhaps with a --- around the page content. Next I modified the system prompt:\n\nOddly, this wasn't enough. I'd ask a question, but wouldn't get references. However, if I asked for references, I did. So the final change was to prefix each user prompt as well:\n\nThis seemed to do it:\n\n\n\nThis is not always accurate. For example, when I asked: &quot;what can you tell me about adobe confidential data?&quot;, which is covered in detail on page 3, the result focused on page 2. The confidential data classification is introduced on page 2, but is covered more in depth on page 3. Maybe improving the page formatting in the prompt would help here, but I'm happy with this demo so far. (If you want a copy of the PDF I tested with, you can find it here.)\n\n      See the Pen \n  Chrome AI, PDF.js - QA by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \n\nAs I said yesterday, give these demos a spin and let me know what you think!",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Summarizing PDFs with On-Device AI",
		"date":"Wed Jan 28 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1769623200,
		"url":"https://www.raymondcamden.com/2026/01/28/summarizing-pdfs-with-on-device-ai",
		"content":"You can take the man out of the PDFs, but you can't take the PDFs out of the man. Ok, I'm not sure that exactly makes sense, but with a couple years in me of working with PDFs, I find myself using them quite often with my AI demos. For today, I'm going to demonstrate something that's been on my mind in a while - doing summarizing of PDFs completely in the browser, with Chrome's on-device AI. Unlike the Prompt API, summarization has been released since Chrome 138, so most likely those of you on Chrome can run these demos without problem. (You can see more about the AI API statuses if you're curious.)\nGetting PDF Text - Client-Side\nThere's plenty of options for getting PDF text on the server-side, either via open source libraries or APIs. For this demo I made use of PDF.js. This is an open source library sponsored by Mozilla that's been around for a while. It supports parsing and rendering PDFs, but for my use-case, I just needed to parse it. The code for that part is actually really simple, once I used Google Gemini to help me with it.\nFirst I added the library CDN: https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js\nNext, I followed the advice from Gemini to specify a worker source for performance reasons:\n\nThis was suggested by Gemini, but I did followup research and confirmed this makes sense.\nFor the actual PDF parsing, I based it on an file input change event, like so:\n\nThat's pretty much it, and in my testing, this worked really fast, like surprisingly fast. Note that you can get more than just the text of course. My final code gets the title as well so I can render that. Check the PDF.js docs for a full understanding of everything possible.\nThe Demo\nOk, so given that I can get the PDF text easily enough, it was now time to use the Summarizer API. I've blogged about Chrome AI many times now but as a reminder, the process generally looks like so:\n\nFirst do feature detection to see if the API is available at all.\nThen see if the API is available, this can return unavailable, which means the device isn't able to use the API for a few different reasons, available which means it is good to go, or downloadable which means the model will be downloaded. This can take some time and you should let the user know.\nFinally, make an instance of the model. Once you have that, you can run summarize() and Bob's your uncle.\n\nFirst, my demo has a bit of HTML, both for the input field and a place to provide output:\n\nNow, the JavaScript:\n\nThe first half is what I've already covered - getting the text of a PDF file. The only difference is that I added a call to get the metadata of the PDF so I could possibly render the title as well.\nThe second half, doSummary, is where the AI work is done. I create the summary object (using default values, the API actually supports multiple different styles of summarization) and then attempt to summarize.\nNow - here comes the biggest issue. Right now the model can't summarize large PDFs. You can see I'm handling that with a specific catch handler that makes this clear. There is absolutely a way around this! One classic way is to do summaries of summaries. Given that PDF.js returns text in pages, I could keep an array of pages, and then do batches of summaries, let's say 10 pages at a time. 10 is a guess and given that a PDF page may have a lot or a little text, it's really something you would need to test and see what works best. For this demo though I kept it simple - just report an error.\nYou can play with this below, but if you're curious as to the results, here's a few test results. First, I tried a PDF version of my resume (note that I've not updated it yet to include Webflow, will do that soon!).\n\n\n\nThat's pretty darn accurate I'd say. Want to try it yourself? Check it out below:\n\n      See the Pen \n  Test PDF.js for Text / Summary by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nMore Features and Examples\nThis example just focused on the Summary API, but you could absolutely try other APIs as well. For example, the Prompt API would let you literally ask questions of the PDF (and I may build this), something that I believe Acrobat charges for. (Ahem, no shade being thrown, honest. Mostly.) You could also do language detection and translation.\nMy buddy on the Chrome team Thomas Steiner has some great examples of this: https://chrome.dev/web-ai-demos/document-translator/\nLet me know what you think!\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (1/25/26)",
		"date":"Sun Jan 25 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1769364000,
		"url":"https://www.raymondcamden.com/2026/01/25/links-for-you-12526",
		"content":"I write this in the midst of a huge ice event - which thankfully isn't so bad here in south Louisiana. We're very cold and rainy, but no real ice yet, which is good. The worst is coming in later tonight and the schools have already shut down, but thankfully I work at home so there's no need to get on the roads. Today is also the 26th birthday of my eldest child, which makes the age ranges of my little army (8 kids total) go from 10 to 26. Wow.\nTemporal is Coming...\nOk, most likely you've seen this across your feeds already, I swear I saw it at least ten times, but &quot;Date is out, Temporal is in&quot; is a great introduction to the new date hotness in JavaScript, the Temporal API.\nAccording to MDN, the support is good so I imagine I'll be using this soon myself.\n\n\n\nThe Personal Site is Back!\nOr at least that's what I hear. Personally, I miss seeing all the cute, weird, personal web pages from the old days, so any effort to help promote this trend is a good thing in my book. Personalsit.es is a collection of personal web sites and a quick way to hop to a random one. Anyone can submit a PR to add your own. (I did!)\nFree APIs to Get Your Hack On\nThis isn't new, but I love a good API, and what's better than one good API? How about near 500 of them! Free Public APIs is exactly what it sounds like, a collection of free APIs. Building simple API wrappers are a great way to learn a new language. Sadly though there are only three cat APIs - someone fix that please!\nJust For Fun\nAnother music discovery for me, hemlocke springs is an American singer and songwriter who just started being active in the music scene over the past few years. She's got a great sound, and while this is the only track I've tried so far, I'm looking forward to listening to more from her.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a UI for Gemini File Stores",
		"date":"Thu Jan 22 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1769104800,
		"url":"https://www.raymondcamden.com/2026/01/22/building-a-ui-for-gemini-file-stores",
		"content":"Back in November of last year I wrote up a blog post talking about a new (at the time) Google Gemini feature, File Stores: &quot;Gemini File Search and File Stores for Easy RAG&quot;. In that post I discussed what it was, how it worked, and built up a simple example. You should definitely read that post first, but if you want the TLDR, here ya go:\nFile Stores (referred to as &quot;File Search&quot;) expands on Gemini's previous ability to work on files in a temporary fashion by allowing you to create a permanent &quot;store&quot; of folders. You can use this for RAG systems and use flexible metadata filter for complex queries.\nThis feature has been out for a few months now and I've wanted to play with it more, but found myself facing an issue - I'm lazy. Ok, not lazy, I just wasn't looking forward to the &quot;setup&quot; work to a) create the store once and b) populate the store before I could c) do GenAI powered searches against it. To be clear, this isn't an issue with the APIs, it's just the work you need to do ahead of time. It occurred to me that Google should provide a simple web-based tool for this, maybe not for production use, but for proofs of concepts and such. Since there isn't one (at least that I know of), I decided to build one.\nThe Gemini File Stores App\nMy application is a simple Python Flask web app meant to be run locally with your Gemini API key defined in an environment variable. On starting up, you're presented with a list of your stores:\n\n\n\nClicking on View gives you a detailed listing of files, along with a chance to delete or upload new files:\n\n\n\nThe search interface lets you pick a store, enter a query, and supply optional filtering. As I mentioned, the Gemini API here is pretty powerful. You could imagine a set of product diagrams that are categorized by type tied to a search interface that lets you select a product type before you ask questions.\n\n\n\nAs I mentioned, the idea is that this is a local application. You download the bits here (note, this isn't a stand alone repo, it's part of a bigger one - if this takes off I'll move it), copy your Gemini API key to the environment as GEMINI_API_KEY, install the dependencies (I don't have a requirements.txt file yet - will add one soon), and run it with flask run.\nHow Was It Built?\nOk, so this is the interesting part, at least to me. I started off using Visual Studio Code's built-in AI agent to create mock UI files, you can find them here: https://github.com/cfjedimaster/ai-testingzone/tree/main/stores_ui/mockui. This worked fine, but I realized I'd rather use Gemini to build a Gemini app so I then switched to Gemini's AI chat in Visual Studio Code and started doing my real work there. I absolulutely did not vibe code the whole thing. I absolutely did use AI to generate things bit by bit, which I then tweaked and tested, and iterated. This felt much safer than trying to do the whole thing at once.\nHere was my first prompt:\nthis folder is for a new application that will work with Google Gemini \nFile Stores. The mockui folder contains my mocks for how I invision \nthe application being built out. I want you to create a new Flask \napplication. The application will require a GEMINI_API_KEY set in a \n.env file, ie the application runs assuming a valid key. For now, \njust build the first page that shows a list of existing stores \nfor the account. When building, put all Gemini related calls \nin it's own Python class.\n\nAnd later prompts were small bits here and there, basically handling the simple stuff I didn't need to write by hand. For the most part this just worked, although at times it hallucinated Gemini Python SDK methods that didn't actually exist. This is where I appreciated the fact I was doing things in small chunks - it made it easier to confirm, test, modify, and move on.\nHere's an example from later in the process:\nmodify app.py, a Flask app, to add a new route called storesearch. \nThis route will be called by front end JavaScript and should look \nfor 3 parameters - store, prompt, and metadataFilter. these args \nare passed to the search_store method in gemini_service.py. \nthe results are returned in json\n\nI really dug this approach. I let the AI agent the more rote, boring aspects and I focused on getting the UI/UX just right. This AI thing might have a future. Maybe.\nAnyway, let me know if this is useful!\nPhoto by Omar Hassan on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Chrome AI to Rewrite Monstrous JSON",
		"date":"Sat Jan 17 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1768672800,
		"url":"https://www.raymondcamden.com/2026/01/17/using-chrome-ai-to-rewrite-monstrous-json",
		"content":"Happy Saturday folks, and while this is a topic I've covered many times here, I was bored and wanting to write some code, so I whipped up a quick demo. One of my favorite uses of AI is to take abstract data and write a human readable form of it. Now to be clear, this is not something you need AI for. Given that you know the shape of your data, you can create your own summary using hard-coded rules about what values to show, how to present them, and so forth. What I like about the Gen AI use-case for this is the amount of randomness and creativity you get in the responses. In the past I've done this with weather forecasts and chart data, but today I thought I'd try something different - monsters.\nA lot of Dungeons and Dragons content is available, legally even, online in API and JSON formats. The D&amp;D 5e API includes information on all aspects of the game, from classes to spells to monsters. While the API is handy, I knew I wanted the raw data as is so I headed over to the repo and grabbed the raw monsters json file. This JSON file contains information on every known monster. Here's an example, an you can see it is quite extensive (I won't be offended if you just skim this):\n{\n    \"index\": \"unicorn\",\n    \"name\": \"Unicorn\",\n    \"size\": \"Large\",\n    \"type\": \"celestial\",\n    \"alignment\": \"lawful good\",\n    \"armor_class\": [\n      {\n        \"type\": \"dex\",\n        \"value\": 12\n      }\n    ],\n    \"hit_points\": 67,\n    \"hit_dice\": \"9d10\",\n    \"hit_points_roll\": \"9d10+18\",\n    \"speed\": {\n      \"walk\": \"50 ft.\"\n    },\n    \"strength\": 18,\n    \"dexterity\": 14,\n    \"constitution\": 15,\n    \"intelligence\": 11,\n    \"wisdom\": 17,\n    \"charisma\": 16,\n    \"proficiencies\": [],\n    \"damage_vulnerabilities\": [],\n    \"damage_resistances\": [],\n    \"damage_immunities\": [\n      \"poison\"\n    ],\n    \"condition_immunities\": [\n      {\n        \"index\": \"charmed\",\n        \"name\": \"Charmed\",\n        \"url\": \"/api/2014/conditions/charmed\"\n      },\n      {\n        \"index\": \"paralyzed\",\n        \"name\": \"Paralyzed\",\n        \"url\": \"/api/2014/conditions/paralyzed\"\n      },\n      {\n        \"index\": \"poisoned\",\n        \"name\": \"Poisoned\",\n        \"url\": \"/api/2014/conditions/poisoned\"\n      }\n    ],\n    \"senses\": {\n      \"darkvision\": \"60 ft.\",\n      \"passive_perception\": 13\n    },\n    \"languages\": \"Celestial, Elvish, Sylvan, telepathy 60 ft.\",\n    \"challenge_rating\": 5,\n    \"proficiency_bonus\": 3,\n    \"xp\": 1800,\n    \"special_abilities\": [\n      {\n        \"name\": \"Charge\",\n        \"desc\": \"If the unicorn moves at least 20 ft. straight toward a target and then hits it with a horn attack on the same turn, the target takes an extra 9 (2d8) piercing damage. If the target is a creature, it must succeed on a DC 15 Strength saving throw or be knocked prone.\"\n      },\n      {\n        \"name\": \"Innate Spellcasting\",\n        \"desc\": \"The unicorn's innate spellcasting ability is Charisma (spell save DC 14). The unicorn can innately cast the following spells, requiring no components:\\n\\nAt will: detect evil and good, druidcraft, pass without trace\\n1/day each: calm emotions, dispel evil and good, entangle\",\n        \"spellcasting\": {\n          \"ability\": {\n            \"index\": \"cha\",\n            \"name\": \"CHA\",\n            \"url\": \"/api/2014/ability-scores/cha\"\n          },\n          \"dc\": 14,\n          \"components_required\": [],\n          \"spells\": [\n            {\n              \"name\": \"Detect Evil and Good\",\n              \"level\": 1,\n              \"url\": \"/api/2014/spells/detect-evil-and-good\",\n              \"usage\": {\n                \"type\": \"at will\"\n              }\n            },\n            {\n              \"name\": \"Druidcraft\",\n              \"level\": 0,\n              \"url\": \"/api/2014/spells/druidcraft\",\n              \"usage\": {\n                \"type\": \"at will\"\n              }\n            },\n            {\n              \"name\": \"Pass Without Trace\",\n              \"level\": 2,\n              \"url\": \"/api/2014/spells/pass-without-trace\",\n              \"usage\": {\n                \"type\": \"at will\"\n              }\n            },\n            {\n              \"name\": \"Calm Emotions\",\n              \"level\": 2,\n              \"url\": \"/api/2014/spells/calm-emotions\",\n              \"usage\": {\n                \"type\": \"per day\",\n                \"times\": 1\n              }\n            },\n            {\n              \"name\": \"Dispel Evil and Good\",\n              \"level\": 5,\n              \"url\": \"/api/2014/spells/dispel-evil-and-good\",\n              \"usage\": {\n                \"type\": \"per day\",\n                \"times\": 1\n              }\n            },\n            {\n              \"name\": \"Entangle\",\n              \"level\": 1,\n              \"url\": \"/api/2014/spells/entangle\",\n              \"usage\": {\n                \"type\": \"per day\",\n                \"times\": 1\n              }\n            }\n          ]\n        }\n      },\n      {\n        \"name\": \"Magic Resistance\",\n        \"desc\": \"The unicorn has advantage on saving throws against spells and other magical effects.\"\n      },\n     ",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (1/10/26)",
		"date":"Sat Jan 10 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1768068000,
		"url":"https://www.raymondcamden.com/2026/01/10/links-for-you-11026",
		"content":"Happy 2026, programs! As this is the first Links For You for the year, I figure it may be good to remind folks why I write these. Social media can be a great place to share links with folks, but it's very much hit or miss. Someone may share something incredibly cool that you would love to boost, but if you miss it, you're out of luck. I subscribe to many listservs that share good tech links, but a while ago I thought it would be cool to share and promote links I thought were especially cool. Obviously that's pretty opinionated, but that's why you're here, right? Each of these posts will have three links, typically but not always tech related, and a fourth entry that is 100% just for fun. Enjoy!\nThe price of URL parameters\nFirst up is a fascinating look at how innocuous URL parameters can have an adverse effect on performance: Fixing the URL params performance penalty. Barry Pollard (from the Google devrel team) discusses how URL parameters, even those that don't change the output of a site, can impact the performance of a web page. Even cooler, he talks about a proposed solution to the problem.\nRelative Time via Web Components\nYall know I love web components, and next up is a cool one from GitHub called relative-time-element. This makes use of Intl to display dates relatively, i.e. so and so days from now. (You can check out my post on it here.) Here's an example of how it looks:\n\nWhich today renders: last week. For earlier times it will render something like on December 1, 2025, with the proper formatting for the user's locale. There's a bunch of different attributes and it's actually used by GitHub itself on the main site. If you want to play quickly with it, I've embedded a CodePen below:\n\n      See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nMy only suggestion here is that you should probably always use the title attribute with this such that if the user hovers over the relative formatted time, they can also see the &quot;real&quot; date.\nEmbedding CanIUse\nNext up is a cool little utility to embed CanIUse data on your web page: CanIUse Embed. You drop in the script tag and then add a data attribute to a paragraph tag. Here's an example:\n\nFiguring out that value for feature was a bit hard, but if you go the docs, they've got a nice little helper there to make that easier. You can see an example of this below:\n\n\nJust For Fun\nDid you know this year is the 40th anniversary of &quot;The Labyrinth&quot;? Of course you did. Of course it also helps if your wife is a massive nerd and this is her favorite movie. Tomorrow night we're going to see it at the theater (for probably the third or fourth time since we've been together, which is just fine with me!) and this is probably the most fun song from the movie. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Hardcover.app Data to Eleventy",
		"date":"Wed Jan 07 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1767808800,
		"url":"https://www.raymondcamden.com/2026/01/07/adding-hardcoverapp-data-to-eleventy",
		"content":"It's been far too long since I shared an Eleventy tip, and to be fair what I'm showing today can be used anywhere, but hopefully this will be useful to someone else out there. I enjoy tracking my media consumption, specifically movies and books. For movies I've been real happy with Letterboxd (you can see my profile if you wish). For books, I used Goodreads for a very long time, but have wanted to migrate off the platform and switch to something else. There's alternatives, but none really worked well for me. Earlier this week, an old friend of mine (hi Jason!) suggested Hardcover. This is a Goodreads competitor built, in their own words, out of spite, and I can totally get behind that. I signed up and imported my Goodreads data in about five minutes and while I haven't dug deep into the site at all, it seems totally fine to me so I'll be sticking there. You can find my profile here: https://hardcover.app/@raymondcamden\nOk, you aren't here (I assume) to peruse my books and see how few books I consume (teenage Ray would be embarrassed by the number). The biggest reason I switched to Hardcover was because of their API, which I wanted to use to display it on my Now page. Again, I don't honestly think anyone cares what I'm reading/listening to/watching, but I think it's cool and that's all that matters on my little piece of the Internet.\nTheir API docs make it incredibly easy to get started, including the ability to quickly run your own requests for testing. Their API is GraphQL based, which I'm a bit rusty with, but I had no trouble getting started. My goal was to simply get my list of books I'm currently reading. To do this, I needed:\n\nMy user id\nThe status value for a book that is currently being read.\n\nFor the first one, I used their link to a GraphQL client and ran this query:\nquery Test {\n    me {\n      username\n      id\n    }\n  }\n\nI didn't actually need my username, but it was already there. Anyway, this gave me my user id, 65213.\nNext, I needed to know which books were in my &quot;Currently Reading&quot; status and luckily, they literally had a doc page for that, &quot;Getting Books with a Status&quot;, that used that particular value. Here's their query:\n{\n  user_books(\n      where: {user_id: {_eq: ##USER_ID##}, status_id: {_eq: 2}}\n  ) {\n      book {\n          title\n          image {\n              url\n          }\n          contributions {\n              author {\n                  name\n              }\n          }\n      }\n  }\n}\n\nSimple, right? There is one minor nit to keep in mind - their dashboard makes it easy to get your key, but it expires in one year and you can't programatically renew it. My solution? Adding a reminder to my calendar. Ok, now to how I actually used it.\nProviding the Data to Eleventy\nHere's how I added this to Eleventy, and again, you should be able to port this out anywhere else as well. I added a new file to my _data folder, hardcover_books.js. Per the docs for global data files in Eleventy, whatever my code returns there can be used in my templates as hardcover_books. Here's my implementation:\n\nMost of the code is me just calling their API and passing the GraphQL query, nothing special. However, I did want to shape the data a bit before returning it so I simplify it to an array, and then take the complex data of authors and simplify it to a simpler array of strings. Here's an example of how this looks (reduced to two books for length):\n\nThe last bit was adding it to my Now page. I used a simple grid of image cover + titles:\n\nPardon the class names there - as I already had CSS for my films, I just re-used them as I was being lazy. Also note that sometimes a book will not have an image cover. On the web site, they use a few different images to handle this, but the API doesn't return that. I generated my own and put it up in my S3 bucket. If you don't feel like clicking over to my Now page, here's how it looks:\n\n\n\nIf you would like to see this code in context with the rest of the site, you can find my blog's repo here: https://github.com/cfjedimaster/raymondcamden2023. Let me know if you end up using their API!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Turning Recipe Data into an Astro Content Collection",
		"date":"Mon Jan 05 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1767636000,
		"url":"https://www.raymondcamden.com/2026/01/05/turning-recipe-data-into-an-astro-content-collection",
		"content":"As I continue to dig into, and learn, Astro, I thought I'd take a look at creating custom content collections. Content collections are pretty much exactly how they sound - collection of content items you can use within your Astro site. If you go through the excellent Astro tutorial you will find this discussed at the end in the final optional step step. Content collections aren't required - you can build dynamic sets of data just using file system operations (and that's how the tutorial has you build the blog) - but they make it easier (imo) to re-use content throughout the site.\nI encourage you to check out the docs, but generally content collections come down to three types:\n\nA glob pointing to a folder of Markdown, MDX, Markdoc, JSON, YAML, or TOML files.\nA pointer to one file that has multiple records of data (think big ass JSON file or CSV)\nAnything and everything else if you're willing to write code for a custom loader\n\nFor my demo today, I decided to revisit a post from 2022, &quot;Use Your Saffron Recipes in the Jamstack&quot;.\nRecipes and Saffron\nFor many years, I made use of Saffron, an elegant web-site/mobile app for recipe management. It supports reading and parsing ugly recipe URLs (which I've covered on this blog quite a bit) as well as letting you manage recipes in different cookbooks. It is a damn good site, but I hit the limit of the free tier a few months back and as I don't really use the import feature much, switched to simply using OneNote instead. That being said, I absolutely think it's a cool site and worth the $$ if you want the additional storage above their free tier.\nOne more reason to like them is that you can, at any point, without wait, get an export of your data. This will give you a zip file of recipes in text file format which look like so:\nTitle: Soft and Chewy Chocolate Chipless Cookies\nDescription: \nSource: Sofi | Broma Bakery\nOriginal URL: https://bromabakery.com/chocolate-chipless-cookies/\nYield: 16,16 cookies\nPrep: 15 minutes\nCook: 11 minutes\nTotal: 1 hour\nCookbook: Deserts\nSection: Cookies\nImage: \nIngredients: \n\t3/4 cup unsalted butter\n\t1 cup brown sugar, packed\n\t1/4 cup granulated sugar\n\t1 egg + 1 egg yolk, room temperature\n\t1 tablespoon vanilla extract\n\t1 3/4 cup all purpose flour\n\t3/4 teaspoon baking soda\n\t1 teaspoon sea salt + more for sprinkling\nInstructions: \n\tBrown the butter over medium heat, stirring constantly until the butter begins to foam and turns a golden brown, emitting a nutty aroma. Make sure you only brown the butter lightly. When butter browns the liquid evaporates off which can dry out your dough. As soon as the butter starts to turn brown and smell nutty, take it off the heat to prevent any more liquid from escaping. Take butter off the heat and allow to cool.\n\tIn a large mixing bowl combine the cooled brown butter, brown sugar, and white sugar. Beat until mixed together. Add in the egg, egg yolk, and vanilla extract. Mix well.\n\tIn separate bowl mix together the flour, salt and baking soda. Mix half the dry ingredients into the wet until everything comes together. Slowly add in the remaining flour a little bit at a time, stopping if the dough starts to get too dry.\n\tRefrigerate the cookie dough for at least a half hour, or overnight.\n\tWhen you are ready to bake the cookies, preheat the oven to 350°F and line a cookie sheet with parchment paper. Use a 1 ounce cookie scoop to scoop the cookie dough out into balls, placing them 2 inches apart on the prepared sheet. Bake for 11 minutes*, or until the edges are just golden brown and the centers have puffed up but are still gooey.\n\tAllow to cool before eating!\n\nThe format follows a pattern of &quot;Key: Value&quot;, but with multiple line items being tabbed over from the key defined on the previous line. Back in 2022 for my original post, I wrote a simple function that parsed this data into a basic JavaScript object. Here's the version I have now (slightly modified from the original):\n\nThe only real &quot;fancy&quot; part here is how I handle noting the multiline line data by looking for tab characters.\nCreating the Astro Content Collection\nTo create my Astro custom content collection, I started by defining src/content.config.ts. This file is where all collections are defined, in my case my demo only has the one:\n\nBasically - scan the folder of recipes (where I extracted the zip Saffron exported) and add the information to an array. Per the Astro docs, I ensured I defined an id and slug value to uniquely identify the data.\nUsing the Collection\nOnce defined, it's rather trivial to use the information. On my index page, I simply list out all of the recipes:\n\nAnd then defined a dynamic route for each recipe in src/pages/recipes/[id].astro:\n\nI used Google Gemini to help me define a simple layout and deployed it to Netlify here: https://astro-recipes-demo.netlify.app/. For the most part, it worked well, but one recipe, and it just so happens the first one, https://astro-recipes-demo.netlify.app/recipes/ban",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An Astro site for my CSS Snippets",
		"date":"Fri Jan 02 2026 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1767376800,
		"url":"https://www.raymondcamden.com/2026/01/02/an-astro-site-for-my-css-snippets",
		"content":"As I think I've mentioned a few times already, I'm learning Astro and attempting to build random stuff with it just as an excuse to help practice and learn. With that in mind, during the Christmas break and between marathon sessions of Baldur's Gate 3, I built a little site I thought I'd share here on the blog. To be clear, this is nothing special, and doesn't come close to using all of the possible Astro features of course, but it was a useful coding exercise for myself and fun to build.\nThe web platform as a whole has gotten dramatically better over the past decade, and CSS improvements are a big part of that. There is a huge amount of new CSS features I'm &quot;kinda&quot; aware of but don't really have much experience with. One of the things I do to help me in that regard is keep notes of CSS snippets I find myself using again and again so I don't have to Google for them. I use Microsoft OneNote to track these and just write it down in quick and dirty blocks of text like so:\nCenter vertically in div: \nalign-content: center \n \nCenter iFrame: \ndisplay:block;margin:auto \n\nCSS for Borders: \nfieldset {  \n     border-style:solid; \n     border-width:thin; \n} \n\nTable CSS for borders: \ntable { \n    border-collapse: collapse; \n    border: 1px solid black; \n    width: 100%; \n    max-width: 500px; \n} \n\nth, td { \n    border: 1px solid black; \n    padding: 5px;  \n} \n\nTwo Cols: \n.twocol { \n    display: grid; \n    grid-template-columns: 33% 66%; \n} \n\nThere isn't any additional information here as I know what I'm typically searching for and just do a quick copy, paste, and modify to suit whatever I'm building.\nI thought it might be interesting to take these tips and create a simple Astro site out of them. I'd use one Markdown source file per tip (which admittedly it perhaps overkill for some of these short snippets) and see if Astro could render the code and the output.\nIf you don't actually care about how I built it, you can go ahead and navigate to https://css-snippets.netlify.app/ and check it out. Here's a sample of one of the snippets:\n\n\n\nAlright, so here's how I built it.\nSource Markdown\nAs I mentioned, I wanted my content to be driven by simple Markdown files. For this, I made use of Astro's content collections feature. First, I created a directory for my snippets called... snippets. In there I placed one Markdown file per snippet. Each file has a title, a set of tags, and the CSS snippet along with HTML to demonstrate it. Here's one for zebra striping table rows:\n---\ntitle: Zebra stripe a table\ntags: [&quot;tables&quot;]\n---\n\n&lt;style&gt;\n/* just to make it easier to see */\ntable {\n    width: 500px;\n}\n\ntbody tr {\n  background-color: #0a5b20;\n}\n\ntbody tr:nth-child(even) {\n  background-color: #000000; \n}\n&lt;/style&gt;\n\n&lt;table&gt;\n    &lt;thead&gt;\n    &lt;tr&gt;\n        &lt;td&gt;Name&lt;/td&gt;\n        &lt;td&gt;Age&lt;/td&gt;\n    &lt;/tr&gt;\n    &lt;/thead&gt;\n    &lt;tbody&gt;\n    &lt;tr&gt;\n        &lt;td&gt;Luna&lt;/td&gt;\n        &lt;td&gt;13&lt;/td&gt;\n    &lt;/tr&gt;\n    &lt;tr&gt;\n        &lt;td&gt;Elise&lt;/td&gt;\n        &lt;td&gt;15&lt;/td&gt;\n    &lt;/tr&gt;\n    &lt;tr&gt;\n        &lt;td&gt;Pig&lt;/td&gt;\n        &lt;td&gt;10&lt;/td&gt;\n    &lt;/tr&gt;\n    &lt;tr&gt;\n        &lt;td&gt;Zelda&lt;/td&gt;\n        &lt;td&gt;2&lt;/td&gt;\n    &lt;/tr&gt;\n    &lt;/tbody&gt;\n&lt;/table&gt;\n\nTo let Astro know about the collection, I then added content.config.ts to the root of my src file and defined how it should find those Markdown files:\n\nThis was enough to make it available to my home page.\nRendering Snippets\nFirst, I added a simple list to my home page. Right now this just lists everything, but I've only got a few snippets.\n\nI won't bother sharing the layout file, but will note I made use of a nice little CSS framework, Simple.css.\nNext, I added a template that would render one file per snippet. For this, I made an Astro file named src/pages/snippets/[id].astro. The [id] portion makes it dynamic. This page both handles the logic of &quot;how do I know what routes to support&quot; and &quot;how do I render each one&quot;:\n\nOn top you can see where I get my collection and define the paths. I just use the Markdown's id value (which comes from the filename) and pass the content in as well. That's picked up in the template as snippet and then rendered both using Astro's native Code component for source code rendering and as raw HTML in a div.\nThe last part of the site is similar - handling tag pages - but the logic is a bit more complex. First, I added src/pages/tags/[id].astro. Here's how I handled the logic:\n\nBasically, loop over my content, get unique tags, and create an array of pages for each tag. You can see an example of this here: https://css-snippets.netlify.app/tags/tables/\nDeployment\nThe last step was deploying it, and here I had multiple options. I chose Netlify as I host most of my sites there. Webflow supports Astro apps as well, but they are tied to existing web sites, not really standalone. ",
		"tags":[
	        
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "year plus plus...",
		"date":"Mon Dec 29 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1767031200,
		"url":"https://www.raymondcamden.com/2025/12/29/year-plus-plus",
		"content":"Welcome to my yearly &quot;roundup&quot; where I look back at my wins, accomplishments, and just about everything I did over the past year. As I always say, I assume no one really cares about this, but I like to take stock and honestly just remind myself of what I actually did. The years go by faster the older I get, but I swear they also feel longer as well. Of course, this year has been incredibly tough and that could account for that as well. Honestly, I don't feel like rehashing the bad stuff. If you read this blog on the regular, yall already know what I went through, and as I'm ending the year on a high note, I'm going to focus on that. I love my new job, I'm relatively healthy (if a bit more fluffy than I was last year), and I've got a lot to look forward to!\nThis Blog\nIf you ever want to see how the blog is doing, stat wise, you can head over to my stats page and check it out yourself. This post will be number 146 for the year, slightly beating my count for last year. Of course, quality means more than quantity, but I try to keep a pace of a post a week at minimum and I had no problem with that in 2025.\nI primarily use Netlify Analytics to check my traffic, check what content is popular, and so forth, but Netlify Analytics stops at 30 days of history. (As an aside, if anyone wants to donate to cover Plausible or Fathom Analytics, just reach out.) I also use goatcounter.com and according to it, my top ten page views for the past year only had one article from 2025 in the top ten - Using AI in the Browser for Typo Rewriting. I'm fine with that. I've been blogging for a while so I always see a mix of old and new content in my stats. The top post overall last year was one on Leaflet: Custom Markers with Leaflet. I haven't used Leaflet in a while, but I still recommend it.\nPublic and Virtual Speaking\nWith being out of a job for most of the year, it made it difficult to attend, or even CFP, to conferences. I only had 7 presentations with only 3 in person. I'm ramping up my submissions for 2026 and Webflow is supportive of my efforts to help spread the love of the web platform as a whole, so hopefully I'll be on the road and on stage quite a bit more next year. Obviously, if you run or help organize a conference, reach out to me, I'd love to speak at it!\nFavorite Media\nIt's always difficult to figure out what my favorite movie, music, or game is, as usually the one I watched/played last is freshest in my memory. I started using Letterboxd last year to track movies which makes it easy to see what I watched over the past twelve months. There's a couple movies that really stand out, but the best has to be The Substance. I don't really enjoy gore, and this movie has quite a bit of body horror, but it's so freaking crazy, so well shot, it was easily the best movie I saw.\nIn music, I'm sure I encountered new artists, but I honestly can't remember one in particular that feels like an outstanding artist new to me. I will say I've been loving the mashups and remixes by matt_one on Youtube, enough to go through the trouble of ripping some to MP3 locally so I could play them offline. If you are into that kind of music, absolutely check him out. I'll warn you that the videos are typically AI slop, so I rarely watch them and instead just listen to the music.\nGame wise, I ended last year discovering, and loving, Powerwash Simulator, and with the release of the second one last month, it was easily my favorite game. There's something incredibly relaxing about a game you can't lose. I played it much too quickly but there will be plenty of DLC and I plan on getting every single one they release.\n2026\nI'm going to make this part easy - I've got no plans for 2026. Don't get me wrong, there are absolutely things I want to get done, but after this year, I'm not writing anything down. Whatever I accomplish, great, whatever I don't get around to, also great. Heck, if this time next year I've still got a job, I can still walk upright and my wife and kids are happy, then nothing else matters.\nThat's it - happy new years!\nPhoto by Towfiqu barbhuiya on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "My Tech Stack (2025)",
		"date":"Mon Dec 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1766426400,
		"url":"https://www.raymondcamden.com/2025/12/22/my-tech-stack-2025",
		"content":"Every now and then I like to share my current tech stack, not that I think I'm doing anything special in my day to day, but I know I enjoy reading about other devs and their stacks as it's a great way to get introduced to tools I may want to adopt myself. As far as I can tell, the last time I did this was back in 2020 and things have certainly changed for me. So without further ado, here's what I'm using.\nOperating System - OSX\nI'm back to Mac. To be honest, this was part frustration and part practicality. I've been on Windows for maybe 7 or 8 years now and generally happy with it. I usually did most of my work in WSL (Ubuntu) and as most of what I did was web or API based, the OS didn't really matter.\nAbout two months or so ago, my new personal laptop, a pretty cool Predator one with light up keys (yes, that's not helpful for development but it made me happy) started to crap the bed really, really badly. No amount of tweaks would help. Acer was willing to let me ship it back but as I was doing contracting, I'd be without any hardware to actually do work. I was frustrated so I returned it, got a decent Macbook Pro, and figured it was time to switch back for a while.\nHonestly, the only issue I ran into was muscle memory for key combos. Outside of that, it just works, and I've got greater confidence on the hardware.\nWriting Code\nI write code primarily in three tools:\n\nVisual Studio Code is my primary editor for most &quot;real&quot; work. In the past I've shared my extensions but I don't think I'm doing anything special or noteworthy there. I've got extensions for Python, BoxLang, Astro, the stuff I'm learning, and one specifically for sharing blog posts via PDF - Mardkwon PDF. I use one of the random dark themes and I couldn't tell you which one - just the default I believe.\nRunJS - RunJS is a very handy app (Windows or Mac) that lets you quickly run and test JavaScript code. Yes, you can write in your browser console, but I find it difficult to organize anything more than a line or two there. Yes, you can make a 'scratch' file locally and run that, but RunJS just does all of this simpler and easier to handle. Oh, it also lets you quickly install npm packages as well which is handy. RunJS is not free, but is well worth your money imo!\nCodePen - CodePen is my primary place for web demos that can be run well in a simple window. That's kinda vague but what I mean is, I'm not typically building PWAs/SPAs in CodePen, but rather using it to demonstrate a particular web-based API/feature.\n\nCode\nAs for the code I write, it's basically web platform stuff (HTML, JavaScript, and CSS) and Python (nearly all of my AI stuff). This year I started playing with BoxLang, a dynamic JVM-based language from the fine folks at Ortus. For a new language, it's moving really fast and adding a lot of impressive features quickly. While I don't do much ColdFusion anymore, I'll still dabble from time to time. I even built a fun little submission for Adobe's Hackathon earlier this year. I lost, but it was fun to build. :)\nBrowsers\nFor years I used Microsoft Edge but decided to switch back to Chrome. There wasn't any particular reason for this, Edge wasn't acting up or anything, I just switched. It could be based on how much AI in Chrome work I was doing, but for whatever reason, I'm on Chrome again. I use multiple profiles - one for my personal use, one for work, and one for presentations.\nI also use Firefox for my social media usage and Reddit. I like having it in a completely separate app which helps me not use social media too much. Speaking of, you can find me on Mastadon (https://mastodon.social/@raymondcamden) and Bluesky (https://bsky.app/profile/raymondcamden.com). I've gone back and forth between which I prefer. I'm leaning more towards Bluesky as it feels more active, but generally when I post something, I'll post to both. I use openvibe typically when I do that.\nFront-End / Back-End\nVanilla JavaScript FTW! I'll still go to Alpine.js for more complex needs, but I don't use Vue anymore and I definitely don't use React if I can help it. (Although I may end up doing a bit for work.)\nOn the back end, I'm still rocking, and loving, Eleventy, but have been learning Astro lately (again, for work) and really dig it. I'll probably do most of my blog posts on that at work, but some may show up here as well.\nEverything Else\nAnd here's a small list of other tools I use:\n\nMicrosoft To Do - this is still my primary way of managing lists of things I need to - well - manage as well as my place to store ideas for writing.\nMicrosoft OneNote - I got off the Evernote platform a few years back and honestly, it's been a struggle. I was looking for a solution that worked for me and my wife, that was crucial, as we both needed to share information. OneNote is far from perfect, but works well enough. I may still migrate my own stuff off there in the future.\nFor my mobile phone, I'm using a Samsung S24 Ultra.\nLastly, I host on Netlify, who graciously hos",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "The Twelve (Generative) Days of Christmas - 2025 Edition",
		"date":"Fri Dec 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1766167200,
		"url":"https://www.raymondcamden.com/2025/12/19/the-twelve-generative-days-of-christmas-2025-edition",
		"content":"For the past two years I've done a fun little expirement - using GenAI to create illustrations from the Twelve Days of Christmas song. You can check out the 2023 and 2024 editions to see how things have progressed. In previous years, I mostly just kept things simple - passing only the day's gift as the prompt:\npartridge in a pear tree\ntwo turtle doves\nthree French hens\nfour calling birds\nfive golden rings\nsix geese a-laying\nseven swans a-swimming\neight maids a-milking\nnine ladies dancing\nten lords a-leaping\neleven pipers piping\ntwelve drummers drumming\n\nWhich is absolutely not ideal at all, but part of the fun was seeing how the various tools handled it. This year I decided to give the engines a bit of direction by prefixing it with a request to base the results on the song, for example:\nYou will generate an image based on one day from the Twelve Days of Christmas song: twelve drummers drumming\n\nIn general, I did not change any settings, so for example, Meta produced portrait sized images and I'm sure I could have tweaked it, but I left it as is. In cases where the engine output multiple results, I used my best judgement and picked what I thought was the best.\nLast year, nearly every service did not like &quot;eight maids a-milking&quot;. None of the services blocked it this year.\nOnce again, numbers are a problem. I'd ask for X of Y and generally get X+1, X-1, or not even close. The services did better, but still struggled.\nThis year I tested with:\n\nBing - Bing did really good in the past and for some reason did horrible this year. Like, shockingly bad. It reminded me of the first results from GenAI a few years back. I'm not sure what went wrong here but they just dropped the ball.\nMeta - Meta made some stunningly beautiful images but also universally seemed to ignore my prompt requesting it be Christmas based. I tried tweaking it a bit (removing the colon and wrapping the day portion in a quote) but it didn't help.\nNano Banana Pro - this was the clear winner in my opinion. I used the Pro version which is not free, so that's something to consider when comparing results. I didn't actually pay though, I get credits as a Google Developer Expert.\nNo Adobe Firefly this year. You get 10 generations for free, but I need 12. Oh well.\n\nFor each image, you can click for the larger, original image.\n\n\nBing\n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \n\n\n  \nMeta\nPay special attention to number 3, I loved that. And for option 4, there were a few options more &quot;Christmas-like&quot;, still not great, but I love corvids.\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\nNano Banana Pro (Gemini)\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\n  \n\n\n \n\nHeader Photo by Chad Madden on Unsplash\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Chrome AI for Color Suggestions",
		"date":"Thu Dec 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1766080800,
		"url":"https://www.raymondcamden.com/2025/12/18/using-chrome-ai-for-color-suggestions",
		"content":"Today's blog post came to me on the way to dropping of my kids at school and made complete sense to me, but I've also got the flu and am heavily medicated, so take that for what you will. The idea was simple, given a description of something in the real world, could I use AI to generate RGB colors that would represent that abstract idea. I thought this could be a good use of Chrome's built-in AI model and decided to whip up a quick demo.\nThe front end is pretty simple, just a form for you to enter your description and a place for the results:\n\nThe JavaScript is where the magic lies of course. I'll skip over the DOM manipulation stuff and focus on the parts specific to Chrome's AI implementation. First, I created a schema to constrain my results:\n\nEssentially I wanted the model to explain it's results at a high level, and then just return a list of colors.\nNext, here's how I create my session from the model - the important bit being the system prompt:\n\nAnd the last bit is just taking the user's prompt, running it, and displaying the results:\n\nI'm going to embed the demo below, but as the Prompt API is still behind a flag, here's some screenshots:\n\n\n\nBonus points if you identify this prompt:\n\n\n\nYou can see the full code, and play with it yourself (again, if you enable that flag in Chrome), below:\n\n      See the Pen \n  Description to Color by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \n\nHeader photo by Mika Baumeister on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (12/14/25)",
		"date":"Sun Dec 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1765735200,
		"url":"https://www.raymondcamden.com/2025/12/14/links-for-you-121425",
		"content":"I cannot stress how much happier weekends are when you're not looking for a job. I used to dread the weekends. No one was posting jobs, no one was responding to applications (usually), and it just made me anxious. Now - I'm enjoying the hell out of the weekend, and looking forward to Monday as my job continues to be fun as hell. Thing are good - and I'm doing my best not to be nervous/anxious about that. ;) Here's some links for your Sunday enjoyment.\nWest of House\nFor us old-timers, Infocom is fondly remembered as one of the great companies of early computing gaming history. Their text-based games were surprisingly deep and thoughtful, and fun to own as they always had fun accessories in the box. (Remember physical games?) A few weeks ago, Microsoft announced the open source release of Zork 1, Zork 2, and Zork 3. You can peruse the code and even play the games if you want.\nAs an aside, my personal favorite Infocom game was A Mind Forever Voyaging, something I'd love to see turned into a movie.\nSanitized HTML with setHTML\nNext up is an article on something I wanted to look at over a year or so ago, and I'm glad I didn't as the spec changed a bit. Ollie Williams had written up an article looking at the Sanitizer API as a method to safely inject dynamic HTML into the DOM: setHTML(), Trusted Types and the Sanitizer API. The setHTML method does multiple things, including filtering out inline event handlers and certain tags. Read his post for more information.\nSize of Life\nLast up is an incredibly beautiful web site demonstrating the relative sizes of living organisms on Earth, Size of Life. Not only is this informative, it's also truly gorgeous and a great reminder that you can use the web for art and science at the same time.\nJust For Fun\nLast up is a mashup with Cookin Soul (who I had never heard of) and MF DOOM (who I only discovered about a year ago). I did listen to it and don't remember, but I'd probably assume it's NSFW in the office so wear headphnes. Either way, it's a great mix and great for the holidays.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "My Last Spotify Demo (this time I mean it - honest)",
		"date":"Wed Dec 10 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1765389600,
		"url":"https://www.raymondcamden.com/2025/12/10/my-last-spotify-demo-this-time-i-mean-it-honest",
		"content":"Earlier this month, I blogged about building my own Spotify Unwrapped. I did this by requesting a data export from Spotify and playing around with the data in Python and Astro. I built a simple, and probably bad, Astro site to view my stats. When I built it, I had an idea for a slight tweak to make it a bit better, but one that would require API usage. I stand by what I said about not wanting to use the API anymore (feel free to ask why in the comments), but I couldn't resist tinkering one more time. Here's what I did.\nUsing Python to Enhance Artist Info\nIn the output from Spotify's export, you get a detailed listing of the tracks you've listened to. As a reminder, here's an example:\n\nWhile this gives a good amount of information, there isn't much about the artist outside of their name. One thing in particular I was interested in was the genres of the tracks I listened to. Genres are associated with artists, so I thought, I could use my exported data to get a list of artist names and then transform this into detailed artist info so I could get the genres. This involved a few steps:\n\nFor each track in my history, I have an artist name. I can turn this into a unique list of artists.\nTo get artist data, I need the ID, not the name of the artist. Spotify has a search API, but I wanted to use the batch APIs to get multiple artists at once to reduce the total number of API calls. The only way to get the artist ID is to get the track. So when I make my unique list of artists, I associate one track with them.\nGiven I now have a list of unique artists with one track associated to it, I can use the batch API call, Get Several Tracks, which will give me artist ID values.\nNow I can use Get Several Artists to get full artist records.\n\nAll of this is done in a Python file that stores results to a JSON file for caching. I decided to do 50 results at a time, and run it manually, as I figured it wouldn't take terribly long. My total number of unique artists was under 10000 and I thought, I'll just run it a few times by hand and eventually I'll cover all the artists from my export. Obviously that's a hack, but it's my hack so it's beautiful.\nWith that in mind, here's the script:\n\nThis seemed to work well, and generated a pretty huge artists.json file. Here's one example:\n\nNote that this includes images of the artists which would be cool to use as well, but all I cared about were the genres.\nUpdating my Astro App\nAs I mentioned in my last post, I wasn't terribly happy with how I built my Astro app, it felt like it could have been done much better, but I'm still very new to Astro so I'm giving myself a break from stressing over it. If you remember, I had an Astro page that read my Spotify export and served it as data.json, which was then used by client-side JavaScript to render my stats.\nI followed the same technique for my artists information. I copied over my JSON from my Python directory and placed it in my app's data folder. Because of this, I made a slight tweak to the glob in data.json.js:\n\nAnd then built artists.json.js pretty simply:\n\nAlright, this just left using the data in my front end. I added a new div to my HTML to handle the genre report:\n\nI then updated my initial fetches to get both files, and combine:\n\nLastly, I added code to render the genres, using a new function to return a sorted genre list:\n\nAnd the result is pretty much what I expected - when looking at my entire history, it's really Enya-weighted:\n\n\n\nBut if I filter to this year, it's a bit more on target:\n\n\n\nAnyway, that's it, I'm done with Spotify demos, although definitely not done with Astro. If you want to look at the source, you can peruse it here: https://github.com/cfjedimaster/astro-tests/tree/main/spotifydata2\n",
		"tags":[
	        
            "python",
            
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Generating Relevant Random JSON with Chrome AI",
		"date":"Sun Dec 07 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1765130400,
		"url":"https://www.raymondcamden.com/2025/12/07/generating-relevant-random-json-with-chrome-ai",
		"content":"A few weeks ago I blogged a demo where I used Chrome's on-device AI feature to parse a &quot;generic template language&quot; and return random strings. If you're so inclined (and of course you are), you can read that post here: &quot;Creating a Generic Generative Language with Chrome AI&quot;. The idea was to give the AI model a template string that described what was random, and how it was random (this is a name, this is a number, this is a color, etc) and have the model fill in the blanks with appropriate values.\nAt work, I've been digging into our platform and trying to learn as much as possible. One of the cooler features of Webflow is the CMS. You define a collection (type of content) which has a name and a set of fields. Each field has properties that define it. As an example, here's my Cat collection:\n\n\n\nMake note of the custom fields there. Along with and slug, and some metadata fields, they make up a definition of &quot;Cat&quot; that I can use within my site. One of the cooler features of this part of the product is that when you first define the collection, it offers to make some random content for you, and when it does, it does it &quot;appropriately&quot;. By that I mean it's not just lorem ipsum, but relevant values. In particular, &quot;Breed&quot; had multiple different real cat breeds, and the names were on target as well.\nI found this darn handy and it got me thinking recently how I could recreate it with Chrome AI. Specifically:\n\nGiven a name that describes the content\nGiven a basic JSON shape\nGenerate a random value with sensible data\n\nI was able to get it working - mostly - and here's the result.\nVersion One\nFor my first version, I built a simple form that asked for two things - the name of the content your JSON was describing, and a JSON packet that described the shape of the content. I also pre-loaded some content so I'd have less to type:\n\nOut of the box, my demo says it's describing a cat with properties for name, age, gender, and breed. Now, let's look at the JavaScript. I'm going to skip over some of the basic DOM manipulation stuff and instead focus ont he AI related code.\nWith that in mind, here's how I setup the session:\n\nI'm rather proud of that system prompt. I usually don't do a good job there but this is pretty clear I think. Now, when the user submits the form, this is run:\n\nPretty simple, right? So how does it work? Using the out of the box defaults I used in the form, I get:\n\n\n\nThat's pretty good! But as you'll note in the JavaScript above, I'm expecting Markdown back. In fact, this is actually what I got:\n\nWith this in mind, I started on a second version to try to get just the JSON back. You can play with the full demo of the first version below:\n\n      See the Pen \n  Chrome AI to JSON by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nVersion Two\nNormally when I want to have control over the generation of model output, I turn to structured output. You generate JSON schema that matches your desired output, pass it to the model, and then your result should match it exactly. The issue I had though was that my JSON itself was dynamic. I had no idea what was going to be passed, which means I couldn't determine what schema to use. Turns out - there's actually multiple libraries out there that will take sample data and attempt to turn it into a JSON schema.\nI came across schematized which does exactly that. Here's an example from their docs:\n\nWhich produces:\n\nThat seemed logical enough, so I gave it a try. I imported the library and built a schema from the form input:\n\nThis is then passed to the prompt call:\n\nAnd... it worked... kinda. But I noticed something odd with my results. Here's the result, again using my default JSON from the form:\n\nSee the issue? Each property ended up with a min and max length based on my input. Schematized supports passing in multiple examples to create better output, so it's not really the libraries fault, but these constraints were screwing up my output. It's kinda lame, but I went with this approach:\n\nNow when I run, I get almost perfect results:\n\n\n\nI say almost perfect because my age went from being really numeric to a string, but if you remember, this is how it looked in the input:\n&quot;age&quot;:&quot;number&quot;,\n\nThe quotes there around number, when sent to Schematized, made it think it should be a string, and to be honest, I don't blame it. I could absolutely do some magic on my input and look for &quot;number&quot; and change it, but, I figured that was enough for this simple experiment for now. As before, you can see the full code, and test (if you've enabled the right flags) below:\n\n      See the Pen \n  Chrome AI to JSON V2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nCC0 licensed photo by Shinya B from the WordPress Photo Directory: https://wordpress.org/photos/photo/873640b0d0/\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Make Your Own Spotify Unwrapped",
		"date":"Thu Dec 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1764871200,
		"url":"https://www.raymondcamden.com/2025/12/04/make-your-own-spotify-unwrapped",
		"content":"Tis the season - the Spotify Unwrapped season. If you aren't aware, Spotify creates a yearly &quot;recap&quot; of your listening habits and packages it up in a nice and fun slide show/animated doodad only available on your device. It's a cute thing and as someone who both loves music and stats, I look forward to it. If your curious, here's a part of mine:\n\n\n\nEarlier this week I knew Spotify Unwrapped was as eminent as Mariah Carey and I pondered building my own report. Of course, a few months ago I swore off building anything Spotify related again. But given that Spotify lets you export your data, in theory, I could build something without using APIs, right?\nGetting the Data\nThe first step was to get my data. This can be done via Spotify's private page. On there you can request your data for the past year, or, your entire lifetime. They warn you that it may take up to 30 days to generate the extended history, but for me it only took one day. Take that with a grain of salt - your results may vary.\nOnce you get your report, it's a zip file that consists of a set of JSON files:\n\n\n\nYou get a readme that explains the structure, N json files for your audio history and one for video. Honestly I didn't know you could do video on Spotify, but I just ignored that.\nIf you open up one of the audio files, you'll see an array of track entries. Here's one from this year.\n\nYou get a timestamp, track information, as well as how long you listened to it.\nInitial Exploration\nI started off with Python. I wanted to scan the files, collect the data, and analyze it. So first, I grab the JSON files, read them in, and append the entries to one big uber array. (As a quick note, in my testing directory, I removed the JSON file related to video stats.)\n\nCool - now I start analyzing. First, I generate a report of my top artists. I also add the times together to see how long I listened to music:\n\nSong info was similar:\n\nAnd finally, I report on the total minutes:\n\nHere's my report:\nTotal records loaded: 128932\n\nThere are 9844 unique artists.\n\nArtist: Enya, Records: 14814\nArtist: Above &amp; Beyond, Records: 3696\nArtist: Depeche Mode, Records: 2628\nArtist: London Grammar, Records: 2351\nArtist: The Cure, Records: 1558\nArtist: M83, Records: 834\nArtist: Ludwig van Beethoven, Records: 724\nArtist: Nine Inch Nails, Records: 714\nArtist: Still Corners, Records: 697\nArtist: Cocteau Twins, Records: 689\nArtist: The xx, Records: 675\nArtist: Agnes Obel, Records: 629\nArtist: The National, Records: 550\nArtist: Duran Duran, Records: 536\nArtist: The Sundays, Records: 523\nArtist: The Smiths, Records: 520\nArtist: The Decemberists, Records: 514\nArtist: Johann Sebastian Bach, Records: 510\nArtist: Howard Shore, Records: 495\nArtist: Beach House, Records: 488\n\nSong: A Day Without Rain, by Enya Records: 414\nSong: Only Time, by Enya Records: 406\nSong: Caribbean Blue, by Enya Records: 400\nSong: Aníron, by Enya Records: 397\nSong: The Humming, by Enya Records: 395\nSong: Wild Child, by Enya Records: 385\nSong: May It Be, by Enya Records: 384\nSong: Lothlórien, by Enya Records: 379\nSong: The Council of Elrond (feat. &quot;Aniron&quot;) [Theme for Aragorn and Arwen], by Howard Shore Records: 372\nSong: So I Could Find My Way, by Enya Records: 363\nSong: Pale Grass Blue, by Enya Records: 362\nSong: One by One, by Enya Records: 362\nSong: Fallen Embers, by Enya Records: 361\nSong: Book of Days, by Enya Records: 358\nSong: Solace, by Croquet Club Records: 354\nSong: Echoes in Rain, by Enya Records: 353\nSong: Amarantine, by Enya Records: 352\nSong: Flora's Secret, by Enya Records: 345\nSong: Remember Your Smile, by Enya Records: 339\nSong: Dark Sky Island, by Enya Records: 337\n\nTotal minutes listened: 469014.35 minutes\n\nIf you're curious, that total time in minutes is near eight thousand hours and over three hundred and twenty-five days. That's a lot of music!\nThe Enya Problem\nI love Enya. I have for a very long time. I remember picking up &quot;The Celts&quot; in... Walmart I think, when I was probably 12 or so. She's also the richest female musical artist in England and Ireland.\n\n\n\nHer music is incredibly soothing at times, and as someone who has a crap-ton of anxiety on a good day, I need relaxation music. For about two years or so I'd also play Enya when going to bed, which means that for 6 hours, I had Enya playing each and every night. Therefore, she kinda stands out in my stats. Every single one of my top tracks is from her. It's accurate. That being said, I was tempted to write a filter just for her, but decided against it to keep it simple.\nBuilding the Web App\nI decided to build a simple web app for this data and for that, I wanted to use Astro. At work, we support Astro in multiple ways so I've been looking for excuses to build out Astro apps as way to get more familiar with the framework. Before I continue, I will say that at the end, I feel like Astro was overkill for what I did, and I'll talk about how I'd build this &quot;for real&quot; later on, but I didn't mind using ",
		"tags":[
	        
            "python",
            
            "javascript",
            
            "astro"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (11/30/25)",
		"date":"Sun Nov 30 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1764525600,
		"url":"https://www.raymondcamden.com/2025/11/30/links-for-you-113025",
		"content":"Once again I'm here, sharing links, and apologizing for the lack of content this month, although I did publish seven times, and I think I had some good demos, so maybe I should just stop worrying about it? ;) As I've hinted recently, I am no longer on the job market! I haven't said where yet as I wanted to get a few weeks under my belt to ensure things were going to be ok. I'm not going to lie, my last role (and quick lay off) really messed me up a bit. That being said, I feel relatively safe now, and really like what both my role and my coworkers, so I figure it's time to share!\nMy new role is a Senior Developer Evangelist for Webflow. Webflow has a web-based designer for creating and managing web sites, and while the primary tool is focused on designers and business-type users, the platform has multiple different developer-centric endpoints. As someone who has been working on the web for an incredibly long time, I look at visual/web-based tools like this with a bit of skepticism. They've come a long way since the days of Dreamweaver 1.0, but I still worry about not having direct access to code. One of the really cool things about Webflow is all the ways a developer can extend and work with the platform itself. I feel like our Designer can cover 90% of most people's needs, and the extensions available to coders easily cover the rest. I'm also beyond excited to be working on a web-platform centric product. I love APIs, but I haven't had an opportunity to work directly with web sites like this for a while. And with that, on with the links!\nBuild a Router\nFirst up is a cool post on jshof.dev where he makes use of URLPattern (read more about that here on MDN) to create a web component based router. It's a great little experiment and another example of how useful web components are in the platform.\nHelping Seniors with AI\nNext is a book by an old friend of mine, &quot;How Seniors Learn AI&quot; (as an FYI, that's an affiliate link). The author, Dan Wilson, is someone I've known for probably two decades now. He's a darn good educator and a darn good person as well. This book is meant to help introduce AI to senior citizens and provide useful guides to help them make use of it in their day to day lives. My assumption is that most of my readers don't need this book, but I'd bet you know someone who does.\nAdvent of Code 2025\nIt's that time of year - my favorite time of year - Advent of Code is back! I've shared this site now for probably ten years, but for those of you who aren't aware, the Advent of Code is a yearly coding challenge that presents two holiday-themed puzzles per day. Usually the second challenge is a slight variation of the first so it doesn't take too much time. Difficulty ranges from pretty easy to more complex as the days go on, and usually I get about ten days done before I tap out, but it's always fun to take a stab at it. Oh... and I just saw on the site they've gone from 24 days of challenges to 12, so maybe this year I'll actually finish.\nJust For Fun\nYall know I'm a big fan of covers, so how about a rather conventional cover from a rather unconventional band, Ninja Sex Party:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Google Static Maps in Your Print View",
		"date":"Thu Nov 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1763661600,
		"url":"https://www.raymondcamden.com/2025/11/20/using-google-static-maps-in-your-print-view",
		"content":"This is just a quick thought experiment really. Yesterday I was working on a demo that made use of Google's Static Map API. I've blogged about this API for probably over a decade now and I rarely see people use it, but it's a lightweight, image only &quot;API&quot; for when you need simple map images without interactivity. Honestly, I see a lot of sites using the full JavaScript maps library when a simpler image would be fine. It's also an excellent way to use maps in presentations or emails as well. It occurred to me that the static map image could be a great way use of print media queries in CSS and I thought I'd build a quick demo to show this.\nMedia queries and print support has been around for years, but I didn't play with it myself till earlier this year when I blogged about cleaning up my print view for this site. (Although I probably need to revisit that as I've changed my design since then.)\nI was curious to see if a print media query could be used to &quot;swap out&quot; a JavaScript map for a static one. I began with an incredibly simple map demo:\n\nThis is about as simple as you can get. If you're incredibly bored and want to see this running, you can do so here: https://cfjedimaster.github.io/webdemos/printmap/index.html\nOk, so how well does this print? I did a quick ctrl+p, saved the PDF, and this is what I got:\n\nIt's actually really, really good. My only complaint is the zoom icon in the upper right corner. Also, the &quot;Report a map error&quot; won't actually be a link.\nSo that works, but can we make it better? I began by adding in a static version of my map. (Note that the static map API does support markers and quite a few parameters, so you can do a lot more than what I'm showing here.)\n\nNote I added a class to it. I then used this CSS:\n\nBasically, hide the static map normally, and when printing, hide the map div that includes the JavaScript map and show the static version. And here's the result:\n\nSlightly better! ;) Worth the effort? Probably. I will note that this does add additional load to the page as the static image is loaded but hidden. You can check for a matched media query in JavaScript using matchMedia, so in theory, you could delay adding the image to the DOM that way, but that feels a bit overkill.\nYou can hit this version here if you want to see, and the source for both may be found here: https://github.com/cfjedimaster/webdemos/tree/master/printmap\nPhoto by Matijn Palings on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Gemini File Search and File Stores for Easy RAG",
		"date":"Mon Nov 17 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1763402400,
		"url":"https://www.raymondcamden.com/2025/11/17/gemini-file-search-file-stores-for-easy-rag",
		"content":"I am really excited about this post as it's one of the most powerful changes I've seen to Google's Gemini APIs in quite some time. For a while now it's been really easy to perform searches against a document, or a group of documents. You would upload the file (or files), ask your questions, and that was all you needed.\nHowever, the files you uploaded were only there temporarily. This was fine for processes like summarization or categorization where you could automate the process and be done with it. This was also fine for basic chat uses. I blogged an example of this last month: &quot;Building a Document Q&amp;A System with Google Gemini&quot;.\nThe new features I'm talking about today change all of that. You can now create permanent collections of files for introspection over time, creating a real RAG (retrieval augmented generation) system in a few minutes. Well, a simple one at least. Let's see how.\nFile Stores\nGemini's new RAG support is centered around File Store. A File Store is simply a collection of documents. The APIs provide full control over them by letting you create, read, update, and delete. Files can be uploaded directly to a store, or you can use the &quot;old&quot; API for uploading files and then import it into a store. Within an existing store you can also get a list of files and add or delete. I don't believe you can &quot;edit&quot; a file and would need to delete and re-upload instead, but overall, you can pretty much do anything here - and do it rather simply. I'll be showing code for this in a minute.\nThat's the good part. The bad part? Well, it's not really bad per se, but the actual how of what you do with them is going to be really different based on every different application you build. I can see many different scenarios:\n\nYour developer could build a script that simply checks to see if a store has been created, and if not, makes it. It then reads a folder and uploads each file. This could be run &quot;by hand&quot; once at the beginning of a project.\nYou could automate that process and on a schedule, see if new files need to be added, or removed from the store.\nYou could build a web interface that lets your business users work with one file store, adding and removing files as they see fit.\nYou could build a web interface that lets you build multiple file stores and work with them.\n\nThat's just a few examples, but you get the idea. The actual API is going to be trivial, minutes of work probably. Actually figuring out what makes sense for your application/organization will probably take much more time.\nOk, code time. For my demo, I'm going to search against a collection of Shakespeare's works. (Initially I had a copy of everything he wrote, but I removed a bunch to help speed up testing.) My code is going to do the following:\n\nAsk Gemini for a list of my file stores.\nSee if one exists with a particular name.\nIf not, make it\nThen iterate over every file and upload it.\n\nI started off my template with some imports, and a constant value for my store name and source directory:\n\nNext, I see if the store already exists.\n\nNote that I print out how many docs are in the store. I don't really need that, but it's a good sanity check. Now I create the store:\n\nThe name value I print here is a unique ID. I'll copy that down and use it later. Now I scan the folder and ensure I've got some PDFs:\n\nAnd finally, I upload each one. For this, the documentation was super useful. Document processing is async, so for each upload I wait until it's done. This is also I do something import - add metadata.\nIn general, you will probably ask questions about your file store as a whole, but what if you want to work with a specific document, or set of documents? For example, an e-commerce store that sells products may want to put their manuals online in one store, and use metadata to separate products into categories, or perhaps price ranges. You can use any logic you want here. In my case, I want to allow a search against a specific piece of work so I'm going to store the filename in metadata. Another option would be to use a PDF parsing library to get the PDF's document title instead, but the filename is enough for now.\n\nAnd that's it - the last thing I do is simply output a completed message:\n\nI'll share a link to all the code at the end so you can grab the entire file if you want. In my testing, each upload took maybe 10 or so seconds, which isn't bad I think, but keep that in mind if you have hundreds or thousands of documents.\nWorking with the Store\nAlright, at this point, I've got a store. I copied out the name so I could use it again. To work with the store, all I need to do then is use the FileSearch tool in my generate_content call. It is ridiculously simple:\n\nHere's the result, and keep in mind this is about half of Shakespeare's works.\nThe works presented cover a range of themes, often with a comedic and \nromantic focus.\n\nA prominent theme is **mistaken identity**, particularly seen in \n&quot;The Comed",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (11/16/25)",
		"date":"Sun Nov 16 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1763316000,
		"url":"https://www.raymondcamden.com/2025/11/16/links-for-you-111625",
		"content":"Good morning, programs. I'm sorry for the light posting the last few weeks, but, it's all for a good reason. Last week was my first week at my new job, which means all of my anxiety and fear are gone. Ok, maybe not, but, I'm absolutely delighted to be off the market again. I haven't blogged about the new job yet (or even gotten around to updating LinekdIn), but I'll do so soon. (My job isn't top secret or anything, I'm just waiting a bit.) Ok, let's get to the links!\nA Conditional Form Field Web Component\nI love web components, and I especially love really practical examples like this conditional form field component by Aaron Gustafson. As you can probably guess, it allows conditionally showing and hiding form fields based on other fields on the form. Here's an example from his post that demonstrates that I mean:\n\nBoth the label and field will only show up when the form field contact_method is set to &quot;phone&quot;. In that example only one field is checked, but your logic can check against multiple as well.\nWeb MIDI with Katie Fenn\nThis is from back in May, but this presentation by Katie Fenn covers the Web MIDI spec, and of course, goes into MIDI itself. Best of all, it features music by Daft Punk, not something you see often with web platform talks.\n\n  \n    Play Video\n  \n\n\n\n\nThe Origin Story of JavaScript\nFinally, another video, but one that's fun as hell to watch. Annie Sexton gives an entertaining look at how our favorite language (ok, maybe that's a bit controversial) language came to be:\n\n  \n    Play Video\n  \n\n\n\n\nJust For Fun\nOk, so I don't usually like so many of my links to be videos, but as yall know, I like to end these posts with a music video typically. This track is absolutely not new, but is one of the most happy songs I've ever heard.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Checking for Spam Content with Chrome AI",
		"date":"Fri Nov 07 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1762538400,
		"url":"https://www.raymondcamden.com/2025/11/07/checking-for-spam-content-with-chrome-ai",
		"content":"Earlier this week I mentioned I'm looking at my previous server-based generative AI demos and seeing which could possibly make sense using on-device AI with Chrome's AI support. I remembered a demo from last year where I tested spam detection using Google Gemini. That demo had worked out rather well and so I thought I'd try it out in Chrome. (Note the update towards the bottom!)\nOk, but why?\nSpam detection is important, and a server-based solution could have many users, especially in sites that make use of a lot of user generated content. But what would be the point doing this in the browser? Consider the fact that many of the Chrome AI APIs help with writing, I think such a solution could be useful in helping flag content that may be considered spam by others. So for example, your web-based CMS system may let you craft email content, and being able to see in real-time if the content would be diverted to someone's spam folder could be incredibly useful.\nThe Code\nTo build my spam checking tool, I relied on the following features:\n\nThe Prompt API, which is the general purpose aspect of Chrome AI.\nA system prompt that guided the Prompt API to focus on the task of determining if the content would be considered spam.\nStructured output so I could properly display the results.\n\nHere's how I did this. The first two aspects are handled by the code that creates the session object:\n\nYou'll notice that I'm using the system prompt to both explain what I want and help shape the output. But to really specify how the output should be returned, you must make use of JSON schema. I created this schema for my demo:\n\nBasically, a top level spam true/false value and an array of reasons backing up why something was flagged a certain way.\nThe last aspect is the easiest - taking the user input and passing it to the model:\n\nAnd that's it. The rest of my demo is just DOM manipulation, and I'll include it below, but keep in mind that as of today, this feature (the Prompt API specifically) is behind a flag in Chrome. It's released for extensions, but not the main browser yet. With that in mind, here's a few samples:\nGiven:\nCAPITAL CREDIT ASSOCIATES\nThe Capital Recovery Division\nAttorney of Record: Stanley Woods\nEmail: attorney.stanley.woods@gmail.com\n\nCase Reference: CF-9814725D3\n\nSubject: Past Due Loan Amount – $1,825.60\n\nThis correspondence concerns the above case reference linked to your account with Capital Credit Associates. Despite prior efforts to connect with you by phone and mail, we have not received any acknowledgment or response. Accordingly, your file has been escalated to our Legal Resolution Department, which has now categorized your account as non-compliant and ready for formal litigation review.\n\nThe National Financial Compliance Office has reported the following statutory violations connected to your account activity:\n\nBreach of Federal Lending Guidelines\n\nMisuse of Negotiable Instruments\n\nUnauthorized Funds Withdrawal\n\nObtaining Credit Under False Pretenses\n\nI get:\nWas it spam? Yes\n\nReasons Given:\n\nAggressive tone and threat of litigation\nDemand for payment with vague details\nGeneric subject line (\nPast Due Loan Amount\nwithout specifying the lender directly)\nReference to escalation to Legal Resolution Department\nMention of statutory violations without specifics (creating fear)\nLack of specific details about the alleged debt\nGeneric email address (gmail.com)\nPressure tactics to elicit a quick response\n\nThat seems right on the money. Now if I try something more innocent:\nHi, I attended your presentation about cat developer relations, \nand I was wondering if a) I could get a copy of your slide deck\nand b) would you be open to giving the talk to our local cat \nuser group?\n\nThe result is:\nWas it spam? No\n\nReasons Given:\n\nRequest for information (slide deck)\nRequest to present at a user group (potentially legitimate outreach)\nNon-aggressive language\nNo attempt to solicit personal information\nClear and straightforward query\n\nAll in all, this seems to work rather well I think. If you want to give it a shot yourself, or just see the code, you can check out the embed below.\n\n      See the Pen \n  Spam Detection Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nAn Update\nI posted this yesterday (Friday, November 7th), and my buddy Thomas Steiner noted a potential issue. After multiple calls using the same session, it's possible the model could get confused by the previous messages. To me, the operation was &quot;one and done&quot;, but the session persists and I can absolutely see why he's right about the issue. Luckily, it took approximately 10 seconds to correct using his suggestion of just cloning the sesison. So I went from this:\n\nTo:\n\nThe clone method is just one of many you have available to provide more control, and stability, to your use of the model in the browser, so as always, check the docs for more information, and specifically, this one on session management written by Thomas.\nHere's the updated Co",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang's Improved PDF Handling",
		"date":"Wed Nov 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1762365600,
		"url":"https://www.raymondcamden.com/2025/11/05/boxlangs-improved-pdf-handling",
		"content":"I've blogged about PDF support in BoxLang previously, including a quick introduction and a more robust demo later. Basically, the free PDF module provides excellent PDF creation capabilities out of the box. But what about PDF manipulation?\nMy last two jobs involved PDF APIs, and while my next one most certainly does not (thank god), it's still a feature near and dear to my heart. The BoxLang folks have made a shockingly huge amount of updates over the past few months, more than I've had a chance to keep up with, but one in particular caught my eye and I thought I'd call it out - the new PDF+ module.\nBoxLang's commercial modules are part of the BoxLang+ plan. It's still open source, but includes additional features, support, and a set of premium modules. To be clear, everything done in these premium modules could be done by any BoxLang developer if you want, but obviously having a supported, tested, and already existing solution is compelling. The PDF+ module is a great example of this as it gives you both manipulation features but also form handling. Let me show you a simple example of this in action.\nGetting Started\nGetting this version of the module is slightly different from the 'free' tier. Instead of:\ninstall-bx-module bx-pdf\n\nYou would do:\ninstall-bx-module bx-plus,bx-pdf\n\nThat's it. There's additional work done once you have your license (see the Plus Core docs) but you can trial the functionality at this point.\nPDF Manipulation\nAs I mentioned, the premium version of the module provides both manipulation and form capabilities, but let's focus on manipulation. These features include:\n\nAdding attachments\nAdding a header of footer\nAdding and removing a watermark\nDeleting pages\nExporting and importing form data\nExtracting text and images\nGetting information about the document\nMerging PDFs\nModifying PDF protection\nCreating PDF thumbnails\n\nThis is a great set of features, and the only one I really think is missing is a split type feature.\nPrimarily you will use the bx:pdf component. In tag-land, here's a simple merge example from the docs:\n\nOk, how about a real example?\nPDF Workflow\nI built a simple workflow process that:\n\nScans a directory of PDFs\nFor each one, it creates a new version that's 4 pages, at most, long.\nSaves to a &quot;samples&quot; directory\nUses the thumbnail action to get a thumbnail of page one\nUses BoxLang's Image module to then resize it down to a smaller size.\n\nAll in all, the idea is to create a set of PDF samples that could be used on a web site. (Although PDFs smaller than 5 pages will be saved as is.) The thumbnails could be used when listing them out.\nHere's the entire script:\n\nSo from the top, I first set up a few variables I'll be using, namely the source and destinations.\nNext, I loop over them - and make note of true at the end - that makes this process multi-threaded.\nFor each PDF, I first figure out how big it is using getinfo, then delete everything from page 5 and on using deletepages, saving the result in a new folder.\nNext, I use the thumbnail action. This will save the image with a filename that includes _page_X.jpg as the filename, where X represents the page being generated. I'm only generating one, so I can look for that, read it in and scale it down.\nAnd that's it! The end result gives me a set of PDF samples and thumbs.\nIf you want to see the results, and the sample code, you can grab it here: https://github.com/ortus-boxlang/bx-demos/tree/master/misc/pdfplus\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Generic Generative Language with Chrome AI",
		"date":"Mon Nov 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1762192800,
		"url":"https://www.raymondcamden.com/2025/11/03/creating-a-generic-generative-language-with-chrome-ai",
		"content":"As I explore Chrome's on-device AI initiatives, one of the things I'm doing is looking at some of my older demos (kinda funny to think of 'old' GenAI demos) and seeing which may make sense in the browser versus API calls. Last July, I investigated creating a template language parser with Google Gemini. The idea was - take a string with tokens that defined a type of word and have Gemini replace it. So for example:\nMy name is {{ name }} and my favorite food is {{ food }}.\n\nI asked Gemini to look for values inside brackets, use that as the seed of a random word, and replace it. So for example:\nMy name is Frederic Dinglehooper the 3rd and my favorite food is sushi.\n\nI thought this would be a natural candidate for exploration on the client-side, so I took it for a whirl.\nAttempt One\nMy first attempt tried to recreate the Gemini demo with nearly the exact same code. So I started off defining a system instruction - this is the same from the previous demo with the addition of the second paragraph:\n\nI've got some code to handle basic DOM manipulation (on clicking a button, read the user's input, then do your magic), that then runs this function to do the actual transformation:\n\nThis is a fairly simple use of the Prompt API, so it should just work.\nAnd it did! I promise!\nAnd then... it just didn't. I don't know why. I tested it in the morning, and it seemed perfect, I tested it in the afternoon and it 100% failed, or mostly failed. Every now and then it would replace a token, but mostly, it just didn't. You can see this failed attempt below, but note that the Prompt API is still behind a flag on Chrome.\n\n      See the Pen \n  Generative Story Telling with Chrome AI by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nAttempt Two\nSo - I did what I normally do when I see something weird like this - reach out for help - and my buddy Thomas Steiner, devrel at Google, had a simple suggestion. Use JavaScript to handle the tag parsing and then Chrome AI to handle the replacements. I figured I'd give that a short.\nI began with a function to find my tokens:\n\nNote that I return each match including the actual match, and the token with brackets. I return the index (where it was found) but I never used it.\nI then wrote a new utility function that literally just takes a word, the token, and generates the new word for it:\n\nNext, I wrote a function to handle clicks and run the entire process:\n\nI think the only real interesting part here is the caching aspect. As with the previous demo, I wanted the ability to reuse values in a string, so you could get a name, and then use it a second (or more) time. I have a simple lookup system for that in place.\nSo how does this run? Pretty good. Since most of you probably won't have the flag enabled, here are some inputs and outputs:\nINPUT:\nMy name is {{name:myname}} and my favorite color is {{ one of red or green }}. \nMy favorite season is {{ season }} and I enjoy {{ hobbie }}.\n\nMy name was {{ myname }}.\t\n\nOUTPUT:\nMy name is Alice and my favorite color is red. My favorite season is \nAutumn and I enjoy Reading. My name was Alice.\n\nAnd another:\nINPUT:\nA dragon once lived in {{ place }}, where she hoarded over a \ncollection of {{ plural items }} while feeling quite \n{{ sad emotion }}.\n\nOUTPUT:\nA dragon once lived in Paris, where she hoarded over a \ncollection of books while feeling quite melancholy.\n\nIf you want to give it a try, or just see the code, check it out below.\n\n      See the Pen \n  Test finding tokens by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nPhoto by Nong on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (10/25/25)",
		"date":"Sat Oct 25 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1761415200,
		"url":"https://www.raymondcamden.com/2025/10/25/links-for-you-102525",
		"content":"Greetings, programs, and welcome to another links post. The weather is finally beginning to turn here which doesn't necessarily mean cold weather, but days in the low 80s, mid 70s, which is an absolute relief from summer. Just in time for Halloween as well. Last year we unfortunately got rained out - this year it looks to be clear, and I can't wait to walk with my kids (and yes, I'll be in full costume myself). Here are some links for your reading enjoyment. Stay safe out there.\nOn DevRel and the Unnatural Act\nUp first is a stellar post by Leon Adato, HOW TO DEVREL: The Most Un-Natural Act. In his post, he describes how he not only got people to stop by his company booth but to also partake in a challenge that involved setting up a game with monitoring. I can attest to this working because I actually saw this at a conference, was immediately drawn to it, and did the challenge.\nEleventy Performance Tips\nI haven't blogged about Eleventy in quite some time, mostly because it &quot;just works&quot; and continues to be awesome. Alex Russel is a recent (I believe) user of Eleventy and shared a bunch of cool tips for how to get the most performance out of it. As an FYI, I found out about this article thanks to the excellent 11tybundle.dev newsletter.\nAbort Your Lack of Knowledge about Abort\nFinally, an older post, but &quot;Dont Sleep on AbortController&quot; is the first blog post on AbortController that really clicked with me. This is a great overview by Artem and credit goes to Stefan Judis for sharing the post on Mastodon.\nJust For Fun\nLast but not least, I'm not sharing a music video for once, but rather a fascinating project called WindowSwap. WindowSwap shows random videos taken from windows around the world. I can't stop watching this.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Generative Summaries with Chrome AI",
		"date":"Fri Oct 24 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1761328800,
		"url":"https://www.raymondcamden.com/2025/10/24/adding-generative-summaries-with-chrome-ai",
		"content":"Earlier this year (sigh, when I had a job), I built a demo using Chrome's built-in AI support to do something I thought was really interesting - progressively enhance product reviews to make it easier to see which were trending negative versus positive. It was a great example (imo!) of how AI support could enhance the experience in supported browsers without impacting the experience for others. That demo was on my mind this week, and it occurred to me that it would also be a great place to add summarization.\nThe Summarizer API is now fully released, for Chrome that is, and does not need a flag enabled or anything like that. I've blogged about this API a few times already, most recently when I used it to summarize comic books. The docs do a great job covering the API, but essentially it works pretty much just like the other ones:\n\nYou first check to see if the API exists at all ('Summarizer' in window perhaps)\nYou then check if it's available, or needs to be downloaded\nYou create an instance of the summarizer, with options for the type and length of summary and more\nAnd then pass your input and get the summary.\n\nI thought this would be a great addition to my &quot;product review&quot; page from the earlier demo. As a reminder, I used Gemini to generate a JSON array of reviews for a cat carrier. Here's part of the data:\n\nOk, let's get to it.\nHTML/CSS Design Changes\nMy initial review page simply took the reviews and rendered them out as a bunch of divs. I added a block on top for my summary:\n\nAnd made it initially invisible:\n\nMy thinking here was two fold - if the feature was available, I'd reveal the div (which gives a slight vertical 'shift' I'm not a fan of) and reveals the button. To use this API, you do need to have some form of user interaction, so I've tied it to a button. Also, not everyone is a fan of generative AI so I don't want to force it on them. (Just look at poor Windows Notepad.)\nThe JavaScript\nI'll share the complete demo below, but let me focus on the important bits of code. First off, a simple feature detector:\n\nWhen my application starts up, if the feature detector returns a good result, I'm going to reveal that div and add an event handler:\n\nThe doSummary function is where the magic happens, but honestly, this too is fairly simple:\n\nI initialize the Summary object with all the defaults, but as I mentioned, you do have control over the style and length of summarization. It's totally possible that better choices can be made here. Once done, I parse the Markdown (which could be plain text instead if I wish) and add it to the DOM. I also go out of my way to mark it as AI generated so folks know for sure where it came from.\nGiven my sample data, this is the result I get:\n\nThe Cat Carrier Ultra 1000 receives mixed reviews, with many users praising its sturdiness, ventilation, and secure locking mechanisms, while others report issues with assembly, quality control, and durability.\nSome customers found the carrier to be a game changer for vet visits due to its comfort and safety features, while others experienced problems with broken parts, sharp edges, or flimsy components.\nThe carrier's weight and design were also points of contention, with some finding it cumbersome to carry and others wishing for additional features like storage pockets.\n\nFrom what I can tell, this is a real good summary of the results. I'd love to try it on a few hundred just to see how long it takes. Here's the complete demo below, and for once, a good portion of you may be able to try it:\n\n      See the Pen \n  Review Summary by Raymond Camden (@cfjedimaster)\n  on CodePen.\n      \n      \nAs always, let me know what you think. Love it, hate it, concerned about the Saints ever having a winning record, leave a comment below!\nPhoto by Siarhei Palishchuk on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - Better Web Debugging",
		"date":"Tue Oct 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1760464800,
		"url":"https://www.raymondcamden.com/2025/10/14/boxlang-quick-tips-better-web-debugging",
		"content":"Today's BoxLang Quick Tip is incredibly quick, but also, really darn useful and something I had wished I knew earlier. The BoxLang MiniServer is a lightweight web server that you can use to test your web applications. I say lightweight but it's gotten some really good improvements over the past few months, including flexible URL rewriting. Today I'm going to talk about something that's going to be really useful to those of you who, like me, make mistakes from time to time. As with most of my BoxLang Quick Tips, you can skip to the video version at the bottom of the post.\nLet's consider a BoxLang web application that consists of a grand total of one file:\n\nI've got a function definition on top, then some HTML that calls it and prints out the result. As you can see though, I screwed up the function name there so an error is going to be thrown. If I fire up BoxLang's MiniServer and run the file, I get:\n\n\n\nOk, that's fairly clear, and given that our entire application is about ten lines of code, not difficult to correct. But in a real-world application with many files and much more going on, it could be a bit more difficult to track down. The error message is pretty short and doesn't give us much to go on. In production, this is actually safer, as revealing too much information could give attackers information they need to attempt a hack on your application, but locally, it sure would be nice to have more information, right?\nTurns out, there's an incredibly easy way to do this. The BoxLang MiniServer takes multiple different arguments at the command line, one of which, --debug, will turn on detailed error information. The docs on this weren't quite clear on that previously, but when I discovered this, I added that information to the reference to make it clear. Here's what happens when you run the same file with that flag enabled:\n\n\n\nThat is incredibly more useful, right? To be honest, I can't see ever not using that flag when running MiniServer locally, so make it part of your default workflow.\nAs an aside, of course BoxLang has proper error handling semantics with try/catch and more, and in particular, for web applications, you have multiple error handling routines you can add to your application to have 100% control over what happens when an error is thrown.\nOr do like me - and just write perfect code. (Ahem.)\nAnyway, I hope this is useful, and you can check out the video version below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Upcoming Speaking Engagements, and Code Break",
		"date":"Mon Oct 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1760378400,
		"url":"https://www.raymondcamden.com/2025/10/13/upcoming-speaking-engagements-and-code-break",
		"content":"With full-time employment being a bit up in the air this year, I've not submitted to nearly as many conferences as I have in the past (but hey, my rejections are at an all-time low!), but I've actually got not one, but two upcoming talks this month.\nFirst up I'll be giving an introduction to BoxLang to the Mid-Michigan CFUG user group on October 21st. You can find details here, https://mmcfug.org/. This meeting is 100% open to the public so if you've been curious about BoxLang from my posts here, you can get yourself a good intro later this month.\nNext, and this is somewhat new for me, but I'll be giving a presentation on something near and dear to my heart, developer relations and advocacy. I'll be presenting to the Tech Academy online on one of my favorite days of the year, Halloween. You can find the details, and RSVP, here: Tech Talk | Developer Relations and Advocacy: A Career in Explaining.\nCode Break\nI launched Code Break (sometimes &lt;Code&gt;&lt;Br&gt;) back in January 2024, and since then have run twenty-seven episodes of me exploring, building, and sharing, while many of yall provided feedback and kept me on my toes.\nAs I've said (maybe too much), this year and the job situation has made it difficult to find time to host the show, and I've been on an unannounced break for the past few months.\nI fully attend on starting the show again. I love that I had 'regulars' showing up and felt like it was really picking up steam before, well, life hit me with a curveball. It's coming back. I'm going to get a job. Life is going to go from incredibly chaotic and scary to &quot;normal&quot; chaotic and scary and I'll be in a much better place to be able to host the show. That being said, it will no longer be on Certified Fresh Events.\nLast month, Brian Rinaldi announced that CFE is shutting down after nine years. CFE was an incredible resource for the development community and I'm incredibly proud of what Brian worked so hard to build. Brian has been both a resource to the tech world and, more importantly, a dear friend of mine. He's the combo of being smart and a damn fine person which is rare.\nWhen I bring Code Break back, most likely I'll be starting a new Twitch stream. That will be new for me, but I absolutely want to continue what I started, and see those familiar faces in the audience again.\nI hope to see you there!\nImage by Irina L from Pixabay\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Links For You (10/12/25)",
		"date":"Sun Oct 12 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1760292000,
		"url":"https://www.raymondcamden.com/2025/10/12/links-for-you-101225",
		"content":"I blinked and somehow missed posting this a week or so ago. Time is, to quote the good Doctor, a bit wibbly wobbly. I'm currently watching a recording of the Saints/Patriots game and hoping we can follow last week's win with another, but I'm not sure. Of course, the game's been done for hours now but we've managed to miss the news so... we can still hope. Speaking of hope, as a reminder, I'm still looking for my next role, and if you know of a good developer evangelist/advocate position, please reach out! Alright, time for some links.\nWhen JSON Isn't...\nFirst up is a great look at how JSON may not be as universally parsed/transferable/etc as you may have though. In &quot;JSON is not JSON Across Languages&quot;, you get a great look at all the little edge cases that can impact transferring JSON between different languages and platforms.\nIn 2025, you still don't always need JavaScript...\nI feel like this is something that's been shared before, and heck, I've talked about this myself many times as well, but it's a useful reminder that many things we've used JavaScript for in the past are not actually necessary and can be done by simpler, less complex means. Check out &quot;You no longer need JavaScript&quot;, a great post focused on CSS improvements primarily but also some relevant HTML features you may not be aware of.\nNew Node.js Features\nSpeaking of &quot;you may not need JS&quot;, you may also not need that commonly used NPM package. &quot;15 Recent Node.js Features that Replace Popular npm Packages&quot; covers exactly what you think it does - recent improvements and additions to Node that may help you skip installing some npm packages. I knew most of these already, but definitely not all of them. I feel like I do a great job of keeping up with changes to JavaScript, but not so much with Node.js.\nJust For Fun\nI'm a huge Hatchie fan and was pleasantly surprised to find last week she has a new album coming out. Here's one of the first singles, enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "I Know What You Did Last Summer (with val town)",
		"date":"Wed Oct 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1759946400,
		"url":"https://www.raymondcamden.com/2025/10/08/i-know-what-you-did-last-summer-with-val-town",
		"content":"With Halloween a few weeks away, it's officially spooky season. My wife and I usually plan our costumes months in advance (mine's been ready since July or so) and we love decorating the house (and yard) with all kind of fun and darkly horrific decorations. Two years ago, I built a great Halloween-themed web app using Glitch and Cloudflare: I Know What You Did Last Summer (With Glitch and Cloudflare)\nUnfortunately, Glitch is no more. These things happen and I have to thank the Glitch folks for creating an incredibly cool resource and also helping people safely migrate off their platform. One of my projects was the Halloween project from two years ago. While the serverless function was running on Cloudflare, and I could have moved the HTML resources there, I decided to try another platform, val.town.\nval.town is a service I've played with a few times before (most recently earlier this year with my park ride timer app) but haven't really dug deep into it. Initially I only considered val.town as a cool serverless host, but you can host complete web apps there as well.  Their home page cheekily refer to themselves as &quot;Zapier for know-code engineers&quot;, which I just love. They've got a generous free tier so you can test things out. If you want to learn more, check out their docs and signup. (Tell em I sent you. For every 500 referrals I get a brownie point!)\nOk, so what web app am I talking about? I won't repeat the entire previous post, but the idea was somewhat simple.\nI used a Markov chain generating library to generate fun new horror movie titles. For example:\n\n\n\nThat design is pretty much the limit of my ability. Hitting reload just keeps generating new titles, like:\n\nPopeye's Body\nA Quiet Place: Welcome to Zombie Island\nAnacondas: The Long Walk\nIn a Zombie Apocalypse\nChildren of a Violent Nature\n\nThey don't always make sense, but that's part of the fun. In order for the Markov generator to work, I get a list of horror movies using the TMDB API. I hit their discover endpoint, filter to the horror genre, and specify English movies.\nThis gives me the data that's then fed to titlegen, a JavaScript library that generates titles based on inputs using Markov Chains.\nThat's what I built - now here's how I migrated it to val.town.\nServing Static Files\nI forget who pointed me to this, but while it was really easy to create a serverless function in val.town and get the endpoint, I wasn't sure how to do static files. This took all of two lines of code:\n\nAt this point, any resource in my project could be requested via a normal request. This was in my main.tsx and used the URL, https://raymondcamden--d31a59087a0d11f0917f0224a6c84d84.web.val.run, so I then added index.html and it worked right away. My HTML hasn't changed from the previous post and is pretty minimal - a place to display the title and a button. The JavaScript is also simple - hit the serverless val.town resource that gets my titles:\n\nThe only real complex aspect was the serverless function. As I mentioned, it was on Cloudflare, so I had to rewrite it a bit for val.town. The only tricky part was figuring out caching, but val.town has blob storage so that ended up being simple as well!\nHere's the entire method:\n\nYou can view the entire project on my val here: https://www.val.town/x/raymondcamden/randomhorrormovie. And to save you another click, I've embedded the demo below - enjoy!\n\nPhoto by Nika Benedictova on Unsplash\n",
		"tags":[
	        
            "serverless"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Document Q&A System with Google Gemini",
		"date":"Thu Oct 02 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1759428000,
		"url":"https://www.raymondcamden.com/2025/10/02/building-a-document-qa-system-with-google-gemini",
		"content":"Document summarization is a powerful and pretty darn useful feature of generative AI, but a proper &quot;question and answer&quot; system can really enable users to interact with a document. This is why you see various document viewing apps, like Acrobat, adding these features to their programs. I thought I'd take a look at building such a system via a simple web app to see how difficult it would be, and honestly, it wasn't that bad. Having this in your own web app, versus an external vendor, gives you more control over the experience as well. Here's what I built.\nThe Stack\nThe web app lets you drag and drop a PDF into the page, it then renders a preview of the PDF on the left and gets the summary on the right-hand side. After the summary is generated, you can then ask various questions via a chat interface.\n\nFor the web app, I'm using Flask, a Python web framework.\nFor displaying the PDF, I'm using EmbedPDF. This is a free tool to render PDFs on the web and it's dang cool. The only issue with it currently is a lack of documentation, but that's coming very soon.\nOnce again, I'm using the Google Gemini API for my generative AI features. I know I sound like a broken record, but it's honestly been the easiest GenAI system I've used and with a free tier, it's great for building proof of concepts like what I'm sharing today.\n\nThe Application\nHere's how the application looks when you initially start it up:\n\n\n\nDragging a PDF onto the left side will fire up EmbedPDF to render it:\n\n\n\nIn the screenshot above, you can see it mentioning that the summary process is starting. Depending on the size of your document, this can take a bit of time, but once done, it's rendered automatically:\n\n\n\nI used my resume for input, and the summary was spot on. The input/chat button UI is automatically enabled at this point, and you can start asking questions. For example, where did I work in 2020? The response was:\nIn 2020, Ray worked at HERE Technologies as a Lead Developer Evangelist (2019-2021).\n\nAccurate! How about, &quot;Does Ray have skills in C#?&quot;\nBased on the provided resume, Ray (Raymond Camden) does not list C# as one of his skills. \nHis listed skills are:\n\n* Developer evangelism and advocacy\n* Technical writing and editing\n* API development and usage\n* Generative AI\n* JavaScript and the Web Platform\n* Node.js\n* Python\n* Public speaking\n\nHe also mentions working with Java and Node.js in his experience at American Express, \nand ColdFusion in his publications, but C# is absent.\n\nI then asked, &quot;does ray live in the united states?&quot;. I don't say I do in the resume, but I do list my city and state. The response was:\nYes, Raymond Camden lives in the United States. His resume states his location \nas Lafayette, Louisiana.\n\nThe Code\nI'm going to share all the code via a GitHub link at the end, so I'll focus on the most interesting parts. On the front end, it's mainly just JavaScript to handle DOM updates and such. The only real cool part is the &quot;drop and render PDF&quot; aspect via the EmbedPDF library. Here's a bit of that code to show how it's done:\n\nAfter the PDF is rendered, I then send it to the backend. In Flask, I'm just doing this:\n\nRight away little red lights should be flashing in your head, alarms going off, people panicking in general...\n\n\n\nObviously in a &quot;real&quot; app, you would have multiple users sending up files. You don't want any input dropping in web root (although in Flask, this wouldn't load) nor do you want to use the same filename. Also, you could easily take the binary data of the PDF sent and pass it immediately to Gemini for uploading. You would then need to associate the corresponding file object with the current user.\nOncee stored and the front-end gets that Ok, I then fire off a network call to do the summary:\n\nI've got a Gemini Python package that handles making these calls. I'll share it in a second. Once the summary is returned and chat is enabled, this is another simple Flask route:\n@app.route('/chat', methods=['POST'])\ndef chat_pdf():\n\tif request.is_json:\n\t\tdata = request.json \n\n\t\tprint(f&quot;Sending {data.get('input')} to gemini&quot;)\n\t\tresult = gemini.chat(PDF, data.get('input'))\n\t\treturn { &quot;result&quot;: result }\n\nThe Gemini package handles uploading the document, doing the summary, and ad hoc chat requests:\n\nIt's pretty short as the Gemini Python SDK handles most stuff. I'm missing error handling of course but... nothing goes wrong, ever, right?\nWhat Else?\nSo as I promised, here's the GitHub link to the complete application: https://github.com/cfjedimaster/ai-testingzone/tree/main/pdf_qa\nThere are multiple ways this could be improved. For example:\n\nIf this tool were built for a particular company, you may have a particular type of document you will almost always be working with. That information could be fed to the AI model via a system instruction to help it answer questions better.\nThe chat log could be made exportable and shareable.\nOf course, if you want to get",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a GenAI Document Summarization Workflow in ColdFusion",
		"date":"Wed Oct 01 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1759341600,
		"url":"https://www.raymondcamden.com/2025/10/01/creating-a-genai-document-summarization-workflow-in-coldfusion",
		"content":"So this post comes from - I'm mostly sure - me forgetting to show a simple, but powerful demo at my presentation recently at the\nColdFusion Summit. It's nice and simple, but pretty darn useful so I decided to write a quick blog post about it and highlight the code.\nWhat's a Document Workflow?\nSimply put, a document workflow is any process you would use to handle incoming documents. As an example, and one I've built many times over the past few years, you can use a workflow to convert all your incoming documents into PDF for easier handling. pdfRest has APIs for this and I'll likely share a demo of them soon. (ColdFusion itself can convert HTML, PPTX, Word, and some image types to PDF natively.) In my demo, the workflow is simple:\n\nScan a folder of PDFs\nFor each PDF, see if we have a summary file stored\nFor each PDF without a summary, use Google Gemini GenAI APIs to create the summary\nStore the summary\n\nIn my case, I'm using the file system to store the summary, but if you've got a database table that keeps track of your document resources, that could be used as well.\nThe Code\nThe first part of the process simply finds the PDFs:\n\nNow, I need to loop over each file, and for each, I look for a file with the same name, but a txt extension itself. This represents the cached summary:\n\nOk, now for the fun part - creating the summary via Google Gemini. The first step is to upload it to Gemini:\n\nThis is done via this function:\n\nYou'll notice a heck of a lot of headers there. In pretty much all aspects of Gemini's APIs, this one was the most trouble to figure out. You can read more details of that back in my post from last year: Using Google Gemini's File API with ColdFusion\nThe net result of this is that Gemini will have our PDF (for a short time, and we can force a deletion if need be) that can be referenced in prompts.\nWhich of course, brings us to that part:\n\nThe promptWithFile method wraps calls to Gemini:\n\nUnlike the API to work with files, the Gemini REST APIs are incredibly simple. The body of the call is perhaps a bit complex - you need to correctly include your prompt and a reference to the file, but it's relatively simple once you've done a few calls. The last bit is to simply return the result after parsing it from JSON.\nFinally, I store the result:\n\nThe md2HTML command simply converts the Markdown result to HTML. I covered this in yet another blog post from almost exactly a year ago: Parsing Markdown in ColdFusion\nThe final result is a web page showing file names and summaries, but honestly, I'd expect something like this to be run on a schedule with minimal to no output. As an example of the output, this is what it said about my resume:\n\n\nThe provided document details the extensive career of Raymond Camden, an accomplished Developer Evangelist and Advocate with a strong focus on building developer communities and promoting successful API adoption.\nKey Highlights:\n\nDeveloper Advocacy and Evangelism Expertise: Camden possesses a proven track record in fostering relationships with developers, ensuring their success, and acting as a trusted voice within technical communities. His responsibilities consistently involve advocating for developers, gathering feedback for engineering teams regarding API design and usability, and contributing to the overall developer experience.\nProlific Content Creation and Thought Leadership: A significant aspect of his work involves creating a vast array of educational and inspirational content. This includes writing over six thousand blog posts, publishing multiple technical books (e.g., on Vue.js, Serverless Applications, Static Sites, Apache Cordova, and ColdFusion), and delivering over 30 presentations and workshops (at Adobe) and 20+ (at HERE Technologies) to global audiences.\nDemonstrated Impact and Growth: At Adobe, he significantly increased developer signups by 33% through new documentation and revamped resources. He introduced over 100,000 developers to APIs via his content and presentations. He also played a key role in launching new API offerings and frameworks at Foxit, Adobe, Auth0, and IBM.\nTechnical Collaboration and Mentorship: Camden frequently collaborates with engineering and product teams to guide new releases, improve API frameworks, and refine onboarding processes. He has also actively mentored other developer evangelists, helping expand and develop teams.\nDiverse Industry Experience: His experience spans various major technology companies, including Foxit, Adobe (in multiple roles), HERE Technologies, American Express, Auth0, and IBM, working with diverse platforms and technologies from web and mobile development (HTML5, Apache Cordova) to serverless (OpenWhisk), Node.js, and API services.\nTechnical Skills: His skill set includes developer evangelism and advocacy, technical writing and editing, API development and usage, generative AI, JavaScript, the Web Platform, Node.js, Python, and public speaking.\n\nIn essence, Raymond Camden is presented as a highly",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "generative ai"
            
		]

	},

	{
		"title": "ColdFusion (2025)'s CFOAUTH Tag",
		"date":"Tue Sep 30 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1759255200,
		"url":"https://www.raymondcamden.com/2025/09/30/coldfusion-2025s-cfoauth-tag",
		"content":"Back in May of last year, I wrote up a blog post on ColdFusion's oauth tag. This was based on a feature from way back in ColdFusion 11 that I thought I'd take a look at to see if it was useful. I'm not going to repeat the entire previous blog post, but in general... it was almost something I'd recommend.\nThe tag did a good job of handling creating the right oauth link for you. So you could (after setting stuff up with your provider of course) drop the tag on a page, and when the user hit it, they would be prompted to login with the third party provider. When returned, the tag would handle getting the access token and such and giving you a nice little structure of data for you to use.\nI generally dislike my server-side code from doing anything on the client-side, but this felt like a good compromise in regards to what it was doing. That being said, I ultimately could not recommend using the tag as it failed at two crucial aspects:\n\nIt did not return the expires_in value so you knew how long your access token was valid.\nIt did not return a refresh token, even if you used the right parameters to get that.\n\nI filed a bug report for this and moved on. Now it's over a year later, ColdFusion 2025 was released in the meantime, and it looks like everything was fixed... despite my original bug report not being updated.\nFrom the release notes:\n\ncfoauth changes: The cfoauth tag has been updated to updated to support enhanced workflows and configurations. This release also introduces Microsoft as a new auth type along with Google and Facebook. View the cfoauth tag doc for more information.\n\nCool! So... does it work better? Absolutely. As I said, I'm not going to repeat everything from the previous post, but this code now works as expected in terms of getting a refresh token and having the expiration value:\n\nThat auth struct now contains what I said above, expires_in and refresh_token:\n\n\n\nCool! Even more interesting, according to the reference doc for changes to this tag in CF2025, you can also refresh your access token using the refresh token via the tag, saving you a bit more work.\nI hereby now approve usage of this tag. (Allow me to pretend that I somehow dictate how folks use ColdFusion. ;)\nPhoto by Kaffeebart on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "New Web Thing - Wander",
		"date":"Fri Sep 26 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1758909600,
		"url":"https://www.raymondcamden.com/2025/09/26/new-web-thing-wander",
		"content":"I've built a few web games in the past (IdleFleet and Cat Herder are two examples), but what I'm sharing today doesn't really fit into the category of a game. This is going to sound terribly pretentious and I apologize in advance, but what I'm sharing today is more an &quot;experience&quot; for lack of a better term. It's part technical exploration, and part cathartic dumping, and just kinda weird. But honestly, the web needs more weird and I'm happy to contribute to that.\nAs with most of the things I've built, I think it's more interesting if you experience it first before taking a look at what's behind it, so with that in mind, click this to open up what I built in a new tab: https://cfjedimaster.github.io/webdemos/wander/.\nAs a warning, there's no help text, no explanation. It just is. So be prepared to be confused a bit.\nWhat is Wander?\nWander is a few things. Technically, it's a procedurally generated landscape that you can explore. The &quot;world&quot;, as it is, is defined in a series of &quot;plates&quot;, where each plate is what you see on screen currently:\n\n\n\nA plate consists of a series of open spaces and obstacles (#), where the subject (@) can navigate using keyboard controls. (No WASD, sorry, just arrows.) I'm using the term &quot;subject&quot; as this isn't a game. (Although you'll see me user the word player in code.)\nThe plate is just a 2D array where I fill the inner portion with obstacles. Why inner portion? As you navigate around the plate, when you get to the edge and continue, a new plate is created. If I didn't keep the edges clear, it's possible you would enter into an obstacle. I also didn't want to simply avoid where the player enters as it's possible you would be 'wrapped' in obstacles and couldn't explore further.\nThis is less maze and more... landscape with things in it. Yeah, vague, but that was kind of the mood I was going for.\nThe plates themselves are persisted in the browser via IndexedDB. When you start, I generate and persist one for you, and as you explore, more are added. I keep track of their location such that if you return in the direction you travelled, and then come back, the same one is loaded.\nIn theory, this means you could explore until the browser starts blocking your storage calls and considering how small each plate is, that would be a very, very long time.\nOh, and I'm using Alpine.js to handle the UI/UX. Let's dig into the code a bit, eh?\nThe World\nFirst, here's the initial data (most of it) and init:\n\nThe call to setupDb initializes my IndexedDB database. About two years ago I blogged about IndexedDB and Alpine.js if you want a good introduction to the topic. Here's my database setup:\n\nOf note - I've got one object store, plates, and the unique identifier is the location value, i.e. where the plate sites in the world.\nNow things get a bit complex. The init function gets the initial plate located at 0,0. This will attempt to load a persisted plate, and if it doesn't exist, generate a new one.\n\nHere's load plate:\n\nAnd if returns null, here is how I generate a new plate:\n\nFinally, every new plate gets stored based on its location:\n\nRendering of the plate is done over in HTML:\n\nSo, movement was kind of fun. You can see the handlers in the HTML above. Of special note is the use of window to tie the event handlers to the Window object. Without that, you would need to click on the table to focus it in order for them to work.\nThe Atmosphere\nOk, so there's a bit more code to talk about, but this is also the part where I get a mushy. Feel free to stop reading now, I wouldn't blame you.\nThis year has been, without a doubt, the second worse year of my life. I cannot adequately explain the feelings I've had being laid off, twice, and trying to land a job in a landscape that's incredibly bad. I've been to therapy, but not lately, and I should probably return, but I'm also contracting now and any time away from work is time spent not earning and I'd have difficulty focusing with that in mind. It's not all bad! I've had some incredibly good times this year as well. It's just... a lot.\nPart of the appeal of writing Wander is to give a digital form to what I'm feeling. Without a job I feel aimless, worthless, and worse. I truly believe that as a father, and husband, I am not worthless, but those nice sane thoughts have to deal with a multitude of other not-so-sane emotions fighting for their part of my internal CPU.\nMatthew Inman (creator of the Oatmeal) has a great comic about intrusive thoughts. This led to a fun as hell game, Horrible Therapist, but the comic itself really spoke to me.\nI wanted to give voice to some of those thoughts, some of which, ok many, are somewhat dark, but also hopeful, silly, and honestly just a bit weird/random. To reflect this in Wander, I added whispers. In HTML, it's just this:\n\nA bit of CSS is used to fade out the div such that the content at top is dimmer. When Wander starts, a 'heart beat' function is kicked off which will randomly disp",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Testing with BoxLang",
		"date":"Sat Sep 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1758391200,
		"url":"https://www.raymondcamden.com/2025/09/20/testing-with-boxlang",
		"content":"One of the fun things about immersing myself in BoxLang these past few months is my expose to other products from Ortus. Most recently, I've been doing some contracting with a client that makes use of ColdBox, which for my non-CFML readers out there is probably the most well known, and probably most popular, framework for building enterprise web applications with ColdFusion. As part of that work, I've been integrating TestBox, a testing and mocking framework that works well with ColdBox, but also (somewhat recently, I think a month or so now), supports BoxLang as well. For the most part, &quot;it just works&quot;, but as I was new to it, I did run into a few small issues I thought I'd share a simple walkthrough of how to get started.\nMy &quot;App&quot;\nCan you see the giant &quot;air quotes&quot; up there? By &quot;app&quot;, what I mean is I created the simplest possible BoxLang web app I could think of. I gave it an Application.bx file that just had a name:\n\nAn index.bxm with just a hello message:\n\nAnd then a class that I wanted to build tests for, services/catService.bx:\n\nInstalling TestBox\nIf you read (and you should!) the BoxLang TestBox guide, it demonstrates how to add TestBox to your app like so:\nbox install testbox\n\nThe box command is CommandBox, a core CLI app for most products from Ortus, so you'll need to have that installed first. Running this will install TestBox under your application in a folder appropriately named, testbox. You can, as far as I know, completely ignore this folder.\nSetting Up Your Tests\nBefore we go on, one quick note. The TestBox guide mentions your root application configuration and shows a sample, but there's nothing in here you need to copy. If you have an existing Application.bx file, it's fine. I can say that it's important to note that some of your settings here may be important in your test folder Application.bx I'll be getting to in a second. Things like mappings and data sources may need to be copied over. This was my experience working on the large ColdBox site and the same advice may apply here, but it's not noted in the BoxLang docs... yet. Just keep it in mind.\nCreate a tests folder under your application. If you look at the project structure part of the guide, it demonstrates a tests folder with multiple subdirectories to help organize all your tests:\n\n\n\nI was going to build one simple unit test for my cat server, so under the tests folder, I created specs/unit and added catServiceTest.bx. The guide has a large sample test, but here's my simple one:\n\nThe most important bit is up on top, the extends, which brings in the core TestBox class. TestBox supports BDD (Behavioral Driven Development) and TDD (Test Driven Development), which as you may know approach testing differently. I've always leaned towards the BDD style, so I followed that. Even if you've never written a test before, you can figure out what's happening here. My top level describe block is going to wrap a set of tests for my cat service code. In a real app with lots of services and such, I could imagine multiple of these. My beforeEach will run before every test in my file and in this case simply instantiates my cat service object.\nMy actual test, in the it block, calls the getCats method of the service and tests to see if it's both an array and has at least one result. The list of toX methods is quite extensive. It took me a little while to find the reference for this, but you can see the complete list here: https://apidocs.ortussolutions.com/testbox/6.0.1/testbox/system/Expectation.html\nThe next thing I did was setup the Application.bx file for my tests folder, and for that, I copied the one in the docs:\n\nOnly thing I'll really note here is the setting to allow a long request timeout which makes sense for running tests.\nI then added runner.bxm, this is a web-based runner for your tests and goes in the tests folder as well. At the time I'm writing this, I had noted an odd bug in the docs that I've yet to correct because I'm unsure of what should have worked versus what I saw. For the time being, use the code I'm about to show, and note that if you read this in the future (hey, do I have a job yet? please tell me I've got a job), you can use this version:\n\nThe change here is to add system to the path before TestBox and semicolons on the params. If you don't care about the why, that's fine.\nOk, in theory, this is the bare minimum, so let's actually run the tests.\nTesting Your Tests cuz Testing is Good - so Test\nYou've got two options for running tests. The first is the CLI option. This is really flexible and lets you run all your tests, or just a specific one. For me, I literally just ran ./testbox/run and got this:\n\n\n\nIf you want a web-based version, remember we created tests/runner.bxm, so opening that up in the browser gives you a nice visual result:\n\n\n\nNice and simple, right? So to complete the puzzle, let's actually work on a new feature, a service method that gets one cat. I'll start off writi",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My Experience Asking GenAI to Design My Blog",
		"date":"Tue Sep 16 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1758045600,
		"url":"https://www.raymondcamden.com/2025/09/16/my-experience-asking-genai-to-design-my-blog",
		"content":"What was my experience using GenAI tools to design my blog? Well, you're looking at it! As I mentioned last week, my new design came from one of my experiments using GenAI to help me design a new theme, but I wanted to share a bit more about the experience when I had time, and that time is now.\nAbout two or so months ago, I had the idea of testing out GenAI to create themes for small web apps. While my blog is actually huge (near seven thousand pages), design wise it's basically:\n\nA home page\nA post page\nA &quot;everything else&quot; page\n\nThat's just three basic pages, all sharing a main layout with slight differences in what's the main content of the page. I thought it would be fun to give multiple different GenAI tools a chance to spec out a blog theme to see what would happen.\nThe Process\nFor my tests, I made use of the same prompt every time:\nI want to create a new theme for my blog. My blog is developer-centric. \nMy theme should consist of one main HTML file, one CSS file, and minimal \nif any JavaScript. (You can create an empty JS file and include it \nthough if you wish.)\n\nAbsolutely NO framework must be used. Absolutely no React, or Vue, or \nanything, and no build process. I literally just want static HTML and CSS. \n\nI want a dark green centric theme as green is my favorite color. \n\nThe theme should be responsive and look on mobile devices.\n\nThe main page you create should include a title for the blog and 10 \nblog entries listed in reverse chronological order. Each blog post \nshould include a date.\n\nThe top level menu should include a links to: Home, About, Speaking \n(I'm a frequent speaker at conferences), Subscribe. You can also \ninclude a link to a search page, or perhaps a small search field. \n\nFinally, using this template, create a page for an individual blog post. \nThat template should include a date and a list of categories associated \nwith the blog post. \n\nI tried to be specific in terms of what I wanted as well as what I didn't want.\nFor the tools I used, I wanted something that integrated into my editor and could write to the file system. I didn't always do this as one solution I'll share below made use of the CLI.\nOnce the initial results were created, I'd open them up and see if I wanted any tweaks. To be clear, not in regards to make the design 100% complete, just initial reactions and things I thought should change immediately.\nFinally, for each of these (except one), I made a video, and that's primarily what I'll be sharing below. Ok, let's get to it!\nCopilot in Visual Studio Code\nMy first attempt made use of Copilot in Visual Studio Code. This is baked into VSC now with no need to install anything extract, and you get a decent free tier with a GitHub sign-in I believe. Here's how it went down.\n\n  \n    Play Video\n  \n\n\n\n\nYou can find the resulting code here: https://github.com/cfjedimaster/gen_my_new_blog_theme/tree/main/copilot_in_vsc\nAnd you can see it in action here: https://cfjedimaster.github.io/gen_my_new_blog_theme/copilot_in_vsc/index.html\nAll in all not bad, but not my favorite shade of green.\nGemini in Visual Studio Code\nNext was Google Gemini in Visual Studio Code. Nearly all of my experience with GenAI has been via Google Gemini, so it has a special place in my heart. This one had a few issues while recording though.\n\n  \n    Play Video\n  \n\n\n\n\nYou can find the code for this version here: https://github.com/cfjedimaster/gen_my_new_blog_theme/tree/main/gemini_in_vsc\nAnd demo it here: https://cfjedimaster.github.io/gen_my_new_blog_theme/gemini_cli/index.html\nCursor\nNext was Cursor, an editor built on the open source portion of Visual Studio Code. You can see how it went down here:\n\n  \n    Play Video\n  \n\n\n\n\nThe code may be found here: https://github.com/cfjedimaster/gen_my_new_blog_theme/tree/main/cursor\nYou'll note it did not listen to my instruction to create a blank JavaScript file, but to be honest, this is pretty much what I do in a lot of apps when starting anyway:\n\nYou can demo this one here: https://cfjedimaster.github.io/gen_my_new_blog_theme/cursor/index.html\nThis was almost the design I picked.\nGemini via CLI\nOk, so this is cheating a bit, but it was suggested I give the Gemini CLI a try and I figured, why not. As I said, I'm a huge fan of Gemini but I had not tried out the CLI. Here's how this one went:\n\n  \n    Play Video\n  \n\n\n\n\nYou can peruse the source here: https://github.com/cfjedimaster/gen_my_new_blog_theme/tree/main/gemini_cli\nAnd see it in action here: https://cfjedimaster.github.io/gen_my_new_blog_theme/gemini_cli/index.html\nNot surprisingly, it's pretty similar to the Gemini in VSC one.\nClaude\nFor my fifth attempt, I used Claude, again in Visual Studio Code:\n\n  \n    Play Video\n  \n\n\n\n\nThe generated code can be found here: https://github.com/cfjedimaster/gen_my_new_blog_theme/tree/main/claude\nThe resulting blog can be seen here: https://cfjedimaster.github.io/gen_my_new_blog_theme/claude/index.html\nAnd the Winner Is...\nNone of the above! Ok, technically it's ",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Integrating Algolia with BoxLang",
		"date":"Mon Sep 15 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757959200,
		"url":"https://www.raymondcamden.com/2025/09/15/integrating-algolia-with-boxlang",
		"content":"I've been using Algolia for my search on this blog for years and absolutely love the service. At a high level, Algolia is a hosted search service that lets you easily create search indexes (think of it as a search optimized version of your content) while also providing easy libraries to add a search UI to your page itself. If you type in the search bar on top and perform a search, you'll see this yourself. My site here is static, all simple flat files with no database, so a solution like Algolia is vital. I thought I'd take a look at integrating Algolia's REST APIs with BoxLang and was able to build a quick demo in less than an hour. Here's what I did.\nInitial Setup\nAs I said, I've been using Algolia for years, but if you're new to the platform, you'll have to sign up of course. Their pricing page documents the particulars, but you'll not have to pay a thing to test, and in fact, I don't pay for Algolia as well. Their free tier includes up to one million records (yes, a million) and ten thousand searches, so you can absolutely consider Algolia for a whole range of sites.\nOnce you've got an account, you need to hop into the dashboard and create an Application, which is a top level container for your account (you can have more than one, but one is enough for testing) and then beneath that, an index. Roughly, an index is like a database. I've got one for my blog, and I made one for this demo. Each index has multiple different things you can configure for optimal searching and such, but the out of the box defaults are fine for quick testing.\nFinally, in your account settings you can get your keys. You will have a &quot;Search API Key&quot;, which can be shared publicly as it just does that - search - and an &quot;Admin API Key&quot; which does CRUD on your index. You'll need that value for the code I'm going to show.\nThere's a lot more to Algolia than I'm showing today, and I'd recommend checking out the docs for a deeper look. I'm just going to do the bare minimum to demonstrate an integration with BoxLang.\nMy Content\nIn order to have something to search, I used my blog content here as a base, specifically, the Markdown files for content from 2025. These Markdown files have YAML-based front matter on top and text beneath them.\nHere's an example where I removed a bunch of the content to keep it shorter. The frontmatter on top is like metadata for the content:\n\nFor this demo, the content covers 118 different blog articles. All high quality, super serious, important blog posts.\nWorking with the Index\nThe first thing to know about Algolia and your index is, you need to ensure you keep a one to one connection between the data on your side and the data in the index. There's multiple different ways of handling that, but for this example, I'm going to keep it rather simple and use a BoxLang script that will:\n\nRead and parse my Markdown files\nUse the Algolia REST API to update the index\n\nThe Algolia REST API covers the full aspect of the all the things you can do, and SDKs exist for other languages if you want to skip doing direct calls.\nTo keep things simple, we'll make use of &quot;Add a new record&quot; endpoint which nicely handles adding or updating values based on a primary key (more on that in a moment). This makes our logic much easier as we can simply gather our data, send em to Algolia, and let them worry about if the record is new or not.\nI created a script, source_index.bxs, that handles this. The first portion handles getting and parsing my content:\n\nNote that I make use of BoxLang Yaml module to handle parsing the frontmatter. The net result of this block is - all of my blog posts will be returned in an array consisting of objects that have data and contents keys. Note that I'm using the HTML version of my content and it may be better to use the simple text version instead.\nNow for the fun part - getting this to Algolia. I read in my credentials from the environment:\n\nThat last statement is how the REST API works, your endpoint matches your application ID value. Next, we send to Algolia:\n\nSo, for each blog post, I make a new record object. If I don't give it a primary key in the form of the objectID value, Algolia will assign it, but my blog content has a permalink value that is unique and works perfectly for this. I also pass the title, date, content, and link. Note that Algolia records max out at 10K characters each, so I use the first 9K which leaves more than enough room for the other properties.\nSending it to Aloglia is a simple matter of hitting the endpoint with the data.\nWhat's cool is - at this point you can go into your Algolia dashboard, confirm the records were added, and immediately start doing searches. Here's a screenshot from mine:\n\n\n\nI cannot stress how useful this is. If you don't get good results here, there's no point going forward. You can easily clear the index from the dashboard, tweak your code, and test, all before writing anything to search the content in your app. Here's an exam",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (9/13/25)",
		"date":"Sat Sep 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757786400,
		"url":"https://www.raymondcamden.com/2025/09/13/links-for-you-91325",
		"content":"Yeah, I'm not even going to try to comment on this past week. I can say I had two interviews, which I think went well, and I finished some demos I've been working for a while, so that's a positive. Outside of that, just want to not think too much about the state of things and focus on sharing awesome, nutritious links for you to enjoy.\nIntl and Segmenters\nYou know me and you know I love the Intl API, so first up is a look at getting accurate text lengths using Intl and the Segmenter feature. This post comes to us from Sangeeth Sudheer with the nicely named &quot;Automagic&quot; blog.\nFlight Data to the Database\nNext up is an incredibly cool post that demonstrates streaming real-time flight data from MS Flight Simulator into InfluxDB 3. I've been a fan of Microsoft's Flight Sim since version 1 or so, but I had no idea you could hook into the data being generated from it. Heather Downing  does a great job explaining all the details here, and it's definitely worth a read.\nAn Intro from Doctor JavaScript\nOk, &quot;Doctor JavaScript&quot; isn't a name he goes by, but it just suddenly made sense to me, so I'm going with it. I've shared posts from Dr. Axel Rauschmayer many times here and he easily the world's expert on JavaScript and has an incredibly deep understanding of the language. Last month, he began a new series for people learning web development that I recommend checking out, even if you've been in the biz for a while. As of today, he has 12 comprehensive posts covering multiple different aspects. Check it out!\nJust For Fun\nLast up is an incredible song by a new artist (new to me anyway), John Glacier. That's not the artist's real name, but is instead the stage name of a rapper from England. You can read more about her here, an article from 2022, so I was definitely late in discovering her. Anyway, this track is absolutely incredible:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Chrome AI to Summarize Comic Books",
		"date":"Fri Sep 12 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757700000,
		"url":"https://www.raymondcamden.com/2025/09/12/using-chrome-ai-to-summarize-comic-books",
		"content":"A few weeks back, I blogged about analyzing comic books with gen ai, and honestly, it worked really darn well. I extracted the pages with Python, and send them to Google Gemini to create the summary. I was naturally curious to see if this could be done entirely on device, using Chrome's AI support. Here's what I found.\nFirst, a reminder - a few days ago I updated my web-based comic book reader and described that process. The code I'm sharing today is built upon that first application, so if you missed that post, I'd strongly suggest reading it first. (And if you don't want to miss any of my posts, don't forget to subscribe!).\nHow It Works\nAlright - so given that we've got a way to handle zip and rar based e-comics, how do we integrate AI into the picture?\nRemember that my web demo handles you dropping a .cbr or .cbz file onto the web page, checking the contents of the archive, and then using the respective library (zip.js or Unarchive.js) to extract one image a time. My code figures out the total number of pages (images) and uses simple buttons to let you navigate through them. I say simple, but honestly it was a bit complex to set up, so again, I definitely recommend reading that previous post.\nOn the AI side, we're going to make use of two core features:\n\nFirst, the Prompt API, and specifically, the multimodal capabilities that allow us to examine images. The context window doesn't allow us to pass all the images in a comic book, so we'll use the API to analyze each page of the book, one by one, and generate a summary of what's on the page for each.\nGiven our pages are being turned into summaries, we can use the Summarizer API to create a summary of summaries, which in theory, cover the entire book.\n\nLet's dive into the code.\nKicking off the Process\nAs I've mentioned multiple times now to read the previous post, I'll skip over the stuff covered there, and instead focus on the AI centric part. The displayComic method is what's used after the zip or rar has been parsed. It sets up the UI and such and enables navigation. In the first iteration, my zip and rar methods returned a &quot;reader&quot; function that's used to display the image. For my AI work, I knew I needed the raw binary data (or think I did, I could be wrong there), so I modified both functions to return a 'binreader' function.\nIn handleRar, it's the getBin part:\n\nIn handleZip, it's getBlob:\n\nLet me say right away that if I were shipping this to production, I'd absolutely look at simplifying this such that each method returned one helper function that could be used both for display and AI.\nWhen displayComic is done setting up UI and such, it kicks off a call to start the fancy AI process: \thandleAISupport(pages, reader, binreader);\nThis first method is all about checking for support and setting stuff up, let's take a look:\n\nThe first half checks for the Prompt AI and if it can, sets it up. The prompt I used came about from multiple iterations of trying to force the model to focus on the content, what's going on basically, and not comment on the art style and such. It... worked... mostly.\nThe second half focused on the Summarizer API. I specified a tldr style summary, long form, and in plain text.\nNote for both I'm updating the UI if a download is required as it could take a while to get those models the first time.\nNow for the real work! Let's look at the beginning of doAISummary:\n\nAs a quick aside, I passed reader to this function and didn't use it. I freaking love that feature of modern editors. Did I fix that, no, but I love it. The method begins by giving some UI feedback. This process is not zippy, so it's going to be important. I'm going to be doing a summary for each page, so I initialize an array for that.\nThe next line is incredibly important. In my tests, when I'd loop over each page and ask for a summary, I quickly found myself overfilling the context window of the model. In my testing, this did not throw an error in my console and led to a lot of debugging. My Google contacts can't reproduce the issue, but the work around was quite simple - just cloning the session (remember that points to the Prompt API instance). You'll see me user this again in the next block:\n\nI iterate over each page (to a max of 50), and also check my usage to see if I'm nearing quota. If I get to over 75 percent, I nuke my copy of the session with a new clone. I pass the image to the prompt to get the summary. I forgot to share this earlier, but here's my schema I used to try to get Chrome to give me a simple paragraph:\n\nFinally, note the the UI update. In my testing, it took about 2-3 seconds per page. That's not too bad, but for a 'regular' comic of 30-ish pages, that's a good minute long process, so I wanted to ensure the user knew things were being worked on.\nThe last portion is rather simple:\n\nSo how well does it? Eh.... kinda ok. It's definitely not as good as the pure Gemini example. It's promising, but probably needs more tweaking to improve the qual",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Recognizing Abundant, Deficient, and Perfect Numbers",
		"date":"Thu Sep 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757613600,
		"url":"https://www.raymondcamden.com/2025/09/11/recognizing-abundant-deficient-and-perfect-numbers",
		"content":"Ok, this post falls into the &quot;I'll never actually use this again&quot; category, which frankly, my normal readers know happens all the time, but it was a fun little diversion and a reminder of why I used to love math so much.\nYesterday I found out that one of my kids' homework was to look at a set of numbers and determine if they were abundant, deficient, or perfect. Right now you are probably (at least I know I was) asking, &quot;what in the actual heck is that???&quot;\nA quick bit of Googling turned up this explainer that basically boils it down to a simple principal.\nGiven a number, find all the divisors of that number, excluding the number itself, and add them up. If the result is less than the original number, it is deficient. If the number is equal, it's perfect. Finally, if the sum is over the input, it's abundant.\nSo for example, 6 is considered perfect because the divisors, 1+2+3, equal 6 when added together. 5, which only has a divisor of 1 (remember, you exclude the number itself) is deficient. 12 is abundant (1+2+3+4+6 == 16).\nYou can read more, and see more variations, on the Encyclopedia.com article if you would like. Honestly, I see absolutely see use for this, but I thought - why not whip up a quick demo of this in BoxLang\nVersion One\nThe first version I built uses one main method to get divisors, minus the input itself, with a special case for 1:\n\nThe result of this is an array of divisors. I then built one function each for the three types of numbers:\n\nFairly simple, right? I whipped up a quick test:\n\nAnd you can try this yourself below.\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"700\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJzNUs9rgzAUPutf8dEyiHR0uh1tOgY9j93HDhqT7oGNLpoiDP%2F3RautKLs3h0Dee9%2BP9%2FKU1aKmQuMo6wOdqSpMxXSAXx%2Fu5IVI8m02xMHx%2BRX7HimmwTlHFMDI2hqNsSTuYaowYBcsOVAUg7DjEJJy0keWpJ3E03Pg4pvNqAWQgiN%2BcMWOO7zFcaXflrb6ZhTEQ6b1b%2FfCSev7auyOqg9plBT1uz2l0jAjfywZmYF0jaaT8ha9TifSOMmhorInl7w6ciRWSMZKI8%2BPENYE4PvRjHtig0sq7CiGeM%2FB0cxNvqVWZ4m%2BI5f7pcmDVCRI3pPL3eiyMIx4FNPuJYyH3fJK44zlmq2owprWSFBedgG6b%2BDVRWfrQcF61UksoBrJ8EVT8Ozb%2FkUjG0c3Rc%2FnuYB3r%2FYPrSIstA%3D%3D\">\n    \nFinally, given that a number must be one of three types, it's probably easier to just have one function:\n\nYou can try this below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"700\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJxlUD1vgzAQneFXPAlV2EqVQjuCO3Xu1K3qQMw5OYkYanCUqsp%2Fr0kgoQqDJe7eu%2FdhvNUDtxZbGt74wH3remElfmOEr2l11azraQ6Fz68ijtgIC6UUcglHg3cWM6Q400zrIC5cDqS8AKNU0MQN262oNqPE07MM89Vq1gLYIBx%2BCOBwO7vNcT2%2F7ny%2FEyyLaXOKb%2B%2Bdk1Mcm0W6d7%2FfkPv46Ug4%2BvbsqAbbAcdRKLpLuuzjGAQnRO%2F3YXn1E454TUJ0jg6P0N5JqNfZSvjFCpdVJi%2FFjfxy1JwwaU2GNZMd0htg7HYB6cgZ0mfAPKo23tbVmTTGbJ1glRdcvmTFVGnUuZCusSJNOAH3SP53wDJJg6fTH6VCnXw%3D\">\n    \nImage from the National Gallery of Art, Cats and Kittens, by an unknown artist.\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Time for something new...",
		"date":"Wed Sep 10 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757527200,
		"url":"https://www.raymondcamden.com/2025/09/10/time-for-something-new",
		"content":"So, maybe you're noticing a new look here. Or maybe this is your first time here. Either way, welcome to the new (and hopefully) improved raymondcamden.com, my little home on the internet.\nFor the past few weeks, I've been sharing my experience using GenAI to generate a blog theme on my YouTube channel. My plan was to share those results here and ask folks which theme they thought would be best. I still plan on sharing those results here, but yesterday while frustrated and anxious, I decided, screw it, one theme really spoke to me, and it's the one you see here. While I know I've still got stuff to fix here and there, I'm really happy with this update and I hope yall like it. Or if not, I hope it doesn't bug you enough to never return. ;)\nAs an FYI, the actual tech stack didn't change at all. I'm still using Eleventy and writing most of my content in Markdown. If you're bored, you can dig into the source code and see how I've patched this huge (nearly 7000 page) beast of a site together.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Chrome's Built-in AI to Improve AI Prompts",
		"date":"Fri Sep 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1757095200,
		"url":"https://www.raymondcamden.com/2025/09/05/using-chromes-built-in-ai-to-improve-ai-prompts",
		"content":"Props for this article go to my best friend, Todd Sharp, who yesterday said something along the lines of, &quot;Hey Ray, you should blog a demo of ...&quot; which is pretty much akin to bring out a laser pointer in front of a cat. Not only do I love getting ideas for new demos, his idea was actually pretty freaking brilliant, which means I get to pretend I'm brilliant as well.\nHis idea was this: Given a user created prompt meant to be shipped off to a &quot;proper&quot; (i.e. maybe expensive) Generate AI API, can we use tools to help improve the prompt and make it &quot;cheaper&quot; before used. Given we've got AI in the browser via Chrome (ok, we will have it soon), this seemed absolutely possible and I quickly whipped up a demo.\nAs usual, the normal caveats apply. To test this, you'll want to use Chrome Canary with the appropriate flags enabled and such. As I've suggested before, join the EPP to get access to additional docs and support forums for help getting started. (Or just subscribe to my blog and keep reading my posts.)\nLet's get started!\nThe General Strategy, and What Can Be Done Better\nThe general idea for this demo is to take your input, a text prompt, and ask Chrome's Rewriter API to recreate it in a better form.\nThis assumes a text only prompt, which of course isn't always the case. Multimodal prompts could include many different forms of binary data and optimization could be done in that area as well. For example, a 4000x4000 hires image could be resized and optimized down quite a bit to reduce the size it adds to the context window. Audio could be sped up 10% or so for tasks involving transcription. PDFs could be optimized as well.\nFor now, I'm keeping it simple and just focusing on &quot;cleaning&quot; up the prompt.\nInitially my thinking was to take the result, send it to Gemini over API, measure the tokens used, and compare against the original. However, looking at the GitHub explainer for the Prompt API, I found a method, measureInputUsage(), which tell you how many tokens a prompt will use.\nNow, the docs have some important notes on this API I'll share here:\n\nWe do not expose the actual tokenization to developers since that would make it too easy to depend on model-specific details.\nImplementations must include in their count any control tokens that will be necessary to process the prompt, e.g. ones indicating the start or end of the input.\nThe counting process can be aborted by passing an AbortSignal, i.e. session.measureInputUsage(promptString, { signal }).\nWe use the phrases &quot;input usage&quot; and &quot;input quota&quot; in the API, to avoid being specific to the current language model tokenization paradigm. In the future, even if we change paradigms, we anticipate some concept of usage and quota still being applicable, even if it's just string length.\n\nGiven that, and given that different API engines may tokenize different, using this method to measure the effectiveness is not perfect, but feels like a good ballpark figure for how effective it may be.\nThe Code\nAlright, let's take a look at the code. On the HTML side, I'm literally just plopping down a textarea for your input and a button, so I won't share that here. Let's get into the JavaScript. Nothing every went wrong using JavaScript, right?\nFirst, the startup crap:\n\nI've got a few global variables and an init function run when the document loads. It checks to see if the user has access to the Prompt and Writer APIs, and if so, enables the button so we can analyze their input.\nNow for the complex stuff. The first thing I did was build wrappers to get the Prompt and Rewriter model. In the past, I've not done a good job of handling 'loading' states and such, and this demo handles it better, showing in the UI as the model downloads. I've got a blog post on just this particular topic coming up in a day or so:\n\nHere's the actual method called when you click the Analyze button:\n\nI begin getting my models, if I need to, and in theory, this will gracefully handle the case where we have to wait for a download.\nNext, I get the token usage on your input.\nThen I can use the Rewriter API with context explaining what I want it to do. This could possibly be improved and I welcome suggestions!\nFinally, I measure the new prompt and report it all back to the user.\nExamples\nHere's an initial prompt:\nHello my AI best friend! I'd like to learn more about working with Alpine.js. Specifically I'd like \nhelp figuring out how to integrate that awesome framework in with API calls to a weather system. \nI need a simple to understand explanation for someone who is kinda new to JavaScript.\n\nAnd the rewrite, with 'scores' before and after:\nThe initial prompt measurement was 69.\n\nI rewrote the prompt to:\n\nAlpine.js and API calls for a weather system. Need a beginner-friendly integration explanation.\n\nThe new prompt measurement is 27.\n\nAnother example:\nI'm pretty dumb when it comes to astronomy, but watched Star Wars a few times. I'd like to understand bet",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding a Web Debugger to BoxLang (First Version)",
		"date":"Mon Sep 01 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1756749600,
		"url":"https://www.raymondcamden.com/2025/09/01/adding-a-web-debugger-to-boxlang-first-version",
		"content":"This one's been sitting in my &quot;to do&quot; blog queue for sometime now and I figured the beginning of spooky season (September 1 - don't we all start celebrating Halloween then?) was a good excuse to finally kick this out the door. Many, many years ago I learned to love one of the simpler features of ColdFusion, the debugging output. This is a feature ColdFusion has probably had for near twenty or so plus years and while it's not the same as a &quot;full&quot; debugging service (which also exists), or the powerhouse Fusion Reactor, it's an incredibly simple way to look at what's going on in your web application.\nIt works by first enabling it (something you would do in your admin and in development only of course) and tweaking the settings. Once enabled, it will print a large set of debug information at the end of very web page. How and what's printed depends on your settings, but it will show things like:\n\nDatabase queries, including the SQL, number of results, and how long the query ran\nMethod executions of components (think classes) and how long they took\nTemplate timings as well\nVariables used\nAny exceptions thrown in the request\n\nThis information can be incredibly useful in terms of not only finding performance issues but also just realizing what in the heck's actually going on in any one particular request.\nWhile useful, this feature of ColdFusion is a bit of a black box. In the past, you could actually build your own debugging templates with modifications to suit your needs, but when I checked recently, that was no longer possible unfortunately.\nAs an example of how this could look, I've got a very simple web page here that's including a very slow page, and you can see it flagged in the debug output (after the &lt;hr&gt;).\n\n\n\nIn this case, the main template took 6055 total ms, but the included template took 6028 ms, so it's plain to see that's the culprit for the slowness of the page. There's a lot more that be shown here, but this is all I had ready to put on a screen shot.\nSo can we do this in BoxLang? Sure!\nThe BoxLang Version\nIn order to support something like this in BoxLang, I looked at two features - the module system which lets you add to BoxLang itself and interceptors which let you tie into the platform at a low level.\nMy initial version of this project (I've got plans - big plans - and will detail at the bottom) support reporting the following metrics:\n\nEach template used in the request and their execution time.\nEach database query including the number of records returned and execution time.\nHTTP calls including the size of the response and execution time.\n\nIn terms of how this is rendered, I did it a bit differently than ColdFusion. My debug module adds two new BIFs (built-in function). The first, debugDisplay(), will render out the debug information wherever you call it. So for example, you can include it in every request like so:\n\nYou probably don't want it like this, but it's a quick and dirty way to add it in. As an example, this renders like so:\n\n\n\nI don't yet have &quot;flag slow things with red&quot; added, but that's coming. I instead spent my time on the nice rainbow rule as that was definitely more important.\nIn the sample, note how it shows the file, cats.bxm, being included 3 times. Right away that should concern a developer as it may be a mistake. But then also notice how each time it's executed, it takes longer to run. Again, another possible issue. It's stuff like this that the CF debugging template was great at exposing and is what inspired me when building this in BoxLang.\nThat's the &quot;pretty&quot; version, but you also get a function, getDebugInfo(), that returns all of this data and lets you do whatever you want. You could save it to a log file, store it in a database, or do your own checks (&quot;If I see a request to an external domain, flag it.&quot;)\nHow's It Built\nAt a high level, this proof of concept starts off as a module. I built it locally into a simple web app by adding a boxlang_modules folder, and under that, a folder named debug. The first file is the ModuleConfig.bx class which defines how the module acts - in my case, specifying interceptors:\n\nThis class is basically just metadata with the important line in configure specifying what interceptors to add to the runtime. Let's look at that:\n\nAlright, this one's pretty complex. When you define a class of interceptors, BoxLang will look for methods that match recognized interceptors (see the docs for the full list)) and automatically connect them to the runtime. So for example, postQueryExecute will be called after every single database query. onHTTPResponse will be called whenever your code makes a HTTP request. (Note that I just now found a bug with this, remote images loaded via the Image module aren't calling this interceptor. I've reported it.)\nThat's the interceptors - the data is a bit interesting. The getDebug function does two things. First, it gets the current execution context of the reques",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Web Based Comic Book Reader",
		"date":"Thu Aug 28 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1756404000,
		"url":"https://www.raymondcamden.com/2025/08/28/building-a-web-based-comic-book-reader",
		"content":"Ok, so I know I've been spending way too much time lately talking about comic books, but I've been reading them for roughly 80% of my life now so they're just a natural part of my life. Now, my best friend Todd Sharp told me this crazy lie that he's never read a comic book before, but surely that's a lie. Surely.\nEarlier this week, I took a look at parsing electronic comic books and sending them to GenAI as a way to get summaries of stories. That was a fun experiment and it actually worked quite well. I thought I'd take a stab at trying a similar approach with Chrome's Built-in AI support as well when I discovered that... wait... I don't actually have a way to view comics on the web. Or so I thought.\nWay, way, back in 2012 I wrote a post on that very topic: &quot;Building an HTML5 Comic Book Reader&quot;. This was back when you would still describe 'modern' web apps as HTML5 apps. Now that looks dated as hell. The code in this post is absolutely outdated now. It made use of the FileSystem API for extraction versus just doing everything in memory. It also only used CBZ files as I wasn't able to find a RAR library for JavaScript back then. I decided to take a stab at updating it to a more modern version and here's what I came up with.\nThe Stack\nFor the updated demo, I made use of the following libraries:\n\nShoelace - I love Shoelace's look and web component API, but I have to be honest, I barely used it in my demo and it's probably over kill for what I built. But I like it - so I'm keeping it.\nzip.js - for supporting CBZ files.\nUnarchiver.js - for RAR support. Technically this library supports zip files (and more) too, but I came to this after I had zip working well and ... I didn't want to poke the bear. If I were to be shipping this as a 'real' project, I'd probably remove zip.js and just use this library.\n\nAnd that's it. The application is entirely client-side code. Oh, and no React. Is that allowed?\nDrag/Drop Comics\nAlright, let's get into the code proper. I began by simply adding a div to the page where you could drop your file. To be honest, I could have supported it on the document as a whole, but I liked the idea of a nice little box.\nHere's the HTML I used:\n\nAnd here's the JavaScript that's going to handle it. To keep things a bit simpler, I'm going to ignore some of the DOM setup code and such. I'll be linking to everything below.\n\nThe function to handle file drops is below:\n\nI've got a few things going on. First, I look for the file data associated with the dropped file and check the extension. If it doesn't match what I'm looking for, I show an error toast (provided by Shoelace).\nFor my RAR files, I can pass the file object directly to a function to work with it. I don't believe zip.js supports this so for that case, I'm reading in the bits and then passing it off to the function to handle it. (This is probably another clue I should have just used Unarchiver.js.)\nParsing the Archives\nThis is the cool part I think. I wrote two functions, one to handle RARs, and one to handle Zips. My thinking is that these functions would hand off the results, a set of images, to a display function, but I also knew both libraries had a wrapped interface to working with archive entries. So I thought - what if these functions also created a function that literally says, &quot;Given you want page X, here's a function to return that image data.&quot;\nHere's both those functions, and make note of the inner functions. This is that special handler for images.\n\nNote that I've got code in to filter directories. Many comic book archives begin with a folder of images rather than simply storing the images as is. I also look out for Thumbs.db, at least in my CBZ files.\nRendering the Comic Pages\nNext up - actually rendering the pages. I've got a bit of basic HTML for this that will handle rendering a page count, buttons, and the image:\n\nAnd here's the JavaScript:\n\nAgain, I'm pretty proud of this. I love that the logic for getting the actual bits is passed in by the corresponding zip/rar handlers and this can be done more generic.\nThe App\nI assume most folks won't have electronic comic books handy unless you're a big nerd like me. If you want, head over to ComicBook+ and grab a few. Here's the app before you upload:\n\n\n\nAnd here's a sample comic. Note that I could probably render the image a bit better here.\n\n\n\nWant to try it yourself? You can play with it here: https://cfjedimaster.github.io/ai-testingzone/comic_web/index.html\nAnd the full code may be found here: https://github.com/cfjedimaster/ai-testingzone/tree/main/comic_web\nThe next step will be to add AI integration!\nImage by kidsnews.hu from Pixabay\n",
		"tags":[
	        
            "javascript",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Connecting Comic Books to Generative AI",
		"date":"Tue Aug 26 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1756231200,
		"url":"https://www.raymondcamden.com/2025/08/26/connecting-comic-books-to-generative-ai",
		"content":"I've blogged quite a few times about electronic comic books (most recently earlier this month when I demonstrated a comic book reader built in BoxLang). I've been reading comics pretty much my entire life and enjoy building development projects that work with the various file types associated with comics. As a reminder, these typically fall into two categories:\n\ncbr - A RAR file of scanned images\ncbz - A zip file of scanned images\n\nThis week I was wondering - given that GenAI tools are pretty good at understanding images - how well could a GenAI system take a set of images, in order, and understand the context of the story behind them. I decided to give it a shot and honestly, I'm pretty impressed by the results.\nHow this works - high level\nMy demo assumes a folder of comic book files and will do the following:\n\nScan the folder for .cbr and .cbz files\nFor each, look for a corresponding file with the extension .txt, this represents the already generated summary and a comic we can skip\nIf there isn't a summary, use the appropriate code to read in the archive\nFor each image, upload to Gemini's Files API to temporary store the image\nSend a prompt and the list of images and ask for a summary\nFinally, save the summary to the file system\n\nAs usual, I'm making use of the Google Gemini API for my demo.\nOK, let's get into the code.\nSetup\nMy script begins by importing my dependencies and setting some initial values:\n\nThe prompt's job is to setup the task based on the images that will follow. It describes how they are in order and also warns the model that some pages can be advertising. Finally, a one paragraph summary should be enough of a summary for a comic book.\nThe last value, comic_dir, simply points to the folder of comics.\nA note on rarfile. As always, RAR support in any language is a royal pain in the rear. For Python, I used the rarfile module which unfortunately also requires a CLI installed in your environment as well. For me, this was unrar for Ubuntu. Once done it worked fine, but keep in mind it's not just a module install. I'll also point out, and I didn't handle it in this demo, you may find comic books using the .cbr extension that are actually zip files. You could try/catch a zip call to flag those. (I did not - sorry.)\nWhat comics need processing?\nNow I'll get the comics and figure which need to be worked on:\n\nAs you can see, my logic to figure out the summary simply relies on the existing name with a .txt extension instead.\nWorking with Archives and Images\nNext, my script needs to split off based on the file type. Remember what I said above about how sometimes .cbr files are actually zip - I'm just not going to worry about that for now.\n\nIn both cases, I get a list of files in the archive, filter to JPGs, and then upload to Gemini via the Files API. These results are appended to an array. Note that I skip the file system completely, streaming right from the archive to Gemini.\nPerform the Summary\nLast but not least is actually performing the AI analysis:\n\nThe Gemini Python SDK lets you pass an array of items to the prompt so I simply create a new array based on my prompt and the uploads. I pass that and save the result to my expected summary filename. I don't check for errors because I'm an S Tier programmer and that's ok. (Ok, it isn't, you should definitely check for errors here.)\nThe Results\nSo I don't necessarily expect you to read the comics, but are the sample results. First are two very old, public domain comics:\n/Strange_Journey_001__1957_09.Americas_Best__noads.narfstar_.txt\nThis comic book features four distinct tales. &quot;The Phantom Express&quot; \nfollows Vance Jackson, a man tormented by his conscience after \nplotting with his partner, Bill Porter, to steal securities. He \nexperiences a vivid, nightmarish &quot;dream&quot; of the Phantom Express\n and his partner's betrayal, which he interprets as a warning; \n however, he soon discovers that Bill was awake and cunningly \n orchestrated the entire charade to trick Vance into confessing, \n then absconded with the stolen funds, leaving Vance to take the fall. \n In &quot;The Bedeviled Vault!&quot;, Inspector Curan investigates a bizarre \n bank robbery involving a goat found in a vault. He uncovers an \n intricate scheme by Vice-President Thornton Hartshorn and his twin \n brother, Jim Watts, who used the goat as a diversion while trying to \n frame Hartshorn as an innocent bystander, only for their &quot;simple&quot; \n plan to unravel. &quot;Ballast of Gold&quot; recounts the tragic true story \n of the steamship &quot;Marine&quot; and its ill-fated voyage in 1867, laden \n with two million dollars in gold and over a thousand souls; caught \n in a catastrophic storm, the ship sinks, with its honorable Captain \n Hernoon choosing to go down with his vessel. Finally, &quot;When the Sea \n Goes Dry!&quot; sees two treasure hunters, Deke and Margot, led by a \n mysterious guide named Pedro, pursuing legendary Aztec gold in the \n Caribbean, only to encou",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (8/24/25)",
		"date":"Sun Aug 24 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1756058400,
		"url":"https://www.raymondcamden.com/2025/08/24/links-for-you-82425",
		"content":"So yeah... not a great two weeks or so since my last Links post. If you missed the announcement, my time at Foxit was unceremoniously cut short which came as a complete shock. This may end up being the year I spend more time without a full time job than with. To be honest, I'm a really, really bad place right now, but, trying to maintain, and trying my best to once again find a job in the worst market I've seen in my career. One way or the other I'll get through this again, but I'm really, really tired of this. Let me pretend to think positive for a moment and maybe in the next Links post I'll have better news. Maybe. Ok, enough complaining, let's get to the links.\nEven more HTML you may not know about\nFirst up is a post by Jim Nielsen discussing some aspects about the mighty anchor element you may not be familiar with. His post, &quot;A Few Things About the Anchor Element’s href You Might Not Have Known&quot;, mentioned at least 4 things I didn't know so it's definitely worth your time.\nContext Windows Explained\nI'll often (and the link above is an example) click on things where I'm pretty sure I know the topic anyway because I figure there's always a chance I'm missing something important. Context windows are an important factor when working with GenAI systems, and Rizel Scarlett does a great job introducing the topic in, &quot;The AI Skeptic’s Guide to Context Windows&quot;. I liked her coverage of the topic, but even more so, she talked about how Goose has some fascinating ways to help manage those windows. (I've been hearing about Goose for a while now but hadn't had the chance to check it out - now I'm motivated to do so.)\nThe Anti Low/No-Code Manifesto\nOk, &quot;manifesto&quot; is a strong word, but the next link is one that is firmly in the &quot;anti&quot; low/no code space: &quot;Every Visual Workflow Tool is Just Excel for Developers Who Gave Up&quot;. This is a post I 100% disagree with, strongly, firmly, etc. That being said, I think it's a good thing to challenge your own beliefs about development and this post does just that. I think the author gets pretty much every part of their post wrong and the whole thing comes off as the strongest piece of technical gate keeping I've seen in a while, but I wanted to share it as it was, like I mentioned, thought-provoking. Let me know what you think.\nFinally\nLife sucks right now, but one bright spot from the past week was this excellent mashup. Enjoy.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Unit Formatting with Intl in JavaScript",
		"date":"Fri Aug 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755885600,
		"url":"https://www.raymondcamden.com/2025/08/22/unit-formatting-with-intl-in-javascript",
		"content":"It's been a little while since I last blogged about my favorite web platform feature, Intl. I think it was maybe two or so years ago when I was prepping for my first conference talk on the topic and using that as an opportunity to dig much deeper into the spec then I had before and wow, I was unprepared for how flexible, and powerful, this functionality is in the browser.\nI blogged about localized relative timings back in March of 2024 (ah, I remember March 2024, I had a job then), and discussed how to dynamically handle different quantities of time differences.\nMore recently, I blogged about dynamic time durations and how best to select the right duration for the formatter object.\nIn both cases, the interesting aspect wasn't so much Intl, but rather, how best to use Intl when rendering your results. It was a pretty fascinating set of posts I think (ok, I'm biased perhaps) and I'm glad I investigated those parts of the spec.\nToday I'm looking at another part of Intl - number formatting with units. When working with NumberFormat, the style option of the constructor reflects what kind of formatting you want to do. The options are:\n\ndecimal (default)\ncurrency (money money money)\npercent\nunit\n\nThat last one may not be obvious and is the focus of my post today. Unit formatting is used for formatting a number of a certain type of thing, so for example, 5 ounces of water, or 9 pounds of sugar. Intl lets you handle formatting those measurements in a locale specific format. Let's look at some examples.\nWhat's the Unit, man?\nFirst off, what unit values are supported? There's an API for that! The [Intl.supportedValuesOf()] method can return valid values of units like so:\n\nThose values, as of today, in my browser, are:\n\nacre\nbit\nbyte\ncelsius\ncentimeter\nday\ndegree\nfahrenheit\nfluid-ounce\nfoot\ngallon\ngigabit\ngigabyte\ngram\nhectare\nhour\ninch\nkilobit\nkilobyte\nkilogram\nkilometer\nliter\nmegabit\nmegabyte\nmeter\nmicrosecond\nmile\nmile-scandinavian\nmilliliter\nmillimeter\nmillisecond\nminute\nmonth\nnanosecond\nounce\npercent\npetabyte\npound\nsecond\nstone\nterabit\nterabyte\nweek\nyard\nyear\n\nI built a simple demo for this that let's you enter an arbitrary numeric value, select a unit, and it renders the results in seven different locales:\n\n  See the Pen \n  Intl Unit Test1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFormatting Bytes\nOk, so the whole reason I actually went down this route of research this week was for a simple task - given a file size in bytes, I wanted to format in kilobytes, megabytes, and so forth. I was curious if Intl maybe had this baked in, and it doesn't... not exactly. As with the blog posts I mentioned from earlier, it's up to you to decide what unit of measure to use, i.e., what makes sense, and then you can use Intl to render it properly.\nNow, here's where I have to make a confession. I had Googled for this, and Google actually spat out a JavaScript function to do this for me. And... from what I could see... it worked well and was kinda clever. Here's the function it created:\n\nThe clever part comes in from figuring out what 'level' of size to use, from byte to terabyte, but doing a bit of match. Honestly, that never would have occurred to me and is a prime reason I fail those high end coding challenges in interviews. I'm ok with that. Probably the only thing I'd change in that is to swap out the default of en-US to navigator.language.\nThat being, I whipped up another CodePen that takes in a set of inputs and renders them for multiple locales. I'll share the CodePen below, but make special note of this:\n\nDon't forget that JavaScript lets you add underscores in numbers to make them easier to read. They're completely ignored by the parser. Alright, here's the demo:\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLet me know if you've got any questions, or anything else in Intl you would like me to dig into!\n\"Round Measuring Spoons\" by Theen ... is licensed under CC BY-NC-SA 2.0 .",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "First Stab at a BoxLang Log Viewer",
		"date":"Wed Aug 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755712800,
		"url":"https://www.raymondcamden.com/2025/08/20/first-stab-at-a-boxlang-log-viewer",
		"content":"The BoxLang folks have a proper &quot;administrator&quot; desktop client coming in the future, but lately I've been finding myself needing a quick way to work with logs and preferring a web-based tool versus using tail in my terminal (I know, I'm crazy like that). I thought I'd take a stab (Halloween is coming soon, can you tell?) at a simple web application that could do what I wanted - let me quickly view a log.\nLogs - Just exactly where are they?\nI had a vague idea of where my logs were, but if I'm building a tool that others may use (I'll be linking to the repo at the end) than I'd need that to be dynamic.\nMy initial attempt made use of the fact that BoxLang code can get access to the current runtime via getBoxRuntime(). The docs for this function are a bit sparse, but that's mostly because the object returned from this is an instance of the Java class. This link, https://apidocs.ortussolutions.com/boxlang/latest.html, will take you to the latest Java source for BoxLang.\nFrom there, you can dig down to the BoxRuntime class and start looking into the various available methods.\nMy first code was pretty simple:\n\nBasically, get the runtime, get the configuration, map it to a structure, and get the log directory. I figured out that last part by simply dumping the result from asStruct() and figuring out what I needed.\nThis worked, but Brad Wood had some interesting comments about this use case. He mentioned (and if I misspeak, blame me, not him), that it's better to get the current Box context instead. He mentioned that configuration could be changed based on how it's being run, and while unlikely, this version was safer:\n\nI perhaps didn't do the best job there channeling Brad's reasoning, but, I truly appreciate how BoxLang makes it easier to get access to low level settings like this. Given this, I placed this within a class, along with two more methods:\n\nMy class supports getting the log directory, getting a list of logs, and getting the contents of a log. As mentioned, I do a bit of checking in getLog() to prevent path traversal types of attacks. Right now I'm just returning the contents, but at the end of this post, I'll share some thoughts about what I would change here.\nThe App\nThe web app itself is a grand total of two pages supported by a bit of JavaScript and CSS. I went with Bootstrap for the UI. I've been preferring Shoelace generally, but thought I'd try the old reliable again. To support that, I created a BoxLang component I can use as a wrapper. I created components/layout.bxm like so:\n\nIt's got little to no logic outside of sniffing for a title attribute and checking the 'mode' of execution, which basically boils down to, am I in the 'opening' of my layout or end. I can then use this like so:\n\nThe home page is just a simple tabular view of the logs:\n\nYou'll notice I'm wrapping the table in my web component, TableSort (which I oddly called 'talbe-sorter' on GitHub - I need to rename that soon). This web component adds sorting to tables by simple wrapping and progressively enhancing an existing data. The only thing I needed to add on top of that was numeric=2, which means treat the second column as a number, and data-sortval=&quot;#size#&quot; as a way to provide the pure, numeric size of the log file. Note I'm rendering the size in kilobytes. When I get a chance, I'll write a good generic function to render B/KB/MB/GB/etc.\nHere's the home page in all its loveliness:\n\n\n\nThe next page is a bit more complicated. For now, I'm getting the entire log (and yes, if that's raising red flags in you, stand by) and render it to a textarea. But I also wanted basic sorting as well. For that, I turned to Alpine.js. I'm actually quite rusty with Alpine, it's probably been months since I used it for anything interesting, so that integration took a bit longer than expected just because I had forgotten so much, but once it was done, it worked perfectly. Ok, first the HTML:\n\nThis is fairly simple - get the log, render it in the textarea. But you'll notice a few connections to Alpine via the x- attributes. This is handled by the JavaScript:\n\nBasically - on any change to the filter field, I need to update the textarea. I kept a copy of the original and can use that when searching. As it's a log, I'm considering it as an array of lines that I can filter via simple string match. (I should probably make it case-insensitive as well.) Here's a basic view:\n\n\n\nAnd here's a filtered version:\n\n\n\nYou can find the complete source here, https://github.com/ortus-boxlang/bx-demos/tree/master/webapps/logviewer\nSo about those logs...\nKudos to you if you can see the issues that are going to come out of this demo, specifically, file size. Right now if I click on a multi-gig log file, BoxLang will need to read it completely, it will be sent to the user completely, and even worse, JavaScript is making a copy of it in ram to allow for filtering. This is all going to fail miserably, so what can we do?\nFirst off, BoxLang supports reading ",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using StringBind in BoxLang",
		"date":"Mon Aug 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755540000,
		"url":"https://www.raymondcamden.com/2025/08/18/using-stringbind-in-boxlang",
		"content":"Ok, to be honest, this is going to be a pretty lightweight post as it's about a simple little string function in BoxLang, but as I discovered it rather recently and was intrigued by what it did.\nSo first off - how did I find this? In the BoxLang docs, there's a whole section on built-in functions and a subcategory just for string. I was looking it over and realized there were quite a few that I had not known existed. There are some interesting ones in there like pascalCase and snakeCase. I was pretty sure I knew exactly how these worked, but I went ahead and built a quick demo that demonstrates both:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"600\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJx9jLEKwjAQQPd8xZEMbUH8AamLCg7BgggO4nC0FxqMSUliHcR%2FN7ZQQcTh4N7x7mnb3WKAEk4MgLdkjIO786bhs%2Fdhu5GyOlZ7uR5ZaR8i7PBKI0tMaCdU2DuvI4FyruHszJge%2BnPCus2HHcolPJLbeW2jsTk%2FtAQdhhrNCgNBTz5oZ8EpEMODAB1AfIwxUwheLL4zweKF%2FlUm4XckyxI%2B07wAp7hXAw%3D%3D\">\n    \nOk, that works well enough, but let's talk StringBind. StringBind lets you create a string that acts as a template, letting you pass data and re-evaluate the result every time.\nNow, on one hand, you may be wondering why that's necessary, as BoxLang already has a template language support baked in. As a trivial example:\n\nThis works, but is also a one time operation. What I mean is, as soon as you use that string (in my case I printed it, but I could have saved it as well), you can't re-evaluate it. Now typically that also isn't necessary an issue, as you can simply keep creating new versions in a loop or some such:\n\nBut what StringBind gives us is a more abstract way to create a template and re-use it. It begins with a different definition of variable tokens. Instead of pound signs, you wrap tokens with ${name} where the item inside represents a token named name. You can also define tokens with default values, ${name:Nameless}. The name of your token doesn't necessarily need to a valid variable name, so for example, this is allowed as well: ${full name}, which could perhaps be more readable than fullName.\nUsing this feature requires you to define the template, duh, and than call it with stringBind where you'll pass the template and data:\n\nThe template is passed first, the structure of data second. Note that I didn't pass food so it should default. The result is as you expect: This is a big string with Ray and 52. My favorite food is sushi.. Obviously passing food will change the result:\n\nThis gives: This is a big string with Ray and 52. My favorite food is nature valley bars.\nYou can try this yourself below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"600\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJyVjrEKg0AMhvc8xU9wUBCHQhdLl%2B5dSl8g4qkH9iyX0yLiu%2Feu3boVMnzhT75EgwfOYLoPVhFL0NgeGrx1PV42DMg2Jw%2BzQ1wbWXqzV7iu6GSZvA0G3TS1aTPbEtU662D3iphIo%2FlruljX5hFLbARwEnLNN1m5TH2Ucn080F4QPeN8GF2uxel%2Fw4fTGzFzEmZvsMg4mhWNeOXfA2%2B2Ik2s\">\n    \nOk, so fair enough to say, this is one of those functions I don't see using day to day, but I thought it was a pretty cool idea and something I want to keep in my (BoxLang-ified) tool belt for use later. What do people think? I'd love to hear some ideas of how this could be useful, so leave me a comment below!\nPhoto by Steven Van Elk on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "On the Market... again (Hire me!)",
		"date":"Fri Aug 15 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755280800,
		"url":"https://www.raymondcamden.com/2025/08/15/on-the-market-again",
		"content":"Sadly, I find myself in the same situation I did a few months ago. My time at Foxit has abruptly come to an end. While it didn't work out, I am incredibly proud of what I was able to achieve in such short time. I'm a bit in shock now, but, I love my work, I love helping developers, and I can't not work if my kids want to eat. (Rudely they tend to get hungry - like every single day.) As always, if you know me, and know of an opportunity, I'd love any introductions you can give. I got through this once, I can get through it again!\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Building a File-Based Router in BoxLang",
		"date":"Thu Aug 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755194400,
		"url":"https://www.raymondcamden.com/2025/08/14/building-a-file-based-router-in-boxlang",
		"content":"Earlier this week I took a look at BoxLang's new rewriting feature ((&quot;URL Rewriting with BoxLang MiniServer&quot;)[https://www.raymondcamden.com/2025/08/11/url-rewriting-with-boxlang-miniserver]). It basically boils down to telling the miniserver app, &quot;here is a file I want you to run on a 404&quot;, and given that you can write code for anything you would like, it's really flexible. I like this approach, but it got me thinking, what if BoxLang also supported a non-code based rewriting system, something where you can define paths, and rewrites, in a file? I took a stab at architecting such a feature and thought I'd share.\nMy Inspiration\nMy inspiration for this idea comes from Netlify's robust Redirect/Rewrite support which has multiple different features. It can map simple paths to one another and also map dynamic paths. It can even create simple proxies, letting you build apps that use client side code to APIs where you can't expose the keys in JavaScript. I took a look at the various options supported by Netlify and decided to try to tackle a subset of them as a proof of concept.\nThe File\nMy input file, rewrites.txt, will be a simple text-based and tab-delimited set of input paths and rewrite destinations. Let's start simple:\n/blog\t/news\n\n# We renamed this in 1921\n/pr\t\t/pressrelease\n\nIn the sample above, I've got two rewrite rules and a comment that should be ignored by the engine. In theory, any non-technical person can grok this and add or modify rules easily enough.\nThe Engine\nAnd now for the engine itself. Again, starting simple, here is my rewriter.bxs:\n\nUp top, I simply default the filename to look for and do a quick check for its existence. Next I've got a basic file parsing utility that will go over every line in the input, split it by tabs, and ensure there's at least 2 values after the split. I use a third space to optionally let you set a status code for the redirect.\nI iterate over the rules and begin with my first supported logic, a simple A=&gt;B type match. If the cgi.path_info matches a from value, I'm going to redirect the user. By default, this is done via bx:location, which means the user will see the new URL. Typically this is what you want I'd say, and the user can bookmark the new location if they want. However, you may also want to 'blindly' do the redirect where the location doesn't change. That's when the 200 status code check comes in and I switch to simply including the new template. You'll note for that to work though you need to redirect to a specific file. Here's an example:\n/blog2\t/news/index.bxm\t200\n\nSplat!\nI love &quot;splat&quot; - as a word it's just fun. That being said, one of the cooler Netlify redirect features is the idea of a wildcard match like so:\n/prods/*\t/products/:splat\n\nIn this case, everything after the path /prods/ becomes the splat value and the direct will include that. In my loop above, I added support like so:\n\nThis just boils down to looking for the asterisk and :splat, and then doing string manipulation to handle the redirect.\nThis worked well but led to another problem.\nMapping URLs to Data\nAfter I supported mapping /prods/foo to /products/foo, I realized this would only work if /products/foo/index.bxm actually existed, which is fine of course. But what if I wanted to map to /products/index.bxm and have the value, foo, available to the code there?\nI began by adding a new rule to my text file:\n/products/*\t/products/:product\n\nAnd then modified my rewriter code like so:\n\nNow it handles cases where the end isn't :splat and considers it a variable. This is stored in the request scope and made available to the included document, which for now assumes index.bxm. All in all it means this set of rules:\n/prods/*\t/products/:splat\n\n/products/*\t/products/:product\n\nWill take a URL like /prods/catbox and redirect to /products/catbox in the browser while then loading products/index.bxm and making a request variable, product, contain the value catbox. Whew. T\nThe Code\nOk, so as I said, this is all a proof of concept and not nearly as powerful as the system Netlify has in place, but it absolutely shows you could build something like that. Again, my idea here was to make it easier to both write rules for our app as well make it easier to read those rules later to understand behavior.\nYou can find the complete demo here, https://github.com/ortus-boxlang/bx-demos/tree/master/webapps/rewritedemo/filebased, and I've included both my text and BoxLang rewrite code below.\nFirst, the text file:\n/blog\t/news\n/blog2\t/news/index.bxm\t200\n\n# We renamed this in 1921\n/pr\t\t/pressrelease\n\n/prods/*\t/products/:splat\n\n/products/*\t/products/:product\n\nAnd now the engine:\n\nPhoto by Susan Q Yin on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding Programming Language Detection with Built-in Chrome AI",
		"date":"Wed Aug 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1755108000,
		"url":"https://www.raymondcamden.com/2025/08/13/adding-programming-language-detection-with-built-in-chrome-ai",
		"content":"As I've been playing, and thinking, more and more about how to best add Chrome AI support to web apps, I came across an interesting use-case that I think could be helpful, and like in my previous examples, be completely ok if it didn't actually work. When I write on the developer blog at Foxit, I make use of WordPress plugin for code samples. This editor has a place for you to both paste in your code, and select the language so the proper highlighter is used:\n\n\n\nThis works well enough, but it gets a bit annoying to have to constantly keep selecting Python in the dropdown. Ideally the form would use the last language (simple enough via LocalStorage), but I was curious how well Chrome's Prompt API could handle the task.\nTo be clear, this is not the same as the Language Detection API which is good for identifying spoken languages. What I wanted was something that could understand programming languages, or code.\nI forked one my earlier examples and built a form with a textbox and a simple button to kick off the analyzation. So for example, I pasted in the JavaScript code for the actual Code Pen and analyzed that:\n\n\n\n(As a quick aside, after I took this screenshot I was curious if I could disable the spellcheck in the textarea. Turns out you can as easy as spellcheck=&quot;false&quot;.)\nYou'll notice it had no problem detecting JavaScript. So I threw in a couple of others tests and was surprised how well it worked:\n\nIt had no trouble with Python\nOr HTML and CSS\nI even took the code from this post and it recognized it as Markdown\nI tried an old Perl CGI script - worked!\nI even tried ActionScript - and it worked!\nColdFusion? Yep\nPHP? Yep\n\nAnd how about the ultimate test:\n\n\n\nSo my demo is fairly simple, just printing out the result, but if I wanted to tie it to something like I said above, you could imagine taking the result, comparing it to the list of options in a dropdown for your editor, and selecting one on a match. (Of course, only do this if the user hasn't selected a language already.)\nAs for the code, it's pretty close to my other demos so I'll focus on the AI parts, not so much the DOM parts. First, the session:\n\nAnd then the call:\n\nAnd that's it. I decided against a JSON schema this time as it was so simple, but that could be done as well to help enforce the &quot;one word&quot; response constraint.\nAs with my other demos here, you'll need Chrome Canary with the flags enabled and such, or if you are reading this in the future while flying with your S-Tier jetpack, congratulations!\n\n  See the Pen \n  Detect Code Language by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Tai Bui on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "URL Rewriting with BoxLang MiniServer",
		"date":"Mon Aug 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1754935200,
		"url":"https://www.raymondcamden.com/2025/08/11/url-rewriting-with-boxlang-miniserver",
		"content":"BoxLang recently released it's 1.4 version, and one of the cooler parts of that update was many improvements to MiniServer. MiniServer is a lightweight web server that makes it easy to spin up and test BoxLang web applications.\nUpdates in the last version included automatic .env loading (which is coming soon to the boxlang CLI as well), websocket support, health checks, and more, but the one I care the most about is URL Rewriting support.\nRewrite support is fairly simple. To turn it on, pass --rewrites when running boxlang-miniserver. This will use the file index.bxm for any request that doesn't match a file (no matter what the extension). You can also specify a particular file as well: boxlang-miniserver --rewrites router.bxs.\nWhen you running MiniServer this way and make a request for something that can't be found, your file will run and you can inspect the request (typically via cgi.path_info) to decide what to do. Here's an example from the docs:\n\nI think the most interesting part of that example is the third one where you can enable clean URLs in the form of, /product/500 or /product/999. For the most part, this all &quot;just&quot; works, but I thought I'd kick the tires a bit and build out two demos.\nBuilding a Mini Blog Server with MiniServer\nI've already built a simple blog in BoxLang earlier this year when I was first learning the platform. I thought it would be interesting to see if I could get BoxLang to grok my 'real' blog source files and URL structure. My blog uses 11ty which is a powerful state site generator built in Node. I've configured my blog to look for blog posts under posts folder. As I've got way too many blog posts, I've organized them by year, month, and day.\nEach blog consists of front-matter on top, and excellent enterprise-grade content in Markdown. Here's an example from the post I wrote this weekend:\n\n\n.gist{font-size: 18px}.gist-meta, .gist-file, .octotree_toggle, ul.comparison-list > li.title,button.button, a.button, span.button, button.minibutton, a.minibutton,span.minibutton, .clone-url-button > .clone-url-link{background: linear-gradient(#202020, #181818) !important;border-color: #383838 !important;border-radius: 0 0 3px 3px !important;text-shadow: none !important;color: #b5b5b5 !important}.markdown-format pre, .markdown-body pre, .markdown-format .highlight pre,.markdown-body .highlight pre, body.blog pre, #facebox pre, .blob-expanded,.terminal, .copyable-terminal, #notebook .input_area, .blob-code-context,.markdown-format code, body.blog pre > code, .api pre, .api code,.CodeMirror,.highlight{background-color: #1D1F21!important;color: #C5C8C6!important}.gist .blob-code{padding: 1px 10px !important;text-align: left;background: #000;border: 0}::selection{background: #24890d;color: #fff;text-shadow: none}::-moz-selection{background: #24890d;color: #fff;text-shadow: none}.blob-num{padding: 10px 8px 9px;text-align: right;color: #6B6B6B!important;border: 0}.blob-code,.blob-code-inner{color: #C5C8C6!important}.pl-c,.pl-c span{color: #969896!important;font-style: italic!important}.pl-c1{color: #DE935F!important}.pl-cce{color: #DE935F!important}.pl-cn{color: #DE935F!important}.pl-coc{color: #DE935F!important}.pl-cos{color: #B5BD68!important}.pl-e{color: #F0C674!important}.pl-ef{color: #F0C674!important}.pl-en{color: #F0C674!important}.pl-enc{color: #DE935F!important}.pl-enf{color: #F0C674!important}.pl-enm{color: #F0C674!important}.pl-ens{color: #DE935F!important}.pl-ent{color: #B294BB!important}.pl-entc{color: #F0C674!important}.pl-enti{color: #F0C674!important;font-weight: 700!important}.pl-entm{color: #C66!important}.pl-eoa{color: #B294BB!important}.pl-eoac{color: #C66!important}.pl-eoac .pl-pde{color: #C66!important}.pl-eoai{color: #B294BB!important}.pl-eoai .pl-pde{color: #B294BB!important}.pl-eoi{color: #F0C674!important}.pl-k{color: #B294BB!important}.pl-ko{color: #B294BB!important}.pl-kolp{color: #B294BB!important}.pl-kos{color: #DE935F!important}.pl-kou{color: #DE935F!important}.pl-mai .pl-sf{color: #C66!important}.pl-mb{color: #B5BD68!important;font-weight: 700!important}.pl-mc{color: #B294BB!important}.pl-mh .pl-pdh{color: #DE935F!important}.pl-mi{color: #B294BB!important;font-style: italic!important}.pl-ml{color: #B5BD68!important}.pl-mm{color: #C66!important}.pl-mp{color: #81A2BE!important}.pl-mp1 .pl-sf{color: #81A2BE!important}.pl-mq{color: #DE935F!important}.pl-mr{color: #B294BB!important}.pl-ms{color: #B294BB!important}.pl-pdb{color: #B5BD68!important;font-weight: 700!important}.pl-pdc{color: #969896!important;font-style: italic!important}.pl-pdc1{color: #DE935F!important}.pl-pde{color: #DE935F!important}.pl-pdi{color: #B294BB!important;font-style: italic!important}.pl-pds{color: #B5BD68!important}.pl-pdv{color: #C66!important}.pl-pse{color: #DE935F!important}.pl-pse .pl-s2{color: #DE935F!important}.pl-s{color: #B294BB!important}.pl-s1{color: #B5BD68!important}.pl-s2{color: #c5c8c6!important}.pl-mp .pl-s3{color: #B294BB!important}.pl-s3{color: #81a2be!important}",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (8/9/25)",
		"date":"Sat Aug 09 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1754762400,
		"url":"https://www.raymondcamden.com/2025/08/09/links-for-you-8925",
		"content":"Hello friends - another two weeks has gone by which means another links post to share. Sometimes, ok, pretty much every time, I'm shocked at how quickly I have to do these posts. It's like I blink and half a month has flown by. In my last Links post I mentioned dealing with some frustrations, and they've gotten better, but I'm now dealing with the start of school which brings its own challenges. That being said - the family is happy and healthy, I'm employed, so I'll take that as a win. Let's get to the links!\nWhy Women in Tech isn't enough\nFirst up is pretty important post from Salma Alam-Naylor, &quot;Why Woman in Tech isn't enough&quot;. As her disclaimer explains, this is her perspective based on personal experience in regards to &quot;non-men&quot; in the tech space. This was a pretty eye-opening piece. I already knew women were underrepresented in tech, but I had no clue that almost half of then leave tech by 35. That's incredibly depressing. Salma discusses why efforts to improve these numbers may not be having the impact desired. While you're at her site, I strongly recommend signing up for her weird wide web hole newsletter as well.\nStop Using CustomEvent\nThe next post is about... wait for it... not using CustomEvent in JavaScript. In this post by Justin Fagnani, he arguments against the use of CustomEvent as opposed to either subclassing the native Event object, or just relying on Event and relying on event.target checking. It's great food for thought!\nUnlocking Beast Mode\nLast but not least, let's unlock the beast mode in our AI tools, specifically in your AI agent within Visual Studio Code. &quot;Beast Mode&quot; is a detailed Markdown file of instructions to help guide how Copilot works when helping you work. Written by Burke Holland from Microsoft, he goes into details into what these directions do and why, and it's a great reminder of how helpful AI can be if given a detailed set of instructions to follow.\nJust For Fun\nMy friends know I've got a thing for covers, and this is a great one my wife found a few days ago. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Integrating Location Data with Built-in Chrome AI for Better Image Insights",
		"date":"Thu Aug 07 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1754589600,
		"url":"https://www.raymondcamden.com/2025/08/07/integrating-location-data-with-built-in-chrome-ai-for-better-image-insights",
		"content":"A few weeks ago, I shared an interesting demo that integrated location and AI analysis of images using Chrome's Built-in AI support and Mapbox's Revervse Geocoding API. The idea was rather simple - let the user select an image and then:\n\nRun one API call to Mapbox to get the location, if possible, via EXIF information.\nAsk Chrome to analyze the image for items with in it, returned as a list of tags.\n\nYou can see the full code plus more explanation on that previous post, but today I want to share a very cool follow-up suggested by Thomas Steiner on the Chrome team.\nHis suggestion was to see if using the location information could improve the prompt. So to be clear, instead of simply reporting the location and a list of tags, use the location itself in the prompt.\nHere's my updated demo that does just that. Now, for this demo, I wanted specifically to see the improvement, if any, so I actually run a first pass with Chrome's AI and a second pass if location information was found. Usually I'd just report the best set of tags possible, but for this I'll show both.\nAgain, I imagine most of my readers won't be able to run these demos, so here's a few examples of how well this works (spoiler, pretty dang well):\n\n\n\n\n\n\nThese first two represent shots I took in Europe, and the location-based prompt was able to tell me the name of the buildings, which is pretty cool.\n\n\n\nThis last test was pretty interesting. It didn't really get the 'name' per se, &quot;superdome&quot; is returned in both cases but not &quot;Superdome&quot; which feels like it would be more right, but notice how it recognized it as a sports event which is spot on.\nOk, so the code, and again, in a real application I'd not bother showing both outputs, just the best. I'll share just the JavaScript as the HTML/CSS is pretty minimal:\n\nThe important bit is where we modify the prompt if a location was found. The original prompt is:\n&quot;Identify objects found in the image and return an array of tags.&quot;\nThe &quot;improved&quot; prompt is:\n&quot;Identify objects found in the image and return an array of tags. Use this photos location to help determine what is in the photo: ${locationStr}&quot;\nI'm seeing now I forgot the apostrophe, but thankfully AI is forgiving. You can see the complete demo below, but be sure to test with Chrome and the proper flags and such enabled:\n\n  See the Pen \n  MM + AI (Tags + Location Info + Use it in prompt) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nI'm 100% going to keep iterating on this and if you've got any suggestions, leave me a comment below!\nPhoto by Janayara Machado on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Comic Book Reader in BoxLang",
		"date":"Tue Aug 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1754416800,
		"url":"https://www.raymondcamden.com/2025/08/05/building-a-comic-book-reader-in-boxlang",
		"content":"I've been a comic book reader for just about the same amount of time as I've been writing code. Any computer using comic book reader (and there's probably quite a few) will know that electronic versions of comics let you read comics on your devices. These comics typically come in one of two formats, CBR and CBZ, which are literally just RAR and ZIP files, nothing more. Over the years, I've had fun building my own web-based readers for this format, with my last one from a bit over three years ago, &quot;Reading Comic Books in the Jamstack&quot;. I thought it would be fun to tackle this in BoxLang and see what worked well and what proved difficult. I've got a complete demo done that I'll link to at the end, but let's dig into what I created.\nWorking with Zip and RAR Files in BoxLang\nLet's tackle the easy one first - zip files. BoxLang has native support and I blogged some examples of that a few months ago. So that parts done. Sweet.\nRAR is... problematic. Every time I work with RAR it's a problem.\n\n\n\nMy Googling for Java RAR solutions led to a few places, mostly abandoned code, but finally, a repo with a bit of life at https://github.com/junrar/junrar. It took me a little to get this working in BoxLang, mostly because I was fumbling around a bit with the Jars and trying to parse the documentation, but eventually got a simple use case working:\n\nThe junrar sdk supports more operations than that, but honestly that's all I needed.\nThe Process\nOk, so now comes the complicated part. My plan is to build a web application that lets readers view available comics:\n\n\n\nClicking a comic than loads the first page, with buttons to go to the next (and after clicking once) and previous pages:\n\n\n\nOne possible way to build this would be to read in the comic and parse out the image on every request. While that would work, it would be horribly inefficient. Instead, I built a system that scans for comics, determines which need to be extracted, and then only extracts the newer ones. It will also handle creating the thumbnail seen in the screenshot above.\nMost of this logic is done with a comic class named comic.bx. First, I built a method to return &quot;available&quot; comics, these are comics that have been extracted with a thumbnail created.\n\nBasically, get all the files in the source directory and check the cache. The cached, extracted comic will be stored in a directory based on the filename, ran through a slugify function. I check for the existence of a thumbnail as my marker that the comic is ready. I then proceed to get the images and add the result to my array of available comics.\nThe directoryList filter is important. What I found when extracting comics from my samples is that every one extracted to a subdirectory named for the comic with images underneath that. So my directoryList uses recurse=true to get everything from the root of the cache for the comic and then filter to images.\nNext, I built a function to handle processing new comics:\n\nEssentially - get all of the comics in the source, compare to those that are already processed, and process the remaining ones. Now, I think for the most part this makes sense, basically use the built-in zip extraction for CBZ files and the junrar library for CBR files, but in my testing, I kept getting an error on a few CBR files. I dug up a Windows app, I think 7Zip, to look at the file and discovered that despite having a CBR extension, it was actually a zip file! Hence the OR in this condition:\n\nHonestly I could probably get rid of the first clause I suppose, but I do like that I can read the intent better.\nAfter the images are extracted, I grab the first image and create a thumbnail from it.\nNow, one important question is - when should this code run? There's multiple options that would work:\n\nOn a schedule, using BoxLang's scheduled tasks support.\nIn an admin system of some sort, it could be kicked off manually.\nWhen the application starts up.\n\nI took the final approach, and you can see it in my Application.bx onApplicationStart method:\n\nOf course, you could imagine using all three approaches in an application.\nThe Rest of It...\nThe rest of the application is fairly simple. I'm using Bootstrap for the UI which kept the design rather basic. The home page handles getting and displaying the available comics:\n\nNotice I'm passing the comic slug via the URL to the template that handles rendering comics. That one is a bit more complex as it needs to look at the URL to see which image to display:\n\nFrom the top, I first validate a comic was passed in the path, available via cgi.path_info. This calls a simple utility method in my class:\n\nI'll note that getAvailableComics could be, should be, modified to cache its results. Anyway, back to the template. After validating a proper comic was requested, I then check for image in the URL, defaulting it to 1. I validate the value is a number and within a valid range.\nInside the template, I made use bx:saveContent to store my simple button nav to a",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Calendar with BoxLang - Part JavaScript",
		"date":"Mon Aug 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1754330400,
		"url":"https://www.raymondcamden.com/2025/08/04/creating-a-calendar-with-boxlang-part-javascript",
		"content":"So back in early July, I wrote up two blog posts on building a calendar with BoxLang. If you forgot or (gasp!) didn't see them, you can read the first two parts here:\n\nCreating a Calendar with BoxLang\nCreating a Calendar with BoxLang - Part Deux\n\nIn the first post, I simply focused on rendering a calendar, which is mostly easy but can get a bit tricky based on the HTML/CSS you used. This is one of those cases where CSS can actually make things a heck of a lot easier than a table approach. The follow-up then made the calendar dynamic using an events class that output static (mostly) data. I say 'mostly' because I wanted static dates that were based around the current month so the demo would always have something to show based on when you ran it.\nThis third and final piece was planned, but ran into a bug that was fixed in the BoxLang 1.4.0 release from this weekend. Woot! With that new release, I can share this final version, which honestly isn't a big deal technically, but does demonstrate a pretty different approach from the last version. Now, instead of generating the calendar completely on the server, instead I'll make use of FullCalendar to render the calendar in JavaScript with BoxLang providing the data via HTTP.\nLet's do the BoxLang change first. Here's the original class I built:\n\nAs I said, it's static, but a bit dynamic as the data is all based around the current date and time. Normally this would be a lot shorter and based on a database query or some other logic. If I want to turn this into something a client-side library can use, I have to take this one line of code:\n\nAnd change it to:\n\nLiterally, that's it. As long as the class is under web root, I can then make an HTTP call to events.bx and pass a query param to specify the method to run, in this case: events.bx?method=getEvents. Now, as I said, this is a simple little demo, and in more complex cases it may involve a step or two more. For example, a class may not under web root, may be cached in the application and so forth. In cases like that, you can create a new class under your web root specifically to proxy calls to the cached internal classes. This can also do things like adding their own caching layer, validation, and more. But for now, I just flipped the bit and I've got an API.\nThe next step is to work with FullCalendar. For my demo, I went with the simplest version of their quick starts that just add a script tag and a few lines of code. This is their initial version from the quick start, with a bit of additional CSS to size the calendar:\n\nTheir docs are pretty intensive, but I focused on event sourcing and specifically, sourcing the events as an array, which at minimum requires an array of events that include a title, start, and optionally end values. There's definitely more, but that's the bare minimum and also works well with my date.\nHere's the modified JavaScript:\n\nI now make a call to the BoxLang class to get my events and modify the values to match what FullCalendar wants. Finally, I smply pass it to the constructor. Here's how it looks, and keep in mind, I didn't do any stying on this and I absolutely could have to make it match a site theme and so forth:\n\n\n\nTo add interactivity I'd need to write a bit more JavaScript, but this demonstrates the basic process. It's also a good reminder when using BoxLang for web apps that you always have a choice between what you do server-side versus client-side. Options are good!\nIf you want to see any of these calendar examples, head over to the BoxLang GitHub repo here, https://github.com/ortus-boxlang/bx-demos/tree/master/misc/calendar\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Progressively Enhancing Product Reviews with Chrome AI",
		"date":"Thu Jul 31 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1753984800,
		"url":"https://www.raymondcamden.com/2025/07/31/progressively-enhancing-product-reviews-with-chrome-ai",
		"content":"While writing up my last blog post I mentioned that a new idea had occurred to me in regards to employing sentimenet analysis with Chrome's built-in AI support (that, remember, is still way in beta). At lunch today I took a quick stab at a simple demo of what I had in mind and honestly, I'm pretty happy with how it came out.\nThe Initial Demo\nThe idea I had was an &quot;imagined&quot; ecommerce site with product reviews. I went to Google AI Studio and used a prompt to generate a set of product reviews. I used this prompt:\n\nGenerate a list of 20 product reviews for a cat carrier named Cat Carrier Ultra 1000. The reviews should be a mix of positive and negative, with some being extremely positive or negative. For each review, include the text, a made up name, and a date formatted like \"July 31, 2025\" - the dates should be random but from the past month or so\n\nI also defined a schema in the structured output section:\n\n\n\nI asked for an array of reviews including a text, name, and date value. This worked fine except the reviews were in random order and set in 2024 for some reason. I just followed up with:\n\nsort the reviews such that the newest review is first, and use dates in 2025\n\nThis gave me a nice JSON data set I could use (I got 20 back but I'm cutting out a few for length):\n\nWith this in place, I started off with just a demo to render this out. First, the HTML:\n\nAnd then the JavaScript. I copied the data above as is, but changed it from an object to just an array. I won't include it in the sample below:\n\nAfter a bit of CSS (and I cheated a bit, I asked Gemini for help), I get this nice display:\n\n\n\nOk, let's enhance this with some client-side AI!\nAdding Sentiment Analysis\nAs I covered in my previous post, I do sentiment analysis with the Prompt API by:\n\nDefining a system message defining what the LLM should do: &quot;You rate the sentiment value of text, giving it a score from 0 to 1 with 0 being the most negative, and 1 being the most positive.&quot;\nUse structured output to fully define the shape of the result:\n\n\nGiven this, I can create a session:\n\nAnd then execute it on ad hoc content like so:\n\nAlright, so given that approach, I begin by checking for support, and once again, I'm being lazy and not supporting &quot;you can do this after you download the model...&quot;\n\nThen later in my init:\n\nThe return up there just leaves the init function as I'm doing this after everything else. Remember, the idea here is to progressively enhance the experience for folks who can do this, not ruin it for everyone else.\nThe magic happens in processProductReviews:\n\nSo, lets break this down. First off, I'm doing this operation in a synchronous fashion over each review in order. In theory, I could do multiple calls at once although I haven't tried that with built-in AI support. My thinking is that this synchronous approach is ok as it will &quot;decorate&quot; from the top up and handle the items the user sees first most likely. I'm not entirely sold on this approach, but it seemed to make sense.\nIn the loop it's just a simple matter of calling the model with the review text and asking for the score. As the order of review objects matches what I displayed in the DOM, I can use nth-child CSS selectors to find them. Now for the part that's a bit arbitrary. I decided the values of .7 and higher would be positive and .2 and lower would be negative. That just seemed to make sense to me, but certainly that could be tweaked. All I do for those conditions is add a CSS class which changes the blue border of the review - as you can see here:\n\n\n\nI kinda like how subtle this is, but certainly you could imagine a different way of modifying the review. Also remember that a tool like this could be real useful in an admin interface where customer support specialists are looking at lots of reviews at one time. You get the idea.\nNow usually when I embed these demos, I warn folks that it won't work for them, but in this case, it does render the reviews just fine, but most of you won't get the 'enhanced' display. In my not-scientific-at-all testing, I usually saw results within a few seconds. Anyway, let me know what you think!\n\n  See the Pen \n  Prompt to Sentiment (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Sahand Babali on Unsplash\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Chrome AI for Sentiment Analysis (Again)",
		"date":"Tue Jul 29 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1753812000,
		"url":"https://www.raymondcamden.com/2025/07/29/using-chrome-ai-for-sentiment-analysis",
		"content":"Every now and then I get an idea for a blog post/demo, prepare to write about it, and realize I've actually covered the topic in the past. Sometimes, though, it works out really well especially when the technology has changed quite a bit. Almost a year ago, I blogged about doing sentiment analysis with Chrome's AI upcoming AI feature. At the time, it worked.... ok. The biggest issue at the time was the inability to provide a system instruction to the model as well as being able to shape the response a particular way. Thankfully, both of those are now supported.\nAs a reminder, the prompt API for the web is still in an origin trial, see the docs for more information and join the EPP to get access to forum and such.\nThe Demo\nOk, so as I mentioned, the two biggest issues when I first tried this was the lack of system instructions or the ability to specify a specific, structured response. Adding a system prompt is now fairly simple - this example is modified from the docs for initial prompts:\n\nNotice I've specified the kind of output I want as well as explaining how I want the model to act. But that's not enough to ensure you get exactly what you ask for. This is now doable by passing a JSON schema to your prompt.\nMy schema is fairly simple:\n\nJSON Schema is pretty handy so be sure to check their site for more information. Also see Thomas Steiner's blog post on structured output.\nSo putting the two together, I can get a sentiment value with input like so. First, some simple HTML for the demo:\n\nAnd then the JavaScript. I'm checking for support, but as I've mentioned before, I'm being lazy by not checking for a supported browser that hasn't downloaded the model yet. That being said, here's the JavaScript:\n\nI'll embed the demo below, but my assumption is that a lot of you won't be able to run it yet, so here's some sample inputs and output:\n\ni kinda like dogs. im not sure. they are fun to play with for sure\n\nResult: 0.7\n\ni don't think I like cats. maybe i've just been fooling people for years now. dont tell anyone - im ashamed...\n\nResult: 0.2\nI asked Gemini to generate a very negative product review for a shovel:\n\nThe Foo shovel is an absolute catastrophe of a tool and I wouldn't wish it upon my worst enemy. On its very first use in my Lafayette garden, the flimsy metal scoop bent into a useless, crumpled shape when it met soil that was only slightly compacted. The handle feels like it's made of cheap, hollow plastic and flexed so much I was sure it would snap and send me flying. This isn't just a poorly made product; it's an insult to the very concept of a shovel. Avoid the Foo at all costs unless you're looking for an expensive, oddly-shaped piece of garbage to take up space in your shed.\n\nThis is incredibly negative and gives... 0.0. I then asked for an overly positive shovel review (why shovels - I don't know - I don't work in the dirt and I hate gardening):\n\nThe Foo shovel is an absolute triumph of engineering and has completely revolutionized my gardening work. From the moment I picked it up, I could feel the superior quality; its perfect balance and comfortable, ergonomic grip make it a joy to use, even for hours on end. I tested it against the tough, clay-heavy soil we have here in Lafayette, and it sliced through dirt and roots with an ease I've never experienced before, turning a daunting task into a satisfying one. The high-carbon steel blade is incredibly durable and shows no sign of wear, proving this tool is a long-term investment. Honestly, this is the shovel I've been looking for my entire adult life, and it has earned its place as the undisputed champion of my tool shed.\n\nNot surprisingly, this gave me 1.0 as a result.\nSo obviously, this isn't going to be perfect, but given that it's on device, it could be helpful in a CMS or other web app where you may want to ensure your content isn't overly negative. It could also provide visual feedback and heck, I just figured out what my next blog post will cover. Anyway, the full embed is below, give it a try (if you can) and let me know what you think.\n\n  See the Pen \n  Prompt to Sentiment (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (7/27/25)",
		"date":"Sun Jul 27 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1753639200,
		"url":"https://www.raymondcamden.com/2025/07/27/links-for-you-72725",
		"content":"Good afternoon, programs. This past week was... frustrating. Incredibly slow. Don't really want to get into it but luckily I've had a very lazy and relaxing weekend. I'm counting my blessings but - like I said - frustrating. Let's just get to the links, shall we?\nImporting JSON is Easier Now\nNormally when importing JSON into my code, I'll do a fetch and parse the response into JSON. Not a big deal, but, it's now simpler as explained by Thomas Steiner on the web.dev blog: &quot;JSON module scripts are now Baseline Newly available&quot;. This feature is &quot;Baseline&quot; available means it works in modern browsers. Check the post for a full example, but here's a one-liner:\n\nI think for the most part it just works, but do note the warning in the post to ensure you server JSON files with the right content type. (Also, Thomas Steiner is the guy who helps me/comments on my Chrome AI posts and he's been super helpful!)\nOld School Weather\nI love weather apps, sites, and APIs. I can't get enough of them. I especially love this old-school retro-TV interface at WeatherStar 3000+.\n\n\n\nLiterature and Calendars\nI've recently done a few posts on calendars with BoxLang, so calendars have been on my mind lately. This post on Reactor discusses different books and calendaring systems, specifically focusing on how it impacts the story, reflects what's important to the society discussed in the books, and so forth. It sounds very academic, but it's honestly a good short read and great food for thought.\nJust For Fun\nLastly, a few days ago my wife introduced me to Laufey, an Icelandic singer who is a bit Bluesy, folksy, poppy, etc. A fantastic blend. I feel like I'm still getting to know her music so while this track may not be my favorite so far, I thought it was a pretty cool video. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Getting Image Insights with Built-in Chrome AI and EXIF Data",
		"date":"Fri Jul 25 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1753466400,
		"url":"https://www.raymondcamden.com/2025/07/25/getting-image-insights-with-built-in-chrome-ai-and-exif-data",
		"content":"It's been a busy few weeks for Chrome's Built-in AI support. Since the last time I blogged about it, four features have gone GA (which still means they are Chrome only but not behind a flag anymore):\n\nTranslator\nSummarizer\nLanguage Detector\nPrompt API (for extensions only)\n\nAnd while announced back at the end of May, Gemma 3n as a model is available in Canary, Dev, and Beta Chrome builds.\nTo be clear, the percentage of folks who can use these new features is still really low, but all of these features also work really well in progressive enhancement, and can be backed up by server calls to an API if need be. I continue to be really excited about the possibilities these APIs unlock, and thought I'd share a new demo I built.\nBack towards the end of May, I blogged about new multi-modal support in Chrome's Built-in AI. My demo promptly stopped working at some point and I corrected that. You can see this in action in the CodePen below, but keep in mind it still requires flags enabled and such. As always, join the EPP for more information.\n\n  See the Pen \n  MM + AI (Tags) (v2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo, that's the updated version of my previous blog post. I know most of you won't be able to run it, but it basically lets you select an image and hit analyze. It uses Chrome's built-in support to figure out what's in the image and return a list of tags. Here's an example:\n\n\n\nCool, so what's the new demo I want to share? It occurred to me that these APIs do not need to work in isolation and could be mashed up with others to provide even more value. With this in mind I built a new demo that does the following:\n\nGet the EXIF data from the image\nLook for longitude and latitude values\nIf they exist, pass them to Mapbox's Reverse Geocoding API to get the location\nJoin this with what Chrome gets back as well\n\nTo do this, I first searched for an EXIF library and found exif-js. I found the docs to be a bit lacking, and it doesn't support Promises, but I was able to get around that by wrapping the logic in my own Promise.\nThe EXIF information, if it exists, will be in four tags:\n\nGPSLongitude\nGPSLongitudeRef\nGPSLatitude\nGPSLatitudeRef\n\nThe values are in degrees and such, so math has to be done to translate it to decimal values. I also discovered that the library caches EXIF results in the DOM which means manually deleting it if you are using the same image DOM element as my tool uses. Here's my function:\n\nIf the values exist, I can then pass them to Mapbox's API:\n\nNotice that I'm only working with one small part of their API result to keep things simple.\nAlright, so all of the above is now part of my image analyzation routine. This is mostly what the previous blog past had, updated for the most recent updates, and now including my EXIF/ReverseGeoCode call:\n\nThere's two things I want to call out here. First, I'm doing my EXIF/Geocode call in a blocking manner, and then the AI call. It would be better if the location stuff and AI stuff both fired and then I used Promise.all to get the results when done. However, in my testing, the EXIF/ReverseGeoCode was done in a split second, incredibly fast, so I figured I wouldn't bother.\nFinally, and I've mentioned this before, but Chrome's Built-In AI stuff can be unsupported in the browser, supported and ready to go, or supported but needing to wait for the model to download the first time. My code rarely bothers with the last option and it should in a &quot;real&quot; app. I keep telling myself I'll include that in demos, but honestly, I think I'll wait to worry about that till this is GA.\nSo, how does it work? Again, I assume most of you can't run this yet, so here are a few examples:\n\n\n\n\n\n\n\n\n\nHere's the complete demo:\n\n  See the Pen \n  MM + AI (Tags) (v2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIf you've played with these APIs, I'd love to know - leave me a comment below!\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using AgentQL and Pipedream to Fix Missing RSS Feeds",
		"date":"Wed Jul 23 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1753293600,
		"url":"https://www.raymondcamden.com/2025/07/23/using-agentql-and-pipedream-to-fix-missing-rss-feeds",
		"content":"Last week I blogged about how I used AgentQL to scrape a web page, this blog to be precise, into pure data. If you don't remember, AgentQL lets you pass a simple query string that is run against a web page and parsed into data. So for example, I was able to use this query on my home page to get a list of entries in pure data:\n{\n  blogposts[] {\n    url\n    title\n\t  date\n  }\n}\n\nThe REST API was incredibly easy, and I demonstrated in BoxLang how I could use AgentQL to turn my blog's home page into a much simpler, smaller, HTML version. This of course begs the question, how else could we transform it? What about creating a RSS feed for a blog that doesn't have one!\n  Of course, if your blog does *not* have a RSS feed... you should just add it!\nThe Blog In Question\nThe blog I was trying to create an RSS feed from is on Google's developer blog, specifically focused on the Gemini AI product: https://developers.googleblog.com/en/search/?product_categories=Gemini. I did not notice an RSS mentioned in the UI, and when I did a View Source and looked for xml, feed, etc., I wasn't able to find it. To be clear, they may indeed have an RSS feed and I just don't see it. But that's ok, it wouldn't have stopped me from building this demo anyway. ;)\nUpdating My Query\nAs I showed above, it's relatively simple to write the AgentQL query to get a list of blog posts, but I knew that if I wanted to create an RSS feed I'd need the date in a different format. My original query returned the date as you see on the page, July 22, 2025 for example. AgentQL lets you pass 'hints' to the query, so I tried a few things and eventually ended up here:\n{\n  blogposts[] {\n    url\n    title\n\tdate(convert to time since epoch)\n  }\n}\n\nI tested this using their browser extension and it worked fine, since of course I can convert epoch to real time in my head. Here's part of the response (I removed a few entries to keep the output shorter):\n\nCool, now I've got a good query, time to build a workflow.\nBuilding the Workflow with Pipedream\nI went, of course, to Pipedream, my workflow service of choice. (Although I've been using n8n as well lately and will have some blog content on it soon.) Here's a visual representation of the workflow I created:\n\n\n\nThe first item in the workflow is simply a HTTP trigger. This gives me a URL I can use for the RSS feed.\nThe second item is a bit of Python code meant to define variables I use later in the flow. Pipedream supports environment variables of course, but these values are more like constants. Here they are:\n\nAs the comment says, the first value is the URL I'm parsing and the next three are used when generating the RSS.\nThe third step in my flow uses AgentQL, but also makes use of a Pipedream feature, Data Stores, for a lightweight key/value caching system. In the step itself, I configured it to use a particular store, and I then reference it in the code. Here's the entire step:\n\nThe cache check is up on top and I print out a message just to confirm the cache is working. At the bottom of the step, you can see where I store the value with an hour cache. To be honest, I could probably cache for a heck of a lot longer. The rest of the code is simply me hitting AgentQL's REST API, much like the code in my previous blog post, except in Python now.\nThe fourth step actually generates the RSS. For that I make use of a Python library called python-feedgen. For the most part, this is relatively simple, except for two small things. Let me share the code first:\n\nOk, for the most part, this looks relatively easy, right? The first issue I ran into however was that I needed my datetime value to have a timezone. I kinda thought the Python datetime value I got from converting the epoch time would have a &quot;natural&quot; timezone, but it did not. I Googled, saw the pytz library, and basically copied over the code it used. The actual timezone is back in my Define_Constants step and was a bit arbitrary. I figured Google is in California so I'd used that timezone.\nNow, the last few lines are the most confusing. The rss_str returns a binary, not &quot;regular&quot; string of XML. I have no idea why. There's probably a good reason and I asked on their repo about it. But it took all of one additional line to make it a string.\nAnd that's it! The last step is just a built-in Pipedream action to output an HTTP response and for that, I used the result of the last step. I also added the appropriate header:\n\n\n\nAnd that's it - I mean for real this time. You can see the generated RSS here: https://eop3qtblng22rbi.m.pipedream.net/. I do wish it returned a bit quicker. I could, in theory, cache the RSS string instead of the data and return earlier, and it may help, but I'm fine with it now. If you are a Pipedream user and want to see the raw source of the flow, you can find it here: https://github.com/cfjedimaster/General-Pipedream-AI-Stuff/tree/production/rss-from-gemini-blog-p_G6C5QeB\nPhoto by Ella Ivanescu on Unsplash\n",
		"tags":[
	        
            "python",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Simple Charting with BoxLang",
		"date":"Fri Jul 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752861600,
		"url":"https://www.raymondcamden.com/2025/07/18/simple-charting-with-boxlang",
		"content":"Building a BoxLang and want to know how to do charting with it? The answer is simple... don't!\nI kid! Sorry, but for many, far too many, years now I've argued that ColdFusion should not include a charting library. It's a server-side language, and in my (definitely not) humble opinion, client-side code doesn't belong there. (I'm fine not worrying about that though - I trust that team knows their clients better than I do and knows what they're doing.)\nThe good news (again, imo) is that BoxLang doesn't ship with an embedded client-side charting library. That being, I can absolutely see cases where you may be building a web app in BoxLang and want to add charting to it. Even though there's nothing in BoxLang that will create a chart for you, it can definitely help you build those charts. Here's a few simple examples.\nThe Data\nFor my charts, I'm just going to render a set of sales data over a few months. I built a class with some static data:\n\nI considered making sales random, but I really wanted the chart to look the same for every reload.\nThe Charting Library\nAny charting library would be fine, but I went with Chart.js as it was the first one that I remembered. There's probably a good twenty to thirty other different options you can use as well. I started off by creating a BXM file (BoxLang tag-based template) and literally copying over the sample from the Chart.js Getting Started guide. This was just a sanity test to ensure things worked ok.\nThe First Demo\nFor my first example, I decided to bundle the logic to get the data into BoxLang itself and output dynamic JavaScript in the response. This means when the page is viewed by the user, they're getting everything they need, and just need to load the charting library and execute the code to render it. Here's the template as a whole, and I'll explain the bits after:\n\nThe beginning of the template creates an instance of my data class, gets the data, and then filters out the array to a new array of labels and sales. Generally speaking, I don't like to put a lot of logic on top of templates like this - it was (usually) a bad idea in the ColdFusion world and not one I want to recreate in BoxLang, but as it's 3 lines - I'll deal with it.\nMoving on down - the last dynamic portion is converting those two values to JSON and including it in the JavaScript. I wrapped that portion with bx:output to flag the parser to replace variables within the tag pair. Nice and simple, right? Here's how it renders:\n\n\n\nThe Better Demo\nOk, that first version worked, but honestly, I could do less on the server and more on the client if I change things up a bit. Remember that class I made earlier? I can turn it into an API in about 1 second by changing public to remote on the access property of the method:\n\nAnd literally, that's it. As long as that file is under webroot somewhere, I can now get the code in JavaScript by hitting it at datasource.bx?method=getSales. Here's the new version of the template that uses BoxLang for data, but renders the HTML/JS as is, no server-side parsing at all:\n\nNote how in the JavaScript, I was able to copy and paste the BoxLang syntax right into JavaScript. Obviously that won't always work, but it's darn handy when it does. While I named this file with the bxm extension, I didn't really need to do that and would have used html in a &quot;real&quot; application.\nFeel free to grab these files up on the BoxLang demos repo, https://github.com/ortus-boxlang/bx-demos/tree/master/misc/charting_demos.\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Extracting Data from Web Pages with AgentQL and BoxLang",
		"date":"Wed Jul 16 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752688800,
		"url":"https://www.raymondcamden.com/2025/07/16/extracting-data-from-web-pages-with-agentql-and-boxlang",
		"content":"I discovered AgentQL a few weeks ago and have been thinking about it quite a bit. In a nutshell, it lets you perform queries against a web page. They've got a simple query language that kinda reminds me of GraphQL, but simpler. So for example, consider the page you are on right now - if I wanted to get the tags, I could use this query:\n{\n\ttags[]\n}\n\nAnd it would return:\n\nWhat if I wanted the links? I could change my query to express this:\n{ \n    tags[] {\n        label\n        url\n    }\n}\n\nAnd then get:\n\nTheir API supports both integration with Playwright for complex parsing (go to a page, find the search button, enter cats, search, get the results) and simpler 'go to a url and get stuff' workflows as well. They even have a handy browser extension that lets you test directly in the browser (that's how I got the results above).\nThis API is seriously powerful, and I've got some demos for other blog posts I want to share soon, but I thought I'd share a quick example of using their REST API with BoxLang. First, I started with a simple idea - hit my blog home page and turn the list of articles into an array of data. While my blog has an RSS feed, this would be super helpful for sites that do not.\nFirst off - their docs for the REST API show it's relatively simple. You pass the URL, the query, your key of course, and that's it. You can pass additional optional arg for thigs like, &quot;wait X seconds before you scrape&quot; and such, and you can even ask for a screenshot, but the basic operation is relatively simple. Let's look at an example:\n\nI begin by checking and getting a key from my local environment, because I never use keys in my code. Ever. (Ok that's a lie, I did for this one and then changed it to use the environment value before blogging. ;)\nMy query is looking for blog posts, as an array, and specifically the URL, title, and date. I put this along with the URL into my body and send it to the endpoint. The result contains two keys, data and metadata. You can see the result below:\n\nPretty slick, right? Your next question may be - what can we do with this data? What if we took it, and recreated the blog in a much simpler HTML version. Check out this version (as it uses some Markdown and my blog didn't like that, I switched to a gist):\n\n\n.gist{font-size: 18px}.gist-meta, .gist-file, .octotree_toggle, ul.comparison-list > li.title,button.button, a.button, span.button, button.minibutton, a.minibutton,span.minibutton, .clone-url-button > .clone-url-link{background: linear-gradient(#202020, #181818) !important;border-color: #383838 !important;border-radius: 0 0 3px 3px !important;text-shadow: none !important;color: #b5b5b5 !important}.markdown-format pre, .markdown-body pre, .markdown-format .highlight pre,.markdown-body .highlight pre, body.blog pre, #facebox pre, .blob-expanded,.terminal, .copyable-terminal, #notebook .input_area, .blob-code-context,.markdown-format code, body.blog pre > code, .api pre, .api code,.CodeMirror,.highlight{background-color: #1D1F21!important;color: #C5C8C6!important}.gist .blob-code{padding: 1px 10px !important;text-align: left;background: #000;border: 0}::selection{background: #24890d;color: #fff;text-shadow: none}::-moz-selection{background: #24890d;color: #fff;text-shadow: none}.blob-num{padding: 10px 8px 9px;text-align: right;color: #6B6B6B!important;border: 0}.blob-code,.blob-code-inner{color: #C5C8C6!important}.pl-c,.pl-c span{color: #969896!important;font-style: italic!important}.pl-c1{color: #DE935F!important}.pl-cce{color: #DE935F!important}.pl-cn{color: #DE935F!important}.pl-coc{color: #DE935F!important}.pl-cos{color: #B5BD68!important}.pl-e{color: #F0C674!important}.pl-ef{color: #F0C674!important}.pl-en{color: #F0C674!important}.pl-enc{color: #DE935F!important}.pl-enf{color: #F0C674!important}.pl-enm{color: #F0C674!important}.pl-ens{color: #DE935F!important}.pl-ent{color: #B294BB!important}.pl-entc{color: #F0C674!important}.pl-enti{color: #F0C674!important;font-weight: 700!important}.pl-entm{color: #C66!important}.pl-eoa{color: #B294BB!important}.pl-eoac{color: #C66!important}.pl-eoac .pl-pde{color: #C66!important}.pl-eoai{color: #B294BB!important}.pl-eoai .pl-pde{color: #B294BB!important}.pl-eoi{color: #F0C674!important}.pl-k{color: #B294BB!important}.pl-ko{color: #B294BB!important}.pl-kolp{color: #B294BB!important}.pl-kos{color: #DE935F!important}.pl-kou{color: #DE935F!important}.pl-mai .pl-sf{color: #C66!important}.pl-mb{color: #B5BD68!important;font-weight: 700!important}.pl-mc{color: #B294BB!important}.pl-mh .pl-pdh{color: #DE935F!important}.pl-mi{color: #B294BB!important;font-style: italic!important}.pl-ml{color: #B5BD68!important}.pl-mm{color: #C66!important}.pl-mp{color: #81A2BE!important}.pl-mp1 .pl-sf{color: #81A2BE!important}.pl-mq{color: #DE935F!important}.pl-mr{color: #B294BB!important}.pl-ms{color: #B294BB!important}.pl-pdb{color: #B5BD68!important;font-weight: 700!important}.pl-pdc{color: #969896!important;font-style: italic!important}.pl-pdc1{color: #D",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Cleaning Up My Print View with CSS Media Queries",
		"date":"Mon Jul 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752516000,
		"url":"https://www.raymondcamden.com/2025/07/14/cleaning-up-my-print-view-with-css-media-queries",
		"content":"I don't know why this popped into my head today, but I was thinking about the print version of this page, and others, and what steps could be done to improve the result. Specifically, I was thinking a lot about what should be hidden from the print version as it has no real meaning on paper, or in PDF. With that in mind, I did a quick test - just how well do my pages print now? You can see the result below:\n\nHonestly, I think that's just fine! And I give a lot of credit for that to the blog design I paid for and implemented years ago. That being said, note that on page 6, the real content ends and the entire rest of the page, and all of page 7, isn't really important. These items include:\n\nThe &quot;Support this Content&quot; block, which is pretty important to me, but not necessary in a PDF version.\nRelated content - sure the links will work in a PDF (not paper obviously), but given that the user printed this particular post, this extraneous content isn't really useful.\nThe comments - I love it when folks comment, but again, in a PDF version this isn't necessarily important. Now, it's possible there's great feedback in the contents you want to preserve, but I'm going to go with the idea that you want to focus on the core article itself.\nThe share menu - again, useless for print.\nThe final site footer which includes my name and other stuff. That's important for the site, not for a PDF/printed version, especially since the URL is already included in the output.\nWhile not present in the PDF, the ad is still present.\nOn, and the black header on top isn't really needed either.\n\nThat's quite a bit we can get rid of - how should we do it? The first thing I did was search of course, and the AI answer from Google was pretty spot on, and matched what I had expected, a simple CSS media query:\n\nThat was easy, but before I began testing, I then Google for something else. My plan was to edit the CSS in my editor, go to the browser and hit CTRL+P and see the results. Turns out you don't need to, at least on Chromium browsers (I'm using Edge right now). Go into your DevTools, ensure the Rendering tab is available (it wasn't for me, so hit the + sign to add it), and scroll down to Emulate CSS media type. Once you've found it, select print:\n\n\n\nAs soon as you do this, you'll see the print version of your site, although keep in mind it isn't the exact same as the PDF output, namely you don't see the header and footer that are placed there in the PDF.\nOnce I had that, it was simply a matter and using DevTools to identify elements I wanted to nuke and add them one by one. Here's the full list I used for my site:\n\nAfter adding this change, here's the new PDF version:\n\nThe file size is also a bit smaller too which is nice. Unfortunately there's a grand total of one line on page 6, but I don't think there's anything you can do about that. Although it wouldn't surprise me if CSS somehow supports a fix for this.\nAnd... I'm glad I googled before I published, because of course CSS can that, via the orphans and widows properties. It took me a bit to get the values just right, but from what I can see, this adjusts the line spacing a bit to not leave a page so empty. I used this CSS:\n\nAnd this was the result:\n\nAs you can see, page 6 is a bit less empty now.\nFinally, and I struggled with this, I added div.post-thumbnail to my list of things to exclude. I love my cute pictures on top... but that's all they are - a cute picture. This final version of the printed PDF is now down to 5 pages.\n\nBut wait - there's more...\nFor my particular test, I inline all my CSS and minify it in my build process, so it's just included in a big clump of data. I could have also used a completely different file, loaded via link with the media attribute:\n\nAnd of course, you can find out more at MDN: Printing. While there, make note of break-inside, which can help you avoid having tables and images over page breaks.\nThat's it for my changes here. I think more could be done, but I'm pretty happy with the updates.\nPhoto by 宁 宁 on Unsplash\n",
		"tags":[
	        
            "css"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (7/13/25)",
		"date":"Sun Jul 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752429600,
		"url":"https://www.raymondcamden.com/2025/07/13/links-for-you-71325",
		"content":"My goal for this series of posts is to share interesting links every two weeks, and I've noticed that sometimes it feels like a split second between when these posts go out. I'm actually a week or so late on this one, which is fine, but dang does time go by quickly. Usually summer is pretty slow, but with the new job, new product launch, and lots of camps for the kids, I don't feel like it's ever let up. Despite that, I've managed to put in near ten hours of Star Wars Outlaws this weekend, so I'm still getting a chance to catch my breath. I put the controller down for a bit so I can share these fun links with yall. Enjoy!\nUsing Transformers.js to Find Related Posts\nHere's a really cool example of GenAI-style tech used for content - finding related posts using Transformers.js. The example given here is for a blog written in Astro, but could be applied to any other tech, like Eleventy. (And if someone asks, heck, I'll make an example of that.) For my blog, I'm using an API call with Algolia, but this solution would be all done on your server, and at build time. I'm not sure it would work for this blog due to the size of the content, but I may give it a shot later this week.\nWhat Script is This?\nNext up is an excellent look at something I've never considered before - using JavaScript to determine which script block/tag it exists in. In his post, Alex MacArthur looks at document.currentScript and how it could be helpful to web developers. This is a feature I've never heard of, and it's been around for a decade.\nUsing AI for Fitness\nFor the last link, I'll share a post that's both cool and inspiring, Using AI to Get Fit, by one of my best friends, Scott Stroz. In it he talks about how he made use of MCP, Claude, and Spotify, to help build a playlist to aid in his exercise routine.\nJust For Fun\nNext time you make a quick GitHub repository, hope and pray you aren't accidentally triggering a huge milestone.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using GenAI to Create a SDK from Sample Code",
		"date":"Fri Jul 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752256800,
		"url":"https://www.raymondcamden.com/2025/07/11/using-genai-to-create-a-sdk-from-sample-code",
		"content":"I've been experimenting more and more with AI-assisted coding (not gonna call it vibe coding, even when I do), and the results have been incredibly interesting. Today, I decided to try something interesting - generating a SDK for an API.\nThere are multiple tools out there that can convert an OpenAPI specification to an SDK (and those tools I've seen are pretty darn cool), but I wanted to attempt another route. Earlier this month I blogged about the new Foxit PDF APIs I'm working with. These APIs are all really simple, which is good of course, but when prepping for the release, I wanted to build code samples for every possible operation.\nI did so, and they're up in a repo. I've got a complete set of Python examples here: https://github.com/foxitsoftware/developerapidemos/tree/main/pdfservices/python If you look at any one of them, and then another, you'll see they are very similar:\n\nUpload a doc.\nCall an API.\nCheck the API status.\nDownload the result.\n\nI was curious how well Copilot (in Visual Studio Code) could handle a request to turn this into a SDK. I began by going into my Python folder (my Node folder isn't quite complete yet), and asked:\nthis folder contains a set of samples that make use of a PDF Service API. \nI'd like to build a one page Python class as a simple lightweight SDK \nwrapper. It should cover at least Word to PDF and PDF to Word initially. \nThe developer will make an instance of this class and pass in their \ncredentials for ease of use.\n\nNotice I focused on two examples, Word to PDF and PDF to Word. I wanted Copilot to start small. Here's part of the response, and it's near perfect in understanding:\nI have reviewed both wordToPDF.py and pdfToWord.py. Both scripts use \nsimilar logic for authentication, uploading documents, starting a \nconversion task, polling for completion, and downloading the result.\n\nI will now create a single Python class, PDFServiceSDK, that wraps \nthis logic for both Word-to-PDF and PDF-to-Word conversions. The\n user will instantiate the class with their credentials and \n methods like word_to_pdf(input_path, output_path) and \n pdf_to_word(input_path, output_path).\n\nI will keep the implementation concise and easy to use.\n\nAnd ... it did it. Perfectly. I did notice, of course, it wrote code a bit differently than me. I tend to use a bit more whitespace in my code, and I tend to use camel case for variables in Python. However, most Python code uses snake case. When Copilot generated the SDK, it used snake case, and honestly, that was the right call. I'd rather have something that follows the more standard Python way of doing things.\nI then asked:\nthis looks good, look for all of the &quot;to PDF&quot; samples, \nand all the &quot;from PDF&quot; samples, and add those other methods\n\nThe wording here was pretty specific. My folder has a few files that don't relate to a particular API but are more general, and this would, in theory, cover most of the API.\nOnce again, it did it near perfect, except that I had a demo called pdfToThumbnail that I didn't want included. Ditto for one that attempted to convert PDF to Markdown. I was incredibly lazy and asked Copilot to remove these instead of me just selecting and deleting. I further proved how lazy I was by asking it to alpha sort the non private members.\nThe end result is an SDK I can now call like so:\n\nPretty simple, right? I then decided to push my luck:\ncan you make a copy of the Python SDK for Node.js. it should work \nwith the latest version of Node.js and use fetch() for network \ncalls. API methods should use async.\n\nThe initial result was CJS, so I then said:\nplease convert this to ESM support with imports.\n\nAnd voila - it did it just fine: https://github.com/foxitsoftware/developerapidemos/blob/main/sdktesting/nodejs/PDFServiceSDK.mjs\nHere's an example of using it:\n\nAs with the Python version, this code isn't exactly as I'd write it, this time mostly with the lack of white space in methods, but I can get over it.\nYou can see both SDKs, and 2 sample files, here: https://github.com/foxitsoftware/developerapidemos/tree/main/sdktesting\nWhat's Missing?\nI focused on the &quot;to&quot; and &quot;from&quot; methods, added Extract, but did not get the optimization APIs in. Those will be done by hand as by this point, it's just a matter of copying and pasting one of the existing methods.\nThe other issue is that these SDKs basically give you a sync-like interface to our async process. I think that's fine. However, I think developers may like to have option to do things more async as well. What I'm considering is this:\nRight now, when you do an operation, like wordToPDF, the signature looks like so: wordToPDF(input file, output file). What I'm considering is simply letting the output be optional. If you don't pass it, you get a task back. With that, I'd offer two more options. You can do your own looping and pausing with checkTask, or use pollTask(task, int) to poll until completion. Both would give you a bit more control, and give ",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Calendar with BoxLang - Part Deux",
		"date":"Thu Jul 10 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1752170400,
		"url":"https://www.raymondcamden.com/2025/07/10/creating-a-calendar-with-boxlang-part-deux",
		"content":"Earlier this week I posted a quick look at building a simple calendar with BoxLang, specifically an HTML one meant for a web page of course. This was a bit complex due to the needs of creating a proper HTML table, but generally I was... ok with the result. Yeah that's nice and vague, but there's some code I could state at and think of alternatives for nearly forever and it's ok to just put it down and walk away. So obviously, I'm returning to it today. Specifically, how to get events on the calendar.\nSample Data\nI started off with some sample data. Initially I thought about finding something online, perhaps a list of holidays, but I really wanted something good for a local demo. I also didn't want random events as it would make testing layout stuff a bit more difficult. So I went with an approach that has a static set of events based around a dynamic date - today. I whipped up a BoxLang class for that purpose:\n\nThis will create a set of events for last month, this month, and today, and in theory, work just fine if you test my code in the year 2092. Let me just say now and for the record, I have always supported our robotic overlords. While this isn't terribly interesting by itself, do make note of the date manipulation methods BoxLang supports. I find them really handy and flexible.\nRendering Events\nFor the demo today, I decided to use the first approach from my last post, the one that made use of a table. It's definitely not as simple as the second approach, but felt like, design-wise, I had more room to spare.\nI began by getting my events:\n\nMy class was named events.bx, hence the new call above. Then I realized I'd need a way to get events for a particular day, so I wrote a simple UDF:\n\nMy logic is simple - given that my events have times, I want to ignore them and simply see what day of the year it is and what year. As I loop over my calendar, I can then use this to filter to any matches.\nI display the current month in two places - first in the initial row (which has to display the previous month), and then in the logic that displays the rest of the month after the first week. I'll share the entire template in a bit, but I'll just show the second one here:\n\nHere's how this renders, and again, it's not terribly pretty, but you get the idea:\n\n\n\nI made the choice to not check for events in the previous and next month as I thought it would be distracting, but I can absolutely see disagreeing with that decision. To change that behavior you would just add similar rendering logic to those blocks.\nIf you want the complete source for this version, find it here: https://github.com/ortus-boxlang/bx-demos/tree/master/misc/calendar. It's in just_a_calendar_with_events.bxm as I get paid by the number of letters in the files I commit.\nLastly, and I should have mentioned this in the last post (thank you to Jon Clausen for reminding me!), the logic I wrote is not internationalized, which means it won't work in countries that have different starts of the week. You could absolutely update the code to handle that - but I'm going to cheat a bit and show you another idea in my next post. (Coming soon. Honest.)\nPhoto by Olga Gasheva on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Results from My Vibe Coding Live Stream",
		"date":"Tue Jul 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751997600,
		"url":"https://www.raymondcamden.com/2025/07/08/results-from-my-vibe-coding-live-stream",
		"content":"Usually for my Code Break shows, I assume if folks miss it they'll just check out the recordings, but while my earlier session is fresh in my mind, I thought I'd share a bit more about the session and a look at the code generated. It actually went a heck of a lot better than I anticipated to be honest, and was fun. To be clear, it wasn't perfect, and I'll touch a bit on what my tool of choice struggled with, but overall I'm really impressed with what I got built in an hour.\nWhat I Used and Why\nConstant readers here know I almost always use Google Gemini for anything AI related, but I've been trying to branch out a bit lately. Also, I found their Visual Studio Code integration... weird. Not bad but the few times I tried to developer with it I found it a bit awkward. That's probably just me and I probably just need to give it a bit more time, but for today I decided against it.\nInstead I decided to use GitHub Copilot. Again, in the few times I had tried it previously to today's stream, it just seemed to click a bit better with me. It could be because this support is more &quot;first party&quot; in VSC (i.e., a core part of the product versus an extension like Gemini) but whatever the reason, I felt like it would work better.\nWith that in mind, I created an empty directory, opened up the chat interface, and started with a prompt:\nWeb app that generates a randomized maze (of a set size), \nplaces a player character (@) in the start of the maze, \nmarks an exit (+), and then lets the player use keyboard \ncontrols to navigate the maze. \n\nWe are only using HTML, CSS, and JavaScript. No libraries. \nNo React. No Preact. Don't even say the word React. Just \nvanilla JavaScript. No Node.js for the server. \n\nThe first paragraph describes what I wanted to build, while the second was based on my experience with other tools really, really, really wanting me to use React and me just doing my best to Nope the hell out of that world.\n\n\n\nI also didn't want any kind of server aspect involved, even Node, as I didn't want a build process of any sort - just simple, static HTML, like my grandmother used to make on the weekend, with the lovely smell of div tags in the oven. Glorious I tell ya.\nThe initial result was actually really darn good. I especially loved how Copilot dropped the files in my folder with decent names (index.html, script.js, and style.css) and followed my instructions to not create any server or use a JavaScript framework.\nIf I had been smart, I'd have save all my work in source control (it is now, and I'll link to it in a bit), but for now I'll share a screenshot from the stream that shows how well it worked.\n\n\n\nIt was, honestly, perfect. Keyboard commands worked fine, I loved the dark theme, and it just plain worked.\nThe Iterations\nNow came the fun part. I started doing various iterations, mostly based on UI and some interactions.\nOne of the first things I did was have it work on the walls. I wanted it to add a 'brick' effect to the walls so that they looked more like, well, walls. This... did not work terribly well. We went through three or so iterations and then I gave up, and I was fine with that as the wall was a CSS declaration I could come back to later and make prettier if I wanted.\n\n\n\nI also tweaked the character representation from an @ symbol to an SVG of a stick figure. It's first output was fine, but had a blank white face. When I asked it to add two small eyes to it, again, it did so perfectly. I had some more feedback from my buddy Scott about adding a border around the maze, and again, Copilot added that just fine.\nOne of the last things I had Copilot do was change the 'end of maze' routine. Initially it just alerted something like &quot;You made it blah blah blah&quot;, but instead, I had it have you go to the next level of the maze, basically just render a new maze and increment a level counter.\nAt this point, it did something really well. It didn't just build what I requested, but also added a small delay. Here's the relevant part of the code.\n\nSpecifically what impressed me was the timeout. It occurred to me that if instantly went to a new maze, the user may think it glitched out a bit. By adding the slight pause, it's more obvious that a transition happened. I had not even thought of that and it was a great addition.\nAll in all, I'm really happy with the result and you can play it here: Maze Game. Try not to look at the URL, trust me on that. ;)\nMonsters Make Everything Better\nSo at this point, it was roughly 15 minutes or so before the show was supposed to end and I figured, what the hell, let's add monsters. My prompt was:\nlets add monsters! for now, represent them as *, add one to the maze, \nand it moves slowly, randomly, unless it sees the player, and in \nthat case it will move in the players direction. if the monster touches\nthe player (or the player touches the monster), the game is over. use\nan alert to say as such.\n\nThis worked better than I expected. The only issue is that the mazes a",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Calendar with BoxLang",
		"date":"Mon Jul 07 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751911200,
		"url":"https://www.raymondcamden.com/2025/07/07/creating-a-calendar-with-boxlang",
		"content":"Chalk this up to - &quot;Here's a simple idea for a quick and dirty blog post&quot; that turned into a few hours of my holiday weekend. Not only that, this is probably the first of three or so blog posts so... buck up, buttercup, this is going to be a fun ride. A while ago I had written down (well, typed in, I use Microsoft To Do to record writing ideas) the idea of demonstrating calendar creation with BoxLang, specifically creating a dynamic calendar, with or without events, either entirely server-side, or using a combination of client-side code with BoxLang providing the API. For today's post, I'm going to focus on (what I had assumed would be) the simplest version - just rendering a calendar for this month.\nIt's probably been a good ten plus years since I last created a calendar, by hand, in a server-side language, most likely Adobe ColdFusion. But I remembered the basic flow:\n\nCreate an HTML table\nGiven that you start a month on a day of the week, begin by rendering the end of the previous month in table cells\nFinish the first week\nCreate the rest of the month, ensuring you start and end new table rows where required\nAt the end, figure out how &quot;early&quot; you ended the month and add table cells for the next month to 'finish' the row\n\nSeems simple enough... right? While I knew I could create a function to encapsulate all of that, I started off with a simple template script. My plan was to put as much logic as possible up top in the script section, and then handle layout issues beneath that. Let's start there.\nVersion One\nI began with this block on top, where I created variables I thought I needed, and as I iterated, added and removed as I tweaked my approach.\n\nI normally remove testing stuff from blog posts, but that first commented out line was real useful in ensuring my code actually worked.\nAfter creating a variable for the current time, I figure out:\n\nThe first of the month, so I can get the day of the week\nThe number of days in the previous month, that helps render the previous month blocks in the table\nWhat day of the week the last day of the month is - so we figure out if we need to 'finish' the last row.\n\nNext my template has some CSS. I'll wait to share that till I get to the end of this section and share the entire script, but I did the bare minimum. Here's a look at how it renders:\n\n\n\nLovely, isn't it? Ok, now for the rendering. I begin by starting my table, adding the date label and the top level of the table for days of the week:\n\nNow for the first row:\n\nSo basically, I first figure out if the month doesn't start on Sunday, and if so, I render the previous days using the math you see in the loop there. I'll be honest - I kinda guessed at that - reloaded - tweaked - and eventually got it.\nThe next loop handles finishing the first row. I'll need to know what the last day of the month, first week size is, so I save it as lastDay.\nThis next block handles the rest of the days of the month:\n\nFor the most part I think this is relatively simple - the only kinda complex part is determining the day of week. The final bit handles &quot;trailing&quot; days - i.e. months that don't end on Saturday:\n\nHere's the entire script, and honestly I completely understand if your eyes glaze and you just scroll past:\n\nThis .... works ... and in theory could be nicely wrapped up in a BoxLang component (think web component, re-useable tags I could embed in an application) and some of the ugliness would be hidden, but it just feels gross to me. It feels like, yet again, a good example of why I always failed the Google tech screen.\nThat being said... I'm pretty psyched about the next version.\nVersion Two - Behold the Power of CSS\nAfter finishing that first version, the thing that struct me the most was that the complexity really seemed to be in the HTML table. I was curious about what CSS options I'd have to possibly simplify the code if I leaned on CSS more. A quick Google turned up something interesting, &quot;A Calendar in Three Lines of CSS&quot;. 3 lines of CSS? Surely you jest!\n\n\n\nThe approach documented at CSS-Trick is based on an article at the appropriately named &quot;Calendar Tricks&quot; and makes use of a simple CSS grid. The only 'code' aspect you need is to dynamically set the starting position of the grid, which comes from the first day of the month.\nUsing their approach, I re-built my demo to just this:\n\nThe code on top, and bottom, is way simpler. Now, to be fair this version doesn't show the previous and following month values, but the simplicity more than makes up for it. Here's that result:\n\n\n\nTo be fair, this version would probably make it a bit harder to include events - I'd probably add borders for that - but the logic is so much more easier to work with I think.\nWhat makes me most happy about this is that usually when I think about moving logic from the backend to the frontend, my brain goes to JavaScript. I don't consider offloading &quot;logic&quot; to CSS. Now I will!\nI plan on doing so",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Upcoming Code Break and Live Streams Next Week",
		"date":"Fri Jul 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751652000,
		"url":"https://www.raymondcamden.com/2025/07/04/upcoming-code-break-and-live-streams-next-week",
		"content":"Hey folks. Next week I've got not one, not two, but three different presentations happening. Here's what I've got going on.\n\nCode Break Episode 29\nMy next Code Break episode will be Tuesday, July 8th at 12PM CST. I'll be doing some playing around with vibe coding, and yes, I still hate that term. But I've had some recent successes, and failures, with it and I thought it would be fun to explore. Whether you are pro, or anti, generating code with AI, I'd love to have you come in and share your feedback.\nThe DevRel Show\nI'll be on Frédéric Harper's DevRel show on July 10th at 2PM CST, talking about APIs, specifically Foxit APIs. This is my first time on his show and the first time I'll be talking publicly about our new APIs, so I'm pretty excited. Here's the direct link to the stream: https://www.youtube.com/watch?v=KSrhIwRWIe0\nThe Ben and Ryan Show\nOk, let me start off by saying I don't have a link for this one yet, sorry, I should have it Monday and in theory I could have waited to post this till then, but with Code Break coming up soon I wanted to get it out the door before the weekend. I'll be joining Ben Nadel and Ryan Brown on their show, appropriately named &quot;The Ben and Ryan Show&quot;, also on July 10th (busy day!) at 12PM CST. I'm 90% certain of that. ;) As soon as I get a link, I'll edit this post so just check back and you'll get the deets then (as the kids say).\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a CSV Report CLI Tool in BoxLang",
		"date":"Thu Jul 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751565600,
		"url":"https://www.raymondcamden.com/2025/07/03/building-a-csv-report-cli-tool-in-boxlang",
		"content":"Remember some time ago (yesterday) when I wrote about CSV parsing in BoxLang using the opencsv Java library and Maven? As I said then, my initial impetus for that post was to recreate my ColdFusion Hackathon project, but once I got it working, it turned out to be really useful for something completely different.\nThe Data\nIf you're on a desktop machine and look down to your right, you'll notice I've got an ad from from EthicalAds. I've been using them as an ad network for a bit over a year now. I'm not going to get rich anytime soon with the money I've earned, but it's the first ad network in a while that felt low key and less &quot;in your face&quot;. My expenses here are pretty minimal, and the money earned from them covers it just barely, so I come out ahead. (Approximately enough to get one six pack of 'good' beer.)\nMy traffic has been mostly stable this year, but has had a slight uptick the past two or three months. The EthicalAds report system makes it easy to check my earnings, but one thing it doesn't show is a &quot;month by month&quot; view of earnings. I could go into their dashboard and check a month at a time manually, but why do something by hand in 5 minutes that I could automate in 30???\nThe earnings report has a handy CSV export function, so I set it up to display data for a complete year (June 30th 2024 to June 30th 2025) and downloaded that data. Here's a few rows from that export:\nindex,views,clicks,ctr,ecpm,revenue,revenue_share\n&quot;Jun 30, 2025 (Mon)&quot;,218,0,0.0,2.5586697247706427,0.5577900000000001,0.39045300000000005\n&quot;Jun 29, 2025 (Sun)&quot;,106,0,0.0,2.596320754716981,0.27520999999999995,0.19264699999999996\n&quot;Jun 28, 2025 (Sat)&quot;,91,0,0.0,2.4438461538461542,0.22239000000000003,0.155673\n&quot;Jun 27, 2025 (Fri)&quot;,219,0,0.0,2.9222831050228315,0.6399800000000001,0.44798600000000005\n&quot;Jun 26, 2025 (Thu)&quot;,257,0,0.0,2.592801556420234,0.6663500000000001,0.46644500000000005\n&quot;Jun 25, 2025 (Wed)&quot;,301,0,0.0,2.481495016611296,0.7469300000000002,0.5228510000000001\n&quot;Jun 24, 2025 (Tue)&quot;,266,0,0.0,2.5290225563909785,0.6727200000000002,0.4709040000000001\n&quot;Jun 23, 2025 (Mon)&quot;,263,0,0.0,2.203041825095057,0.5794,0.40558\n&quot;Jun 22, 2025 (Sun)&quot;,115,0,0.0,2.5464347826086953,0.29284,0.20498799999999998\n&quot;Jun 21, 2025 (Sat)&quot;,97,0,0.0,2.3290721649484536,0.22592,0.158144\n&quot;Jun 20, 2025 (Fri)&quot;,228,0,0.0,2.456184210526316,0.56001,0.392007\n\nEach line represents one day of stats. From this, I wanted to build a tool that would aggregate the data into months and show me the results. Enter BoxLang.\nThe Code\nBefore we get into the code, remember that yesterday I covered how to add CSV support to BoxLang with the opencsv Java package. My initial code simply imported what I needed, and parsed in the file:\n\nI dumped this and noticed that the very last row of data was a 'totals' row, so I spent 8 hours vibe coding and added:\n\nBoom - data imported. Now, I'm going to loop over my raw data and a) parse the date, b) copy over the values I care about which are earnings, and because I was curious, views and clicks:\n\nNotice I also rounded the earnings a bit. I'm no Gus Gorman after all. (The first person to identify that name and image below without Googling gets 500 Ray Respect points.)\n\n\n\nAt this point, I've got an array of values that consist of a valid date, the amount earned in rounded cents, views, and clicks. I now needed to aggregate this information over a set of months. Here's how I accomplished that:\n\nI create a key based on the month and year of the data and if it doesn't already exist, add it to my final array with default values. After that, I then update the three values: total, views, and clicks.\nThe final part is just rendering it to screen:\n\nAnd voila - why I'm not retiring and living off my blog anytime soon:\nDATE            TOTAL$  VIEWS   CLICKS\n6/2024          0.34    199     0\n7/2024          3.53    2,150   9\n8/2024          10.46   7,005   9\n9/2024          12.05   6,983   3\n10/2024         10.75   8,180   8\n11/2024         9.52    8,272   8\n12/2024         12.75   8,620   20\n1/2025          8.31    8,412   8\n2/2025          8.99    7,756   11\n3/2025          10.01   8,243   23\n4/2025          8.83    5,811   12\n5/2025          11.03   6,310   12\n6/2025          10.86   5,952   5\n\nHere's the complete script so you can see it in its entirety. As always, let me know if you have any questions:\n\nPhoto by Dan Dennis on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Parsing CSV in BoxLang - Maven Style",
		"date":"Wed Jul 02 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751479200,
		"url":"https://www.raymondcamden.com/2025/07/02/parsing-csv-in-boxlang-maven-style",
		"content":"I recently did some CSV parsing in ColdFusion while working on my ColdFusion 2025 Hackathon submission, and while I didn't win, I really enjoyed the little utility I built. That tool made use of CSV parsing support in Adobe ColdFusion and I thought I'd take a look at what I'd need to use to support that in BoxLang. This led me to look for a Java tool and gave me a chance to try something new in BoxLang, Maven support.\nBoxLang runs on the JVM, but doesn't really require you to know any Java. Which is good. I've been &quot;casually&quot; familiar with Java since it came out, but have never done any real work in it nor really spent any time learning the language. I know enough to know there's a huge amount of open source projects out there and for nearly all of them, you can grab a jar (think of this like a packaged zip), drop it in your server, and go to town. I blogged about this back in February, but once you've gotten a jar, it can be as easy as this:\n\nThat works just fine, but BoxLang also supports Maven integration as well. Again, this is something I've been casually familiar with. In ways, it's a bit like Node's package.json support where you can define a dependency, run a command, and it will install a package along with any required dependencies of the package you want. In the Maven world, this is done via XML...\n\n\n\nWhich I can get over. It's what's used in Java-world and if I'm going to play in Java-world, I got to get over it.\nBoxLang's Maven support has really good documentation. If you've never even heard the word before, the docs walk you through installation and usage, and have a lot of great examples. While BoxLang runs on the JVM, folks can't expect to find every bit of Java documentation there as that would be a) distracting and b) repetitive of existing docs out there. However - I'm really grateful that the BoxLang team made an exception here and included documentation for working with Maven right within the docs.\nOk, given all that, when I searched for CSV support in Java, I came across opencsv, and while digging around in the docs, found that I needed to add this to my BoxLang pom.xml file:\n\nFor me, that file was here: ~/.boxlang/pom.xml. After pasting that in, I then ran mvn install, and it proceeded to grab the library and everything it needed. As I'm not running a server, just testing via the CLI, that was literally it. I got to testing.\nFirst, I added a short CSV file:\n&quot;name&quot;,&quot;age&quot;\n&quot;ray&quot;,52\n&quot;scott&quot;,90\n&quot;todd&quot;,45\n\nAnd then using the opencsv docs, created a quick sample:\n\nThose imports came right from the opencsv docs, trust me I didn't come up with them on my own. The readAll method returns an array of arrays where I can access each column by the inner array index. Here's the output:\n\nNotice it included the header, which makes sense of course, and a simple solution would be to just skip row 1, but instead I did the unthinkable and continued to look at the docs. Turns out the library has a CSVReaderHeaderAware method that not only will ignore the header in it's results, but use the header to create an array of objects instead of an array of arrays. Well, if you do a bit of work. Here's a 8 line UDF wrapper:\n\nRunning allData = readCSV('test.csv') now returns:\n\nAs an FYI, BoxLang 1.3.0 added a 'pretty' argument to jsonSerialize, if I use:\n\nI get:\n[ {\n  &quot;name&quot; : &quot;ray&quot;,\n  &quot;age&quot; : &quot;52&quot;\n}, {\n  &quot;name&quot; : &quot;scott&quot;,\n  &quot;age&quot; : &quot;90&quot;\n}, {\n  &quot;name&quot; : &quot;todd&quot;,\n  &quot;age&quot; : &quot;45&quot;\n} ]\n\nEasy-peasy! I've got a real world use case for this (more than just supporting my bug tracker hackathon application) that I'll share in my next post.\nPhoto by Mika Baumeister on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Take Foxit's New PDF APIs for a Spin",
		"date":"Tue Jul 01 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751392800,
		"url":"https://www.raymondcamden.com/2025/07/01/take-foxits-new-pdf-apis-for-a-spin",
		"content":"Back in May I announced my new role at Foxit as an API evangelist. At the time, I couldn't talk much about what I was working on (although the title kinda gives it away), but yesterday we launched our new offering and I can finally share some news about it, and even better, some code.\nSo, the TLDR is that we launched a new API offering and developer portal. These APIs are all related to document management and PDFs, so much like I was doing in my previous role at the company famous for subscription-based desktop apps, which is nice as I can bring my existing expertise to the table.\nOur APIs fall into four basic categories:\n\nPDF Services - a set of basic PDF manipulation APIs like converting to and from PDF, optimizing and so forth\nDocument Generation - APIs related to creating dynamic PDFs from a Word template\neSign - not new, but part of the new umbrella, APIs related to electronic signing\nEmbed - PDF embedding library for the web\n\nWe're still ironing out a few kinks, but there's going to be some rapid updates to the offerings. You can sign up to get credentials and there's a free tier, not trial, so you can absolutely kick the tires before you commit to buying anything. How about a quick example?\nConverting to PDF\nThe simplest example is just converting an Office document to PDF. The PDF Services tools all fall into a similar flow.\n\nYou upload your document.\nYou start a job.\nYou check the job.\nYou download the result.\n\nLet's take this bit by bit. The Upload API requires your credentials and the binary bits of your data. Here's a Python wrapper for that:\n\nThis returns a document ID value. Here's an example calling it, where CLIENT_ID and CLIENT_SECRET are my credentials loaded from environment variables:\n\nNext, I'll call the Word to PDF endpoint, which only requires the document ID from the previous call. Again, I've got a Python wrapper:\n\nHere's how I call it using the previous result:\n\nThe result of the operation is a task ID that can then be checked to see how the process is going. This API returns a task object containing things like the status, progress, and so forth. Here's my implementation of a function that loops until the task is done or an error is thrown:\n\nThis could probably fancier. Anyway, here's the call:\n\nThe final part, assuming everything works ok, is to use Download API to get the bits. Again, a wrapper:\n\nAnd honestly, that's it. All of the rest of the PDF Services APIs are tiny variations of that flow. Here's the complete script just to show you the entirety of the operation:\n\nWhat's Next?\nAs I said, we literally just launched this about twenty-four hours ago and we've got a lot more coming down the pipe. If this interests you, check out the site, and you can join our forum to ask questions. (Although feel free to leave a comment below.) I've got a live webinar coming up July 17th that you can register for to see these APIs in action, and I hope to see you there. We also have a developer blog launching soon on the main site, so I'll be doing most of my blogging there. Finally, I've got a repo, https://github.com/foxitsoftware/developerapidemos, of code demos supporting these APIs. I've got Python examples for everything, and Node examples for some things. Check it out.\nPhoto by Hoyoun Lee on Unsplash\n",
		"tags":[
	        
            "foxit",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "AI with BL",
		"date":"Fri Jun 27 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1751047200,
		"url":"https://www.raymondcamden.com/2025/06/27/ai-with-bl",
		"content":"Forgive the somewhat cutesy title. I hate vague titles that only serve to be clickbait, but given the subject, I couldn't help it. This post is about AI, specifically Generative AI, with BL, AKA BoxLang. This has always been possible with BoxLang and any GenAI service that had a REST API, but recently the team released an impressive AI Module that makes this a lot easier. So what does it do?\nMuch like LangChain, the BoxLang AI module provides a unified interface to work with multiple different AI providers. This makes it a bit easier to switch from one service to another. I don't necessarily see people doing that willy nilly. Each service has it's unique strengths and weaknesses and pricing schemes. But the module at least makes the technical aspect of switching easier. And heck, even if you don't plan on switching, it still greatly simplifies making use of those APIs in your code. Let's consider an example.\nThe quickest and simplest way to use the module is via the aiChat BIF:\n\nThis returns the response as a simple string. Want to use OpenAI?\n\nLiterally the only change was to the provider and apiKey values. The docs cover this and you should check them for the latest updates, but currently you get support for Claude, DeepSeek, Gemini, Grok (eww), OpenAI, and Perplexity out of the box.\nWhile aiChat supports a simple string for messages, you can also pass an array which is a handy way to handle system messages:\n\nIn this example I've used the same prompt with two different system messages. This has a wild impact on the output as you can imagine (the line in the output below delineates between the two versions):\n\nThat's a wonderful question! Stars are like giant, sparkly babies that come from something called a nebula.\nImagine a big, fluffy cloud in space, much bigger than the Earth! This cloud is made of gas and dust, like tiny little specks of stardust. This cloud is called a nebula.\nOver a very, very long time, gravity (which is like a big hug that pulls things together!) starts to pull the gas and dust in the nebula closer and closer.\nAs the gas and dust squish together, it gets hotter and hotter in the middle.  Imagine rubbing your hands together really fast - they get warm, right?  It's kind of like that!\nEventually, the middle gets so hot that it starts to glow, like a tiny spark. This spark gets bigger and bigger, until it becomes a bright, shining star!\nSo, to recap, stars come from:\n\nA big, fluffy cloud of gas and dust called a nebula.\nGravity pulls the gas and dust together.\nThe middle gets hotter and hotter until it starts to glow.\nThe glowing ball of hot gas becomes a star!\n\nIsn't that amazing? Space is a magical place! Do you have any other questions about stars or space?\n\nAlright, let's talk about where stars come from. It's a pretty grand story, spanning billions of years and involving vast cosmic processes.\nEssentially, stars are born from giant clouds of gas and dust called nebulae. Think of these nebulae as the raw ingredients of the universe – mostly hydrogen and helium, with traces of other elements.\nHere's a more detailed breakdown:\n\n\nThe Nebula Exists: These nebulae are vast, diffuse regions in space. They're cold and not very dense, just hanging around.\n\n\nSomething Triggers Collapse: This is the crucial step. Something needs to disrupt the equilibrium of the nebula and start the process of gravitational collapse. Possible triggers include:\n\nShockwaves from a nearby supernova: A massive star exploding can send out shockwaves that compress regions of the nebula.\nCollisions between nebulae: Two nebulae bumping into each other can create dense regions.\nDensity fluctuations: There could be some inherent random density fluctuations in the nebula. If a region is slightly denser than its surroundings, its gravity becomes stronger, attracting more material.\n\n\n\nGravitational Collapse: Once a region within the nebula becomes dense enough, gravity takes over. The gas and dust start to collapse inward, pulled by their own mutual gravitational attraction. As the cloud collapses, it fragments into smaller and smaller clumps.\n\n\nFormation of a Protostar: As a fragment collapses, it heats up. This hot, dense core is called a protostar. The protostar is not yet a true star because it's not undergoing nuclear fusion. It's just a ball of hot gas contracting under gravity.\n\n\nAccretion Disk and Jets: The protostar is often surrounded by a rotating disk of gas and dust called an accretion disk. Material from the disk spirals inward onto the protostar, feeding its growth. Powerful jets of gas are often ejected from the poles of the protostar, perpendicular to the accretion disk. These jets help to carry away excess angular momentum, allowing the protostar to continue collapsing.\n\n\nIgnition of Nuclear Fusion: As the protostar continues to collapse, the core gets hotter and denser. Eventually, the temperature and pressure in the core reach a critical point where nuclear fusion can begin. This is where hydrogen at",
		"tags":[
	        
            "boxlang",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Updates to my Table Sorting Web Component",
		"date":"Thu Jun 26 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1750960800,
		"url":"https://www.raymondcamden.com/2025/06/26/updates-to-my-table-sorting-web-component",
		"content":"It's been a while since I touched my &lt;table-sort&gt; web component, but last night I had a few interesting ideas and thought I'd do a quick update. For folks who may not remember, I first blogged about this way back in March of 2023. The basic idea was to take an existing table, wrap it in my web component, and sorting would be added automatically. Nice and simple.\nAs an example:\n\nThe only real &quot;feature&quot; was that if you included numeric=&quot;X&quot;, it would consider the Xth column as numeric and ensure sorting worked properly. X in this context could be one column, or a list, and it was 1-based of course.\nYou can see that initial version here:\n\n  See the Pen \n  PE Table for Sorting (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAt some point, and I don't know if I blogged it, I even put it up on npm: https://www.npmjs.com/package/@raymondcamden/table-sorter\nChange One - Support Custom Sorting\nOk, so what did I change? I recently realized that it would be nice if you could sort by dates. I could have added something along the lines of the numeric attribute, but I wanted a more generic solution that could apply to any &quot;weird&quot; sorting use case. To do so, I added support for a data-sortval column. Why?\nWell, as I said, my initial goal was to make it as simple as possible to use the web component. You shouldn't need to do anything to your table, just wrap and go. While that's the ideal, there's going to be cases where if I ask the HTML developer to do a bit of work, the component can do it's job better as well.\nTo enable this, I went with a data attribute in your td cell. So for example, imagine you've got dates, you could use the data attribute to store a numeric version of the date, usually being the number of seconds since epoch, but in the example below, I just used simple small values:\n\nNow when it comes time to sort, my code will use the values in the cells for the first three columns, and the value from data-sortval for the fourth. I love data attributes as they allow for customizations to the web page like this and are easy to use from JavaScript as well.\nThis worked great.. until I actually sorted and realized I needed to make another change.\nChange Two - Rendering the Table\nMy initial implementation generally worked like so:\n\nGet the table I'm wrapping.\nGet every cell and copy the data to an array of objects\nOn sort, sort my copy of the data and re-render the HTML\n\nThe re-rendering was destructive, which meant that if you're original table cells had something custom, perhaps a class applied to cells where the value met certain criteria, it was lost. Essentially this:\n\nBecame this after a sort:\n\nI knew this was a problem and had opened an issue on my repo a year ago. I finally addressed it by sorting the TR rows in the DOM, preserving any custom attributes in your existing table.\nYou can see this in the example below - if you open your browser the onmouseover in the very last td is preserved on sort.\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOk, that's it! If you want to share any bugs or suggestions, hit up the repo and you can install this yourself like so: npm i @raymondcamden/table-sorter\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (6/22/25)",
		"date":"Sun Jun 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1750615200,
		"url":"https://www.raymondcamden.com/2025/06/22/links-for-you-62225",
		"content":"Greetings, programs. This has been a quite excellent weekend so far, and today will be awesome for one reason alone - there's not one dang thing on our calendar. A day with nothing to do is a glorious day indeed. I spent this morning finishing prestige 10 in COD and catching up on my comics (&quot;One World Under Doom&quot; is incredible). Now let's get to the links...\nWhat exactly do I do?\nI've had to explain developer advocacy and evangelism for years now, but I absolutely love this explanation by Ashley Willis, &quot;What Is Developer Advocacy (2025 Edition)&quot;. In it, she goes into great detail about the job myself, and many of my friends, love, and talks about how the role has changed in the both post-COVID and in the current economic hellscape. (Have I mentioned how happy I am to have a job?) It's a great read and worth your time.\nYet Another Josh Comeau Link\nI've shared Josh Comeau links many times here and his ability to explain technical matter with incredibly simple and well-built demos is easily the best in the industry. His Operator Lookup tool is no exception. If you were ever curious about random and unusual JavaScript operators then this tool will explain them in detail. As an example, here's the Logical AND Assignment Operator. That was one I had absolutely zero clue about.\nDid You Know CSS Could...\nDid you know CSS could style alt text for images? I didn't. Andy Bell goes into great detail about how CSS can be used to style alt text in images with plenty of great examples. Frankly I'm at a point where I think CSS can pretty much do anything.\nJust For (Musical) Fun\nOk, this isn't really a fun song, it's actually quite moody and brings up way too many emotions in me, which makes it an incredible song. I play this one almost daily, although I try to avoid if I'm already in anxious/depressed mood.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Upcoming Webinars",
		"date":"Thu Jun 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1750356000,
		"url":"https://www.raymondcamden.com/2025/06/19/upcoming-webinars",
		"content":"Good morning, programs. I wanted to share some news about two upcoming webinars. The first is my next Code Break show, &quot;Building Desktop Apps with Python and Flet - Part Deux&quot;, happening next Tuesday, June 24th, at 12PM CST. As you can tell by the title, I'll be continuing my look at the awesome Flet project and seeing if I can finish the app I started last time. If you don't remember, it's quite deadly... (And be sure to check out the last session for the first part!)\nNext, on July 23rd, 11AM CST, I'm hosting a free webinar where I'll live code building a BoxLang application. My plan is to see how far I can get in an hour (which means I need to figure out what I'm building soon-ish) and if it's a success, I'll follow up with a second part and keep hacking away. I'm sure you've seen my BoxLang posts, but if you haven't given it a try yet, this will be a great time to see it in action. I hope to see you there!\n",
		"tags":[
	        
            "python",
            
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Sorting Out Your Monarchs with BoxLang",
		"date":"Tue Jun 17 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1750183200,
		"url":"https://www.raymondcamden.com/2025/06/17/sorting-out-your-monarchs-with-boxlang",
		"content":"I know what you're thinking right now - a monarch problem? How did Raymond know I had a monarch problem? What can I say, with great age comes great wisdom, or, more likely, random code challenges. I've mentioned &quot;rendezvous with cassidoo&quot; before as one of the newsletters I subscribe to. Authored by the very interesting Cassidy Williams, this short and sweet newsletter always has interesting content and always ends with a basic code challenge, what she calls her 'interview question of the week'. This weeks question was pretty fun:\n  Given an array of strings representing the names of monarchs and their ordinal numbers, write a function that returns the list of names sorted first by name and then by their ordinal value (in Roman numerals), in ascending order.\nHere's two examples:\n\nI decided to take a stab at this with BoxLang and in doing so, ran into some interesting tidbits I thought I'd share. First, let's handle the Roman problem.\nThose Pesky Romans\nQuick trivia question for you. What is the only number you can't represent in Roman numerals?\nZero.\nOk, that being said, my first task was to build a generic &quot;roman numeral to integer&quot; conversion method, and for that, I totally cheatedwent to AI for a bit of help. I knew I was going to be writing in script, and with BoxLang still being kinda new, simply asked for a JavaScript solution:\n&quot;write javascript code to convert roman numerals to integers&quot;\nI used Google Gemini for this, and one of the things I like about their code solutions is that they almost always provide test calls with expected solutions. Here is their solution. (The original solution made use of console.log, so to make it easier for you, dear reader, I made it output to the DOM.)\n\n  See the Pen \n  Roman to Int by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe solution seems sensible enough, although I would not have bothered making a variable for the uppercase version of the input. I'd just have done: s = s.toUpperCase(). Outside of that, the logic makes sense to me.\nOk, so now to convert this to BoxLang. I'll share the complete rewrite below, but let me share the interesting bits first.\n\nArrays in BoxLang start with 1, not 0.\nAn interesting part of the JS code is that nextVal will, one time, reference an index past the end of the length of the string, which is null, and check against romanMap, which is basically asking the map for a null key. JavaScript handled that, still returning null, but I had to break that apart a bit in BoxLang.\nBoxLang automatically scopes variables defined in a method to the method itself, so I was able to get rid of let and const.\nIn the original code, you can see where Gemini mentioned you could throw an error on invalid input instead of NaN. I did that in BoxLang.\n\nAnd that's basically it. I also changed the console logs to println statements instead. You can test this out here:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"600\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJytVW1v2jAQ%2Frz8ilsrlaQ0oayttsLo1rFKQyqatHYIqeqHNDnAarAz26FUFf99ZycBAmirtPEpd757nudebBqHhw4cQlfwGUqtIIQfYhpy4NkUZZiA0pLxMWgB5GRc4xhlQBkm6XMaynAKL3nMAhTcTnBnfmDDJepMcgUvdPiAcmHDC0zAXxmbhQlyDWIEehPIIDScUcYjzQQHac5uRY9rV3nw4jiQu%2FphCh2ygX61Xq0FzaP8e0DfZ8X30PiPC%2BPaHJRG156U1ld7VFr9%2FOyYrEXbEGqhqcAOHLfJyNIUZTdUeEMeFWjxs3S4no1uNOBdAD1NxWik%2BqTIxhNbZ9HiERUACY60abZk44mmmmEkJLiMMJttYPCxs0YUULMIHFi9XrQAgI3ct2Ujgkd8vpozpZW7Srpj955XNAisjCd3r8ep8yzeGF00ofFGJLgF%2BxWA%2FT2vbQEWgIlCQ0rStpTBwQH8TUy9%2Bc9yCGIlKO9ClElJizSw4ykV3FVquM8TOM6LsEoFqqjg0%2B5soryHFilLknbZd3BLrIs1%2FlVtNP%2Bb7EGbEuzUixigUjPMh2%2FcdqmCsh92w%2FzOGmCl8WvY3ylZPjGFR8B0TZnbGsYx02xGVKQ6gMs43iauMtV3MDlFV%2FPLmwe2HXIRqe%2F7cDUPp2mCyhjW%2BSVULLKcyklps3XCaaS9HvgXsL92b2vkq3lmdLCs4mqeIo04BpHpNNMtOFlBXA92gVjvEmYXxNkHcK87dMVh0Dk7AgrvnHhWaTmPskfregfbcgcVtbvJTtcghtsQw1dAnFtt%2FW5%2F2KXagJk3ORIi8RMhHs1Dkb%2BewYppGbvJVx4UrLvYmufn7y2hWW2fcYVcma1h%2BhkwH%2B2KaBpN5xGbbfEU%2Fj%2FTnDqWp7zcy%2Btc0ihHy2da6TdLuuHwcovqsiBxFjQyHU1c9Co530IeJ0SM8whT81dBrwUGU1QqHKN9J2h1X0NEvv9B9RsaFTr%2F\">\n    \nCool. The Roman problem is solved! Now to sorting.\nPutting On Our Sorting Hat\nSorting now comes down to a simple callback function provided to the built-in sort method. Here's how I did it:\n\nAs per the requirements, I sort by name first, then the value of the roman numeral. I tested like so:\n\nYou can see the output below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"600\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJylVF1P2zAUfe%2BvuCsSJLSk7aS9NIQNMTRFAm0aDCFVfXAbt7GW2pnt8DHU%2F75rx2mSkmlIy5Pv9bnn3K94dHzcg2O4EPyBSq2AwHexIRx4saGSZKC0ZHwNWgA6Gdd0TWWAESboU04k2cBLidmCgtuUdsYHFi6pLiRX8IKXCyq3Fu44gf4q2APJKNcgVqD3iQzDqLcq%2BFIzwUGau1sRc%2B0pH156PShd1ySHCG3A7yg%2BmsJkWJ7v8PzBne%2BNf%2ByMK3NRGRf2prI%2B26vKui7vxmhtQyOohcYCIxiHaBR5TuUFUfQGPSrQ4kfl8HyLHo3gfQCxxmI0xfqkKNaprdO1eIUFQEZX2jRbsnWqsWZYCQkeQ85JCAxOo4ZQgM1CcmCDgWsBAFt576pGBD%2Fp8%2BUTU1p5ddCMzX3fN",
		"tags":[
	        
            "boxlang",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My ColdFusion 2025 Hackathon Submission - QuickTracker",
		"date":"Fri Jun 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1749837600,
		"url":"https://www.raymondcamden.com/2025/06/13/my-coldfusion-2025-hackathon-submission-quicktracker",
		"content":"Earlier this month, the ColdFusion team announced a hackathon that started today, and ends Monday night. Full disclosure, when I saw the announcement, I thought that the date range is when things had to be turned in. I spent a few hours on what I'm going to share below, but when I found out that the intent was to start today, I wrapped up and stopped. My submission only took a few hours, and outside of a quick readme update today, I feel fine with my submission. And heck, it was fun to build, so I don't really care if I win (ok, that's a bit of a lie). With that out of the way, let me share what I created, QuickTracker.cfm.\nQuickTracker.cfm is based on a tool first built by a buddy, and previous boss of mine, Nathan Dintenfass, near twenty years ago. The tool was a quick and dirty bug tracker that could literally be copied and pasted into a directory and put to work. No database, no authentication, just a quick way to record issues. I took that initial script and iterated on it quite a bit, and eventually turned it into a &quot;real&quot; tracker I called Lighthouse Pro. (Apparently I last touched that 11 years ago.)\nI'm not sure why this popped in my mind, but when I was thinking about submitting to the hackathon, I headed over to the &quot;what's new in ColdFusion 2025&quot; page and saw a callout on updated spreadsheet and CSV functions. I thought why not resurrect the idea and tie the storage to a CSV file.\nThe idea would be the same - copy a file into a folder and start reporting bugs - but with the &quot;database&quot; being a CSV file, it would be portable. You could email it, print it, or heck maybe even copy and paste the information into Jira at a later time.\nTo get started, you can grab quickstart.cfm from the repository, and then drop it into a ColdFusion web directory of some sort. As long as it's running ACF 2025, that's all that should matter. Open it up and you'll see the default UI:\n\n\n\nI made use of the excellent Shoelace web component library for the design here. Click New Issue, and you can start reporting:\n\n\n\nKeep adding issues and you'll see them in a nice table format:\n\n\n\nI didn't add sorting or filtering, but may consider that post hackathon. Again, this is not meant to be a &quot;real&quot; tracker, just a quick tool.\nBehind the scenes, this all records to a CSV file, bugs.csv, that could be opened in Excel:\n\n\n\nI included basic reporting, making use of some newer ACF 2025 changes:\n\n\n\nFinally, I added PDF export for an issue:\n\n\n\nBehind the pretty UI, the entire thing is one file. Obviously that's not best practices, but again, my intent was for you to paste this into a folder and go. The basic 'skeleton' of the code is:\n\nWrap the layout in a variable that includes a header, footer, and body area. This is also where I load in Shoelace\nSet up some UDFs on top that get commonly used\nMake use of a large cfswitch statement to handle &quot;routes&quot;, which define the various views of the application - issue listings, issue editing, and reports.\n\nThe code can be customized a bit. For example - priority, status, and resolution are all arrays:\n\nThe template, as well, could be modified. As I said above, it's a large string that uses 2 UDFs to get the top and bottom portions like so:\n\nGetting the issues, or an issue, all involve CSV parsing. I don't store anything in RAM and instead always load from the file. The thinking here was to remove any dependencies, even an application cache.\n\nSaving an issue is only slightly complex as I have to handle new versus old:\n\nReporting makes use of the new cfchartset feature in CF2025, letting you group charts together and set some chart settings at once versus per chart. I think I could have done better here, but it worked well enough:\n\nAll in all, I'm kinda proud of this little &quot;toy&quot;, and hopefully it doesn't come in dead last in the Hackathon! You can check out the repository here: https://github.com/cfjedimaster/quicktrackercfm. One note, if you do have suggestions, please remember the idea is to keep this as simple as possible, and also, I'd rather not accept any PRs until the contest is officially over. Enjoy my first new ColdFusion repo in... quite some time. :)\nPhoto by Brian Wangenheim on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Using BoxLang's Cache Services",
		"date":"Wed Jun 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1749664800,
		"url":"https://www.raymondcamden.com/2025/06/11/using-boxlangs-cache-services",
		"content":"Recently I've been looking at BoxLang's Caching service, mostly because the docs were updated which made it easier to dig into it. ;) My usual expectation for a caching service is typically a key/value system with APIs to get and set and hopefully a simple way to handle expiration. So for example, I can ideally store a cache value and an expiration values at the same time, and if I fetch it later and it's expired, I get a nice null value back. As I said, that's the 'baseline' for what I expect, so I was kind of blown away, and a bit overwhelmed honestly, with what you can do on the BoxLang platform. At a high level, here's some of the details:\nOut of the box (heh, get it, 'box', 'BoxLang', I'm hilarious), you get the ability to get a default cache. But on top of that, you can create your own cache and specify things like eviction policy and default expirations. As an example, if you need a very short-lived cache, you can specify that and store values there knowing you'll get that behavior.\nYou also get the ability to introspect all those caches at the system level. You can get them all, poke inside, and so forth. Caches also have built in stats so you can do your own reporting, monitoring, and so forth.\nCaches support a filtering system which provides an API to work with keys based on string patterns. So for example, imagine you are caching results for names, and use a key following this pattern:\n\ncachedname.ray\ncachedname.scott\ncachedname.todd\ncachedname.brian\n\nYou can create a filer based on cachedname.*, and then do operations on matching keys based on that filter. Obviously that requires you to follow a pattern, but if you do, the support makes it easy.\nAlso, since BoxLang gives you 'interceptor' access to low level events, you can listen in to and react to any cache type event.\nFinally, one thing that may trip you up a bit is that the caching functions make use of Attempts, a flavor of Java's Optional support. I'll be honest, Attempts feel a bit awkward to me and I struggle with it a bit, but I'm reminded of JavaScript's Promise feature which was near incomprehensible to me at first. It just took time.\nDefinitely dig into the Caching docs for a look at everything possible, but how about some code? These code examples will make use of the default BoxLang cache, but take a look at the provider list for examples of other flavors.\nSimple Caching\nLet's start with a simple example. First, get the default cache:\n\nThis returns an instance of a cache provider object. You can check the reference for the full spec but it includes methods for getting and setting values, clearing, getting keys, and so forth.\nNow let's set a value:\n\nAnd then immediately get it:\n\nThe result is not what you expect. Instead of the date time instance, you get an Attempt object. If you print it, you will see it, but it's really an object, not a simple value:\n\nThis is where the Attempt logic comes in, and as I said, it's not something I'm 100% comfortable with yet, but you can use a method to get the value like so:\n\nYou can simplify this quite a bit by using the getOrSet method which, as you can imagine, will get a cached value or set it.\n\nHow about another example? You can use orElse on an Attempt to return a value if the attemps value is null:\n\nThis is probably obvious, but this is different from the getOrSet option as this won't set a value in the cache. If you never cache lastUsed3, this code will always return now().\nSo yeah - you've got multiple options here. You can test this out yourself below.\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"600\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJx1j08LgjAYh%2B%2F7FC942IRQcLfEIKRzQXToOOZbCXPKNvvz7ZuKmoGngXv8Pc%2BqTy7kAyED2Z0sTAmphm%2BRRceoEtZdLBZ0A7p%2BsbADerTw%2F4zkfUHOSFTeTgYtasckZDtoTKmd0ozmw8JTqBa3EMiA9sNxDHtduwcawLeoGoWEjLPJ0nc0519p4vtY2DkMutboKXZSzjt92%2BCG0kIw3fiKvuGKDsRfxwjxtWdzGka1OSiLbNXNFz7ufekX%2BnN8Yg%3D%3D\">\n    \nCaching with Expirations\nSetting expirations can be done at the same time you set a value. For example:\n\nThis will cache the value for 5 seconds. To see this in action, consider the following:\n\nI set a cached value to lastTime and set it to live for 5 seconds. I then grab the value immediately. (Technically I already had it in lastTime, but I wanted to show a followup fetch of the value.\nI then sleep for 7 seconds.\nAfter I wake up, I grab all the keys, and as the comment says, I will see my value as it hasn't been reaped yet, but if I get it, the result will be null. If you run the sample below, the second print will not be shown. (Be sure to switch to the Buffer tab which shows the output).\n  A quick note - as of June 11th, there is a bug when getting an expired value *before* reaping. The bug being it still returns. This is corrected in the snapshot build of BoxLang and will be in the next main release, at which point the embed below should automatically show the correct behaviour. Also, try.boxlang.io will persi",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Code Break Tomorrow - Desktop Apps with Python",
		"date":"Mon Jun 09 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1749492000,
		"url":"https://www.raymondcamden.com/2025/06/09/code-break-tomorrow-desktop-apps-with-python",
		"content":"Greetings, programs. Tomorrow (June 10th) at 12PM CST (Cool Standard Time), I'll be hosting my next Code Break, &quot;Build Desktop Apps with Python and Flet&quot;. My last two sessions covered building desktop apps with Tauri, a TypeScript/Rust platform, so I'm looking forward to playing with a Python version of the same idea.\nYou can RSP at the link above, or watch right here:\n\n\nPlay Video\n\n\n\n\n\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (6/8/25)",
		"date":"Sun Jun 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1749405600,
		"url":"https://www.raymondcamden.com/2025/06/08/links-for-you-6825",
		"content":"Welcome to another edition of my &quot;this was supposed to be down on Saturday&quot; biweekly list of links. Yesterday my wife and I made pretzels at home for the first time. It was a rather simple recipe that didn't need any boiling and they came out incredible. We also watched the new Predator movie, Killer of Killers, which was quite spectacular. I definitely recommend watching it when you can. Ok, on with the links!\nReleative Colors in CSS\nCSS never ceases to amaze me, despite me knowing I'll never be really good at it. In this article by Ahmad Shadeed, he introduces the concept of native color modifying in pure CSS. Ie, taking one base color and changing it to be lighter or darker. It looks relatively simple to use and is supported well. Read more at his blog post: Relative Colors. Also note that his post has some great interactive elements to it.\nYet Another SSG\nYes, I'm sharing a link to another static site generator, but Publican looks kinda cool. It's Node.js based with all the usual bells and whistles of SSGs, Markdown support, templates, and so forth. It makes use of jsTSACS for templating. (A lot of times the template engine is the deciding factor for me.) You can read more about the rationale for building another SSG at the product's blog: Why create another static site generator?.\nImplementing Image Uploads in the Background\nLast up is a great article by Amejimaobari Ollornwi on building an offline friendly image upload system. This makes use of IndexedDB and the sync feature of service workers, something I've rarely seen people using. I've got a few nits about his use of IDB (see my comment at the end of the article), but it's a dang good example!\nJust For Fun\nLast but not least, did you know Garbage released a new album and it's pretty damn good? Check out one of the singles below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Working with the Mastodon API in BoxLang",
		"date":"Fri Jun 06 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1749232800,
		"url":"https://www.raymondcamden.com/2025/06/06/working-with-the-mastodon-api-in-boxlang",
		"content":"So remember a long time ago (Tuesday), when I blogged about using the Bluesky API with BoxLang? As expected, I'm following that up today with a look at using the Mastodon APIs. Personally, I'm down to just two social networks, Bluesky and Mastodon. Originally I was using Mastodon a lot more, but I've been vibing with Bluesky more lately so I tend to check it more often. That being said, whenever I release a new blog post, I've got an automated process to post to both, so I thought I should cover both for BoxLang as well.\nEven better... I already did this in ColdFusion! Way back in October 2023, I blogged about the topic and even shared a simple ColdFusion component for it. That made 'translating' to BoxLang even easier.\nAuth\nAs a refresher, authentication with Mastodon is simpler. In your profile, you go into your developer settings and create a token. That's it. All you need along with that is the server your account runs on. So I specified too environment variables for this, MASTO_TOKEN and MASTO_SERVER. I verified it like so:\n\nPosting Messages\nAs I said, you don't need anything more than that token, so posting a toot (message) is as simple as:\n\nLiterally, that's it. You get a large post object back you can inspect if need be.\nUsing Images\nHow about images? Like Bluesky, they've got a different endpoint for that. Here's an example sending up a cat picture.\n\nThis returns a media object where all you need is the id, and to add it to your post, it's one line of code:\n\nHere's a complete script showing this in action:\n\nI turned this into a BoxLang class. It's a bit different from the Bluesky one in terms of API shape and I may address that at some point, but for now, here's that class:\n\nAnd an example using it:\n\nHere's my most recent test on one of my cooler bot accounts:\n   Post by @dragonhoards@mastodon.social View on Mastodon   \nFairly simple, right? If you take this along with the code from the previous post, you could easily automate posting to both in a few lines of code.\nAs before, you can find these samples in the BoxLang demos repo here: https://github.com/ortus-boxlang/bx-demos/tree/master/scripting Look for test_mastodon.bxs, test_mastodon2.bxs, and mastodon.bx. Enjoy!\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with the Bluesky API in BoxLang",
		"date":"Tue Jun 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1748973600,
		"url":"https://www.raymondcamden.com/2025/06/03/working-with-the-bluesky-api-in-boxlang",
		"content":"I've built multiple integrations with the Bluesky API, all making use of either the Node or Python SDK, but I thought I'd take a quick look at what it would take to build a BoxLang integration using the REST API. Turns out it's pretty easy (with some caveats I'll explain at the end) - here's what I built.\nAuthentication\nTo authenticate, you'll need your username and password for your account. I'm picking this up via environment variables and doing a bit of validations:\n\nTo authenticate, I pass this value to the com.atproto.server.createSession endpoint on Bluesky:\n\nIf the status code result from this is 200, you get an object containing information about the user, but more importantly, a accessJwt and refreshJwt value for later use. Here's an example result with anything possibly confidential removed:\n\nHere's how I handle the result:\n\nMaking a Post\nNow that you have an access token, making a post is relatively simple:\n\nBasically I've got a body object that describes what's being added (a post) and includes the text and date created. That's literally it. The result is a record object and, obviously, a post on Bluesky itself.\nTest via API - sorry for the noise!&mdash; Raymond Camden (@raymondcamden.com) June 2, 2025 at 11:56 AM\nUsing Images\nTo add an image to your post requires uploading it first. Here's an example using that endpoint and an image in the local file system:\n\nThis returns a file object that can be referenced in a new post - although now the post object gets a bit more complex:\n\nNote that the endpoint is the same, it's just the body changing. Also note in the example above, I did not include an alt tag. Don't do that. Use an alt tag. Always.\nTest via API - now with an image (for real).[image or embed]&mdash; Raymond Camden (@raymondcamden.com) June 2, 2025 at 12:35 PM\nWrapping it Up\nSo, given that sample code, I decided to build a simple class wrapper for it. This class handles authentication and has methods for login and posting, with optional images. I made the image support require alt text to ensure folks at least think about it before posting images to the API. (Technically you can still pass an empty string.) Here's that class as it stands now:\n\nUsing it requires you to instantiate it with your creds, login, and then make some posts:\n\nMuch simpler, right? You can find the source and sample files here: https://github.com/ortus-boxlang/bx-demos/tree/master/scripting (There's more files in this folder, but look for bluesky.bx, test_bluesky.bxs and test_bluesky2.bxs.\nNow, there's still a bit missing from this. First off, to have links in your text automatically become 'real' links, you need to use the API's &quot;Facet&quot; endpoint. I'm going to add support for that so it happens automatically.\nNext, you can post up to 4 images per post, so I'm going to rewrite image support to allow you to pass a structure for one image, or an array of images instead.\nFinally, I'm considering changing how authentication works. In theory, it should just handle it for you and cache the jwt. That would remove the bs.login() requirement above. I can also make use of the refresh token to automatically update the token for you.\nAll of this will be considered once I turn this into a proper BoxLang module available on Forgebox. And finally finally (for real this time), you could convert this to ColdFusion pretty quickly.\nPhoto by Joshua Hoehne on Unsplash\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (5/25/25)",
		"date":"Sun May 25 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1748196000,
		"url":"https://www.raymondcamden.com/2025/05/25/links-for-you-52525",
		"content":"I missed posting my links post last weekend but I had an absolutely good reason. This previous week was my first week at Foxit and while I've only had three days so far, I'm incredibly excited about this new role and being able to introduce a whole suite of new offerings to developers. The next few months are going to be pretty busy and I'm really happy about that. My new job also coincided with a little vacation my wife and I are taking in Saint Louis. While I've been in Louisiana for sometime now, I grew up in Saint Louis and love visiting here. (My mom still lives up here.) Yesterday we visited the Missouri Botanical Garden which was beautiful.\nThis isn't a pretty picture, but I LOLed at the name:\n\n\n\nOk, on with the links!\nLearning about MCP\nMCP is incredibly hot now in the AI world, and it's something I've yet to truly wrap my head around yet. I plan on getting up to speed with it more very soon, but I thought it would be useful for folks in the same boat to point out an excellent series of articles by Todd Sharp (this is the third week in a row I've led with one of his articles so it's something of a trend now): Hype or Hope - Is MCP The Missing Piece of the Puzzle?.\nThis is part one of a four part series, so buckle up and be ready to learn.\nOn Being Principal Developer Advocate\nNext up is a good read on the good, and bad, of being the principal developer advocate in an organization. Dewan Ahmed wrote about the challenges facing developer advocates in regards to the great freedom the role has in opposition to the great responsibility as well. It's a great read: The Principal Developer Advocate Paradox: Scaling Impact Without Burnout\nTips and Tricks for Better File Uploads\nLast up is a good set of tips for better file uploads in your web apps: 7 Best Practices of File Upload With JavaScript. Most of these are things I knew already, but haven't really thought about in a nice list here as written by StorageBowl. (Who, conveniently, offer a storage solution that I probably need to play with soon.)\nJust For Fun\nMy readers know I love a good cover (in fact, I've got a nearly 11 hour Spotify playlist of covers) so why not enjoy this really good cover of The Cure's &quot;Burn&quot; by NITE:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Multimodal Support in Chrome's Built-in AI",
		"date":"Thu May 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747936800,
		"url":"https://www.raymondcamden.com/2025/05/22/multimodal-support-in-chromes-built-in-ai",
		"content":"  September 4, 2025: I was revisiting this post today and noticed my demos broke. This has happened with many of my Chrome AI posts as the specs change and evolve, but I really wanted to have these working well. I updated the CodePens to fix them, but did not edit the text below, so please keep that in mind.\n\nI'll also note my use `await createImageBitmap(file);` isn't necessary as I can pass the file object directly to it. I've got Thomas Steiner of the Chrome team to thank for pointing that out.\nIt's been a few weeks since I blogged about Chrome's built-in AI efforts, but with Google IO going this week there's been a lot of announcements and updates. You can find a great writeup of recent changes on the Chrome blog: &quot;AI APIs are in stable and origin trials, with new Early Preview Program APIs&quot;.\nOne feature that I've been excited the most about has finally been made available, multimodal prompting. This lets you use both image and audio data for prompts. Now, remember, this is all still early preview and will likely change before release, but it's pretty promising.\nAs I've mentioned before, the Chrome team is asking folks to join the EPP (early preview program) for access to the docs and such, but it's fine to publicly share demos. You'll want to join the EPP for details on how to enable these APIs and use the latest Chrome Canary, but let me give you some examples of what you can do.\nBasic Image Identification\nAt the simplest level, to enable multimodal inputs you simply tell the model you wish to deal with them:\n\nYou can also use audio as an expected input but I'm only concerned with images for now. To test, I built a demo that simply let me select a picture (or use my camera on mobile), render a priview of the image, and then let you analyze it.\nHTML wise it is just a few DOM elements:\n\nThe JavaScript is the important bit. So first off, when the file input changes, I kick off a preview process:\n\nThis is fairly standard, but let me know if it doesn't make sense. In theory I could have done the AI analysis immediately, but instead I tied it to the analyze button I showed up on top. Here's that process:\n\nSo, remember that $imgFile is a pointer to the input field which is using the file type. I've got read access to the selected file, which is turned into an image bitmap (using window.createImageBitmap), and then passed to the AI model. My prompt is incredibly simple - just write a summary.\nAs I assume most of yall can't actually run this demo, let me share with you a few screenshots showing some selected pictures and their summaries.\n\n\n\nYes, I agree, she is adorable.\n\n\n\nThis one was pretty shocking in terms of the level of detail. I'm not sure if that is LAX, and not sure if those are exact matches on the airplane types, but damn is that impressive.\n\n\n\nThis is pretty good as well, although I'm surprised it didn't recognize that it was a particular Mandalorian, Boba Fett.\nHere's the complete CodePen if you want to try it out, again, given that you've gone through the prerequisites.\n\n  See the Pen \n  MM + AI by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nMore Guided Indentification\nOf course, you can do more than just summarize an image, you can also guide the summarization, for example:\n\nWhile a bit silly, there's some practical uses for this. You could imagine a content site dedicated to cats (that's all I dream of) where you want to do a bit of sanity checking for content editors to ensure pictures are focused on cats, not other subjects.\nHere's two examples, first a cat:\n\n\n\nAnd then, obviously, not a cat:\n\n\n\nFor completeness sake, here's this demo:\n\n  See the Pen \n  MM + AI (Cat or Not) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nImage Tagging\nOk, this next demo I'm really excited about. A few weeks ago, the Chrome team added structured output to the API. This allows you to guide the AI in regards to how responses should be returned. Imagine if in our previous demo we simply wanted the AI to return true or false if the image was of a cat. While we could use our prompt for that, and be really clear, there's still a chance the AI may feel creative and go a bit beyong the guardrails of your prompt. Structured output helps correct that.\nSo with that in mind, imagine if we asked the AI to not describe the image, but rather provide a list of tags that represent what's in the image.\nFirst, I'll define a basic schema:\n\nAnd then I'll pass this schema to the prompt:\n\nNote that the prompt API is somewhat complex in how you can pass arguments to it, and figuring out the right way to pass the image and the schema took me a few tries. The documentation around this is going to update soon.\nThe net result from this is a JSON string, so to turn it into an array, I can do:\n\nIn my demo, I just print it out, but you can easily doing things like:\n\nFor my cat site, if I don't see &quot;cat&quot;, &quot;cats&quot;, or &quot;kitten&quot;, &quot;kittens&quot;, raise a warning to the user.\nFor my cat site, if I",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My New Role - API Evangelist at Foxit",
		"date":"Tue May 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747764000,
		"url":"https://www.raymondcamden.com/2025/05/20/my-new-role-api-evangelist-at-foxit",
		"content":"As I mentioned on the blog a few weeks ago, I (finally) landed a new job. The market is beyond tough right now, so I'm very happy that I was able to land a new role, especially one that's going to be just about perfect for me - API Evangelist for Foxit. I'll be talking more about my role and what I'm covering later (we've got some really cool stuff brewing!) but for now, I can't wait to get busy again!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "JavaScript in the morning, JavaScript in the evening...",
		"date":"Mon May 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747677600,
		"url":"https://www.raymondcamden.com/2025/05/19/javascript-in-the-morning-javascript-in-the-evening",
		"content":"I've been a huge fan of the Intl spec for sometime, having done multiple presentations and blog posts on the topic. Every time I think I've explored it completely, I come across another interesting gem. Today I'm going to share one that is possibly not something you would use, but it's a curious feature of the spec I wanted to dig more into.\nWhen formatting dates with Intl.DateTimeFormat, you've got a large set of customizations you can use to display dates exactly as you want. I recently came across an interesting part of the formatting options, dayPeriod.\nAccording to MDN, this specifies:\n\nThe formatting style used for day periods like \"in the morning\", \"am\", \"noon\", \"n\" etc. Possible values are \"narrow\", \"short\", and \"long\".\n\nSo consider this:\n\nWhich right now returns, &quot;in the afternoon&quot;. You can test it yourself below, and obviously the value will be something different.\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOk... that's... interesting. Like, I can't imagine having an event calendar and using this in the display. I mean, I think most people understand that 8AM is the morning and 8PM is night. It's possible someone may not know AM vs PM. And of course, if you use a 24 hour clock than it makes even less sense.\nI just really can't see myself using this, but, I was intrigued. When does morning end? When does afternoon switch to night? At least according to the browser that is. So I built a tool to let me test.\nI began with a date that represents today, but at midnight.\n\nGiven that, I then looped over 24 hours and got the result. I also tried all three variations of dayPeriod.\n\nThe log function there just writes to HTML, making it easier to see in CodePen. I took this code and added a form field to let you specify a locale. I also was curious how many unique values for the day period existed and if that changed by locale.\nHere's how it looks for en-US:\nHour: 00, day period: at night, short = at night, and narrow = at night\nHour: 01, day period: at night, short = at night, and narrow = at night\nHour: 02, day period: at night, short = at night, and narrow = at night\nHour: 03, day period: at night, short = at night, and narrow = at night\nHour: 04, day period: at night, short = at night, and narrow = at night\nHour: 05, day period: at night, short = at night, and narrow = at night\nHour: 06, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 07, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 08, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 09, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 10, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 11, day period: in the morning, short = in the morning, and narrow = in the morning\nHour: 12, day period: noon, short = noon, and narrow = n\nHour: 13, day period: in the afternoon, short = in the afternoon, and narrow = in the afternoon\nHour: 14, day period: in the afternoon, short = in the afternoon, and narrow = in the afternoon\nHour: 15, day period: in the afternoon, short = in the afternoon, and narrow = in the afternoon\nHour: 16, day period: in the afternoon, short = in the afternoon, and narrow = in the afternoon\nHour: 17, day period: in the afternoon, short = in the afternoon, and narrow = in the afternoon\nHour: 18, day period: in the evening, short = in the evening, and narrow = in the evening\nHour: 19, day period: in the evening, short = in the evening, and narrow = in the evening\nHour: 20, day period: in the evening, short = in the evening, and narrow = in the evening\nHour: 21, day period: at night, short = at night, and narrow = at night\nHour: 22, day period: at night, short = at night, and narrow = at night\nHour: 23, day period: at night, short = at night, and narrow = at night\n\nUnique Periods: at night,in the morning,noon,in the afternoon,in the evening (5 total)\n\nFirst thing you'll notice is that short and narrow only impact one time, noon. Noon is also the only &quot;specific&quot; period for one hour. There is no &quot;midnight&quot; or any other one time only value. Finally, you can see all the unique values (5) listed.\nWhat about the French?\nHour: 00, day period: du matin, short = matin, and narrow = matin\nHour: 01, day period: du matin, short = matin, and narrow = matin\nHour: 02, day period: du matin, short = matin, and narrow = matin\nHour: 03, day period: du matin, short = matin, and narrow = matin\nHour: 04, day period: du matin, short = matin, and narrow = mat.\nHour: 05, day period: du matin, short = matin, and narrow = mat.\nHour: 06, day period: du matin, short = matin, and narrow = mat.\nHour: 07, day period: du matin, short = matin, and narrow = mat.\nHour: 08, day period: du matin, short = matin, and narrow = mat.\nHour: 09, day period: du matin, short = matin, and narrow = mat.\nHour: 10, day period: du matin, short = m",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Code Break Tomorrow",
		"date":"Sun May 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747591200,
		"url":"https://www.raymondcamden.com/2025/05/18/code-break-tomorrow",
		"content":"Just a quick note to let my subscribers know that my next Code Break session will be tomorrow, May 19th, at 12PM CST. I plan on continuing my look at Tauri, a platform for using the web platform to build desktop apps. Depending on how much time I've got, I may also start playing with a Python platform as well. You can RSVP, or just watch, here: https://cfe.dev/talkshows/codebreak-05192025/.\n",
		"tags":[
	        
            "misc",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Sending an Alert for Short Wait Time at Disney",
		"date":"Fri May 16 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747418400,
		"url":"https://www.raymondcamden.com/2025/05/16/sending-an-alert-for-short-wait-time-at-disney",
		"content":"Yesterday I had some fun with a web app that made use of APIs to report on rides with the shortest wait times at amusement parks. This was done via the excellent, and free, Queue Times service. The application I built let you select a park, and then rides were displayed sorted by the shortest wait time. While working with the API, I also had another idea for a useful service - notifications for short wait times.\nImagine you're at Disney, or any amusement park, and while you're there, you would like to be notified when rides have a short wait time. How could you automate this? For now, let's skip the 'hard part' of imagining the service that would let you sign up for that. You would need a web site, authentication, and so forth. I'd imagine the site would let me select a park and a date range where I'd be visiting. Lastly, I'd also need some kind of threshold for when to be alerted. Maybe the park is so busy, a wait time of 90 minutes or less is ok. Maybe it's ten hundred degrees in Florida and you just want to know about much shorter wait times.\nFor my hypothetical service, I'll assume Disney's Hollywood Studios because that's where Star Wars Land lives (sorry, &quot;Galaxy's Edge&quot;, I just think &quot;Star Wars Land&quot; sounds more fun.) From the Queue Times API, this is park ID 7.\nFor my threshold I'm going to use 20 minutes.\nSo given the above, I need a service that gets the rides for Hollywood Studios and filters to rides that are both open and have a wait time of less than or equal to 20 minutes. Let's code!\nThe Workflow\nOnce again, I turned to Pipedream, and started off with a time based trigger. I went with once every 30 minutes, but honestly, I'd probably go with something closer to 10. Again, this is all part of a hypothetical service I don't have any plans on building. ;)\nNext, we need to get rides that match our filters, and for this, I'll use a bit of Python:\n\nMy code gets just the name and the wait time for the ride, but I could have included 'land' as well as that may impact your decision. If you are in Galaxy's Edge and a ride is available outside that land, you may defer to something else with a longer wait time just to save you from walking.\nNext, I need to turn that result into a nice string for the notification. Once again, I used a bit of Python:\n\nAs a reminder, the API returns 'attractions', not rides, but I'm ok with just calling them rides.\nNow for the last bit - the notification. This can really be anything you want, and heck, you could even support multiple types of notifications. My initial idea was to make use of Twilio and send a SMS, but due to recent changes on their platform, you can't do this in trial mode anymore. I considered email, which would be simple with Pipedream. In the end, I decided to use Pushbullet, which amongst other features lets you use an API to send a notification to your mobile phone... if the device has the Pushbullet app. In the &quot;real world&quot; that wouldn't be something you would ask random users to do, but for my own personal testing, it was fine. And - Pipedream literally had this done already as an action I could add. I added to my workflow, authenticated with Pushbullet, and then just selected my registered devide. I then specified 'note' as the push type, gave it a title, and used the result of the previous instruction.\n\n\n\nHere's the entire workflow in context:\n\n\n\nIf that seems pretty simple, it's because it is. I've been saying for years that Pipedream is aweesome for stuff like this.\nFor my first test, I got this notification in the app:\n\n\n\nIt isn't terribly obvious, but if you click, it expands in the app for the complete list of rides, which was rather long when I tested earlier this morning. No big surprise there, as the day goes on the wait times will get longer and longer.\nIf you want, you can see the complete workflow in my GitHub repo, and obviously, this workflow could be done in other systems as well. Let me know what you think!\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Finding Your Next Amusement Park Ride with APIs",
		"date":"Thu May 15 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747332000,
		"url":"https://www.raymondcamden.com/2025/05/15/finding-your-next-amusement-park-ride-with-apis",
		"content":"Every now and then I'll wonder if something or another has an API, google for it, and be pleasantly surprised to find that there is indeed an API available. This week that random thought was about ride waiting times for amusement parks. To be fair, it wasn't completely random. I saw an ad on Facebook for a little display you can get for your home that shows live wait times. Obviously that's targetted for amusement park freaksfans but it's a cute idea and something I'd consider. I've only recently become a Disney fan (you don't want to know how much I freak out at Galaxy's Edge) so I get the attraction.\nIn this case, my googling landed me on Queue Times, a website which provides wait times and a whole lot more for amusement parks around the world. Best of all, they've got a free API that's easy to use and doesn't require any kind of authentication. This got me thinking about what could be built with these APIs and what I'd like to see if I was at a park. I came up with not one, but two interesting ideas, and today I'm going to share the first.\nThe Ride Sort App\nMy first idea was rather simple. A web application that lets you pick a park and then fetch the rides. It would then show you all the rides that are a) open and b) sorted by the shortest wait time. So if you are at the park and not sure where to go next, this would give you ideas. To be clear, parks have apps, Disney for sure, and this information is available there too, but I thought having a web page that was super clear and focused, and sorted, could be really useful.\nAs a quick aside, I an referring to 'rides', but the API returns more than roller coasters. It's really returning all the attractions but I'm fine keeping it named 'Ride Sort' for now.\nFor the application, I decided on Alpine.js of course, but for the UI, I used Google Gemini within Visual Studio Code to create my design. My initial prompt was:\ni'm creating a simple, mobile friendly web site that will use an API \nto get wait times for rides. I'm calling the app, Find a Ride,\nand I need some CSS to make it look nice. I'm looking for CSS that\nis responsive and has a playful, colorful look to it that matches\nthe fun you have at amusement parks.\n\nNotice I had another name for the app at first. Gemini responded with:\nOkay, &quot;Find a Ride&quot; sounds like a super fun app! An amusement park \nheme is a great choice for the design. I've whipped up some CSS \nthat should give your site that playful, colorful, and responsive \nfeel you're looking for.\n\nThis CSS uses a mobile-first approach, so it'll look great on phones, \nand then it adapts for tablets and desktops. I've picked some \nvibrant colors and fun fonts (make sure to include the Google Fonts \nlink in your HTML's &lt;head&gt;).\n\nAfter this it spit out a bunch of code, and then followed up with instructions on how to use it. This was hella useful as it made it clear what classes I'd need for certain statuses.\nHere's a screenshot of the final application. Note that I followed up my initial prompts with further requests, including help designing the drop down and the checkbox I added later.\n\n\n\nNow let's get into the code.\nThe HTML\nAs an Alpine app, my front end is pretty simple. I've got directives to bind my UI controls (the drop down, buttons, and checkbox) and a place to display rides.\n\nI don't think there's anything too crazy there, but obviously let me know in the comments if anything doesn't make sense. All of the CSS used came right from Gemini and I just adapted to Alpine and my data.\nThe APIs\nAs I mentioned, the application makes use of Alpine.js, but before going into that, let's discuss the APIs. Queue Times provides two end points. The first returns a list of Parks via https://queue-times.com/parks.json. This returns an array of companies, and beneath each company (or park group) is an array of parks. Here's one example under the Disney group:\n\nOnce you have the ID of a park, you can then request wait times like so: https://queue-times.com/parks/7/queue_times.json. This also returns an array of arrays, this time grouped by &quot;land&quot; with &quot;rides&quot; under it. So for example:\n\nBy the way, the Muppet 3D show is going away and I know I'm not alone in being really sad about that.\n\n\n\nGiven that the APIs are super easy to use, the only issue I ran into is that CORS is not supported. To get around that, I simply created two proxies at val town. That was as easy as making a file, adding a HTTP trigger, and writing code like so:\n\nThat's the main proxy to get all the parks, and here's one for getting rides:\n\nNotice there's no error handling there. Oh well.\nAlright, now let's turn to the JavaScript.\nThe JavaScript\nI love Alpine, but it's been a few months since I made use of it so this part was probably the slowest for me. First, here's the core application definition:\n\nI specify default values for what my UI needs and then have code to support loading my remote data and such. The rides value is dynamic based on whether or n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Parsing Markdown in BoxLang - Take 3",
		"date":"Wed May 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1747245600,
		"url":"https://www.raymondcamden.com/2025/05/14/parsing-markdown-in-boxlang-take-3",
		"content":"Ok, so I promise this will be my last post on using Markdown with BoxLang. At least the last one this month. ;) I first covered the topic last month, &quot;Parsing Markdown in BoxLang&quot; where I demonstrated using the Flexmark Java library in BoxLang code. I then followed up with a revised edition that used BoxLang's Java integration a bit nicer. So, those posts are still very valid, still useful for showing you how to make use of the JVM from BoxLang, and with the vast library of open-source Java stuff out there, that's a good thing. But... you don't need to do any of that for Markdown, because now it's even easier! If you prefer to consume this post while listening to my silky smooth voice, check out the video at the end.\nThere is now an official Markdown module for BoxLang, making use of the same Java library I had used. To install, simply run:\ninstall-bx-module bx-markdown\n\nOnce you do that, you've got two new functions, markdown for conversion of Markdown to HTML, and htmlToMarkdown, for converting HTML to Markdown. Here's a simple example:\n\n\n.gist{font-size: 18px}.gist-meta, .gist-file, .octotree_toggle, ul.comparison-list > li.title,button.button, a.button, span.button, button.minibutton, a.minibutton,span.minibutton, .clone-url-button > .clone-url-link{background: linear-gradient(#202020, #181818) !important;border-color: #383838 !important;border-radius: 0 0 3px 3px !important;text-shadow: none !important;color: #b5b5b5 !important}.markdown-format pre, .markdown-body pre, .markdown-format .highlight pre,.markdown-body .highlight pre, body.blog pre, #facebox pre, .blob-expanded,.terminal, .copyable-terminal, #notebook .input_area, .blob-code-context,.markdown-format code, body.blog pre > code, .api pre, .api code,.CodeMirror,.highlight{background-color: #1D1F21!important;color: #C5C8C6!important}.gist .blob-code{padding: 1px 10px !important;text-align: left;background: #000;border: 0}::selection{background: #24890d;color: #fff;text-shadow: none}::-moz-selection{background: #24890d;color: #fff;text-shadow: none}.blob-num{padding: 10px 8px 9px;text-align: right;color: #6B6B6B!important;border: 0}.blob-code,.blob-code-inner{color: #C5C8C6!important}.pl-c,.pl-c span{color: #969896!important;font-style: italic!important}.pl-c1{color: #DE935F!important}.pl-cce{color: #DE935F!important}.pl-cn{color: #DE935F!important}.pl-coc{color: #DE935F!important}.pl-cos{color: #B5BD68!important}.pl-e{color: #F0C674!important}.pl-ef{color: #F0C674!important}.pl-en{color: #F0C674!important}.pl-enc{color: #DE935F!important}.pl-enf{color: #F0C674!important}.pl-enm{color: #F0C674!important}.pl-ens{color: #DE935F!important}.pl-ent{color: #B294BB!important}.pl-entc{color: #F0C674!important}.pl-enti{color: #F0C674!important;font-weight: 700!important}.pl-entm{color: #C66!important}.pl-eoa{color: #B294BB!important}.pl-eoac{color: #C66!important}.pl-eoac .pl-pde{color: #C66!important}.pl-eoai{color: #B294BB!important}.pl-eoai .pl-pde{color: #B294BB!important}.pl-eoi{color: #F0C674!important}.pl-k{color: #B294BB!important}.pl-ko{color: #B294BB!important}.pl-kolp{color: #B294BB!important}.pl-kos{color: #DE935F!important}.pl-kou{color: #DE935F!important}.pl-mai .pl-sf{color: #C66!important}.pl-mb{color: #B5BD68!important;font-weight: 700!important}.pl-mc{color: #B294BB!important}.pl-mh .pl-pdh{color: #DE935F!important}.pl-mi{color: #B294BB!important;font-style: italic!important}.pl-ml{color: #B5BD68!important}.pl-mm{color: #C66!important}.pl-mp{color: #81A2BE!important}.pl-mp1 .pl-sf{color: #81A2BE!important}.pl-mq{color: #DE935F!important}.pl-mr{color: #B294BB!important}.pl-ms{color: #B294BB!important}.pl-pdb{color: #B5BD68!important;font-weight: 700!important}.pl-pdc{color: #969896!important;font-style: italic!important}.pl-pdc1{color: #DE935F!important}.pl-pde{color: #DE935F!important}.pl-pdi{color: #B294BB!important;font-style: italic!important}.pl-pds{color: #B5BD68!important}.pl-pdv{color: #C66!important}.pl-pse{color: #DE935F!important}.pl-pse .pl-s2{color: #DE935F!important}.pl-s{color: #B294BB!important}.pl-s1{color: #B5BD68!important}.pl-s2{color: #c5c8c6!important}.pl-mp .pl-s3{color: #B294BB!important}.pl-s3{color: #81a2be!important}.pl-sc{color: #c5c8c6!important}.pl-scp{color: #DE935F!important}.pl-sf{color: #DAD085!important}.pl-smc{color: #F0C674!important}.pl-smi{color: #c5c8c6!important}.pl-smp{color: #c5c8c6!important}.pl-sok{color: #B294BB!important}.pl-sol{color: #B5BD68!important}.pl-som{color: #C66!important}.pl-sr{color: #C66!important}.pl-sra{color: #B294BB!important}.pl-src{color: #B294BB!important}.pl-sre{color: #B294BB!important}.pl-st{color: #B294BB!important}.pl-stj{color: #c5c8c6!important}.pl-stp{color: #DE935F!important}.pl-sv{color: #DE935F!important}.pl-v{color: #DE935F!important}.pl-vi{color: #DE935F!important}.pl-vo{color: #C66!important}.pl-vpf{color: #DE935F!important}.pl-mi1{color: #8F9D6A!important;background: rgba(0,64,0,.5)!important}.pl-mdht{color: #8F9D6A!important;background: rgba(0,",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adventures in Vibe Coding - Really, Really Big Numbers",
		"date":"Thu May 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746727200,
		"url":"https://www.raymondcamden.com/2025/05/08/adventures-in-vibe-coding-really-really-big-numbers",
		"content":"I continue to be really against the term 'vibe coding', but also continue to be fascinated by the idea of working with GenAI to help craft/enhance applications in an iterative, conversation-based manner. Ashley Willis recently released an incredibly well done post on the topic, &quot;What Even Is Vibe Coding?&quot;, where she goes into detail on her take on the term and what it means for the industry. I think it is an excellent post and I want to highlight one part that really resonated with me:\n\nIt scaffolds layouts, creates routes, fills in placeholder content, basically roughs out the shape of the thing I described. I still review it, refactor it, test it, and shape it into something I’d actually ship. But that first pass? It saves me hours. And more importantly, it frees up mental space so I can focus on the interesting parts.\n\nThis has been my impression as well and also syncs up with what I loved about both serverless and low-code solutions. Anything that helps get me to the 'fun' parts of a project is, usually, a tool worth having in my toolbelt.\nI thought I'd share an example of this I worked on yesterday. For a while now, I've played a few idle clicker games that have an interesting way to handle large numbers. For numbers in the trillions and lower, they will abbreviate to a shorthand form and use the letters K, M, B, and T to indicate the value. So for example, 301M represents some number that rounds to 301 million. As an aside, the web platform has this baked in already via Intl, you can read about that here: &quot;Using Intl for Short Number Formatting&quot;.\nWhat's interesting is what these games do over a trillion (where Intl fails to go any higher). Immediately after 999T, they will switch to using &quot;AA&quot; for the indicator. Then BB, then CC, and so forth. After ZZ? AAA. And then BBB, etc. Usually they will replace KKK with KFC for obvious reasons.\nI like this a lot. It's easy to grok the scale and apparent that 100DD is less than 9AAA. In the past, I've taken a stab at coding such a solution, but I never could get it working quite right. My brain could see the logic... a bit... but I failed to ever actually implement it. So, why not ask for help? In my case, I turned to Google Gemini.\nRound One\nI began with what I thought was a pretty comprehensive prompt. I'd discover soon that I was missing a few things, but here's how I started:\n\nI need to write a function that helps format incredibly large numbers in JavaScript. For values over one hundred thousand, it should start using K, for example: 321K. For values in billions and trillions, it should use B and T. When the numbers go over 999 trillion, it should use a new numbering scheme using letters starting with AA. From AA it goes to BB, then CC and so forth. After ZZ, it goes to three letters, AAA. There is an exception for three Ks (\"KKK\") which should be rendered as KFC instead to avoid negative connotations with the KKK.\n\nThe output was rather interesting. It spit out a function and then a large number of testing statements. This is only a portion of the output:\n\nI took all the code, threw it up on CodePen, and immediately got an error. Now, I'll admit - I had not read every single line of code. I should have. Because in the large block of test statements was this gem (be sure to scroll to the right):\n\nYep, it broke it. I didn't notice that it then carried on with a revised edition of the function that handled it. It then output a huge amount of text to explain how it was built. I really dug this.\nRound Two\nI realized I had missed some details, so I responded with this:\n\nThis is close. Let's adjust. I want, at most, 3 numbers in any result. So numbers over 1000 should use K as well, so 1.24K for 1240 for example. In general, all results should have, at most, 3 numbers in the result. We don't count the period or the letter.\n\nOnce again, it worked up a revision with a lots of tests and once again, I realized I had missed something.\nRound Three\n\nAnother adjustment is needed, the input will never be a floating point value, it will always be an integer, and always positive\n\nI felt guilty, but Gemini responded. It also 'told' me:\n\nOkay, that simplifies things by removing the need to handle negative numbers, non-integers, or NaNs at the input. The core logic for scaling and formatting the numeric part to \"at most 3 digits\" remains crucial.\n\nI really appreciate it noting that it was going to simplify. This time the code was perfect. Oh crap...\nRound Four\nI noticed that it was not using &quot;M&quot; for millions. I had not mentioned that in my initial prompt so that's on me I suppose, but I responded with:\n\noops, i do want to support millions. can you fix that?\n\nI haven't shared it yet in this post, but over each iteration, Gemini was doing a damn good job talking about the changes. Here's an example:\n\n\n\nNow comes the cool part. In it's final output, it screwed up the output a bit. I was testing via AI Studio and I could see where it messed",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Sneak Peek at BoxLang's Module Feature",
		"date":"Wed May 07 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746640800,
		"url":"https://www.raymondcamden.com/2025/05/07/a-sneak-peek-at-boxlangs-module-feature",
		"content":"Last week I attended and spoke at Into the Box, a conference hosted by the Ortus folks, the company behind BoxLang. While there, I attended a talk from Brad Wood on how BoxLang's module system works. I've been using modules with BoxLang since I first started playing with it. It's how database support is added, mail, PDF, and more. You can see a list of currently released modules here. I had been curious as to how this works so I was excited for Brad's presentation. While hearing him go over the details, I got even more excited, and literally built a 'hello world' module in a minute or so while he talked.\nI'm calling this blog post a &quot;sneak peek&quot; because while the module system is baked and ready (as apparent by the existence of multiple modules you can use now), what isn't ready yet is the documentation for creating your own modules. I've got a bug filed for this and will probably take a stab at writing this soon, but in the meantime I wanted to share some details about what you can do and how it works. Thanks go to Brad Wood for detailing this in his presentation and I'm using his slides as my reference (for now).\nWhat Modules Provide\nWhen a module is registered with BoxLang, it can add to the language:\n\nBIFs (built in functions)\nComponents (these are tags, and can be coded to allow/disallow or require body arguments)\nClasses (which you can then load via a defined import path)\nJars (for both usage as is, or within the module itself)\nJDBC Drivers\nInterceptors (more on this topic soon, but it's incredibly low level code integration)\nCustom variable scopes\n\nThis isn't even 100% of what can be added, but gives you an idea. Taking just the first bullet point above, I could write a module that adds the raymondCamden() function to BoxLang. Once installed, your code could then just make use of it. There isn't any name spacing for these functions so it's something to consider when authoring, and installing, as it's possible two modules could use the same function name, but arguably that's probably not much of an issue right now - just something to keep in mind.\nIt's also important to note that modules can be defined for the entire server or just for one application (a web app or even just a CLI script).\nOk, cool, let's make one!\nMaking a Module\nBefore we begin, note that modules can be written in Java or BoxLang. I don't write Java (well, I can, I just don't want to) so this post is focused on BoxLang modules.\nAt the simplest level, a module is a folder with various things under it. At minimum is a ModuleConfig.bx class. This class has methods for configure, onLoad, and onUnload. The last two let write custom logic for when the module is loaded or disposed, while configure lets you work with any settings your module may have. The class can also be empty if you don't need anything, but I believe you still need the file there.\nNext is the bifs folder. This is where you define built in functions that your module provides.\nThen you've got a components folder. Any component here will be available to BoxLang via a defined class path.\nAnd finally, a libs folder where any jars will be loaded and available to your code. What's cool is that a jar here will be specific to your module, which means if another module uses a similar jar, or a different version, you won't have any conflicts.\nCreating BIFs\nFor me, I think most of the modules I may end up creating will focus on adding new functionality to the language via new functions. To do this, you simply drop a class in the bifs folder such that the name of the class is the name of the function (although you can tweak that via metadata).\nAt minimum, your class will have a @BoxBIF annotation and an invoke method which is run when someone calls your function.\nHow about a real, if incredibly trivial, example? I'm going to start in a folder and under it, create a new folder boxlang_modules. I'll place my modules under this for testing. Next, I'll create a folder for my specific module, which I'm calling cat. In there I'll add this ModuleConfig.bx:\n\nI'm not actually doing any logic here, but it shows the event handlers in action. Next, I'll create a bifs folder and in that, a file named meow.bx:\n\nOn top is a bunch of examples of how the annotations can configure how the BIF runs. I mentioned above the name of the file defines the name of the BIF, but you can customize that with either one or multiple names. Another really cool aspect is that you can define member functions that work with core BoxLang types, so for example, defining an array member function.\nIn the example above, I'm just going for the defaults which means I've added meow to BoxLang. The invoke message defines one argument with a default, but obviously you can do whatever your little heart desires here.\nTo test, I can go within the folder I created and use the CLI like so:\nboxlang --bx-code &quot;meow('ray')&quot;\n\nAnd this gives:\nconfigure\nonLoad\nKitty says ray\nonUnload\n\nI can also create a file, let's say",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using AI to Analyze Chart Images",
		"date":"Mon May 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746468000,
		"url":"https://www.raymondcamden.com/2025/05/05/using-ai-to-analyze-chart-images",
		"content":"I've done a few posts where I've asked GenAI services to analyze and summarize data. Most recently, I blogged about Chrome's built-in AI APIs and summarizing raw data into simple English. In each case where I've done work like this, I've had access to the raw data of what I want the API to analyze. But what if you don't? What if all you have is a simple chart image? I did some testing with this and here's what I found.\nMy Source Data\nFor my source data, I created a set of charts using Highcharts. This isn't my preferred front-end charting library, but I noticed their demos had (usually) a quick CSV export. That made it easy to get both a sample chart and the raw data behind it. This is what I'll use to judge how well the GenAI service parsed the image.\nI used four charts from Highcharts, and one chart from Powerpoint. I tried to Google for pictures of charts, i.e. someone snapping a picture of a newspaper fore example, but didn't have much luck.\nThe Code\nOk, this is usually where I'd tell you I wasn't going to use code, but instead rely on Google Gemini's AI Studio UI instead. If you think about it, there's no real code needed to test what I want to test, but, I ran into an interesting issue.\nAs part of my testing, I wanted a standardized response in JSON that included:\n\nA high level summary of what the chart represents\nA confidence rating from 0 to 1.\nA copy of the data represented by the chart\n\nThe second bullet item ended up being an issue. When defining my schema, I attempted to create a property called data, of type array of objects, and leave it at that. If you think about it, we have no idea at runtime what our chart will represent so the schema needs to be generic. However, AI Studio wouldn't let me do that. It insisted I needed to define properties for the object.\nOn a whim, I tried a quick code sample and via API, it worked just fine. So given that, here is what I came up with:\n\nThis is vanilla Gemini API code to upload a document and then ask a question about it, although in this case the prompt is blank, it's the system instruction that has the important portion. Do make note of this part though:\n&quot;When parsing chart images, be sure to note data points between axis lines.&quot;\n\nIn my initial testing, I saw Gemini returning results for items defined in the x-axis but not data points between them. For example, look at the x-axis on the chart below:\n\n\n\nNotice how the years labels every other year while the data points cover every year. Gemini at first missed those points and the addition to the prompt helped out. Note I said &quot;helped&quot;, it didn't always work.\nOk, so let's look at the results, shall we? The tl;dr is... it seemed to work ok. Reasonably well. Although I think it's also reasonable to say folks may not agree. Let's get to it.\nThe First Chart\nFor the first chart, it's the one I shared above. To save you a scroll, here it is again:\n\n\n\nGemini returned this for the summary:\n&quot;The line chart shows the trends in U.S. Solar Employment Growth by job category from 2010 to 2022. The dominant category throughout the period is 'Installation &amp; Developers', which saw substantial growth peaking around 2016 and then fluctuating before rising again towards 2022. 'Manufacturing' employment saw an initial spike in 2011 but remained relatively stable afterwards, generally hovering between 30k and 40k employees. 'Sales &amp; Distribution' also peaked early in 2011 and then maintained a lower, relatively stable level. The 'Other' category remained the smallest, fluctuating below 20k. 'Operations &amp; Maintenance' data is shown starting from 2018 and exhibits slight growth.&quot;\nSeems pretty reasonable. Now for the numbers. Here is how Gemini returned it:\n\nHow does this compare? Here's the real data:\n&quot;Category&quot;,&quot;Installation &amp; Developers&quot;,&quot;Manufacturing&quot;,&quot;Sales &amp; Distribution&quot;,&quot;Operations &amp; Maintenance&quot;,&quot;Other&quot;\n2010,43934,24916,11744,,21908\n2011,48656,37941,30000,,5548\n2012,65165,29742,16005,,8105\n2013,81827,29851,19771,,11248\n2014,112143,32490,20185,,8989\n2015,142383,30282,24377,,11816\n2016,171533,38121,32147,,18274\n2017,165174,36885,30912,,17300\n2018,155157,33726,29243,11164,13053\n2019,161454,34243,29213,11218,11906\n2020,154610,31050,25663,10077,10073\n2021,168960,33099,28978,12530,11471\n2022,171558,33473,30618,16585,11648\n\nIn general, it looks like Gemini always rounded to the nearest thousand, and in general, was in the ballpark. Not precise, but I wasn't expect precise, but it feels reasonably close.\nThe Second Chart\nFor my second chart, I've got a horizontal bar chart:\n\n\n\nThe summary returned by Gemini was:\n&quot;The horizontal bar chart displays the historic world population, measured in millions, for four regions (Africa, America, Asia, and Europe) in the years 1990, 2000, and 2021. Asia consistently had the highest population across all three years, increasing from 3,202 million in 1990 to 4,695 million in 2",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (5/3/25)",
		"date":"Sat May 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746295200,
		"url":"https://www.raymondcamden.com/2025/05/03/links-for-you-5325",
		"content":"Happy Saturday, Happy Pre-May the 4th, and happy &quot;I don't have to beg for a job anymore&quot;. Yep, I've got a job. I'm going to be announcing where and what later this month when I start, but, finally, I can stop the hunt. Oh, and the crippling fear and despair being gone is an added bonus. So, happy day! Let's get to the links.\nTubes, tubes, tubes\nLast week I started off with a post from Todd Sharp so why not do it again? Todd wrote up his experience using Momento Topics API and Nixie Tubes. Nixie Tubs are beautiful steam punk looking tubes that can be connected to hardware and then changed programmatically. I did a bit of hardware hacking last year but the project Todd describes here is way beyond my skill set.\nHandling Light and Dark Mode for Images\nWhile this post mentions both Markdown and GitHub in the title, the guide is actually a great HTML tip that shows you how easy it is to have your images support light and dark mode. Given that you have two images appropriate for each mode, you can use a small bit of HTML to serve up the right one automatically while falling back to a default. This tip is from Cassidy Williams and her excellent newsletter.\nTracking Web Analytics with Val Town\nLast up is a good article by Orestis Papadopoulos on using Val Town to track web analytics. I used Val Town in an earlier post to stand up a simple API and it's a service I've been meaning to dig more into for like a year now. Orestis' article is a great example of why the service is so cool.\nJust For Fun\nDid I mention I got a job? I did? Well, I got a job. Which means (at least for now and completely ignoring everything going on in the country) ...\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Pixel Art via Vibe Coding",
		"date":"Fri May 02 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746208800,
		"url":"https://www.raymondcamden.com/2025/05/02/pixel-art-via-vibe-coding",
		"content":"I am 100% against the term &quot;vibe coding&quot;. To be clear, my issue is with the name, it just strikes me as, well, horrible. That being said, I've been using gen AI to generate code samples over the past few months and it's been incredibly interesting at times. It absolutely requires technical skill to evaluate the results and to help guide the AI when mistakes happen, but it's been fun to give this a few tries recently.\nThis morning I gave Claude's desktop app a try and gave it what I thought was a non-trivial prompt:\ncan you generate JavaScript code that will look at an image \nin the DOM and convert it to simplified pixel art?\n\nI then submitted the prompt and stood by while it worked. I didn't time it, but it seems like it took perhaps two to three minutes or so. It generated the code in the right hand panel of the desktop app and then explained, in great detail, what it did and why.\nIt's result consisted of a core function that would take an image in the DOM and create the pixelized version. This function took arguments for the DOM element to use as well as the pixel size and number of colors. It also created a second function to add a UI that could be used to make it more dynamic.\nSo given something like:\n\nYou could then use this:\n\nI took the code, copied to CodePen and... it didn't work. I did some quick debugging and couldn't find an obvious error. It correctly read the image, made a new one, and added it to the DOM, but the resulting image was blank. Given that, I prompted it to correct the issue:\nThis doesn't properly update the original image when used. I can \nsee the code running but when it adds the result back to the dom, \nit is a blank image\n\nHere comes the cool part. Before I sent this prompt, I had noticed that the code failed with an image loaded on another domain. The error in question is one I've seen before, is CORS related, and is easy to fix with a line of JavaScript. I had added that to my copy but didn't mention it above. Yet somehow, when Claude was fixing the issue I had mentioned, it also found this issue.\nThere was a lot of output from Claude about this new version, but here's the key takeaways it reported:\n\nBetter Image Handling:\n\nCreates a new Image object to ensure proper loading\nHandles cross-origin issues with the crossOrigin attribute\nProperly waits for the image to load before processing\n\nImproved Color Reduction:\n\nUses the median cut algorithm for better color quantization\nWeighted RGB distance calculation to account for human perception\n\nEnhanced UI:\n\nStatus messages to show processing state\nBetter styling and layout\nError handling to show issues to the user\n\nPerformance Considerations:\n\nUses willReadFrequently: true with canvas contexts for better performance\nUses setTimeout to prevent UI freezing during processing\n\n\nThis worked well, but, the function it created just automatically plopped the result into the DOM. I wanted more control. So I followed up with:\nModify convertToPixelArt to let me pass a second argument that is \nthe DOM element to render the result to. If not passed, it acts \nas it does now, creating a new DOM element.\n\nInterestingly, it disobeyed me a bit, and added it as a fourth argument. And... I'm ok with. Here's the new method signature:\n\nMy only complaint is that I prefer document.querySelector but I can use that when calling. I then did one final request:\nRemove the support to create a UI. I just need the core support \nfor working with a DOM element\n\nAnd it correctly removed it. Here's the demo below. If you click to open up the source you can tweak the image URL.\n\n  See the Pen \n  Claude to Pixel Art V2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nA Step Too Far\nWhile sharing this with my buddy, he mentioned trying a demo using a C64 style (and his demo is incredible, going to share that at the end). That spurred me to consider an Atari 2600 version, you know, this bad boy here:\n\n\n\nI still have mine along with a collection of cartridges. Yeah, I'm old. So, in Claude, I tried this:\nbuild a new verion of convertToPixelArt that recreates \nthe look and feel of Atari 2600 games\n\nWhat I got... shocked me again. My thinking here was just a color palette/size that recreated the Atari's resolution, but what I got was a lot more. It added a function that:\n\nMatched the resolution and color palette of the Atari 2600 (cool, I wanted that)\nRecognized that the Atari had a scanline restriction that limited how many different colors could appear on one line. It added this enforcement.\nAdded the effect of a CRT screen, including screen curvature and bleeding\nSome Atari games would use 'mirroring' where a screen was mirrored for performance reasons\n\nThat is way beyond what I was thinking of and actually really appreciated. Even better, it made these effects optional. Here's the sample code Claude created to demonstrate:\n\nYou can see this in action here - and honestly - it's a bit too low rez, but I think a better source image would work better. Perhaps some",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Code Break Back for May",
		"date":"Thu May 01 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746122400,
		"url":"https://www.raymondcamden.com/2025/05/01/codebreak-back-for-may",
		"content":"Hey folks, after a break last month (for reasons), I'm happy to announce that Code Break is back on schedule. I'm ditching any further exploration of React for now, but instead turning my attention to something I think is really fun - building desktop apps. This first session will focus on a JavaScript-based framework while my next one will cover using Python.\nMy next session will not be on a Tuesday, but Monday, May 5th, at 12PM CST. You can RSVP/sign up/etc here: https://cfe.dev/talkshows/codebreak-05052025/\nI hope to see you there!\n",
		"tags":[
	        
            "misc",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Test of Eleventy Fetch",
		"date":"Wed Apr 30 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1746036000,
		"url":"https://www.raymondcamden.com/2025/04/30/a-test-of-eleventy-fetch",
		"content":"It has been quite some time since I wrote about Eleventy. My last post was a quick announcement about me upgrading my site to Eleventy 3.0 and how well that worked. I was going through my list of blog ideas and realized that in March of 2022, yes, 2022, I wrote down that I should take a quick look at Eleventy Fetch:\n\n\n\nI knew it worked, but I was curious about a few things, for example, invalidating the cache, but apparently this idea got buried and forgotten about until... today! So yes, it works, and works really well and if that's all you care about, hit up the docs and you're good to go. I had to see this for myself though.\nMy API\nI began by creating a quick HTTP-based API on Val Town. This API just returned the current time:\n\nYou can see this yourself here: https://raymondcamden-placidyellowptarmigan.web.val.run/\nNot terribly exciting, but real easy to see if caching is working correctly.\nI then scaffolded a super simple Eleventy site. I added a global data file named apidata.js to return the API:\n\nAnd one quick Liquid file on the home page to render it.\n\nThe final bit was to create a new site on Netlify, connect it to the repo for the site, and test out the publication. This worked fine obviously.\nAdding Fetch\nFollowing the directions I then installed the plugin:\nnpm install @11ty/eleventy-fetch\n\nAnd modified my global data file like so:\n\nAs you can see, the plugin adds a new Fetch API that takes a URL and then a value for the cache duration (in this case, one day) and another to enable automatic JSON parsing.\nI'm testing locally, so I did a quick build, made a note of the time, and ran it again, and confirmed, yep, it worked. The plugin creates a .cache folder which includes metadata about the cached request as well as the value.\nSo far so good, and useful.\nInvalidating the Cache\nGetting rid of the cache is easy - just delete the .cache folder. Do not do what I did and delete the files inside of it. That confused the plugin for some reason. When I switched to just completely nuking it, that worked as expected.\nThe Production Site\nI then did some testing on Netlify itself. I had my site tied to GitHub so every time I did a deploy, it would get the updates, run Eleventy, and deploy. The Fetch plugin warns you against committing your cache folder so I added that to .gitignore.\nWhen testing in Netlify, either via commits or a forced deploy, I saw that Netlify was not persisting the cache. I was about to just be ok with that. I do a lot of builds locally, especially when working on my blog, but only 10-20 builds on Netlify per month or so, but I did some more digging in the docs and came across this little tip that suggests simply adding the Netlify Cache plugin\nThis required adding another npm package (npm install netlify-plugin-cache) and a netlify.toml file to specify that .cache should persist.\nOnce again I deployed, made note of the time, and did a few tests to confirm it worked. Both GitHub commits and manual deploys.\nInvalidating the Cache (in Production)\nInvalidating the cache in production is simple enough - just trigger a build and specify the clear cache option. Obvious, but you may miss this an option if you haven't clicked before:\n\n\n\nHonestly, even if this plugin didn't work at all in production I'd still find it really useful, especially in cases where an API call is slow and you don't want it running every time you make a quick change.\nYou can see all the code here, https://github.com/cfjedimaster/eleventy-scratch, and if you are terribly bored, you can see the site here: https://eleventyscratch.netlify.app/.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - Working with Zip Files",
		"date":"Mon Apr 28 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745863200,
		"url":"https://www.raymondcamden.com/2025/04/28/boxlang-quick-tips-working-with-zip-files",
		"content":"Time for another BoxLang Quick Tip! This time I'll be demonstrating how to work with zip files and as always, you can find a video version at the bottom of the post. There's one small issue with the video I'll address at the end, but outside of that, you can read, watch, or both! Ok, let's dig in.\nBefore we begin, note that BoxLang supports both zip and gzip files. My demo code works with zip files only, but you can easily perform the same actions with gzip as well.\nCreating Zip Files\nTo create zip files, you make use of the compress built in function. It has arguments for format (either zip or gzip), the source path, and the destination path. It also lets you tweak other settings. For example, when zipping a folder, you can tell it to not include the base folder it the created zip. You can also perform a filter (either by matching a file name or using a function) on what gets included.\nHere's an example showing how easy it is:\n\nBasically, zip up the folder, store it as myzip.zip, overwrite if it exists, and don't include the base folder. I feel like that aspect may be a bit confusing so let me show you an example.\nIf I do not specify that value, or set it to true, when I open up the zipped file I'll see sourceForZip at the root, and if I go into that folder, I'll see my files.\n\n\n\nIf I set the value to false, I instead just get the files:\n\n\n\nPersonally, I much prefer this style of a zip versus a zip of a folder, but you've got both options.\nExtracting Zip Files\nTo extract, you'll use... wait for it... the extract function. Like compress, it has options for the type of zip, where to extract, filtering, and more. Here's an example:\n\nNotice I also demonstrated isZipFile, another built in function that can be used to verify the type of file before working with it. While I'm at it, also note ignoreExists as an option to directoryCreate - I 💖 that.\nI mentioned that both compress and extract support filtering, either via a file glob or callback function. Here's an example of a glob:\n\nThis will only extract files that end with .pdf in their name.\nReading Zip Files\nWhile compress and extract are focused on creating/expanding zip files, the zip component comes with pretty much every feature baked in. The component can do everything the two built in functions can as well as list contents of a zip. Here's an example:\n\nWhich returns an array of elements for each item in the zip. Here's a snapshot of that:\n\nNote how you get details including how much the item was compressed. As I said, the component is pretty powerful, so check the docs for the full syntax.\nYou can find all the demo files for this post here: https://github.com/ortus-boxlang/bx-demos/tree/master/boxlang_quick_tips/zip\nA Quick Note\nWhile working on the video for this post, the BoxTeam and I discovered a small inconsistency with the zip functions versus directoryList. The directoryList function used file globs while compress and extract used a regex. It was decided to switch the functions to use globs which is what you see above. You will not see that in my video as the change was made right after, and, the change is not in the release version yet, but will be available in 1.0 which is out in a few days. The docs will be updated to reflect this change as well. (That's on me to fix, so, Ray, don't forget.) Anyway, with that out of the way, enjoy the silky smooth sound of my voice!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Cloudflare's Browser Rendering APIs for Screenshots",
		"date":"Fri Apr 25 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745604000,
		"url":"https://www.raymondcamden.com/2025/04/25/using-cloudflares-browser-rendering-apis-for-screenshots",
		"content":"I've been a Cloudflare fan for a while now, but have mainly focused on their Workers Serverless platform. I was aware, of course, that they did a lot more, but I just haven't had the time to really look around and explore. Recently I was doing some investigation into &quot;url to screenshot&quot; services and discovered that Cloudflare had this, and not only that, it's part of a suite of browser APIs that are really freaking awesome.\nCloudflare's Browser Rendering APIs do things like:\n\nGet the HTML of a page, but after JavaScript has executed, allowing it to get dynamic HTML\nRender a PDF to PDF\nScrape HTML via selectors\nParse out content via JSON schema (I'm absolutely going to be testing this soon)\nConvert a page to Markdown\nAnd of course, make screenshots\n\nThe capture screenshot is incredibly flexible. While you can just pass it the URL, you can also do things like:\n\nSpecify a viewport size\nModify the CSS and JavaScript on the page (could be useful for hiding full page modals)\nPass your own HTML instead of using a URL\nWait until a selector is visible\n\nAnd more. I linked to the doc just above, but it's pretty minimal, the reference page for the API shows a lot more options you can tweak.\nBest of all - this is available on the free tier. The limits are reasonable, but note that there is a max of 6 calls per minute (again, on the free tier), so keep this in mind if you are attempting to grab a bunch of screenshots at once.\nOk, how about a quick demo? I wrote a simple Python script that lets me pass a URL, and an optional width and height to the command line. The script will then hit the API, generate the image, and save it to a slugified version of the URL:\n\nThis is kind of ridiculously simple but that's what you want in an API. As I mentioned above, the reference shows a huge number of additional options you can pass, but this script will let you test out basic stuff. How about some quick examples?\nFirst, this blog, with default width (720) and height (1280):\n\n\n\nAnd here's a larger version, width 1720 and height 1200 (the image here has been shrunk to about 700 wide for display):\n\n\n\nNotice how the responsive design correctly renders in both options.\nSo as I said, this is a pretty cool API, and I've got some thoughts on how to use within a BoxLang application soon. I also want to dig into other aspects, especially the structured data aspect, something I've used GenAI for in the past. If you've used any of the browser rendering APIs from Cloudflare, please let me know in a comment below.\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a PDF Book from Markdown with BoxLang",
		"date":"Thu Apr 24 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745517600,
		"url":"https://www.raymondcamden.com/2025/04/24/creating-a-pdf-book-from-markdown-with-boxlang",
		"content":"Recently I've done some blog posts on BoxLang involving Markdown and PDFs, and I was curious if I could put together something that really demonstrated a complete tool of some sort. With that in mind, I built a &quot;book&quot; system where you can author pages in Markdown and use a BoxLang CLI script to generate a resulting PDF. It's more a POC than a real app, but it was pretty fun to build. Here's what I did.\nFunctionality\nAt a high level, the book is created from a source of Markdown files. Each Markdown file can use front matter (data on top) to define variables that are evaluated at the time the book is created. You can also use a &quot;global&quot; data file to define variables any Markdown file can use, with data defined in the Markdown file itself taking priority.\nHere's an example showing the output from my demo:\n\nSpecifics\nThe CLI script begins by reading a directory of Markdown files. They all are stored in a subdirectory away from the CLI itself. There's no inherit order to the files so my system requires you to sort them by number in the filename. So for my example, I used these files:\n\n01_toc.md\n02_chapter1.md\n03_chapter2.md\n\n\nNext, I read in my 'global' data file. As I explained above, this lets you create variables any Markdown file can use. It will also be used for the book title:\n\nNow comes the fun part. For each Markdown file, I need to pass it to a function that will look for front matter, parse it, apply data dynamically, and return the result.\nFirst, the loop:\n\nNow the function itself:\n\nFor my &quot;parser&quot;, I look for front matter as defined by three dashes, YAML data, and then three more dashes. I use the BoxLang Yaml module to handle parsing the Yaml, and the new Markdown module to convert the Markdown to HTML. (I'm going to have a video and blog post on that this week.)\nThe data that's applied is done by copying over the 'global' data first, and then the Yaml, which lets data at the Markdown level take priority. Then I simply look for tokens defined by brackets, ie: {{ name }}. This is fairly simple and only handles simple values, but it works.\nHere's an example Markdown source:\n---\ntitle: The Dog\n---\n\n&lt;a name=&quot;chapter2&quot;&gt;&lt;/a&gt;\n## 2 - Creating a PDF Book from Markdown with BoxLang\n\nThis is chapter one. Hello world. Lorem ipsum dolor sit amet, \nconsectetur adipiscing elit. Fusce vitae lorem at dolor \ncommodo convallis ut ut erat. Cras quis fringilla augue. \nSed vulputate nunc ac porttitor volutpat. Duis varius mauris \nerat, vel commodo tortor semper a. Donec arcu turpis, tempor \nsit amet neque ac, luctus interdum sem. Nulla euismod at eros\nid pellentesque. Etiam tortor enim, bibendum vitae tellus ac, \nlacinia faucibus felis. Nulla at tempus lectus. Nulla facilisi.\n\nCurious about the named anchor? When generating a PDF from HTML, you can create links to content inside the PDF itself using either anchor names, as I've done above, or HTML ID attributes. Using the name here makes it easier to create a TOC which I did as my first Markdown file:\n---\ntitle: Table of Contents\n---\n\n# Table of Contents\n\n&lt;ul&gt;\n&lt;li&gt;&lt;a href=&quot;#chapter1&quot;&gt;Chapter One - The Cat&lt;/a&gt;&lt;/li&gt;\n&lt;li&gt;&lt;a href=&quot;#chapter2&quot;&gt;Chapter Two - The Dog&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n\nThis is version {{version}} of the book. \n\nBy the way, you'll notice I've got hard coded titles there and dynamic ones in the pages and that obviously would get a bit messy if you edit stuff. I'm not looking to build a full &quot;static book generator&quot; project here. At least not today. ;)\nThe final result of this is an array, htmlParts, of the data used for the template and the HTML content generated from the Markdown. Now it's time to generate the book using PDF BoxLang module:\n\nI begin by adding the book title as a header to every page. Then I loop over each htmlPart element and just output the content. I use documentsection so I can create bookmarks for them. Finally, I add a page count. That's basically it. You can find the complete demo here: https://github.com/ortus-boxlang/bx-demos/tree/master/misc/md_to_pdf\nTo run this, you'll want to install the three required modules (BoxLang will soon support a way to make this easier), and then just boxlang createBook.bxs. Let me know what you think!\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding CORS to Your BoxLang APIs",
		"date":"Wed Apr 23 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745431200,
		"url":"https://www.raymondcamden.com/2025/04/23/adding-cors-to-your-boxlang-apis",
		"content":"CORS, or Cross-Origin Resource Sharing, has been the bane of my existence at times. Don't get me wrong, I get the idea behind it. I get why it's necessary. That being said, I tend to forget about it until I write some client-side JavaScript code that gets hit by it. With that in mind, I thought I'd quickly demonstrate how to build CORS-enabled APIs with BoxLang. It's incredibly simple, which is good, but you'll want to keep it in mind when building out your own APIs.\nWhat and Why?\nI'm not going to repeat the full description of CORS available from MDN, but it basically boils down to a security feature in browsers. When executing code on your domain that makes an HTTP request (either via the ancient XmlHttpRequest or the new host fetch), the browser will look for specific headers on the other domain that basically boil down to, &quot;Are you cool with me accessing your stuff?&quot; This 'question' is done via a 'preflight' network request that checks for specific headers. If those headers do not exist, or exist and do not specifically say the remote host (where your code is) can access the resource, your network request will be blocked.\nAs a creator of an API, if you want people to use it in their client-side JavaScript (and note, this is only an issue in the browser, nowhere else), you are required to add the additional headers to your response.\nAPIs in BoxLang\nCreating APIs in BoxLang is pretty trivial, and follows the model that ColdFusion has used as well. Any particular BoxLang template can output JSON, or XML, or heck, plain text, and be addressable and usable via remote consumers. But typically you will instead use a BoxLang class instead.\nAny BoxLang class under web root will automatically expose any method marked as remote. So for example:\n\nBy having remote in the function declaration, this method can be invoked by using it's path under web root and using method=getCats in the URL to specify the function. So if this were saved as /api.bx, you could use:\nhttps://yourdomain.com/api.bx?method=getCats\n\nThe result is automatically converted to JSON, but you can tweak that if you want to output other formats. You can have as many methods as you want, and can chain some to others, for example:\n\nIn this case, I've added a second method, searchCats, which will return a filtered list of cats. And yes, I 100% could have used one method that had an optional search filter. Honestly, the API you build is up to you and whatever makes sense for your needs.\nIt just so happens I released a BoxLang Quick Tip video on this subject, check it out below:\n\n  \n    Play Video\n  \n\n\n\n\nSupporting CORS\nSo given that it's easy to build an API with BoxLang, let's demonstrate CORS being an issue, and then correcting it. I built a simple BoxLang web app and created api.bx:\n\nI ran this via boxlang-miniserver and confirmed it was accessible. In another folder I created a simple HTML page:\n\nBasically, hit the API on the 'remote' server and render out the results. I fired up a second local web server for this, and immediately get an error in the console:\nAccess to fetch at 'http://localhost:9000/api.bx?method=getCats' \nfrom origin 'http://127.0.0.1:42419' has been blocked by CORS \npolicy: No 'Access-Control-Allow-Origin' header is present on \nthe requested resource. If an opaque response serves your needs, \nset the request's mode to 'no-cors' to fetch the resource with CORS disabled.\n\nDoh! But expected. So let's fix it. How? With one line of code:\n\nNotice I used getCats2 as my source (which I'll share in a minute) has both the 'before' and 'after'. Anyway, using this new method fixes everything.\n\n\n\nNow, to be clear, the Access-Control-Allow-Origin header there is basically saying, &quot;Anyone from anywhere can use me as you will&quot;, and you do not need to be that permissive. For details on how to be more specific, again I'd suggest the MDN CORS docs as your reference.\nIf you want, you can check out the source code for both the API and consumer here: https://github.com/ortus-boxlang/bx-demos/tree/master/webapps/corsdemo Let me know if you've got any questions below.\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Parsing Markdown in BoxLang - Take 2",
		"date":"Mon Apr 21 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745258400,
		"url":"https://www.raymondcamden.com/2025/04/21/parsing-markdown-in-boxlang-take-2",
		"content":"A long, long time ago, ok, it was last Thursday, I posted about adding Markdown processing to BoxLang via the Flexmark Java library. After posting it, a few folks were curious why I didn't use the native import process instead of createObject('java', '...') and the answer was simple - I just didn't think about it! To give you an idea of the difference, let's first consider the initial version:\n\nIn this version, I use createObject for my three Java objects to get to the final result I need, an HtmlRenderer object I can pass a parsed string to. Now consider this version:\n\nThis version is a heck of a lot simpler in terms of what's being done. Technically the imports there break encapsulation from the simple UDF I had written, but I would have used a class in a real application anyway.\nThere is one small issue with this version and that's finding the Jar. If you remember, with createObject I can point to the jar, so what do I do here?\nIf I'm running this in a web application, I'd just use javaSettings to specify it there. Outside of that, for right now there isn't a simple one liner BIF (built-in function) with similar functionality, but we can hack it up like so:\n\nThe bx:application component essentially turns my script into a web application (for the execution of the template) and can therefore make use of javaSettings. As I said, a bit of a hack, but honestly, for CLI scripts I'm fine with that, and, there's an open issue in the BoxLang Jira to add a proper BIF for this.\nYou can find this version in our bx-demos repo here: https://github.com/ortus-boxlang/bx-demos/blob/master/java-interop/flexmark2.bxs\nObviously, both versions work, and it comes down to which style you prefer in your code base, but it's a good example of the flexibility in BoxLang!\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (4/19/25)",
		"date":"Sat Apr 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1745085600,
		"url":"https://www.raymondcamden.com/2025/04/19/links-for-you-41925",
		"content":"Normally I preface these posts with little updates and such, but I'm too busy today and am sitting on what could (possibly) be good news. So, let's just get right into the links, shall we?\nTranslating Live Streams in Real-Time with On-Device AI Models\nI've been real excited about Chrome's upcoming AI on device efforts, so when my buddy Todd Sharp wrote up a cool demo of it, I had to share. In his post, Todd describes using on device transcription of a video source that then makes use of Chrome's new AI feature to perform translation on the viewer's side. Obviously this only works in Chrome Canary, but as an option in the future when it's mainline, this would be absolutely awesome for video streams, especially since the translation is all on device.\nBASIC Python\nNot basic Python, but BASIC in Python. For those of us elderly people, BASIC was often the first language we learned on, and now you can parse/run BASIC code in Python itself. You can even save and load in BASIC files from the file system and the repo includes some examples, including Oregon Trailer. (Spoiler - you will die of dsyentry.)\nMySQL and MCP\nAnyone doing anything in GenAI lately has heard about MCP. I've only done a bit of research into it so far, and honestly, I'm both excited for it and disappointed it's not this:\n\n\n\nIgnoring that for now, you should definitely read this blog post by my other good buddy Scott Stroz where he explains how to connect MySQL with MCP and demonstrates with some interesting golf data. (Ok, don't tell Scott, I don't find golf that interesting, but what he did with his golf data was absolutely fascinating.)\nJust For Fun\nI've been subscribed to @matt one on YouTube for a few weeks now and I believe I probably shared one of his videos before. He creates some pretty stellar remixes and mashups. This one is a remix of one of my favorite Cure songs and I hope you like it too:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Parsing Markdown in BoxLang",
		"date":"Fri Apr 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744999200,
		"url":"https://www.raymondcamden.com/2025/04/18/parsing-markdown-in-boxlang",
		"content":"A few months ago, I wrote up a blog post exploring Java integration in BoxLang and in that post, Robert Zehnder suggested I get Flexmark working as well. Flexmark is a Java Markdown library, and turns out, I blogged about it last September: &quot;Parsing Markdown in ColdFusion&quot;\nThat post demonstrated how to use Flexmark in ColdFusion, and initially I didn't think there would be a point in demonstrating the same thing in BoxLang as the syntax would be near identical, but turns out, there's one small difference that's absolutely worth sharing.\nOk, so first off, I grabbed flexmark-all-0.64.8-lib.jar from Flexmark's Maven repository. In the previous post I made use of ColdFusion's application framework to define my Java load paths so that ColdFusion could find the jar. For BoxLang, I could do that as well but as I'm building a command line tool and not a web app, I instead made use of the BoxLang feature where createObject lets you pass in a jar file. Keep that in mind if you make use of this code in a web environment, you could simply a bit in that context.\nHere's the initial function I wrote which is a copy of the previous one, with the addition of specifying the jar:\n\nAs I mentioned in the previous post, this code came from reading the sample Java code for Flexmark. But this didn't work! Why?\nWhen run, I got this stacktrace:\nortus.boxlang.runtime.types.exceptions.NoMethodException: No such \nmethod [builder] found in the class [com.vladsch.flexmark.parser.Parser] \nusing [1] arguments of types [[class java.lang.Class]]\n\nThis is referring to the second line in the function and seems to imply the ds variable is an instance of java.lang.Class, not the class I had initially created.\nTurns out, this is expected as the initial call of creating the object just loads the class, but doesn't actually make an instance of it. The fix is ridiculously simple (scroll to the right):\n\nI literally just added an init call for the ds object to properly load an instance and get it running. Something to keep in mind, and given the a) huge amount of open source Java out there and b) BoxLang runs on the JVM, it's an important tip to keep in mind.\nBefore I leave, one more quick note. In my test script, I made use of another BoxLang feature, tag islands. Tag islands are a way to incorporate tag based syntax within scripts, and honestly, the whole idea horrifies me. I don't know why, it just does. That being said, I've not actually used the feature before, and it can be really useful, for example:\n\n\n.gist{font-size: 18px}.gist-meta, .gist-file, .octotree_toggle, ul.comparison-list > li.title,button.button, a.button, span.button, button.minibutton, a.minibutton,span.minibutton, .clone-url-button > .clone-url-link{background: linear-gradient(#202020, #181818) !important;border-color: #383838 !important;border-radius: 0 0 3px 3px !important;text-shadow: none !important;color: #b5b5b5 !important}.markdown-format pre, .markdown-body pre, .markdown-format .highlight pre,.markdown-body .highlight pre, body.blog pre, #facebox pre, .blob-expanded,.terminal, .copyable-terminal, #notebook .input_area, .blob-code-context,.markdown-format code, body.blog pre > code, .api pre, .api code,.CodeMirror,.highlight{background-color: #1D1F21!important;color: #C5C8C6!important}.gist .blob-code{padding: 1px 10px !important;text-align: left;background: #000;border: 0}::selection{background: #24890d;color: #fff;text-shadow: none}::-moz-selection{background: #24890d;color: #fff;text-shadow: none}.blob-num{padding: 10px 8px 9px;text-align: right;color: #6B6B6B!important;border: 0}.blob-code,.blob-code-inner{color: #C5C8C6!important}.pl-c,.pl-c span{color: #969896!important;font-style: italic!important}.pl-c1{color: #DE935F!important}.pl-cce{color: #DE935F!important}.pl-cn{color: #DE935F!important}.pl-coc{color: #DE935F!important}.pl-cos{color: #B5BD68!important}.pl-e{color: #F0C674!important}.pl-ef{color: #F0C674!important}.pl-en{color: #F0C674!important}.pl-enc{color: #DE935F!important}.pl-enf{color: #F0C674!important}.pl-enm{color: #F0C674!important}.pl-ens{color: #DE935F!important}.pl-ent{color: #B294BB!important}.pl-entc{color: #F0C674!important}.pl-enti{color: #F0C674!important;font-weight: 700!important}.pl-entm{color: #C66!important}.pl-eoa{color: #B294BB!important}.pl-eoac{color: #C66!important}.pl-eoac .pl-pde{color: #C66!important}.pl-eoai{color: #B294BB!important}.pl-eoai .pl-pde{color: #B294BB!important}.pl-eoi{color: #F0C674!important}.pl-k{color: #B294BB!important}.pl-ko{color: #B294BB!important}.pl-kolp{color: #B294BB!important}.pl-kos{color: #DE935F!important}.pl-kou{color: #DE935F!important}.pl-mai .pl-sf{color: #C66!important}.pl-mb{color: #B5BD68!important;font-weight: 700!important}.pl-mc{color: #B294BB!important}.pl-mh .pl-pdh{color: #DE935F!important}.pl-mi{color: #B294BB!important;font-style: italic!important}.pl-ml{color: #B5BD68!important}.pl-mm{color: #C66!important}.pl-mp{color: #81A2BE!important}.pl-mp1 .pl-sf{color: #81A2BE!impor",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - Working with JSON",
		"date":"Tue Apr 15 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744740000,
		"url":"https://www.raymondcamden.com/2025/04/15/boxlang-quick-tips-working-with-json",
		"content":"Welcome to another BoxLang quick tip - today I'm going to focus on working with JSON in BoxLang. Now, as you can probably guess, JSON is natively supported and supports what you would expect, going to and from JSON, but there's some particularities of the support that may interest you, so I've dug into it. As with my other quick tips, you can skip to the video version at the bottom if you prefer.\nThe Basics\nConverting data to JSON can be done two ways, either via the built in function (BIF) jsonSerialize or the member function toJSON. There's no difference here, just use what makes sense for you:\n\nAs a reminder, variables is a scope in BoxLang.\nGoing from JSON can also be done two ways, either via jsonDeserialize or fromJSON:\n\nThis is all pretty simple and straightforward, but you can test it yourself below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"800\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJx9UMFKAzEQvecrQi7dgqYiCOLSW%2FEg6ELbm3iYdKdr2k2mJGFFv95MqWFl0dNk5r158148OJRLqdbw6ci3qhbQ8eDuthbvZIzFmLtXZRCDulKG6BhzdTRkJD8G2yLJLqtwt4MU1ZsQi4XsyPpObht5iOS11uIUrE%2B9r9QWY2IsYrDQ2y9IlrykvRwgD0yP8UHN68Ln%2Fc2FilXhzEeU2fVMBzwhpOr%2BZgwUtk70tGleKgbF33tTky1ObLLQ2WFOuSPHrMd181yCcs1%2FNr1dC48fK0iQUSatijaeU46MX4j%2FZfytpfeB3M%2BZqco3V6GfaA%3D%3D\">\n    \nQueries are Different...\nSo far so good. Queries in BoxLang serialize a bit differently than you may expect. This is based on past behavior in ColdFusion and represent the 'special' nature of queries as a proper data type in the language. Let's look at an example. First, here's our query:\n\nWhen you convert this to JSON, you can pass an argument to define how it's serialized. There's two values, row and column, with row being the default. Here's how the JSON looks when using row:\n\nAs you can see, you get a value representing the columns of the query and then a data array where each element itself is an array of values that match the same order of columns.\nTo use the column format, you would pass it like so:\n\nWhich returns the information like so:\n\nAs you can see, each element in data now maps to a column with the value being an array of items for each row. You can see it for yourself below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"800\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJyN0MFOg0AQBuCzPMVkLkCytqbaxtB46sGkhxrlaDxMYaQo7OKwSBrju7ugLZh68Lab%2Fb%2F5s1OxqQqGG3hrWPYbbgPUVLKijJWQfkUF%2BE6S7EhUri1nLOrn7p4evbOPPo8RPtC%2BNDqFFZUp6865GRjBfOaO%2FagINUuKn2qk1pSY7YmZzQfTbFngFMZNnTPEZW53g7taDO6F0%2Fw3WZGYAm6Fx1WLy4HUVJJQr7yncOl5lbg%2FFzrA%2B247YA2s47sNtK4UUn6mprBQs7W5ziJ04JCv%2Bq1OrOniAYppMQyXMJ0e1DHpn%2FsT4YrJBtcX4WjEX5V%2BYoqm1P5%2FOr%2BjXa33BUhel3M%3D\">\n    \nI'm not a fan of this output, but as I said, it matches how ColdFusion serialized queries so I get it. Luckily, it's also really trivial to use another format. Query objects have a member function, toArrayOfStructs, that converts the query object to, wait for it... an array of structs. I much prefer this 'shape' and even if I'm not using JSON will add this to my code getting database information. I could use this with the people query like so:\n\nYou can try this below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"800\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJxl0M9LwzAUB%2FCz%2FSvCuyyFbMh0QyoeZAdhhw0snsTDW%2FvsMpsfvqZKEf93s6K0c7ck3%2Ff5hsST8zWJO%2FHeEncb%2BpRg0ZDCihSjfQMl4AO52CMrbQNVxOp3H6Pn5OKrn4cMHrEzzpZihaYke3SxAzKxmMdlX5WBJS7hW43UGgu3OzPzxWDaHbE4h3nbaBK50WE%2FuOvl4A5U6lOyQna1eGAaX7W8GkiDBhl7lbykt0niOb65tnIyncyYPGGQN5dpTP4CeGq0rURw98zYbV%2FzwG0RmgxGM77%2F4tn%2FGZnGo3W%2B3chj4Q8UHXmR\">\n    \nValidating JSON\nYou can also check if a string is valid JSON first with isJSON:\n\nThis works as expected - with the first check returning false and the second true:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"800\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJyrULBViFZKy89X0lHKBZPpQDLWmouroCgzryQnT0Mps1ihQiGzpDg1J00hqzg%2Fz15BObPYC8jQqNBUVtIEKs0CGlKhV5LvFezvpwEUQNaahaYnC6IHAJatI%2FA%3D\">\n    \nPut Some Lipstick On It!\nFinally, you can 'pretty' up your JSON printing with... jsonPrettify:\n\nGiven the data above, you get this in your output:\n\nTry it yourself below:\n<iframe\n        allow=\"fullscreen\" \n        width=\"100%\"\n        height=\"800\" \n        src=\"https://try.boxlang.io/editor/index.bxm?ro=false&code=eJwVTDsOAiEQ7TkFmYpNrEysiHcwWhqLwR3XQWAMEJL19A7V%2B7%2BCmezZwhX3LGUFb3CbxunozVtCYGqq7hCIKhwgiHyaYpahiZLBK4nd9GWqJ%2FYGD2OibmKTcqPKmPhHbqCykKgt3nwrl56Km41Lpd75tbu4aPIHelEuQA%3D%3D\">\n    \nThat's it. Let me know if you've got any questions, and enjoy the video version below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Build Your Own Localized Events Calendar using Diffbot's Knowledge Graph",
		"date":"Mon Apr 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744653600,
		"url":"https://www.raymondcamden.com/2025/04/14/build-your-own-localized-events-calendar-using-diffbots-knowledge-graph",
		"content":"Finding out what's going on in your city can be a bit of a chore. For me, I use a combination of Facebook, specifically accounts for local organizations and news channels, and our local Reddit forum. This is... haphazard at best. I'm sure local &quot;city wide&quot; calendars exist, but I'm not aware of any that is used by the majority of folks nor do I trust them to actually cover everything going on. Having played with Diffbot's Knowledge Graph last month (&quot;Automating and Responding to Sentiment Analysis with Diffbot's Knowledge Graph&quot;), I thought I'd do some digging to see what would be possible via their API. Here's what I was able to build.\nFirst Attempt - City and State\nFor my first attempt, I made use of the Diffbot visual search tool. &quot;Events&quot; are one of the many entity types there so you can start off with:\ntype:Event\n\nTo give you an idea of the size of the Knowledge Graph, this returned over half a million results.\nNext, I wanted to filter by a location. This took a bit of digging around, but I figured out that the state property is locations.region.name while city is locations.city.name. This means it's easy to filter to a city. Now, one of the things I noticed was that there wasn't much data for my particular city, which to be fair, Lafayette isn't a big region. For my demo, I switched to New Orleans:\ntype:Event locations.region.name:&quot;Louisiana&quot; locations.city.name:&quot;New Orleans&quot; \n\nThis returned almost a thousand results. For the final filter, I added a date restriction for the past thirty days. Now, to be clear, today is April 14th, if an event happened on the 2nd, it's too late. That being said, I thought it would be useful to see events for the entire month. For events that occur every month, this lets you see something you may have missed before but can catch next time. I also added a sort as well:\ntype:Event locations.region.name:&quot;Louisiana&quot; locations.city.name:&quot;New Orleans&quot; \nstartDateTime&gt;&quot;2025-03-14&quot; sortBy:startDateTime \n\nThis returns over two hundred results:\n\n\n\nAs you can see, there's an API call setting right in the UI and clicking it gives you the raw URL you need to get this data. Here it is, minus my key off course:\nhttps://kg.diffbot.com/kg/v3/dql?type=query&amp;token=SECRET&amp;query=type%3AEvent+locations.region.name%3A%22Louisiana%22+locations.city.name%3A%22New+Orleans%22+startDateTime%3E%222025-03-14%22++sortBy%3AstartDateTime&amp;size=25\n\nWoot! This works, but I kept digging...\nSecond Attempt - Using the Near Operator\nWhile looking at Diffbot's DQL Search docs, I noticed the section on &quot;Utility Statements&quot; discusses the near operator. As you can guess, this lets you find entities (what Diffbot calls their data) within a radius around a specific location. That radius defaults to 15km but can be changed to a specific km or mile value. The docs specify that you can provide either a city or an ID value.\nIf you use a city, the docs say that it will &quot;default to the city with the most importance within the Knowledge Graph.&quot; That makes sense I suppose, but generally, I'd probably suggest using the ID value of an entity instead to be sure.\nTo try something different, I found the ID for Austin, Texas, and this is the query you would use to find events within ten miles:\ntype:Event startDateTime&gt;&quot;2025-04-01&quot; near[10mi](id:&quot;E8y6iIzzkPbKiZdINSDRnYQ&quot;) sortBy:startDateTime\n\nThis returns 123 results, but if we expand our search to twenty miles, you get 668. Not surprising but useful.\nSo which do we use? I think it depends. For a large metro area, maybe always use the first approach with a specific value for the location. For a smaller area, the near approach may work better. And of course, there's always this option:\n\n\n\nGiven that results from Diffbot have unique entity ID values, you could easily do two calls and dedupe the results. For today, I'm going to stick with the first approach to keep it simpler.\nBuilding the API\nAs with my last demo, I decided to make use of Pipedream. I created a new workflow using a HTTP trigger which gives me an endpoint I can use in my code. Here's the URL it created: https://eofjep94f7kygzd.m.pipedream.net\nAfter adding the HTTP trigger, I added a code step that handled the &quot;figure out 30 days in the past&quot; and than simply hit the API. I made one small tweak for performance. Pipedream has a key-value storage system called data stores, and given that events won't change terribly often, nor will Diffbot's Knowledge Graph be updated every few seconds, I added a basic 12 hour cache to the results. Here's that code:\n\nNote that I could have made the city/state dynamic as properties to the workflow, but again, I'm keeping it simple. Also make note that I changed size to 100. Diffbot's API will let you paginate and it wouldn't be that difficult to do so, but as I didn't want to make this too complex, I figure this was a simple enough solution. I ",
		"tags":[
	        
            "python",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My First (and Last) Spotify Web App - Music Snob",
		"date":"Fri Apr 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744394400,
		"url":"https://www.raymondcamden.com/2025/04/11/my-first-and-last-spotify-web-app-music-snob",
		"content":"I've been a happy Spotify user for a few years now (I transitioned from Amazon Music after they cut features and generally ticked me off) and as I listen to music a lot, I've built a few integrations with their APIs over the years. Those integrations were simple tools that hit my own personal data and were just for fun, but I thought I'd take a crack at building a simple app with their Web API which would allow Spotify users to authenticate and see their own data. I built the app. But I 100% would not recommend working with the Spotify APIs going further. I'll explain everything that went wrong, why I recommend this and so forth, but if you just want to see the app, scroll down to the very bottom for the link.\nIt's not the API...\nSo why the dramatic and dire statements above? It isn't the code. Their REST APIs work perfectly well and are (well, were) full featured. That isn't the issue. The problem is when you want to move your web app into production. This involves, obviously, a check on the Spotify side, to ensure your app meets certain requirements and such. That's totally reasonable.\nI thought... surely this will be a simple process.\nI began with my initial request on April 30, 2024. This is basically a form asking what you're doing, what scopes you are using and why, and including screenshots. I got rejected 3 months later. Not 3 days. Not 3 weeks. 3 months. I corrected my issue and resubmitted where I discovered that &quot;resubmitting&quot; means literally putting every single thing back into the form, including screenshots. And then I waited again.\nChecking the developer forums was a mistake as I discovered I was far from being the only one having to deal with this. Apparently six plus months of waiting is the norm.\nAfter waiting nearly two months again, I was approved. And then... I discovered that some grant I had used had changed - or perhaps worked one in localhost and another live. Honestly I don't remember the exact details, but I had to request a scope and wait... again.\nThe final approval was November 26, 2024. That's just... incredible. And apparently the next day, changes to the Web API were posted on the forums and the top comment is from a person who had spent months on a project to be it completely destroyed by the changes. You can read more about those changes here, and luckily it didn't impact my app, but... yeah.\nNone of this impacts non-web app type uses, but honestly, my confidence in Spotify's support for developers is an infinite curve approaching zero. I would avoid it unless things change.\nSo, with that out of the way... what did I actually build?\nThe Music Snob App\nI like to think I've got eclectic taste in music. While I've got genres I definitely spend a lot of time on (new wave, trance, goth), I enjoy pretty much any style of music, outside of country and heck, even there I can find some tracks I enjoy. I thought to myself - I wonder if something in my data would tell me exactly how much of an eclectic listener I am, or more simply, am I snob? (To be fair, I will rock, and dance, the hell out to some Britney Spears so I can't be too much of a snob.)\nTurns out, there is an API that returns a user's top media. The Get User's Top Items endpoint will return either your top artists or tracks.\nHere's an example showing top artists:\n\nOf note is the popularity field, which according to the docs is:\nThe popularity of the artist. The value will be between 0 and 100, with \n100 being the most popular. The artist's popularity is calculated from \nthe popularity of all the artist's tracks.\n\nIt's similar for tracks as well. So given that I can could get a current user's top values for both artists and tracks, in theory, we could average their popularity, and the lower the value, the more obscure/eclectic your tastes are.\nHere's a screenshot from what I built:\n\n\n\nAnd in case the text is too hard to read, the report said:\nFor your top musical artists, they had an average popularity score of 21.04. \nFor your top tracks, they had an average popularity score of 18.34. Spotify \nranks popularity from 0 to 100 with 100 being the most popular.\n\nTaken together, you ARE a music snob! Congrats!\n\nSee! I told you I was cool!\nThe application itself is a rather simple Alpine.js application using Shoelace for UI. I'll link to the entire code base in a sec, but on the front end I basically have two states - the you need to auth with Spotify state and the &quot;getting and reporting&quot; state. I don't think the HTML is that interesting, but I'll share a bit.\nThe entire &quot;are you a snob or not&quot; comes down to one check that looks at the average of your two average 'scores' for popularity in tracks and artists:\n\nThe value of '40' is used as the cutoff and that was somewhat arbitrary. When looping over tracks and artists, I made use of the Shoelace card component:\n\nWhich renders nicely I think:\n\n\n\nLet's switch to the more fun stuff, the JavaScript. I began by using most of the code from Spotify's How ",
		"tags":[
	        
            "javascript",
            
            "alpinejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Chrome's (Preview) Prompt API for Data Summarization",
		"date":"Thu Apr 10 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744308000,
		"url":"https://www.raymondcamden.com/2025/04/10/using-chromes-preview-prompt-api-for-data-summarization",
		"content":"I probably should not be blogging about Chrome's built-in AI (upcoming) features as pretty much every single post I've done is now broken due to changes to the APIs and such, but given that I just got back from a conference where I had a chance to show off these early APIs, I built a demo that I wanted to share with folks. I imagine that once these APIs become GA (generally available) that this demo will need updating, but I thought it was a cool example and something that has me excited for their future.\nUnlike other Chrome AI features I've covered here that are focused on one particular task (like translation), the Prompt API is more general purpose. It supports any general question and answer, but being based on a smaller model (Gemini Nano), it won't have nearly the same breadth of knowledge as a larger, API based system.\nThat being said, the docs for the API had some pretty fascinating use cases, including scanning text for calendar events or contract data. This got me to thinking about a topic I covered in October last year, AI insights to data. In that post, I used Gemini APIs to translate raw data being used in charts to general one sentence 'executive' type summaries. Even though they pretty much said exactly what was in the chart, I thought it was a great use of AI and was something I was curious about using with Chrome's built-in AI.\nMy Data\nFor my demo, I decided to (once again) make use of the Pirate Weather API. When making a forecast request, you get a large set of data back. Here's a subset of that information:\n\nThe portion I cut out was within data.daily and I simply removed the last five days of the forecast. Given this raw data, can we write a simple and short forecast? Here's what I came up with.\nMy Demo\nFirst, I grab the forecast:\n\nGiven the large set of data, I wanted to 'translate' this into a smaller string that consisted of:\n\nThe high and low temperatures\nThe forecast summary (i.e. cloudy, rainy, apocalypse)\n\nI used the following logic to do so:\n\nNote that the slice at the end removes the first day which is the current day. That may, or may not, make sense. The result from this is:\n\nSpoiler - in Louisiana, if you ever see &quot;possible drizzle&quot; or &quot;possible rain&quot;, you're getting rain. Period.\nNow let's turn this into a forecast. For this demo, I skipped my usual &quot;does the API exist and can it be used&quot; checks, so keep that in mind. First, I create an instance of the model with my system instruction:\n\nAnd then I generate the result:\n\nHere's a sample result:\n\nThe forecast for the next week includes a mix of clear skies and partly cloudy days, with temperatures ranging from the mid-40s to the mid-80s. There is a chance of drizzle on Friday.\n\nThat seems pretty accurate I think. You can test this out yourself, but keep in mind that instructions for enabling this feature is behind a signup (fill out the form here) and probably will not work for you below. But, you can at least see the full code and honestly, it's a trivial amount of work. Let me know what you think!\n\n  See the Pen \n  LanguageModel + Weather by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Come Learn about BoxLang at Into the Box 2025",
		"date":"Wed Apr 09 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744221600,
		"url":"https://www.raymondcamden.com/2025/04/09/come-learn-about-boxlang-at-into-the-box-2025",
		"content":"Later this month, I'll be speaking at Into the Box 2025 in Washington, DC and online. I've spoken at Into the Box in the past and the Ortus team puts on a great show, so I'd absolutely suggest checking it out if you can. In person tickets are available, although close to selling out.  The virtual event gives you everything the in person event does minus the workshops.\nWhat will I be speaking about? BoxLang of course! I'll be giving an introductory session on the language and talking about how you can start using it today. I promise multiple, enterprise-worthy cat demos to go along with it!\nCome join me and many others!\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Comparing Google's Image Generation Models",
		"date":"Tue Apr 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1744135200,
		"url":"https://www.raymondcamden.com/2025/04/08/comparing-googles-image-generation-models",
		"content":"Last month I blogged about Gemini's new image generation support. Previously they had one model, Imagen 3, but recently they added support to the Gemini Flash model as well. It's been on my to do list for a while now to do a proper comparison. While what follows isn't exhaustive at all, it does give you some quick examples of the differences. Before I begin, a reminder about the two models:\n\nThe Gemini Flash model is best for generating images and text (in fact, you can't tell it not to generate text, but it won't always do so and you can ignore it).\nThe Imagen model gives you better quality, control over the aspect ratio, and the ability to generate multiple images at once.\n\nFinally, you can test Gemini's image support for free while Imagen costs. According to the current pricing figures, it costs 3 cents per image. This is pretty low, but I waited to do these comparisons until I got some free GCP credit as a Google Developer Expert.\nThe Code\nWhile I don't expect anyone to actually use this, here's the code I used for my testing purposes. For the most part it's just, &quot;do this prompt for Imagen and again for Gemini&quot;, but the Gemini call is done in a loop as you can't specify the number of results. I've got a different key for imagen so it uses different authentication.\n\nWhen this runs, it creates two images each and uses a file name based on the model, slug, and what number result it was. You can find the source for this here: https://github.com/cfjedimaster/ai-testingzone/blob/main/imagen/compare_gemini_imagen.py\nThe Results\nOk, so here are the prompts and results. For each image, I used Paint.net to resize the width to a max of 500 pixels wide. You don't have precise control over the size with either of the models, but you get consistent results back and could use server side code to resize.\nThe first prompt is:\n\na black cat under a clear moonlit sky, fireflies flit in the background\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nNext up is:\n\na polaroid photo of a cat under a christmas tree, set in the 1970s, the photo has a bit of damage\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nThe quality differences here really stand out, although one could argue the lower quality Gemini results kinda work as well given the prompt for an old photo.\nNext:\n\na cartoon drawing of a cat dressed as a super hero shown flying across a cityscape towards the camera\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nNext:\n\na black and white inked artistic picture of a cat\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nAlmost done, promise. Next:\n\na painting of a cat done in the style of monet\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nNotice how both Gemini results came out like a photo of a painting. I can see wanting that in some cases, but I much preferred the Imagen results here. A better prompt probably would have helped Gemini.\nAnd finally, testing text generation:\n\na comic style picture of a cat holding a sign that says \"free cats\"\n\nFirst, Gemini:\n\n\n\n\n\n\nAnd now, Imagen:\n\n\n\n\n\n\nImagen &quot;wins&quot; again, although I've got no idea what the &quot;POOOW&quot; sign is about.\nLet me know what you think, and if you are using either of these models now, I'd also love to know. Leave me a comment below.\n",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (4/5/25)",
		"date":"Sat Apr 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1743876000,
		"url":"https://www.raymondcamden.com/2025/04/04/links-for-you-4525",
		"content":"Not going to lie, this has been a long week. I gave two presentations at Arc of AI, one of which needed to big updates as Chrome changed their API a day before my presentation. To be fair, the browser AI stuff is in development and the changes were good, but I had to scramble a bit. I'm exhausted (for this and other reasons) so let's just skip right to the links, shall we?\nSearch with Video and Algolia\nA few days ago, Algolia had a small virtual developer conference with one of the most fascinating presentations I've seen. Tim Carry described how he built a tool to index and search short videos from YouTube. The number of different tools and things he did to build this little tool is absolutely fascinating and even if you don't necessarily every need to repeat this particular task, I'd highly encourage watching it just to see all the different moving parts as they come together.\n\n  \n    Play Video\n  \n\n\n\n\nRAG and Python\nNext up is another video, this time by Pamela Fox. I've known her for quite some time and she's an excellent person to follow and scary smart. In this video (part of a series), she discusses RAG (Retrieval Augmented Generation) with Python. All the code is shared, and can be run for free as well, and she does a great job of making it understandable.\n\n  \n    Play Video\n  \n\n\n\n\nYour DevRel Questions... Answered\nFor the last link (well, 'serious' link), I've got an excellent article by my buddy Brian Rinaldi, Your DevRel Questions Answered. Finally!. In this post, Brian goes into detail various aspects of developer relations and answers common questions about the role, including measuring, where it should sit, and what kind of activities should be considered part of the role.\nJust For Fun\nI've been rewatching Andor in preparation of the new season this month, and while I had already thought it (along with Rogue One) was the best part of Star Wars, my rewatch has me blown away. Every aspect of it - the shots, the music, the dialogue, the action, everything, feels absolutely perfect. If you haven't given it a shot, I highly recommend you do, and for fun, here's the fan made Beastie Boys Rogue One mashup that I may have watched over a hundred times.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Scheduling Code in BoxLang",
		"date":"Fri Apr 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1743789600,
		"url":"https://www.raymondcamden.com/2025/04/04/scheduling-code-in-boxlang",
		"content":"While I was busy getting utterly overwhelmed by deep AI talks at Arc of AI this week, BoxLang released it's third release candidate, and while there's multiple goodies in there, the schedular is the one that interests me the most. Currently the only docs are in the release notes, but there's enough information there to get started. Here's a quick look at what's been added.\nFirst off - just in case it isn't obvious, the idea here is to write code that can execute by itself on some predefined schedule. There's multiple different use cases for this - performing backups, refreshing data from an API, logging stats and so forth. In BoxLang, scheduled tasks can be defined in CLI scripts, for web applications, and at the server level itself. Where you do this depends on whatever you have in mind.\nCode wise, you load a BoxLang class that can define one or more tasks, each with it's own name, schedule, and code. This class can also define multiple different lifecycle events giving you the ability to run things before task execution, after it, and more. Let's look at a small example.\n\nThis minimal example has one method, configure, which sets properties (name and time zone) and defines one task. The single arrow call there (-&gt;) defines a lambda function, but closures can be used as well. The task simply prints out a message and finally a schedule of every five seconds is used.\nFrom the command line, this can be run with the boxlang CLI:\n\nAnd will output to the terminal every five seconds:\n\nKilling the script (CTRL+C) kills the scheduled task, so don't forget you can run it as a background task, for example, using the &amp; operator in Unix type shells:\n\nPersonally I'd just open a new tab in my terminal so I don't have to look up the process later to kill it.\nThat's the basic idea, but you can do a lot more, including:\n\nDefine a startup and shutdown hook for the scheduler.\nDefine an error handler for tasks.\nDefine a success handler for tasks.\nDefine a before and after hook for a task.\n\nI'm going to steal from the example Luis shared with me that shows a &quot;complete&quot; scheduled task class using every possible method:\n\nAlright, so how about a slightly realistic example? As most of my readers know, I can't get enough of weather APIs. I built a simple wrapper about the Pirate Weather API that would give me a nice weather report in my terminal. Here's that code:\n\nAll this does is configure a task that wraps a call to getWeather (which itself wraps a call to the Pirate Weather API) and displays the current conditions and temps to my terminal. I set it to run once an hour as I figured that would be sensible for weather information. If the layout of the text above looks a bit weird, that's simply what worked for me in my terminal with spacing. As an example:\n\nYou can find the source for this demo here: https://github.com/ortus-boxlang/bx-demos/blob/master/scripting/weathertask.bx\nSo as I mentioned above, you can use these scheduled tasks at the command line, but also within a web application as well. In fact, I've had a small BoxLang app I've been waiting to share as it needed this feature. I'll share that sometime next week. But the syntax there would be this in your Application.bx file:\n\nAlso, there's multiple new functions that allow for programmatic access and manipulation of tasks. Check the docs I just linked to for more information.\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - Sending Email",
		"date":"Thu Mar 27 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1743098400,
		"url":"https://www.raymondcamden.com/2025/03/27/boxlang-quick-tips-sending-email",
		"content":"Welcome to another BoxLang Quick Tip! As with my other quick tips, I'll end this blog post with a link to the video version so feel free to skip down to it, or read, or both if you prefer! Today's quick tip is a look at how BoxLang supports sending email, and as with my previous tips, an additional module is all you need to do.\nThe Mail module can be installed via the CLI like so: install-bx-module bx-mail. Once installed, you get three new components for your runtime:\n\nmail - This is the core component and handles all mail operations. You'll always use this. It has quite a few options so be sure to check the docs, but in general you'll provide the to, from, subject, and authentication information. Any output within the component will become part of the email message.\nmailparam - Used for additional mail parameters like headers and attachments (although the core mail module can handle attachments as well)\nmailpart - Generally only used to handle sending plain text and HTML emails at once.\n\nFor my testing, I made use of an excellent open source Windows application, smtp4dev, which provides a mail server as well as a Windows client that lets you via the emails that were sent to it. It doesn't actually forward anything along, it just holds on to the email, but it's perfect for testing.\nAt the simplest, you can send an email like so:\n\nA slightly more realistic example may look like so:\n\nIn this script, I've got some data (hard-coded, but you could imagine it coming from a database) that I take and turn into a string that represents order details. After creating the string I can just pass it to the mail component and that's it.\nThe result is pretty much what you expected:\n\n\n\nAs a quick aside, the smpt4dev client displays To twice for some reason. I filed a bug report on it. I'll also note that the email I created is fairly simple and not terribly pretty. That's all me, not BoxLang. You could absolutely create a well designed email.\nSending attachments is also simple. While I mentioned that the mailparam component can do that, you don't need to use it for simpler cases, like the following:\n\nThe mimeAttach argument is all you need to include a file. For multiple attachments though you would want to switch to mailparam.\nAnd how about a mass mail example? You can just... loop:\n\nWhile three emails isn't a lot, you get the idea. Don't forget BoxLang supports parallel looping and the above could execute even quicker with about 30 seconds of modification:\n\nI didn't show this in the video but if you want to support both plain text and HTML via mailparam, here's a simple example:\n\nI'd expect most of your code here would involve creating the mail bodies while the actual action of the mail is a few lines, which is what you want I think! You can find these demo scripts here: https://github.com/ortus-boxlang/bx-demos/tree/master/boxlang_quick_tips/mail\nAlright, if this looks interesting you, definitely give BoxLang a spin. Our installation directions cover all operating systems and you can get up and running quickly. If you've got questions, head over to our forum or join us on Slack\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (3/22/25)",
		"date":"Sat Mar 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1742666400,
		"url":"https://www.raymondcamden.com/2025/03/22/links-for-you-32225",
		"content":"Happy Saturday folks. I just spent an hour or two playing &quot;Avowed&quot; (great game) and eventually need to get off my butt and start some bread I want to bake today, so I thought I'd put it off a few minutes more with a quick blog post. As always, these links are meant to be informative, helpful, and fun, so I hope they brighten your day.\nDate Formatting in JavaScript (Again)\nDate formatting in JavaScript, or, well, any language, feels like a topic that just keeps coming up. When I saw this link I initially didn't bother clicking, assuming it was about the Intl spec. However, it instead talks about toLocaleDateString and toLocaleTimeString, both of which I'm sure I've seen before, but never really looked into. &quot;Human-readable date formatting with vanilla JavaScript&quot; is a great look at these functions. While the end result seems to be practically/functionally the same as Intl, it's worth knowing more about these functions.\nBuilding a Writing Assistant with Python and Ollama\nNext up is a great video that covers multiple interesting topics. First, it talks about Ollama, which is a great way to test generative AI models directly on your own machine, for free. Second, it demonstrates some Python features I had no idea were possible, specifically listening for key events and access the clipboard on your second. Lastly, the topic itself, helping with writing, is one near and dear to my heart.\n\n  \n    Play Video\n  \n\n\n\n\nAnother SSG - Publican\nPublican, definitely not pelican which I wrote more than once, is a new SSG (static site generator) built in Node. SSGs are a favorite topic of mine so I love to see new options in this space. I haven't yet gotten a chance to kick the tires on this one yet, but it's definitely on my TODO list.\nJust For Fun\nI apologize for this in advance, but while driving with my youngest, this song came up after one he had requested I play on Spotify, and, I love it.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Geolocating a Folder of Images with Python",
		"date":"Thu Mar 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1742493600,
		"url":"https://www.raymondcamden.com/2025/03/20/geolocating-a-folder-of-images-with-python",
		"content":"I'm not sure how useful this will be, but as I recently built it in another language (I plan on blogging that soon as well), I thought I'd take a stab at building it in Python. Given a folder of images, can I use Python to grab the Exif information and then using that, figure out where the photos were taken using a reverse geocoding service? Here's what I built.\nFirst - Get the Images\nOk, the first step is simple, just get a list of images from a directory:\n\nWoot! I'm a Python Master!\nGet the Exif info\nFor the next step, I knew I needed to get the Exif info. For that I used the Pillow library, which has a handy getexif() method:\n\nHowever, this doesn't return what I expected:\n\nFrom what I can tell, the getexif method returns only a subset of exif information, not all the possible tags that may exist. That makes some sense as different hardware/services may write ad hoc exif data.\nI did some Googling and this StackOverflow answer provided an interesting hack. This code will search for the GPSInfo tag and return the numeric value:\n\nYou can then combine this to get the right value:\n\nWhew. This returns a dictionary that looks like so:\n\nThis is the direction and GPS info in degrees and minutes returned in a Python dictionary. To convert the values, I did this:\n\nI then needed to convert the longitude and latitude to decimal. For that... I turned to AI. Yep, I cheated. But guess what, it worked? Gemini spit out this function:\n\nAs I had recently did something just like this, I was able to eyeball it and confirm it made sense. I will say the comment is slightly off as the input isn't the full address, but only one portion.\nReverse Geocoding\nNow that I had a location, I needed to reverse geocode, which is basically, &quot;Given a location, what the heck is there?&quot; Mapbox has an excellent free API for this, so I built a quick wrapper:\n\nAnd called it like so:\n\nThis returns a great deal of information in GeoJSON format which I'm familiar with because of my time at HERE. In my mind, I was imagining printing results for public consumption, so while a heck of a lot of data was returned, I simply wanted a formatted address:\n\nYou can check the Mapbox docs if you want to see more of the results, but that worked just fine for me.\nThe Results\nWhen I ran it, I got exactly what I expected:\n20250108_101553.jpg\nBratislava, Bratislava, Slovakia\n--------------------------------------------------------------------------------\nlaf.jpg\nLafayette, Louisiana, United States\n--------------------------------------------------------------------------------\n20250107_160104.jpg\nGyőr, Győr-Moson-Sopron, Hungary\n--------------------------------------------------------------------------------\n20250111_170109.jpg\nGrein, Upper Austria, Austria\n--------------------------------------------------------------------------------\n20250112_154722.jpg\nSalzburg, Salzburg, Austria\n--------------------------------------------------------------------------------\n20250106_073555.jpg\nBudapest, Hungary\n--------------------------------------------------------------------------------\n\nAnd if you're curious, here is each of those images, in the same order as above. (I edited the laf.jpg one to not have my precise address. Sorry. ;) The complete source code for this script will be at the end.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThe Script\n\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating Images with Generative AI via Conversation",
		"date":"Wed Mar 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1742407200,
		"url":"https://www.raymondcamden.com/2025/03/19/creating-images-with-generative-ai-via-conversation",
		"content":"Last week, I blogged about updates to Google's Gemini APIs in regards to image generation. That post detailed how there are now two models for generating images with the experimental Gemini Flash model having a nice free tier. One of the interesting features of the API is the ability to edit existing images, in other words, pass an image to Gemini and via a prompt, have Gemini update it. I thought it would be kind of fun to see if I could build a 'chat' interface for this model, one where you could simply talk to Gemini and have it work on your image along with you.\nNow to be clear, this is no different than what you can do now at the Gemini website, but I figured it would give me yet another chance to play with Flask. Here's what I built.\nImage Chat\nImage Chat is a Flask web application with a simple interface. It begins by asking you what you want to create:\n\n\n\nThis prompt is sent to the Flask app server, passed to code that talks to Gemini, and the response is rendered.\n\n\n\nI can then ask for changes, for example, adding a moon:\n\n\n\nYou get the idea, I can keep asking for tweaks and modifications and see where Gemini takes it. That's the user experience of the application, now for the code.\nThe Front End\nThe front end of the application is a grand total of one HTML file:\n\nYou can see the intro, an empty div where I'll log activity, the form field for user input. As the comment at the bottom says, I loaded the Marked library for processing Markdown, but I didn't end up needing it. I'll explain why I thought I might need it in a minute.\nNow for the JavaScript:\n\nThe code starts off by setting up some DOM pointers and an event listener for my button. When the button is clicked I send the input, and possibly the current image, to the server. I say possibly because on the first use of the application, there isn't an existing image. So yes, I'm basically passing the image back and forth, and that kinda bugs me a little bit. In theory, I could keep the image on the server, but I'd need to handle multiple users, cleanup, and so forth. I know Flask supports sessions so that would be an option, but for this little demo I was fine passing it back and forth. The comment about 'text' response goes back to what I said about the Markdown library.\nAs I mentioned in my last post, Gemini's image generation must be tied to a request for text and images, so your code has to handle both. However, in my testing, I didn't always see text returned. My server-side code is returning any text, but I never saw Gemini actually return any, so for now, the front end doesn't really support it.\nThe Back End\nSo the server is yet another simple Flask server. I'll start with the main application:\n\nBasically two routes, the index that loads the homepage and the endpoint that my JavaScript calls, /chat. I grab the values out of the JSON body sent and pass it off to a Gemini class:\n\nFor the most part, this is just what I had in the last post, outside of the fact that I changed the file upload code to support base64 strings versus files on the local filesystem. That was new to me. For the response, I simply gather up what Gemini gave me, which will also be a base64 image, and return it.\nWrap Up\nThat's it. I still don't have a good place to host my Python apps, but if you want the complete source code, you can find it here: https://github.com/cfjedimaster/ai-testingzone/tree/main/image_chat\n",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - PDF Generation",
		"date":"Tue Mar 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1742320800,
		"url":"https://www.raymondcamden.com/2025/03/18/boxlang-quick-tips-pdf-generation",
		"content":"Today's BoxLang quick tip is one near and dear to my heart, generating PDFs. Creating dynamic, expressive PDFs is fairly easy. Let me show you how. As before, I've got a video version as well so you would rather watch that, just skip to the end.\nStep One - The Module\nBy default, BoxLang doesn't ship with PDF capabilities built-in, you need to add it via the PDF Module. This can be done quickly via the CLI:\ninstall-bx-module bx-pdf\n\nInstalling the module adds three new tags to your BoxLang runtime:\n\nbx:document - This is the core tag for PDF generation. Everything inside it will either be content or directives (see the items below) to control what's produced.\nbx:documentitem - This is used to specify page breaks or header and footer content.\nbx:documetnsection - This lets you create logical sections to a larger PDF document, allowing for section specific page numbers, headers and footers, and so forth.\n\nCheck the docs for full syntax information.\nStep Two - Make a PDF\nHow about the simplest demo possible?\n\nIn this example, the template will use the content within, static HTML, and save it to test1.pdf. Note the use of overwrite=true. Without it, you would get an error if you run the code again. You can actually skip saving the data to the filesystem and instead store the binary data by using the variable attribute instead.\nAnd the result:\n\nWe can make it a bit more interesting by adding some media. Consider this:\n\nNote that I'm using a local image, cat.jpg, and in order for the BoxLang PDF tool to find it, I added localUrl to the tag. This generates pretty much what you would expect - a PDF with a cat picture. (Which improves every PDF I've found.)\n\nOk, so far, these have been static, simple PDFs. How about a more complex example? Consider this script:\n\nI begin with a bit of data, hard-coded, but obviously could have come from a database or API call. It's got an array of people including names, titles, salaries, and locations. For each of the people, I want to create a unique, dynamic PDF. To do so, I use the built-in slugify function on the name.\nInside the PDF, you can see the use of pound-wrapped variables for my dynamic content. I then use documentitem to create a page break for the second bit of content, legal information no one will read.\nFinally, I use documentitem again to create a dynamic timestamp value in the footer. And here's one of the results:\n\nYou can find these demos, and the PDF results, in the BoxLang demos repo here: https://github.com/ortus-boxlang/bx-demos/tree/master/boxlang_quick_tips/pdf\nEnjoy the video version below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Watch me suffer with React more in tomorrow's Code Break!",
		"date":"Mon Mar 17 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1742234400,
		"url":"https://www.raymondcamden.com/2025/03/17/watch-me-suffer-with-react-more-in-tomorrows-code-break",
		"content":"So my last Code Break was... painful. My experience trying to learn React did not go well. Because of that I thought strongly about giving up, but what fun would that be? Join me tomorrow (March 18th) at 12PM CST where I'll, once again, try to learn some basic React features and get a trivial web site built. Will I make it? Probably not! But you can come join me and cheer/heckle me on! I hope to see you there:\nhttps://cfe.dev/talkshows/codebreak-03182025/\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Generative Images with Gemini (New Updates)",
		"date":"Fri Mar 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741975200,
		"url":"https://www.raymondcamden.com/2025/03/14/generative-images-with-gemini-new-updates",
		"content":"Back in January of this year, I wrote up my experience testing out Google's Imagen 3 APIs to generate dynamic images. A few days ago, Google updated their support with new experimental support in Flash. I've been playing with this the last few days and have some code and samples to share with you, but before that, what exactly changed?\nGemini and Imagen 3\nThere are now two different models, and different APIs, to generate images with Google's AI platform. The new one is Gemini 2.0 Flash Experimental and the previous one (the one covered in my blog post) is Imagen 3.\nOf course the next question is, why two, and what do you pick? The docs do a great job of explaining the differences, and I'll share that here:\n\nIf context is important, than Gemini 2.0 is the right choice. Gemini 2.0 is best for producing contextually relevant images, blending multimodal outputs (text + images), incorporating world knowledge, and reasoning about images. You can use it to create accurate, contextually relevant visuals embedded in long text sequences. You can also edit images conversationally, using natural language, while maintaining context throughout the conversation.\n\nIf image quality is your top priority, then Imagen 3 is a better choice. Imagen 3 excels at photorealism, artistic detail, and specific artistic styles like impressionism or anime. Imagen 3 is also a good choice for specialized image editing tasks like updating product backgrounds, upscaling images, and infusing branding and style into visuals. You can use Imagen 3 to create logos or other branded product designs.\n\nI'll also add that the Gemini modal has a free tier and Imagen does not. The price tag though is fairly small, 3 cents a pop. Not in this post, but next week I may follow up with a comparison and actually shell out a few cents to do so. (I kinda feel like I should get some credit though for GCP as I'm basically advocating for the API for free. ;)\nAnother important thing is that the Gemini API will always output text and images. Now, you can just ignore the text. You'll see me do that in demos and you'll want to keep that in mind when building your own examples. Speaking of...\nText to Image\nThe simplest demo is just taking a text prompt and outputting an image. With a hard coded prompt, it's as simple as:\n\nAnd here's the output. Note that for most of the results I'll show in this post, I've changed the size post-production. Gemini's image API model does not let you specify a size or aspect ratio. You can ask it to, for example, &quot;generate a landscape photo of...&quot; and it generally respects that, but keep in mind you may need to add it to your prompt yourself.\n\n\n\nI'd call that pretty decent. And as always, I kinda skimped on the prompt there. The more detail you provide, the better results you'll get. Changing the prompt to:\nA black long haired cat wearing a grey fedora. She is looking towards the camera.\n\nGives:\n\n\n\nYou can make this generic by just checking for the prompt from the CLI:\n\nYou can find this source here: https://github.com/cfjedimaster/ai-testingzone/blob/main/imagen/gemini_text_to_image.py\nText to Image and Text\nAs described above, the Gemini model is going to include text along with image results, and that can be pretty powerful. One example prompt is recipe based, &quot;Generate an illustrated recipe for a paella.&quot; I decided to build a demo around this by letting you pass the type of recipe via the command line and then generating a Markdown file that included the recipe and images. Here's that demo:\n\nI didn't put a lot of effort into the generated Markdown. I could have even converted it to HTML at the end. But it basically takes the output and appends text as is, and images as an image with Markdown code.\nThe results from this was... hit or miss. The Google example of paella seemed to work well. But I tried multiple different cookie recipes and it failed really badly. I'm not sure why. In my most recent test it was able to generate ingredients, but disregarded the request for actual cooking instructions. I then tried chicken and sausage gumbo, and it was... ok. You can see it below (and here's a direct link).\n\nSome of the formatting appears a bit off, and the roux doesn't look quite thick enough, but I'd probably still eat it.\nAs a followup (next week though) I'm going to take another stab at this using a JSON schema to see if I can better shape the results.\nYou can find this demo here: https://github.com/cfjedimaster/ai-testingzone/blob/main/imagen/recipe_tester.py\nEditing Images\nAnother interesting aspect of the API is the ability to give it a source image and ask for an edit. As an example, I gave it this source:\n\n\n\nAnd asked Gemini to replace the glasses with something more serious.\n\n\n\nThe glasses are slightly big for me, but honestly I think it did a great job. The code for this simply involves adding a file upload and passing it to the model:\n\nStylish Images\nOne of the features of Adobe Firefly (and, helpful reminde",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Jira Search Tool in BoxLang",
		"date":"Thu Mar 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741888800,
		"url":"https://www.raymondcamden.com/2025/03/13/building-a-jira-search-tool-in-boxlang",
		"content":"Developers seem to have a love/hate (or perhaps hate/despise) relationship with Jira. I've never minded it, but the biggest issue for me is that if I haven't used it in a while, it can be overwhelming. Yesterday I was thinking about this and wondering if perhaps I could build my own tooling to interact with Jira via an API, if it even had one. Turns out, of course they have an API and it's not terribly difficult to use. With that in mind, I whipped up a quick tool to search Jira via the command line with BoxLang.\nJira API Basics\nThe docs for Jira's API are pretty good and cover the huge set of operations you can perform with it.\nYour root API url will be based on your Jira installation. I wanted to search against the BoxLang Jira instance so that was https://ortussolutions.atlassian.net/. After this you append rest/api/VERSION/OPERATION where VERSION can be set to a specific version or just latest.\nThere's a few ways to do authentication, including oAuth, but basic username/password validation works as well.\nThe Search API\nFor my little script, I wanted a quick way to see open or being worked on issues that matched a term. For this I used JQL, which is the Jira Query Language, along with a bunch of Googling to figure out the right syntax. Given a search term of, let's say cats, a JQL statement may look like this:\nproject=BL and status in (&quot;In Progress&quot;, &quot;Open&quot;, &quot;To do&quot;) and summary ~ &quot;cat&quot;\n\nThe status set of values there was something I found online and could possibly be improved, but it seemed to work well in my testing. Also, I suppose I could search against the body of the issue as well, but I thought a summary match (which is the title kinda) made the most sense.\nGiven this JQL, you could do a quick call like so:\n\nThis is making use of the &quot;Search for issues using JQL enhanced search (GET)&quot; endpoint. Previously I had used this one and I realized today it was deprecated. The API takes a variety of arguments, but the only real important one here is fields. By default the search will only return IDs, so if you want more, you have to ask for it. In my case, I figure the summary and status were enough.\nHere's a sample result set:\n{\n  issues : [\n      {\n      expand : &quot;renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations&quot;,\n      id : &quot;138098&quot;,\n      self : &quot;https://ortussolutions.atlassian.net/rest/api/latest/issue/138098&quot;,\n      key : &quot;BL-1197&quot;,\n      fields : {\n        summary : &quot;Threading Improvements: isThreadAlive(), isThreadInterrupted(), threadInterrupt() bifs&quot;,\n        status : {\n          self : &quot;https://ortussolutions.atlassian.net/rest/api/2/status/1&quot;,\n          description : &quot;The issue is open and ready for the assignee to start work on it.&quot;,\n          iconUrl : &quot;https://ortussolutions.atlassian.net/images/icons/statuses/open.png&quot;,\n          name : &quot;Open&quot;,\n          id : &quot;1&quot;,\n          statusCategory : {\n            self : &quot;https://ortussolutions.atlassian.net/rest/api/2/statuscategory/2&quot;,\n            id : 2,\n            key : &quot;new&quot;,\n            colorName : &quot;blue-gray&quot;,\n            name : &quot;To Do&quot;\n          }\n        }\n      }\n    },\n    {\n      expand : &quot;renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations&quot;,\n      id : &quot;138097&quot;,\n      self : &quot;https://ortussolutions.atlassian.net/rest/api/latest/issue/138097&quot;,\n      key : &quot;BL-1196&quot;,\n      fields : {\n        summary : &quot;Added isVirtual, isDaemon, threadGroup, id to the thread metadata&quot;,\n        status : {\n          self : &quot;https://ortussolutions.atlassian.net/rest/api/2/status/1&quot;,\n          description : &quot;The issue is open and ready for the assignee to start work on it.&quot;,\n          iconUrl : &quot;https://ortussolutions.atlassian.net/images/icons/statuses/open.png&quot;,\n          name : &quot;Open&quot;,\n          id : &quot;1&quot;,\n          statusCategory : {\n            self : &quot;https://ortussolutions.atlassian.net/rest/api/2/statuscategory/2&quot;,\n            id : 2,\n            key : &quot;new&quot;,\n            colorName : &quot;blue-gray&quot;,\n            name : &quot;To Do&quot;\n          }\n        }\n      }\n    },\n    {\n      expand : &quot;renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations&quot;,\n      id : &quot;132011&quot;,\n      self : &quot;https://ortussolutions.atlassian.net/rest/api/latest/issue/132011&quot;,\n      key : &quot;BL-267&quot;,\n      fields : {\n        summary : &quot;New VirtualThread component as a subclass of Thread in BoxLang&quot;,\n        status : {\n          self : &quot;https://ortussolutions.atlassian.net/rest/api/2/status/1&quot;,\n          description : &quot;The issue is open and ready for the assignee to start work on it.&quot;,\n          iconUrl : &quot",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "BoxLang Quick Tips - Database Access",
		"date":"Tue Mar 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741716000,
		"url":"https://www.raymondcamden.com/2025/03/11/boxlang-quick-tips-database-access",
		"content":"Today I'm kicking off a new blog/video series of quick tips for people interested in BoxLang. These 'quick tips' are just that, a look at how BoxLang can simplify working with the JVM and building CLI scripts, web apps, and serverless applications. Each of these posts will include a video along with sample code and help highlight some of the ways BoxLang can be powerful in just a few lines of code.\nFor my first quick tip, let's talk database access, which by the way was one of the reasons I got into ColdFusion nearly thirty years ago (I didn't want to figure out how to do it in Perl!). Working with databases in BoxLang can be done in a few steps.\nStep One - The Driver\nTo work with a database, you need a driver, and that can be done via BoxLang modules, which are easily installed at the command line. There's a lot of modules, but for MySQL, you can simply do:\n\nThis is a one time operation and takes a few seconds.\nStep Two - Define Your Connection\nIn order to work with a database, your code needs to know how to connect to a database. That includes things like a server URL, username, and password, and sometimes additional configuration information. Depending on what your doing with BoxLang, you've got a few options on how to do that. For web apps, you can define 'datasources' at the application level so all your scripts and services can execute queries. For a command line script, or a quick ad hoc connection, you can also define the connection inline. Let's consider that. First, I'll define my connection:\n\nThis should all be self-explanatory, but I'll note I'm grabbing my credentials from the environment.\nNext, I'll do a simple query and pass this along inline. Again, in a web app, this could be done at a higher level and not needed again.\n\nIn the code above, queryExecute runs SQL and uses the datasource information for it's connection. The second empty argument there is used for bound params which are simple SQL doesn't need.\nStep Three - Outputting the Data\nThe last step is how to output the information, and obviously that depends on your particular needs, but in the theme of keeping it simple, you can treat the result as an iterable and simply loop over it:\n\nThere are multiple other ways to output the data as well, for example, as JSON when called as an API.\nThat's it! Database access and output in your code in minutes. Enjoy the video version below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automating and Responding to Sentiment Analysis with Diffbot's Knowledge Graph",
		"date":"Mon Mar 10 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741629600,
		"url":"https://www.raymondcamden.com/2025/03/10/automating-and-responding-to-sentiment-analysis-with-diffbots-knowledge-graph",
		"content":"Diffbot's Knowledge Graph has a simple purpose - bring the sum total of all knowledge to your fingertips via a search that emphasis data and relations over a simple text based search engine experience. Sourced by the entire web, Knowledge Graph lets you perform complex queries against billions of data points instantly via a simple API. I decided to take a spin with their API and build a &quot;relatively&quot; simple tool - news analysis for a product run in on automated platform. Should be easy, right? Let's get to it. Note that the examples in this blog post assume you've gotten a free key from Diffbot. Be sure to do that before trying the samples.\nDesigning the Query\nBefore writing a line of code, I signed into Diffbot and opened up their visual search tool for Knowledge Graph. The tool lets you build queries visually or by hand. Queries are known as 'DQL' statements in Diffbot and are pretty simple to read even if you've never seen the syntax before.\n\n\n\nFrom this tool, I started off by selecting an entity type. This is the high level type of data I want to search and can be one of many numerous options, from people to events to movies and investments. I selected &quot;Article&quot; as my intent is to find news that's speaking ill of my wonderful product. I then selected a &quot;Filter By&quot; option. While you can filter by any property in the entity type, I used tags.label as it's a more precise match than a simple text search. While a text filter does work, using tags.label gives a much better result by ensuring that the results are focused on my search, not just casually mentioning it. For my demo, I'll be looking for articles about &quot;XBox&quot;.\nI also used the &quot;Sort by&quot; value to show newest first and this hit search to see if my results made sense.\n\n\n\nWhile my initial results didn't include any foreign language results, I knew I'd want to filter to results in English, so I next added a filter for language. Hitting the + sign by the current filter, I was then able to add language and en for English. Once again, I hit search:\n\n\n\nAlright, so next, I want to filter to just negative results. Knowledge Graph Article entities have a sentiment score (you can see them in the search results) that go from -1 to most negative to 1 to most positive. Initially, I simply selected items with a sentiment less than or equal to 0.\n\n\n\nWoot, getting there. As a final step, I knew this was going to be automated and filtered to 'recent' items, so I added one more filter, this time on date, selected after, and picked a date from a week ago.\n\n\n\nAt this point, the query looks good, so let's copy out the query value provided by the tool:\ntype:Article tags.label:&quot;Xbox&quot; language:&quot;en&quot; sentiment&lt;=0 date&gt;&quot;2025-03-03&quot; sortBy:date\n\nWrite the Code\nDesigning the query was really the hard part. For the code, I went to the Search docs. The examples are curl/HTTP based but quite easy to port to Python or any other language. Consider this sample:\n\nBreaking this down - I began with my query from the visual tool. This then gets url encoded and passed to the API for Knowledge Graph. The only real new item there is the addition of size=25 to keep the result set to a sensible limit.\nI call the API, print the total results found (from the hits result) and then iterate over each showing various bit of info from the result. Here's a few of the results:\nTotal results, 68\nXbox will release its first handheld gaming console this year, report claims\nd2025-03-10T19:37\nWindows Central expects the console to take advantage of the widgets on the Xbox Game Bar to let use...\nJacob Siegal\nBGR\nhttps://bgr.com/entertainment/xbox-will-release-its-first-handheld-gaming-console-this-year-report-claims/\n0\n------------------------------------\nRumour: Next-Gen Xbox a 'PC in Essence' - What Would That Mean for PlayStation?\nd2025-03-10T19:00\nRecent comments from Windows Central's executive editor Jez Corden have sparked discussion about whe...\nStephen Tailby\nPush Square\nhttps://www.pushsquare.com/news/2025/03/rumour-next-gen-xbox-a-pc-in-essence-what-would-that-mean-for-playstation\n0\n------------------------------------\nXbox handheld out this year and will go up against Nintendo Switch 2 says source\nd2025-03-10T18:50\nNew rumours about Microsoft’s next gen plans suggests that there will be two Xbox handheld consoles ...\nGameCentral\nMetro\nhttp://metro.co.uk/2025/03/10/xbox-handheld-this-year-will-go-nintendo-switch-2-says-source-22703266/\n0\n\nThis works, but now let's make the date dynamic. I began importing from datetime:\n\nI then generated a formatted date for last week:\n\nAnd the last bit was to just include that date in my query:\n\nYou can see the complete source code for the initial version here and the final version here.\nBuilding the Automation\nAlright, time to automate this. For my automation, I'll be making use of Pipedream, an incredibly flexible workflow system I've used many times in the past. Here's the entire",
		"tags":[
	        
            "python",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (3/8/25)",
		"date":"Sat Mar 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741456800,
		"url":"https://www.raymondcamden.com/2025/03/08/links-for-you",
		"content":"Happy afternoon, programs. I just got back one of my kid's soccer games (unlike last season, the weather is pleasant and not scorching hot) and I've got a Saturday now that is 100% open! Which means I'll get a lot done! (Or, more likely, play video games.) So that I can more quickly get to all the important chores and cleaning I'm not going to do, let's get to the links.\nCode Listings via API\nFirst up is a two-fer kinda. Showcode is an excellent web app to create screenshots from code. It supports numerous languages, numerous display options, and so forth, and creates really good output. As an example:\n\n\n\nWhile it's a great webapp, they also have a cool API that lets you automate the process. Here's an example taken from their docs:\n\nThis produces:\n\n\n\nThanks go to my buddy Todd Sharp for finding and sharing this with me.\nVoice Coding\nNext up is a post by one of the coolest folks in tech now, Salma Alam-Naylor, discussing how she's learning to code with her voice. After developing pain in her hands, she realized she'd need to adjust her work style in order to keep developing. Her post goes into detail about how she's addressing that and the tools she's trying. She's also got a YouTube video on the topic:\n\n  \n    Play Video\n  \n\n\n\n\nGemini as a Code Assistant\nNext isn't really an article per se, more a product announcement. As my readers know, my primary GenAI focus has been with Google Gemini. Recently, they created multiple new tools to help with code writing and review. That includes a GitHub action, Firebase support, and most importantly, a Visual Studio Code extension. You can read about all three or just right to the docs for the VSC extension. There is a free tier and I've been using it for over a week now. I'm not quite sure yet if it's better than the free GitHub version from Microsoft. Since switching, it feels like this one hasn't been quite as helpful, or in my face, as the Microsoft one, but it's also possible I just have noticed as much. Mostly, I think I need to kick the tires a bit more and make myself pay attention when it offers help. That being said, it's free and no risk, so it's worth a shot. Over the past few months I've absolutely changed my mind about the usefulness of these types of tools and I expect to keep using them until, well heck, until I retire probably.\nJust For Fun\nAnd lastly, I recently discovered Youtuber matt one who has done some incredible mashups. Here's my current favorite mashing up two of my favorite bands/songs.\n\n  \n    Play Video\n  \n\n\n\n\nI will say he uses some... questionable AI video at times but the music is spot on.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Parsing Uploaded Resumes into Form Fields with Google Gemini",
		"date":"Tue Mar 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741111200,
		"url":"https://www.raymondcamden.com/2025/03/04/parsing-uploaded-resumes-into-form-fields-with-google-gemini",
		"content":"As I've recently become somewhat familiar with job application sites (sigh, thanks Adobe), I've noticed an interesting feature some sites use. After selecting your resume to upload, they will parse the resume and either offer to, or automatically, fill in some of the form fields of the application for you. I thought it would be interesting to try this myself making use of Google's Gemini APIs. Here's what I discovered.\nThe Test Script\nAs always, I began with a script that would take a hard-coded resume and attempt to parse it. For the most part, this is basic &quot;upload a file and ask the AI to talk about&quot;, but in my case, I wanted a very particular set of data back, specifically a JSON object that would attempt to match the following:\n\nThe first and last name from the resume.\nThe email address, phone number, and website from the resume.\nTheir location, but nothing fancy like city and state, just 'location'.\nThe 'introduction' to the resume, or top level paragraph.\nA list of skills\nAnd finally, a list of previous jobs including the company name, time worked, and a string encompassing a description and list of responsibilities\n\nGoogle Gemini supports JSON schemas to let you precisely document how you want your resume. In my previous experiments, I've used the 'formal' JSON Schema support via the Node SDK, but for Python, their docs made use of pydantic and classes, and while I've never used this feature of Python before, it wasn't difficult at all. Here's what I came up with to encompass what I wanted parsed out of the resume:\n\nI liked being able to use JSON Schema in previous demos, but this is way easier to read in my opinion and I think I'll use it whenever I'm doing anything like this in Python. Given that schema, here's how I use it with a basic file upload and prompt:\n\nRunning this on my resume I get:\n\nFor the most part, that's near perfect. The skills area has a few misfires... I mean, I guess 'editing' is a skill, I know I value my editors, and I probably called it out so I guess that's not a misfire, but usage certainly isn't valid. Also, 'generative Al' should be 'generative AI', but again, in the context of prefilling a form for you, there wouldn't be much at all to correct manually.\nYou can find the complete script here: https://github.com/cfjedimaster/ai-testingzone/blob/main/resume_to_data/test1.py\nThe Web App\nFor my web application, I borrowed heavily on the resume review app I built early in February. For review, this was a Python Flask web app that let you upload a resume. It then called a server-side Python script to ask Gemini for a review and returned a new version of the resume.\nFor this application, I had a simple web page prompting for the resume (as a PDF), and an empty form beneath it waiting to be filled:\n\n\n\nI'll link to the entire code base as the end, but basically, the application takes the PDF you select and does a POST with the data to the server side code. For example, in Flask, here's how I define that route:\n\nNote that like my last demo, this uses a static filename for the upload when it should either pass the binary data directly to my class, or use a UUID provided name instead, and of course, clean up the file when done.\nThe real work is done in a Gemini class I built that basically takes my test script and, well, turns it into a re-usable class:\n\nI love how simple that is, but of course, I skimped on error checking (no error checking still counts as skimping) which helped keep down the lines of code.\nOn the front end, my code basically sends the PDF, takes the response, and fills out the form with a bit of manipulation on the more complex fields. Here's the relevant part of that code:\n\nAnd here's how it looked using my resume:\n\n\n\nI then tried a resume from Scott McAllister which parsed as such:\n\n\n\nYou can find the complete source for this demo here: https://github.com/cfjedimaster/ai-testingzone/tree/main/resume_to_data\nAll in all, I'm pretty impressed by the results. I've got an idea for a good followup I'll hopefully get to this week.\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Code Break this Thursday - Ray Finally Learns React",
		"date":"Mon Mar 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1741024800,
		"url":"https://www.raymondcamden.com/2025/03/03/codebreak-this-thursday-ray-finally-learns-react",
		"content":"Ok, so if you attended my last Code Break session you know I was hinting that I was really excited for my next one. For years now I've wanted to give React a fair shake and actually try to build something with it. Finally I'm making time to do so. This Thursday at 12PM CST, my next session will be:\nI Don't like React. Let's Learn React!\nTo be clear, it isn't that I dislike React, it's just that every time I've looked at the code, it just didn't gel with me. I know it's incredibly popular, but I always felt like if I needed to build a proper &quot;web app&quot;, I'd just use Vue (despite my feelings about Vue 3, feel free to ask me in the session). That being said, I'm really excited about this session. I'm going to go through the docs and attempt to build one or two apps (probably over a session or two) and really see what it feels like to work in the platform.\nI hope to see you there! Also, one of the things I tried to do this year was get more consistent with my scheduling. For some dumb reason, I scheduled one for this Tuesday which is Mardi Gras, and while we will probably get rained out, there's a chance I could be downtown with the kids catching beads and watching marching bands, so hopefully the weather holds up.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using AI in the Browser for Typo Rewriting",
		"date":"Thu Feb 27 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1740679200,
		"url":"https://www.raymondcamden.com/2025/02/27/using-ai-in-the-browser-for-typo-rewriting",
		"content":"\nUpdate on April 3, 2025: As expected, Google has continued to evolve and update these APIs, for the better of course, but that means the code I write about here is 100% broken. That's life, eh? I'm not going to edit the text here as if I tried to keep every post up to date it would be a never-ending job. That being said, I plan on blogging on these APIs more and I corrected the CodePen shown at the end with a new version that works correctly. You can see it here: https://codepen.io/cfjedimaster/pen/ZYEwoJx\n\nLast week I gave a presentation on Chrome's new built-in AI support (I'll link the video at the end) and it's gotten me inspired to consider new and different ways these APIs can be used to enhance the user experience. These APIs still aren't quite ready for production use, and it's absolutely possible we may never see these in Safari or Firefox, but the possibility of using them to enhance an application where available is exciting. For today, I want to share an interesting use case that occurred to me a few weeks ago.\nOne of the APIs being built is a translation API (along with a language detection) API as well. In general the idea here is to go from one language to another. But what if you don't want to translate, but rather, &quot;repair&quot;, some input text.\nI've got a best friend who I won't name (although his name rhymes with Mott Moze) and he tends to typo quite a bit. In fact, sometimes myself and another friend will tease him about it, especially when he typos so bad we can't even figure out what he meant. That then brings up the question - how well could these generative AI models help with fixing input that's got typos and returning something more sensible. I did some playing and found some interesting results.\nAttempt One - The Prompt API\nIn my opinion, the Chrome team is really pushing specific GenAI APIs over more generic uses. That's why the docs focus on those specific APIs - translation, language detection, and summarization for example. There is a &quot;general purpose&quot; prompt API but honestly, I imagine that will be used a lot less than the more specific ones. That being said, I thought I'd use that for my first stab at this problem.\nFor my demo, I whipped up a quick Alpine.js form that lets you enter your typo-ridden text and then hit a button to try to fix it. These complete source code will be available below so I'll focus on the GenAI aspects, not the Alpine.js DOM stuff.\nI begin with a bit of code just to see if the API is available:\n\nThis code only supports the API being 100% ready and will not support the use case of, &quot;I support AI, but I need a few minutes to download the model.&quot; To be clear, Chrome's APIs support that and you should do that, but I wanted to keep things simple for now.\nNow comes the important part, creating a session for my application with a system instruction detailing what I want it to do:\n\nActually using this is almost anticlimatic in terms of how simple it is, given your input, you use the prompt method to get the result:\n\nIn case you don't recognize it, marked is a JavaScript Markdown library and as most results include Markdown, I use this to convert it to HTML. I don't think it actually does much here, but I had it from a previous demo and kept it.\nSo, the demo will be right below, but as this feature is still in early testing and not available without flipping some flags, let me share some before and after examples:\nInput: &quot;My name is Scott Stroz. I work asd a developer advocat for Oracle. My focis is on MySQL and the Giants who are the bestfootball team in the NLF&gt;&quot;\nOutput: &quot;My name is Scott Stroz, and I work as a developer advocate for Oracle. My focus is on MySQL and the Giants who are the best NFL football team.&quot;\nThat's damn good to be honest, ignoring the obvious typo of &quot;Saints&quot; where &quot;Giants&quot; was used. I would argue though that by changing the period to a comma, it's doing a bit more than typo correction.  Here's another example:\nInput: &quot;This is Raymonid Camden. I work in Louisian as a developer evangelist and advocoa fovused on generative AI, APIs, and listening to Deepche Mode.&quot;\nOutput: &quot;This is Raymond Camden. I work in Louisiana as a developer evangelist and advocate focused on generative AI, APIs, and listening to Deepche Mode.&quot;\nAs you can see, it failed to fix Depeche Mode, but it did clean it up quite well. You can see the full code, and live demo below:\n\n  See the Pen \n  Chrome AI - Fix Typos V1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAttempt Two - The Rewriter API\nFor my next attempt, I made use of the Rewriter API. This isn't called out in the navigation on the Chrome AI docs, but does live under the Summarization API &quot;family&quot; I suppose. You can find it documented under the GitHub Explainer.\nThe API for the Rewriter API is a bit different than the others, and I imagine it may change soon. For example, to check if it's available, you do",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Java Libraries in BoxLang",
		"date":"Wed Feb 26 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1740592800,
		"url":"https://www.raymondcamden.com/2025/02/26/using-java-libraries-in-boxlang",
		"content":"One of the aspects that makes BoxLang compelling is that it runs on top of the Java Virtual Machine which means you get access to any Java library out there. This is something ColdFusion has as well and in the past, I've integrated Java libraries into my web apps to make use of open source from the Java community. Best of all, you don't really need any Java knowledge to do this. Typically libraries will provide good docs and and examples and the mental model of translating a Java example to BoxLang's language is fairly simple. A few days ago, I blogged an example of parallel processing in BoxLang and in one of the samples, I did a tiny bit of RSS feed processing. I mentioned at the time that I wasn't doing &quot;real&quot; RSS parsing, just quickly grabbing XML items from the feed. I decided to see if I could find a Java library for &quot;real&quot; parsing and see how difficult it would be to use in BoxLang. Here's what I found.\nThe Java Library\nI googled for Java RSS library and came across rssreader. This library can parse a URL or a file (but oddly not a string for some reason). Unlike other RSS parsing libraries I've seen in the past, it has built in support for parsing multiple feeds at once, and 'mixing them' together in the result, which is dang nice.\nThe only issue I ran into here was that the repo didn't have a pre-built jar. I cloned the repo, ran a gradle build, and discovered something had gone haywire locally in terms of my gradle support. That's all just Unbuntu/WSL issues I'm sure won't bite me again and if you want more details, ask me in the comments. While this step took me maybe an hour or so because of that, I expect usually folks will have this done in a minute or two.\nUsing Jars in BoxLang\nThe BoxLang docs have a whole section on Java interop and part of it covers how to load custom Jar files. The first option is to drop it into BoxLang's lib path. The second option would be to specify jars or folders of jars in an Application.bx file. For a web application, this is absolutely the path I'd use. (Technically this is usable in CLI scripts as well, but I didn't want to go that route.) The last option was to use the createObject function and specify the path itself.\nCode-wise, this is one of two ways to get access to a Java library from a jar. The other, the new java syntax, is one I'd rather use as it &quot;feels&quot; more proper, but it doesn't support passing the path to the jar.\nSo given all that, and that I've got my jar built, I could instantiate the library like so:\n\nAs a simple example, this parses my own RSS feed and gets the items.\n\nThe result of the read operation is a Java Stream so the toList() method essentially just collects all the results into an array. Now for the next fun part.\nYou can easily loop over each item returned, and items have methods to get things like the title, link, and content, but these returned Java Optional. I don't really know Java and haven't kept up to date on all the recent changes, but Optionals are a feature that make it easier to deal with values that may not exist. I'll be honest... I'm not entirely sold on the concept, but it's definitely a core feature of Java, and BoxLang actually extends it with their Attempts feature. It's a bit different, but once I used it a few times it wasn't necessarily difficult. I just had to a) recognize that the library was making use of it and then ensure my code used it as well.\nThis means you can't just do, title = item.getTitle() for example, but this instead:\n\nAgain... this isn't bad, and I get some of the reasoning behind the idea, but it's definitely a bit different. That being said, here's a complete script to print out each items title, link, and date:\n\nPutting it Together\nOk, so how about I take my little demo from earlier this week - parsing N RSS feeds - and make use of the library to get 'real' results. I began with a function that takes an array of URLS:\n\nI do a bit of normalization in that I create a simpler structure, result, with just the bits I care about, and then feed them in with values from the RSS item. I also create a proper date/time object, and finally, since I'm working with N different RSS items, I need a way to 'associate' one item with it's parent. (I've got a small concern about this I'll share in my p.s. at the bottom.) I get this from the top level channel object in each item. Oddly, this doesn't use Observables. Also, this means a bit of repetition in my data, but as my result is one array with items from different feeds mixed together, this handles letting me display the blog where the item came from.\nHere's the entire logic to make use of this new utility:\n\nThis returns a large amount of data in an incredibly quick amount of time. I don't know if the library is running each call in threads, I assume it is, but the performance is great and the data result much better than my initial simple demo.\nAll in all, fairly simple. Biggest issue I ran into was compiling that darn jar, but",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "Using Parallel Looping in BoxLang",
		"date":"Mon Feb 24 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1740420000,
		"url":"https://www.raymondcamden.com/2025/02/24/using-parallel-looping-in-boxlang",
		"content":"Last week I wrote about converting a Python file search script to BoxLang. In that post (and the original Python version) I mentioned how the utility wasn't terribly efficient as it needed to recreate an index every time it ran. Despite this, the performance was pretty good, taking about two seconds or so to generate the file index from near seven thousand Markdown files. Right after I shared that post, Luis Majano shared an interesting performance tweak I had missed.\nIn BoxLang (and to be fair, this is a feature both Lucee and ColdFusion have as well), when you loop over arrays, structures, and queries, you can enable parallel execution by simply adding an additional argument to the loop. You also have control over the number of threads used as well.\nLet's consider a simple very unrealistic example of this:\n\nI create an array of six strings and then loop over each element. For each, I print an indicator to the string so I know something's actually happening, and then I pause execution with the sleep command for 10 seconds. I'm using getTickCount() before and after for basic timing tests. As you can imagine, this takes 60 seconds. (Slightly above of course.)\nEnabling parallel execution is as easy as this:\n\nLike, literally, that's it. Now, to be clear, this is absolutely not something you can use in all situations. Heck, maybe not even half the time. It depends. But let's consider my search script. This is the block that iterated over the files:\n\nAnd here's the modified version:\n\nI'm able to use this feature because it doesn't matter what order items are added to my index. In my testing, it was taking around 3.5 seconds to make the index. As I said then, that seemed fair. With this change, it now takes less than a second, about 750-850ms on average.\nHow about another example? Imagine I've got a set of URLs, RSS feeds, that I want to parse and aggregate? I built a simple RSS parser that does, well nothing. A real RSS parser would do it's best to standardize the results to make it easier to work with entries. My code just handles RSS vs Atom and doesn't get any fancier than that:\n\nHere's my first take at creating the aggregation:\n\nThese 11 feeds comprise over 600 entries as some of the RSS feeds don't return the latest content, but instead all of the content. On average, this execution took between 4 to 7 seconds. Now, the parallel version:\n\nNote that this works because order isn't important - I'm just sticking results in the structure keyed by URL. The timing on this version is between .7 and 1.7 seconds.\nYou can read more about this in the docs (that link is specifically for arrays, but as I said, this feature can be used in structures and queries) as well as read more about threading in BoxLang specifically. I put the initial demo and HTTP demo up in the bx-demos GitHub repo here: https://github.com/ortus-boxlang/bx-demos/tree/master/syntax-samples\n",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (2/22/25)",
		"date":"Sat Feb 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1740247200,
		"url":"https://www.raymondcamden.com/2025/02/22/links-for-you-22225",
		"content":"Happy Link Day... oh wait, that isn't a thing? Well, let's pretend it is. Those of us in Louisiana are in Mardi Gras season with the holiday itself coming up on March 4th. If the weather holds out, we'll be outside catching beads and listening to marching bands. It can be a lot of fun if it doesn't get rained out. It's also a great chance to eat some really bad (for you but yummy) food. I'll share a pic or two on the next edition of this post. Ok, on with the links!\nIntroducing AX (Agent Experience)\nMathias Biilmann (CEO at Netlify, they host this blog), wrote up an introduction to the idea of AX, or &quot;agent experience&quot;. His post describes AX as the &quot;expeirence&quot; AI autonomous agents have with our sites and products. It's an interesting piece and be sure to read his follow up post as well.\nOn Being Laid Off\nAh, here's something I'm pretty familiar with (groan). Mert Bulan writes up his experience of a lay off and if you've never had the, err, &quot;pleasure&quot; of experiencing this, it's a great look into what it's like. This 'current' lay off for me was my third over thirty years in the business and it never gets any easier.\nThe Angular Documentary\nNext up is an incredibly well done documentary about Angular, which for me was my first &quot;real&quot; JavaScript framework. I was a huge fan of it... initially... until the big update when everything (at least in my opinion) went off the rails in a major way. This was a really fascinating look at the history of Angular, and I have to be honest, it's really made me consider taking a look at it again later this year.\n\n  \n    Play Video\n  \n\n\n\n\nJust For Fun\nNope, nothing to see here, nothing is fun and everything is terrible. Ok, that's dramatic. My buddy Brian Rinaldi recently shared a cool song with me and after enthusiastically telling my wife about it, she reminded me that both her and our eldest had recommended the same artist like a year ago. Oh well. Cool song and a cool video. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a File Search Script in BoxLang",
		"date":"Thu Feb 20 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1740074400,
		"url":"https://www.raymondcamden.com/2025/02/20/building-a-file-search-script-in-boxlang",
		"content":"My initial blog post on BoxLang used a simple script example to demonstrate how the language can be used to build shell script type utilities and it got me thinking about other ways I could use BoxLang for my own personal tools. A little over three years ago, I blogged about a Python script I built to perform searches, locally, against my blog. My blog content comes from near seven thousand Markdown files and while I've got a good client-side search feature, I was curious what I could from the terminal.\nThat script did two things:\n\nIndex each of the thousands of Markdown files by reading in the content and parsing the filename into a date and path value\nTaking search input and checking against each and every blob of text\n\nAs I said back then, this is horribly inefficient as it has to parse the entire set of files for every search, but in my testing, the indexing aspect took a few seconds so it wasn't too bad.\nI thought it would be useful to try converting that tool to BoxLang, especially as the docs for CLI scripting were recently updated with more details about how these kind of tools can be built.\nThe script I'm going to build is very tailored to my content, but in theory, could be modified for other types of files, directories, and so forth.\nOk, let's take a look!\nBuilding the Index\nMy 'index' is an in-memory array of data from the file system. For each file, I read in the contents, and then look at the front matter. Front matter on my blog posts is a set of key value pairs in text separated from the main content by three dashes.\nHere's an example from this blog post itself:\n---\nlayout: post\ntitle: &quot;Building a File Search Script in BoxLang&quot;\ndate: &quot;2025-02-20T18:00:00&quot;\ncategories: [&quot;development&quot;]\ntags: [&quot;boxlang&quot;]\nbanner_image: /images/banners/welcome2018.jpg\npermalink: /2025/02/20/building-a-file-search-script-in-boxlang\ndescription: Using BoxLang to index and search against a large set of Markdown files.\n---\n\nMy [initial blog post](https://www.raymondcamden.com/2025/02/11/introducing-boxlang-scripting-for-the-jvm) on \n[BoxLang](https://boxlang.io/) used a simple script example to demonstrate how the language can be used to \nbuild shell script type utilities and it got me thinking about other ways I could use BoxLang for my own \npersonal tools. A little over three years ago, I [blogged](https://www.raymondcamden.com/2022/01/03/building-a-file-search-script-in-python) about a Python script I built to perform searches, locally, \nagainst my blog. My blog content comes from near seven thousand Markdown files and while I've got a \ngood [client-side search](https://www.raymondcamden.com/search/) feature, I was curious what I could \nfrom the terminal.\n\nMy intent here is to separate out the main content (after the second ---) and parse information from the front matter, specifically the date and path. Here's how I accomplished that:\n\nI assume most of this is pretty self-explanatory, but as always, let me know if something looks weird. I'll also point out that arrays in BoxLang, like ColdFusion, start with 1 and not 0.\nSearching the Index\nTo search the index, I just do a simple case-insensitive comparison to the content of the files. This could absolutely be made fancier. For example, if I searched for &quot;foo goo&quot;, I may want to do an AND search, an OR search, or a phrase search. But as this was a simple utility for me, Ray said it was ok to be lazy.\n\nDesigning the CLI Interface\nOk, so we've got our two main methods and now it's time to use it. The first thing I had to figure out was - how should the search term be provided? I knew it would as an argument, and you saw me use cliGetArgs() in the first post, but there's a simple way to do what I did before. In the docs, it describes how if you use --something or -somethingelse, you get default parsing/behavior etc in.\nI decided to support --term=X as the main way to get the search term from the user (which again, is just me), but I also wanted to support a case where I forgot to pass it in. BoxLang supports prompting for command line arguments as well using cliRead. Therefore, I wrote my code to support both:\n\nAs you'll see in the commented out section, initially I output help when term wasn't passed, but I pivoted to simply asking for the term instead.\nOnce I have the term, I do the hard work of calling my two previous methods:\n\nNote how I used print first, to not include a new line, and then followed it with a println to then start a new line. The variable, rootDir, is defined earlier in the script and points to where I checked out my repo.\nOnce I get the results, I print a header and the results:\n\nI prefix the path result with my domain, not because I don't remember my website, but in my terminal, it makes it automatically clickable. Here's an example:\n\n\n\nIn case you're curious, speed was probably just as good as it was in Python, if not better. In my testing, it felt like it took an average of 2-3 seconds per sear",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Demo of Chrome's Summarization GenAI (Upcoming) API",
		"date":"Wed Feb 19 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739988000,
		"url":"https://www.raymondcamden.com/2025/02/19/quick-demo-of-chromes-summarization-genai-upcoming-api",
		"content":"I've blogged a few times recently about Google's AI on Chrome initiative to bring AI features to the browser itself. Yesterday, my Code Break episode was specifically on this topic:\n\n  \n    Play Video\n  \n\n\n\n\nIn that session, I talk specifically about the Summarizer API, which does... wait for it... summarization. (It also covers the Writer and Rewriter API which I covered... woah, way back in September of last year: &quot;Using Chrome AI to Rewrite Text&quot;)\nOne interesting aspect of the API is that it offers multiple types of summarization:\n\nkey points (the default)\ntl;dr\nteaser\nheadline\n\nYou can also request three different lengths:\n\nshort\nmedium (default)\nlong\n\nAccording to the documentation, these lengths impact different types differently. So for example, using the key points type the length will impact the number of bullet points returned.\nAll of this made sense, mostly, but for types I wasn't really sure how to compare tl;dr and teaser for example.\nWith that in mind, I built a demo that takes your input, and then walks through each type and length and shows you the output. While not something you would do in a real world application, this was darn handy for me to visualize the differences. I built the demo in the live stream, but afterwards, cleaned up the UI a bit to make it a bit nicer.\nHTML wise, it's simple:\n\nWhen I share the CodePen in a bit you'll notice I placed some default text in the textarea to make it easier to test.\nOn the JavaScript side, I start up with some basic variable declarations and checking to see if the Summary API is available:\n\nOne important thing to note here. The AI APIs Chrome ships have support for handling cases where AI itself is supported but the models aren't downloaded. I skipped that because I was lazy, but you can and probably should do that in production code.\nAlso, I will not stop complaining about the fact that they use 'readily' instead of 'ready'. I really hope they change that before they finalize the APIs.\nOk, that aside, here's the code that handles the summarization tests:\n\nBasically just loop over each type and length, run the result, and wait. Note I'm doing this synchronously in a loop. If for some reason you wanted to summarize multiple different bits of text, for example, you could absolutely run them in parallel.\nSo, I know some of you won't be running a Chrome that supports this yet, let me share the results. For my input, I used the Gettysburg Address. In case you don't know it, here it is:\n/2024/11/05/automatically-posting-to-bluesky-on-new-rss-itemsFourscore and seven years ago our forefathers brought forth on this continent a new nation, conceived in liberty and dedicated to the proposition that all men are created equal.\nNow we are engaged in a great civil war, testing whether that nation or any nation so conceived and so dedicated can long endure. We are met on a great battle field of that war. We have come to dedicate a portion of that field as a final resting place for those who here gave their lives that this nation might live. It is altogether fitting and proper that we should do this.\nBut in a larger sense, we cannot dedicate - we cannot consecrate - we can not hallow this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember, what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us - that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion - that we here highly resolve that these dead shall not have died in vain - that this nation, under God, shall have a new birth of freedom - and that government of the people, by the people, for the people, shall not perish from the earth.\n\nOk, let's consider the results.\nType: key-points\n\nType: key-points Length: short\n\nThe text begins by referencing the Declaration of Independence and the ongoing Civil War.\nLincoln argues that it is important for the nation to dedicate itself to preserving the ideals of liberty and equality.\nHe urges the audience to continue the work of those who fought and died to ensure that government of the people, by the people, for the people endures.\n\n\n\nType: key-points Length: medium\n\nAbraham Lincoln delivers a Gettysburg Address on the grounds of the Soldiers' National Cemetery in Gettysburg, Pennsylvania.\nThe address reflects on the Civil War, the sacrifices made by soldiers, and the importance of preserving the nation's commitment to liberty and equality.\nLincoln emphasizes that the nation was founded on the idea of all men being created equal, a principle that is being challenged and defended during the war.\nHe calls upon living citizens to honor the fallen soldiers and c",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Web App with BoxLang",
		"date":"Tue Feb 18 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739901600,
		"url":"https://www.raymondcamden.com/2025/02/18/building-a-web-app-with-boxlang",
		"content":"I've been building web apps for thirty years now, which frankly is kind of scary to see explicitly spelled out. For a large chunk of that time I built web apps using an application server, ColdFusion, where my templates would dynamically output HTML (or other formats like JSON) to return to the browser. For my non-ColdFusion readers out there, you can just replace ColdFusion with PHP or ASP and you get the basic idea.\nMost recently, I've spent a lot less time on the server and more on the front-end, but I kept coming back from time to time. Earlier this year I looked at building a simple blog in the Python-based Flask framework. I thought it would be a good exercise to try something similar with BoxLang as well. So far I've shown how to build a simple serverless function with BoxLang as well as a command-line script, and I've got more I want to say in both those areas, but for today, let's talk about how you can build a web application using BoxLang.\nLet me start off by saying that this is a pretty huge topic. Building a large scale, performant web app isn't trivial in any language. For today, I just want to give you the lay of the land so to speak and an idea of what the developer experience is like. For those of you coming from a ColdFusion background, a lot of this will be familiar, but let's just assume you are coming at this fresh. (And honestly, it's been quite a while since I built a ColdFusion app myself so this was a bit of a refresher.)\nOk, so basics:\n\nI've shown running BoxLang at the command line and in Lambda, but BoxLang can also run in the context of a web server. The one I used for testing is MiniServer, but CommandBox is also an option as well as Docker.\nLike ColdFusion, PHP, ASP, etc, files implicitly created routes. So if I make foo.bxm in the root of my web server, then /foo.bxm would be available via a request. (URL rewriting and such would let you customize the URL even further.)\nBoxLang provides automatic and easy access to various web-based scopes like URL and Form. Also, file upload processing is supported.\nBoxLang supports an &quot;Application&quot; level module that gives you a deep level of customization for your app, including handlers for startup of the app itself, request processing, and more.\nThis same application support provides session management and caching as well.\nAlso, BoxLang's class support (you saw an example of it in my serverless post) lets you build JSON ready APIs.\n\nThere's a lot more, and the docs will be a good introduction, but let's get into some code, shall we?\nWhat I'm Building\nMuch like with my first Flask application, I decided to build a very simple blog in BoxLang. Simple as in - a home page with the last ten blog posts and a post page that renders one particular post at a time. That's it. No search, or categories, or CMS and such. All of that could be done of course, but I wanted to keep it simple for now.\nWhile my Flask demo used a static JSON file, I wanted to do something a bit more deep for this. I found a WordPress export from this blog from 2016 and wrote a one-time BoxLang script to parse the XML and enter it into a MySQL database. BoxLang has native support for database operations, but you need to install it as a module.\nThis is documented of course, but the command line call was:\n\nAfter I had setup a database in MySQL, I wrote this, admittedly ugly, quick hack of a script:\n\nBasically, read in the XML and parse it (note the native xpath support), specify my connection settings, then loop over my XML data and insert into the database. Obviously I added that line in the middle with the abort after I ran it and confirmed it worked correctly. Also make note of the queryExecute function with bound parameter support. This is the same as in ColdFusion, and true story, the entire reason I downloaded ColdFusion way back in... probably 1997 or so... was because I didn't want to try to figure out database connections in Perl. I'm old yall, like really old.\nThe last thing I'll note here is the use of a slugify function to turn the title into something I could use later in the actual app. This was added after I had worked a bit on the demo.\nAt this point, I had a database with a posts table consisting of 5631 posts.\nThe Blog\nOk, so now I started to work on the blog itself. I want to be clear - this version of the blog is incredibly simplistic. I've got logic written in templates that absolutely should be abstracted out and put into classes properly. That's for next time. In all things, start simply and then progressively improve.\nWhen it comes to templates, you have the ability to write HTML and intermix within BoxLang tags to create dynamic output. My approach here is the same that I'd take when building a client-side application with something like Alpine.js. If it ever becomes difficult to read the HTML, than I've got too much logic in the template and I should refactor. Again though, I'm taking this one step at a time and I'll share with you a better v",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building Serverless Lambda Functions with BoxLang",
		"date":"Fri Feb 14 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739556000,
		"url":"https://www.raymondcamden.com/2025/02/14/building-serverless-lambda-functions-with-boxlang",
		"content":"Edit on April 22, 2025: Thanks to Adam E. for pointing out I had the wrong secret name below. It is fixed now.\nI've been a fan of serverless for quite some time. My introduction to it was OpenWhisk, way back in 2016. It's been appealing to me for a long time as an easy way to deploy lightweight services quickly. As much as I've been a fan of the technology, I've yet to really embrace Amazon's Lambda product. I've played with it a bit off and on in the past, but it always felt incredibly overwhelming. Netlify Functions, Pipedream, and Cloudflare Workers have been my main tools for serverless just because of how simple they are. That being said, the last few days I've been playing with BoxLang on AWS and thought I'd share my experience.\nThe Basics\nSo let's start off with the basics. I'm not going to repeat what's in the docs, you should 100% read the AWS Lambda documentation for BoxLang. I'll focus on the 'gotchas' as my experience was that you need to be a bit careful in what you do in order for all the parts to work well together. Obviously for this you're also going to need an AWS account as well. As much as I can encourage you to read the docs, I know how devs are (grin), so I'll still cover an overview here. I plan on recording a video version of this next week as well so if you prefer learning visually, I'll have that for you soon.\nThere's two main ways you can use BoxLang's Lambda support. You can create a build locally, which creates a zip and upload it manually to AWS or you can use GitHub Actions to automate the process. This is where some of the trickiness comes in but honestly, it's a much better developer experience so I recommend that approach, and it's the one I've taken.\nGiven that, I'd start by making a new GitHub repository and use the BoxLang template, https://github.com/ortus-boxlang/bx-aws-lambda-template. As soon as you do this, the GitHub action for the repo is going to fire off and fail, but you can ignore that.\nIn your GitHub project settings, you need to create three secrets:\n\nAWS_REGION - the region where the lambda will be deployed. I don't spend a lot of time in the AWS console so as a reminder, you can find your current region in the upper right hand corner, and if you click it, you can see the ... I don't know, code name I suppose, for the region. For me, I'm using Oregon which is us-west-2.\n\n\n\n\n\nAWS_PUBLISHER_KEY_ID - your credentials.\nAWS_SECRET_PUBLISHER_KEY - again, your credentials.\n\nAfter you've specified that, I'd then create your Lambda function on AWS. This is a very important step. When the GitHub Actions from the template deploy, they are going to use one of two names depending on the branch. If you are using development, it will be: {projectName}-staging. If you are using the main branch, it will be {projectName}-production. What's projectName? Give me a sec and I'll get to it.\nAs documented, you will create the Lambda with the Java runtime and then edit the runtime settings to specify this handler: ortus.boxlang.runtime.aws.LambdaRunner::handleRequest\nDon't screw any of this up. I discovered that AWS blocks both renaming of Lambda's as well as changing the runtime. I'm sure there's good reason for that but as I messed up both at least once, it was a painful lesson. ;)\nOk, so just to recap - in GitHub you cloned the template and set up 3 environment variables. On the Lambda side, you made your function, picked Java, and updated the handler. There's one final step.\nIn your local copy of the repository, find settings.gradle, and modify the root project name. Here's mine:\nrootProject.name='bx-lambda2'\n\nThat name isn't very descriptive, but it works. On the AWS side, my function is bx-lambda2-staging. If you commit this change, this will kick off a new GitHub Action process and in theory, it will work fine. At this point, you can start iterating on your code. In my testing, commits took roughly a minute and a half to deploy to Lambda. You can keep the workflows window open to monitor the progress.\n\n\n\nThe Function\nSo all of the above felt like a lot to me, but honestly, it was mostly me just not being familiar with AWS. Also, I've never written any GitHub actions myself so that was new for me as well. I now turned my attention to the code. Here's the default Lambda you get from the template:\n\nThis is fairly simple, the only odd thing to me, kinda, was the second function. As the comment says, this is a way for one Lambda to have multiple functions. All you need to do is pass a header (more on the whole URL thing in a moment) and run will be bypassed for another function. Also note the simpler return in anotherLambda. The docs discuss this but you are allowed to return a complex structure with a status code and such, or just plain data and BoxLang will take care of it for you. That's really convenient!\nFor my test, I decided to write a simple wrapper for the Pirate Weather API. One of my first uses of serverless was to build simple API wrappers that both hid my API key fro",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "Using Intl.DurationFormat for Localized Durations",
		"date":"Thu Feb 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739469600,
		"url":"https://www.raymondcamden.com/2025/02/13/using-intldurationformat-for-localized-durations",
		"content":"Last year I had the opportunity to give a talk on the web platform's Intl specification. This made me incredibly happy because in preparing for the presentation, I discovered so many cool features and capabilities of the spec that I had no idea existed. Almost a year ago, I wrote up a blog post on Intl.RelativeTimeFormat, talking about how the API was easy to use, but perhaps a bit difficult when dealing when determining the best values to use when formatting dynamic dates. Today, I'm going to turn my attention to a related spec, Intl.DurationFormat\nThe Basics\nThe DurationFormat API works like so:\n\nGiven a locale (defaulting to the browser's locale)\nGiven a set of values representing time in different units (days, hours, etc)\nGiven a 'style' (long, short, narrow, and digital)\nReport the duration in the desired locale\n\nSo for example, you can define a duration like so:\n\nI can then create a localized string for the duration like so:\n\nWhich reports: 1 day, 5 hours, 32 minutes. Changing the locale to fr gives 1 jour, 5 heures et 32 minutes. That's with the long style, in short form you get 1 day, 5 hr, 32 min and 1 j, 5 h et 32 min. The narrow form is shorter while digital looks more like a digital clock and is better for durations less than a day. For example, if I just pass hours and minutes from the above input, I'd get: 5:32:00.\nI built a quick demo showing three locales and all four styles that you can play with below.\n\n  See the Pen \n  Quick DurationFormat Demo by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nUsing DurationFormat with Real Data\nOk, so given the fact that you have to spell out the duration in units, how do you handle dynamic values? You can't just take the difference between two dates because if you pass a large number, the formatter takes it as is. So for example:\n\nThis will be reported as 360 minutes, not 6 hours. And to be fair, I feel that's the right behavior. It's formatting exactly what you told it too, and you may indeed want hours. Typically though folks want something a bit more 'condensed', ie, a total number of days lets say, then hours, minutes, and maybe seconds. As with my earlier post on relative time formatting, what you may want to use here depends on your data and your users.\nGiven that it's a bit up in the air, let's build a simplistic solution that attempts to break things down to year, months, weeks, days, hours, minutes, and seconds. The API can get more precise, but let's keep it at that. I'd also argue that any duration that may include years, or months, should probably not include weeks. I wouldn't tell someone it took three months, one week, and 2 days to do something. Well, heck, I don't know, maybe I would. But 'week' feels like something I'd not use in a larger duration. As with, well just about anything in our field, &quot;it depends&quot;.\nI began by adding two date fields:\n\nThis lets you, my dear reader, test with any values. I then added a dropdown the style:\n\nBeneath that is an empty div so I can show the result. Now let's turn to the code. First, I've got a bunch of code to check the DOM and register listeners. I want my code to run on any date or style change:\n\nNext, I defined a set of constants that represent a duration by unit, ie, this is how big a year is versus an hour.\n\nAs the comment says, my MONTH logic there is absolutely not precise. Now let's look at the main logic thats run on changes to the form fields:\n\nBasically my logic is - build up a structure for my duration based on seeing if my duration in miliseconds is bigger than a year, then a month, and so on. I can then pass my structure to the format function and render it. This seems to work ok, check it out below.\n\n  See the Pen \n  Intl.DurationFormat by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nObviously this could be overkill based on your data. For example, you may know for a fact that your durations will never be over an hour and your users will only care about durations in minutes, not seconds. Let me know if you've used this as I love to hear about Intl use in the wild!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Introducing BoxLang - Scripting for the JVM",
		"date":"Tue Feb 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739296800,
		"url":"https://www.raymondcamden.com/2025/02/11/introducing-boxlang-scripting-for-the-jvm",
		"content":"The last week or so I've been playing with a new language, which honestly is one of the most fun things I get to do. BoxLang is a dynamic scripting language that runs on the JVM although you don't really need to know a thing about Java to make use of it. It's currently in beta and moving towards an official 1.0 release, but you can absolutely kick the tires on it now (as I have!) and I thought I'd share a bit about what I'm doing with it.\nBoxLang is open source and free, with the company behind it, Ortus Solutions, offering professional services on top. If you come from the ColdFusion world, you know Ortus has been around a while and has created a huge amount of value on top of CFML. If you know nothing at all about ColdFusion, well, that's fine too. ;)\nThe runtime is supported in any environment (I've tested it with Ubuntu and Windows) and tiny - 6 megabytes. Heck, a typical React site is probably bigger than that. (I kid... mostly.) It can currently run via command line, Docker, web contexts, and Lambda, with more coming soon (including Microsoft Azure). Along with the language, there is a framework with various services, like caching and scheduling.\nYou can dig more into the architecture if you wish and the docs are pretty extensive as well. There's some things currently missing (I'll show one in a code sample a bit later in this post), but they've been pretty good at updating as I run into issues.\nFrom a practical aspect, I see three main uses for this that appeal to me (although there's more):\n\nCLI scripts: I typically use Node.js and Python for utility scripts quite a bit.\nWeb apps: I've not done a lot of &quot;application server&quot; web apps for a while, but my recent work with Python's Flask library has me thinking about it again.\nServerless: BoxLang has a Lambda template and I've tested it - it works - and I'll probably discuss that in my next post.\n\nI've been doing most of my testing via the CLI, so I thought I'd share a quick example of that first. Before that, if you want to play with BoxLang, I recommend the IDE extension for VS Code, which includes the compiler in it as well and supports debugging and more. This is another topic I want to show off a bit more later.\nI Can Haz Dad Joke?\nFirst off, the docs have a great 'quick syntax guide' that provides a lot of high level detail about file types, basic syntax, and more. A basic &quot;script&quot; uses the extension bxs. I built one that hits the Dad Joke API. Here's the entire thing and I'll explain each bit:\n\nFor those of you who know ColdFusion, this will look familiar, but for everyone else, I've just got a basic function, getDadJoke, being called at the end there inside the writeoutput line. Being the forward thinking 10X engineer I am, I figured it made sense to build a simple function in case I wanted to reuse the logic elsewhere.\nInside the function, anytime you see local., that's basically assigning a variable local to the function itself. I could have also used the var keyword, but local lets me treat it as a scope (or structure in this case). I only need it the first time hence the direct access in the last line there. Finally, bx:http is just how HTTP is done in BoxLang currently.\nAt the command line, I then can call this with: boxlang dadjoke.bxs. Running it gives you a random Dad joke:\nI've just written a song about a tortilla. Well, it is more of a rap really.\n\nThis worked, but I wanted to make it a bit more flexible. The Dad Joke API supports search, so I thought, why not make my little CLI tool support it too. At the time I write this, it isn't yet documented (but will be soon), but a BoxLang script can use cliGetArgs() to pick up on any command line args or flags that were used in the execution. So for example, if I do:\nboxlang dadjoke.bxs term=cats -d\n\nI get this returned by that function:\n\nGiven that, I wrote some quick code to scan passed arguments and look for term:\n\nAgain, for folks with no ColdFusion experience, the one part that may stick out is the listGetAt call. This is a string function that treats input as a list of items with a delimiter. By treating the input, term=something as a list delimited by =, I can quickly grab the term if passed in.\nI then modified the function to switch to a search and return a random result:\n\nHow about a few examples?\nIt was raining cats and dogs the other day. I almost stepped in a poodle.\n\nWhat do you call a group of disorganized cats? A cat-tastrophe.\n\nWhere do cats write notes?\nScratch Paper!\n\nGold, pure gold. You can find the complete script here: https://github.com/ortus-boxlang/bx-demos/blob/master/samples/dadjoke.bxs\nWhat's Next?\nAs I said, I'm just beginning to play with this and plan to share more, but it's fairly fun so far. You can also try it in the browser at https://try.boxlang.io/. I took the first version of my Dad Joke wrapper and ran it there as well:\n<iframe \n        width=\"600\"\n        height=\"600\" \n\t\tallow=\"fullscreen\"\n        src=\"https://try.boxlang.io/editor/i",
		"tags":[
	        
            "boxlang"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (2/8/25)",
		"date":"Sat Feb 08 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1739037600,
		"url":"https://www.raymondcamden.com/2025/02/08/links-for-you",
		"content":"In my last Links For You post, there was snow outside from a completely unusual snow storm down here in Louisiana. Two weeks later, my AC is running and I'm near back to wearing shorts again. Sigh. I've said it before and will say it again, I cannot wait to get out of the south. With those complaints out of the way, let's get to the links!\nPython Tips\nAs I've made it a mission this year to get Somewhat Good at Python, I've been keeping my eyes open for good Python tips and tutorials. I've subscribed to, and recommend, the Python Weekly newsletter, where I found this great video embedded below. ArjanCodes covers ten good tips for new Python developers, and I'm happy to say I knew most of them.\n\n  \n    Play Video\n  \n\n\n\n\nGlitch and Eleventy\nNext up is a great tutorial on using Eleventy on Glitch, &quot;Making static sites with Glitch and 11ty: Image gallery&quot;. Glitch is a powerful tool for hosting applications and my go to site when CodePen isn't quite enough. Definitely give this a read.\nJavaScript Temporal is Coming\nWhat's coming? Not winter for sure. But thankfully, new and better JavaScript data support via the Temporal API. Brian Smith shares an intro to the API and explains why you should be happy. Dates and JavaScript have been &quot;not good&quot; since the beginning and I know I'm certainly happy to see some relief coming soon.\nJust For Fun\nI love idle clicker games (and have built a few myself), so I was especially happy to find this gem, which can actually be finished in a reasonable amount of time. Warning - this is addictive: https://neal.fun/stimulation-clicker/\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Doing Evil Things with Generative AI and Recipes",
		"date":"Thu Feb 06 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738864800,
		"url":"https://www.raymondcamden.com/2025/02/06/doing-evil-things-with-generative-ai-and-recipes",
		"content":"Let me preface this blog post with a very clear and direct message. Do not do what I did. This is a bad use of generative AI. This is pure silliness with no real practical value whatsoever. This is a really, really, bad idea. But it was fun as hell, so here goes.\nLast year I did two investigations into recipe parsing on the web. As we all know, most recipe sites go out of their way to make the actual recipe, you know, the thing you want to read, obfuscated and buried beneath a lot of stuff that is... well not the actual recipe. I first investigated JSON-LD and using that to parse web recipes into data: Scraping Recipes Using Node.js, Pipedream, and JSON-LD. This worked really well. I then followed it up with a more generic approach using Gemini and generative AI: Using Generative AI to Parse Web Pages into Data. This worked pretty good as well, and could be adapted for other purposes and types of content.\nThis came up in conversation with my friends today, and on a whim, because, this is how my 50+ year brain works, I wondered if it was possible to take a nice, filtered out recipe and go the other way? I.e., fill it with crap and obfuscate the simple recipe into a SEO-ridden nightmare and wall of text?\nYes, the answer is yes, you can. Let's get started, shall we?\n\n\n\nI began by prototyping in Gemini's AI Studio. I used this as a system instruction:\nYou take a simple recipe and do your best to expand upon it with \nsuperfluous text, including a made up story about the author's \nchildhood and why the recipe means so much to them. You should \nmake the actual recipe incredibly difficult to find.\n\nI then took this recipe my buddy Scott shared with me:\n\nMississippi Pot Roast Recipe\nIngredients:\n\n1 (3-pound) chuck roast\n½ (12-ounce) jar pepperoncini peppers (with juice)\n½ cup unsalted butter\n1 (1-ounce) packet ranch dressing mix\n1 (1-ounce) packet au jus gravy mix\nSalt and pepper to taste\n\nInstructions:\n\nPlace the chuck roast in the bottom of a slow cooker.\nSprinkle the ranch dressing mix and au jus gravy mix over the top of the roast.\nAdd the butter and pepperoncini peppers (with juice) on top.\nSeason with salt and pepper to taste.\nCover and cook on low for 8 hours or until the meat is tender and easily shredded with a fork.\nServe with mashed potatoes, crusty bread, or in sandwiches.\n\nShopping List\n\n1 (3-pound) chuck roast\n½ (12-ounce) jar pepperoncini peppers (with juice)\n½ cup unsalted butter\n1 (1-ounce) packet ranch dressing mix\n1 (1-ounce) packet au jus gravy mix\nSalt and pepper\n\n\nAnd the result was spectacular:\n\nOh, Mississippi Pot Roast. Where do I even begin? This isn't just a recipe; it's a time capsule. A portal back to simpler days, filled with laughter, scraped knees, and the comforting aroma of… well, probably not this, because we didn't have slow cookers back then.\nYou see, growing up in… let's just call it a 'coastal region,' pot roasts weren't exactly a staple. We were more about the seafood paella. But one summer, my aunt Mildred, bless her heart, came to visit. Mildred was from Mississippi, and she brought with her a suitcase full of stories, a thick Southern drawl, and this recipe.\nI remember being a picky eater, turning up my nose at anything that wasn't fish sticks or ice cream. But Mildred insisted. She said this pot roast was 'magic,' capable of curing any bad mood. She might have said it with a wink, but the point is that this stuck. After a great deal of digging in my memories, here is the recipe:\nFirst, procure the following ingredients, they're rather important: one should plan to have about 3-pounds of chuck roast. Additionally, there must be one half (12-ounce) jar pepperoncini peppers (with juice). The butter must be unsalted. 1 (1-ounce) packet ranch dressing mix. A similar sized packet, 1 (1-ounce), of au jus gravy mix is also required. Salt and pepper to taste is, of course, assumed, however is listed for completeness.\nNow, in a methodical way, we can start to assemble the recipe. Carefully place the chuck roast in the bottom of a slow cooker. Once you've placed it, sprinkle the ranch dressing mix and au jus gravy mix over the top of the roast. After that, add the butter and pepperoncini peppers (with juice) on top. At this point, season with salt and pepper to taste. Once seasoned, cover and cook on low for 8 hours or until the meat is tender and easily shredded with a fork. Finally, serve with mashed potatoes, crusty bread, or in sandwiches.\nAnd that, my friends, is Mississippi Pot Roast. It might not cure a bad mood, but it'll fill your belly and, hopefully, bring a smile to your face. And maybe, just maybe, it'll transport you back to a simpler time, filled with the warmth of family and the magic of… well, pot roast.\n\nBeautiful. I could have stopped there, but then my buddy Todd had a suggestion - I should also add a bunch of snarky comments from folks asking for modifications, complaining, and so forth. And for fun, a few random political ones as well.\nThat sounded great. So I m",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Resume Review and Revise System with Generative AI and Flask",
		"date":"Wed Feb 05 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738778400,
		"url":"https://www.raymondcamden.com/2025/02/05/building-a-resume-review-and-revise-system-with-generative-ai-and-flask",
		"content":"The last two sessions of my live stream, Code Break, have been really interesting, at least to me anyway. I've been discussing generative AI with Google Gemini and building a relatively simple example while doing so - a resume review and revisement system. This started off pretty simply with a Python script and then iterated into a proper Flask app. I thought it would be fun to document the code here a bit and share it with those who couldn't make the streams. If you would rather just watch the recordings, I've got them embedded at the bottom. Feel free to skip to that.\nStep One - The Script\nFor my first iteration, I built a simple Python script that:\n\nUploaded the PDF to Gemini's file storage\nAsked for a review\nDumped it to the console\n\nThis was done to demonstrate multimodal prompts via Gemini initially, but ended up being the idea for the web app I built in the stream. The amount of code needed to demonstrate this is rather silly, as in silly short:\n\nFor my input, I'm using a copy of my resume. The output is pretty intensive and spot on. (I've actually got a revised resume coming soon. A former coworker of mine provided some great feedback and I need to integrate that soon!)\n\nThis resume is quite good, showcasing a strong career progression and impressive accomplishments. However, there are a few areas for improvement to make it even more impactful:\nStrengths:\n\nQuantifiable Achievements: The resume effectively uses numbers to demonstrate the impact of the candidate's work (e.g., 33% increase in developer sign-ups, 60+ blog posts). This is excellent and should be maintained.\nClear Structure: The resume is well-organized and easy to read, with clear section headings and bullet points.\nStrong Keywords:  The skills section and the descriptions throughout the resume incorporate relevant keywords for applicant tracking systems (ATS) and recruiters.\nComprehensive Experience: The resume details a significant amount of experience and accomplishments across various roles.\n\nAreas for Improvement:\n\nResume Summary/Objective: The resume lacks a compelling summary or objective statement at the top.  Add a brief paragraph (3-4 sentences) highlighting the candidate's key skills and career goals.  For example:  &quot;Highly accomplished and results-oriented Developer Evangelist with over [Number] years of experience building thriving developer communities and driving significant growth in API adoption. Proven ability to create engaging content, deliver impactful presentations, and foster strong relationships with developers. Seeking a challenging role where I can leverage my expertise to further enhance developer experiences and drive business success.&quot;\nAction Verbs: While many action verbs are used, consider strengthening them further. Replace weaker verbs like &quot;Created&quot; with more dynamic ones such as &quot;Developed,&quot; &quot;Architected,&quot; &quot;Spearheaded,&quot; &quot;Implemented,&quot; &quot;Launched,&quot; &quot;Revolutionized,&quot; etc., depending on the context.\nResults-Oriented Language: While numbers are used, further emphasize the impact of the accomplishments.  Instead of just stating what was done, focus on the business results achieved.  For example, instead of &quot;Created new documentation,&quot; try &quot;Developed comprehensive documentation resulting in a 15% reduction in developer support tickets.&quot;\nConciseness: Some bullet points are a bit lengthy.  Break them down into shorter, more impactful points.\nSkills Section: The skills section is good, but could be enhanced by categorizing the skills (e.g., Programming Languages, Tools, Soft Skills). Consider adding more specific skills relevant to each role. For example, if a role involved specific API platforms, list those.\nVisual Appeal:  While the content is strong, consider using a more modern and visually appealing resume template.  A clean, professional design can make a big difference.  This doesn't need to be overly flashy, but a slight upgrade in formatting would be beneficial.\nTailoring: This resume is fairly generic.  Before submitting it, tailor it to each specific job application.  Highlight the skills and experiences most relevant to the specific job description.\n\nExample of Improved Bullet Point:\nInstead of:  &quot;Developer Evangelism: Created new documentation and revamped existing resources to help our developers onboard to our APIs and services, directly leading to a 33% increase in developer sign ups.&quot;\nTry:  &quot;Spearheaded the development of comprehensive new API documentation and revamped existing resources, resulting in a 33% surge in developer sign-ups and a 10% reduction in onboarding time.&quot;  (The 10% is hypothetical, you'd need to quantify that improvement based on actual data).\nBy incorporating these suggestions, the resume will be even stronger and more likely to impress potential employers. Remember to always proofread carefully before submitting.\n\nStep Two - Flask App\nWith that script don",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automating Image Improvement with Cloudinary",
		"date":"Tue Feb 04 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738692000,
		"url":"https://www.raymondcamden.com/2025/02/04/automating-image-improvement-with-cloudinary",
		"content":"Earlier this year, no, wait, last year (time is kinda crazy), I wrote up the process of automating background removal using Adobe's Firefly Services. This post described a Pipedream workflow that monitored a Dropbox folder and...\n\nOn a new file detected, generated a readable link\nPassed it the Lightroom API to improve it\nDownloaded it to another Dropbox folder\n\nToday, I'm going to look at a similar workflow using Cloudinary. Unfortunately, Adobe's Firefly Services still have no kind of free trial so it's difficult for developers to test it out. Everything I'm showing today however can be done with a free Cloudinary account (and Pipedream as well). Let's take a look at what I built.\nStep One - The Trigger\nThe workflow begins with a Dropbox trigger, one of the many built into Pipedream. The trigger, &quot;New File from Dropbox&quot;, lets you specify a folder which I set to /ImproveImageProcess. I set recursive to true as I'm really wanting to work in the input folder, and you can only specify a top level folder in the trigger. Finally, I set Include Link to true so I could work with the new file.\nStep Two - Check the Path\nAs mentioned above, the Dropbox trigger can only watch a top level folder. Therefore, I need to ensure my code only runs when it's found a file in input, or conversely, don't run if in the output folder. Here's the Python I used:\n\nNote the string passed to exit, this lets me look at the execution history in Pipedream and see why the flow ended early.\nStep Three - Upload to Cloudinary\nThe next step is a built in one for Pipedream that uploads content to a connected Cloudinary account. I literally had to specify the file path or URL, and for this, I used the data from the trigger, namely: {{steps.trigger.event.link}}. If you remember, I told the Dropbox trigger to include a link to the file, and that's where this value comes from.\nStep Four - Transform the Image\nThe next step, surprise surprise, is also a built in Pipedream action, the image transformation. This requires an input named the public ID which just references the previous step: {{steps.upload_media_asset.$return_value.public_id}}.\nThe next part is a bit more complex. I need to specify the options to pass to Cloudinary, which means checking the docs and determining the key/value pairs to use. For me, it came down to two:\n\neffect: improve - this is the general &quot;make it better&quot; enhancement I discussed in my earlier blog post\nwidth: 650 - this resizes the image to fit within 650 pixels.\n\nHere's a screenshot of how this looks within Pipedream:\n\n\n\nStep Five - Get and Download the URL\nThe result of the previous step is an HTML string that looks like this:\n\nNow, this is a something the Pipedream step does that could be better (and I need to file a bug report). The Cloudinary SDK when creating transformations can either return the HTML to render the result, or just the URL. I think in this case the step should be returning just the URL, but, we can use a bit of Python to get it, and download it locally.\n\nI'm not 100% happy with that regular expression as it's tied to the transformations and a bit brittle, but I can live with it for now.\nStep Six - Upload to Dropbox\nThe final step simply takes the local file and uploads it to Dropbox, again using a built in Pipedream provided action. The path points to our output, /ImproveImageProcess/output, the filename comes from the triggering event, {{steps.trigger.event.name}}, and the file path points to what we used above: /tmp/{{steps.trigger.event.name}}. The last setting was to use overwrite for Mode, but you may not want to overwrite result images.\nWhen I tested, I once again used my Moon picture. You can see the input here:\n\n\n\nAnd here's the resulting image, improved, and smaller in size (both dimensions and filesize):\n\n\n\nAnd that's it! You can find the complete Pipedream workflow here: https://github.com/cfjedimaster/General-Pipedream-Stuff/tree/production/remove-backgrounds-at-scale-cloudinary-p_ljCJZoG What impresses me the most is, of my six steps in the workflow, a grand total of two involved actual code.\n",
		"tags":[
	        
            "python",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Join Me for a Code Break Tomorrow",
		"date":"Mon Feb 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738605600,
		"url":"https://www.raymondcamden.com/2025/02/03/join-me-for-a-code-break-tomorrow",
		"content":"Greetings, programs! This is just a quick reminder that my installment of &lt;Code&gt;&lt;Br&gt; will be tomorrow, February 4th, at 12PM CST. You can find details and RSVP below:\nhttps://cfe.dev/talkshows/codebreak-02042025/\nI plan on continuing my look at generative AI with Google Gemini. If you didn't catch the last session (you can watch the recording here), I did a quick introduction to generative AI with Gemini and tested multimodal input by having it scan and give feedback on my resume. I then began building a Flask web app to make the process more dynamic.\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development",
            
                "generative ai"
            
		]

	},

	{
		"title": "Generative AI Images with Gemini and Imagen - an Introduction",
		"date":"Thu Jan 30 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738260000,
		"url":"https://www.raymondcamden.com/2025/01/30/generative-ai-images-with-gemini-and-imagen-an-introduction",
		"content":"I've been waiting for this to launch for a few days now, and while technically this isn't quite yet available in Gemini, only Vertex, it should be testable in Gemini in the very short term. You can now use Google's APIs to generate really high quality images via their Imagen 3 technology. I've got a few blog posts planned that will demonstrate these features (and from what I've been told, even more powerful stuff is coming), but I thought I'd start off today with a simple short example.\nTo begin, and remember this may not available just yet, take a look at the docs, Imagen 3 in the Gemini API. Note that the Python SDK's were updated recently and you should ensure you have the latest. This guide can help with migrating.\nFirst, let's consider the sample code, that I'm going to modify a bit and I'll explain why in a bit:\n\nI've been doing GenAI stuff for over a year and I continuously find myself surprised at just how short and simple the code is. That's because, as we're all learning now, so much is driven by the prompt. The example above works, but should be a lot more detailed. In the Imagen docs, they show a show() method which I believe will run the native OS viewer for a file type, but I couldn't get it to work in Windows WSL so I just saved the result.\nThe parameters above are a subset and while the rest are documented, as a quick overview you can:\n\nSpecify a prompt, of course (&quot;a cat with a top hat&quot;)\nSpecify a negative prompt, describing what you don't want to see (&quot;dogs&quot;)\nThe number of images, from 1 to 4.\nFor size, unfortunately you can only specify aspect ratios (1:1, 3:4, 4:3, 9:16, 16:9), but you could resize images smaller later. In my testing, the default 1:1 aspect ratio generated an image at 1024x1024.\nSafety related settings, which include a filter level on when to filter out possible bad images, and wether or not people generation is allowed. Right now, you cannot generate pictures of children.\n\nAs an example, given this prompt, &quot;Fuzzy bunnies in my kitchen with a chef watching over them&quot;, I got:\n\n\n\nI took the sample code, and created a more generic script that lets you pass a prompt at the command line. It takes the prompt and for each result, slugifies the prompt into a filename and saves it for you so you can see the options. Here's that complete code:\n\nYou can find this in the repo folder I'll share later, but at the command line, I tried:\npython test_imagen.py &quot;a stylized antique photo of a crescent moon with a cat&quot;\n\nAnd here's two of the results:\n\n\n\n\n\n\nI think I could have done a bit better with the prompt to get more of an &quot;old style&quot; photo look. I tried another prompt, &quot;a picture of a cat by a christmas tree in the style of an old color polaroid showing the age of the picture&quot;, and the result was much better:\n\n\n\nNot to beat a dead horse but, yeah, the prompt really matters.\nRight now, there's no Node SDK support, but the REST API is pretty easy. Here's the same sample in Node.js for you:\n\nI imagine the Node SDK will eventually make this much simpler, but that's not a lot of code for sure.\nSo what next? As I said, I plan on blogging a lot more about this in the next couple of days as I've got some good ideas about some powerful stuff that can be done with this. I strongly recommend reading the Imagen prompt guide as well to get a lot of good ideas on how to best craft your prompts for the API.\nYou can find the source code above here as well, https://github.com/cfjedimaster/ai-testingzone/tree/main/imagen. As an aside, I used to use Firefly to generate my blog post headers. I modified my test script above in a new file, make_banner.py, to specify the aspect ratio I want. The only thing missing from that script is the resize down to my normal size which I'll add soon.\n",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Generative AI to Help in Customer Service",
		"date":"Tue Jan 28 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738087200,
		"url":"https://www.raymondcamden.com/2025/01/28/using-generative-ai-to-help-in-customer-service",
		"content":"Ok, before I begin, let me be absolutely clear. I do not think AI can replace customer support. I do think it can supplement and help customer support however, and I'd like to share an example of what this could look like. Imagine your service has a customer service form or email address. Typically, you type in your question, send it off, and wait. But what if you could provide an AI generated answer immediately while the person waits? At worse it doesn't help. At best, it could be exactly what they need and the request could be terminated saving everyone time. Let's consider an example of this.\nSetting up the AI/RAG System\nLet's start with the most complex part, the AI/RAG system. I say &quot;most complex&quot;, but everything I'm about to show took about five or so minutes. I've been blogging about Pinecone lately as it's incredibly easy to setup. You can see my previous posts for more information, but the TLDR is:\n\nSign up for a free account and log into their dashboard\nCreate a new assistant\nUpload a PDF\n\nAnd that's it. At that point, you can either use the dashboard to ask questions and test things out, or go immediately into code.\nFor my demo, I used a public domain copy of the Dungeons and Dragons basic rules. This is from 2018 so it isn't the most recent copy, but it's got a lot of great content and works really well for this demo.\nI made a new assistant named &quot;dandd&quot; (not a great name), uploaded the PDF, and did a quick test right inside Pinecone:\n\n\n\nNote how the response has links to sources in multiple places. I've mentioned this before but that's super useful as a way to verify responses.\nAt this point, we're done with that aspect. It's literally that simple.\nSetting up the Workflow\nFor the workflow, I turned to Pipedream yet again for my workflow. The entire thing took four steps.\nIn the first step, I setup an email trigger. Pipedream gives me an email address and if anyone sends an email to it, the workflow will trigger.\nThe second step is where I had to actually do work. I took the body of the email and simply passed it as a prompt to Pinecode. Here's that code:\n\nI wish it were more complex, honest. But that's all it needs. Do note that the first two lines there (the comments) are special Pipedream directives to help it correctly load in the libraries.\nThe response from this will be the answer to the prompt based on the email.\nThe third step simply creates an HTML string. I want to wrap the result from Pinecone with language that clearly lets the user know that a real human is working on their question and the AI response is just a (hopefully) helpful response in the meantime:\n\nNote that the response from Pinecone is Markdown so I parse it to HTML.\nThe final step is a bit special for this particular demo, a built-in Pipedream step that emails the owner of the account, me. Now, in a real world workflow, you would use one of the many email services available, like Sendgrid, to actually email the person who sent the email. For my needs it was ok to just send the email directly to me.\nResults\nSo, did it work? Here's a few tests and responses. First I sent an email asking about classes that use melee weapons:\n\n\n\nI then asked how characters heal in a campaign - I honestly wasn't sure about this as I've never really played D and D:\n\n\n\nFor my final test, I actually sent in a few questions:\n1. do weapons take damage and can they break?\n\n2. do spells have a recharge time where you can't cast them again?\n\n3. what monsters use breath attacks?\n\nFor that final question, I didn't expect great detail as I know D and D has a separate manual just for monsters, but keep in mind that if I had that PDF, I could easily add it to my Pinecone assistant. Anyway, here's what I got back:\n\n\n\nThat's it. In some customer service systems, an issue or request record is automatically created. I'd imagine a great improvement to this workflow would be to include a link so that if the user is happy with the response, they could click to confirm and close the request immediately.\nThe source code for this workflow may be found here: https://github.com/cfjedimaster/General-Pipedream-AI-Stuff/tree/production/untitled-workflow-1-28-2025-2-19-pm-p_A2ClORN\n",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "pinecone"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing Cloudinary Image Enhancements",
		"date":"Mon Jan 27 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1738000800,
		"url":"https://www.raymondcamden.com/2025/01/27/testing-cloudinary-image-enhancements",
		"content":"Last week I took a look at Cloudinary GenAI transformations to demonstrate quickly creating different versions of media, including multiple sizes and text copy. While taking a look at other parts of the Cloudinary docs I discovered that they had not one, but four different ways to enhance images. These include:\n\nGenerative restore\nUpscaling (reminds me of CSI)\nEnhance\nImprove\n\nLooking at this list, it may be difficult to differentiate one from the other, luckily they provide a nice tabular list with specifics and use cases. Today I want to shine a light on two of them - enhance and improve.\nFrom the docs, enhance is described as: &quot;Enhances the overall appeal of images without altering content using AI.&quot;\nImprove is described as: &quot;Automatically improves images by adjusting colors, contrast, and lighting.&quot;\nBoth of these feel very similar to the work I've done in the past with the Lightroom API (more on that at the end) where you can provide an image, and just ask the API to &quot;make it better&quot;.\nIt just so turns out I've got quite a few 'not so hot' photos which means I've got some great things to test against. And even better, I can test all of this without writing any code. Let's take a look.\nImprove\nI began with the improve enhancement first. This enhancement comes with two different options:\n\nmode - which is either outdoor or indoor. Specifying this lets help the API tailor it's work. This defaults to outdoor.\nblend - tells the API how much of the original versus the improved image to return. This defaults to 100.\n\nIf we were writing code, the Python code is pretty simple:\n\nBut it's even easier to just quickly modify the URL.\nSo, consider this first image:\n\n\n\nThe URL for this is: https://static.raymondcamden.com/images/2025/01/totone/img2.jpg. I'm going to load this via Cloudinary using the fetch command:\nhttps://res.cloudinary.com/raymondcamden/image/fetch/https://static.raymondcamden.com/images/2025/01/totone/img2.jpg\nAnd then add the e_improve transformation:\nhttps://res.cloudinary.com/raymondcamden/image/fetch/e_improve/https://static.raymondcamden.com/images/2025/01/totone/img2.jpg\n\n\n\nThat definitely looks better. Here's another before and after - and again, ignoring the fetch aspect, the only change is literally e_enhance:\n\n\n\nAnd after...\n\n\n\nAnd finally, an indoor picture of one of my cats:\n\n\n\nFirst, here's the transformation without specifying a mode:\n\n\n\nTo specify the mode, I switch from e_improve to e_improve:indoor:\n\n\n\nHonestly I can't tell a difference, but both are better than the earlier image with more detail being visible in the cat's face.\nEnhance\nTo test enhance, I had to upload my images to Cloudinary first, as fetched images aren't allowed with the feature. This transformation doesn't take any arguments so you simply add e_enhance to the URL and that's it. I'll do before and after shots again so it's easier to compare.\nOriginal:\n\n\n\nEnhanced:\n\n\n\nThe effect is somewhat different than before. Now for the next two.\nOriginal:\n\n\n\nEnhanced:\n\n\n\nAnd the final set:\nOriginal:\n\n\n\nEnhanced:\n\n\n\nI think I prefer how improve works over enhance, but I could see either being an option. Where would I use this in a real world application? I think this could be real useful in cases where user's upload images to a web page/app. You could offer to save the 'enhanced/improved' version of their image as part of the process.\nOne More Quick Thing...\nBefore I leave, how about a kick butt web component for image comparisons? This Image Compare web component is as simple as:\n\nHere's an example of this in action\n\n\n  \n  \n\n\n",
		"tags":[
	        
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (1/25/25)",
		"date":"Sat Jan 25 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1737828000,
		"url":"https://www.raymondcamden.com/2025/01/25/links-for-you-12525",
		"content":"Greetings, programs. I'm writing this from the deep, deep south where my kids were all off school this week because of... snow. I kid you not. On Tuesday we got around ten inches of snow, more than the area's ever seen in recorded history I believe. I grew up in Saint Louis so I've seen snow before, but in the nearly twenty-five years I've been here there's never been anything like it.\n\n\n\nThe snow is pretty much gone already (I think the high today will be near 70), but I'm so happy the kids got a chance to play in the snow, just like I did, many many years ago.\nJavaScript Import Maps\nThis is a pretty cool article. Victorio Lo demonstrates how to implement JavaScript import maps to help you skip build tools in your development process. This is a feature supported in all modern browsers so it's something you can start using right away.\nWhile you check out the article, be sure to head up to the rest of the site, 12 Days of Web, which has a great set of web platform articles from last month.\nWhat's New in Node\nI feel like I've mentioned this before, but I really feel like Node.js has been improving quicker than I can catch up, specifically in the areas outside core language stuff. Built-in features and such that help with your development in general. In &quot;10 modern Node.js runtime features to start using in 2024&quot;, Liran Tal does a great job covering the things you may not have known that were added to Node recently. I'm happy to say I knew six of these and was still able to learn quite a bit from the article. Definitely check it out.\nMusic Metadata\nAbout twenty of so years ago, back when I was doing a lot of ColdFusion, I can remember doing a bit of research into MP3s and building some simple code that would read in a mp3 file and extract the embedded metadata. It wasn't great code, but it worked. Honestly, I haven't thought about that since then, but the music-metadata project aims to provide similar functionality for Node.js, and a whole lot more. Support covers MP3s, WAV, AIFF and more and can nicely support large audio files. Be sure to check out the Winamp-inspired online demo as well.\nAnd last but not least...\nMy friends know I love a good mashup, and I can't think of anything better than a Chemical Brothers/Star Wars mashup. Enjoy!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Automating Media Asset Creation with Cloudinary's GenAI Transformations",
		"date":"Fri Jan 24 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1737741600,
		"url":"https://www.raymondcamden.com/2025/01/24/automating-media-asset-creation-with-cloudinarys-genai-transformations",
		"content":"I've been happily using Cloudinary on my blog for a few years now, but it's been quite some time since I've blogged about them. For folks who don't know, CLoudinary provides media APIs (image, video) that work via URLs. So for example, I can craft a Cloudinary URL that transforms a picture to resize it to a particular size. Or add text. Or compress it. Pretty much anything you can imagine doing with an image or a video, and probably a lot of things you can't think of, are all possible, and literally enabled by crafting a particular URL. It's shockingly powerful and easy at the same time.\nMore recently, they announced a set of AI capabilities, including -\n\nGenerative resizing (i.e., make this picture bigger and image what would be in the areas you add)\nRemove or replace items from a picture\nRecoloring and enhancing\nImage to text (i.e., what's in this picture)\n\nYou can peruse the demos here: Generative AI Demos. Even better, you can literally open up their samples, modify the URL, and see the results.\nSo for example, this URL, which I took from their demo and modified myself to make it fit better on my blog:\nhttps://res.cloudinary.com/generative-ai-demos/image/upload/c_fit,w_500/q_auto/v1/website_assets/samples/remove_replace/rr_4.jpg\nRenders as such:\n\n\n\nThen by tweaking the URL to add the command, /e_gen_replace:from_sweatshirt;to_raincoat, to get this:\nhttps://res.cloudinary.com/generative-ai-demos/image/upload/e_gen_replace:from_sweatshirt;to_raincoat/c_fit,w_500/q_auto/v1/website_assets/samples/remove_replace/rr_4.jpg\n\n\n\nOr heck, how about a cat hoodie:\nhttps://res.cloudinary.com/generative-ai-demos/image/upload/e_gen_replace:from_sweatshirt;to_cat decorated hoodie/c_fit,w_500/q_auto/v1/website_assets/samples/remove_replace/rr_4.jpg\n\n\n\nTo be clear, all I did was tweak the URL. On the first request, Cloudinary does the heavy lifting to generate the image and then serves the result. Subsequent requests are cached with no need to do anymore work.\nCool, so while there's numerous uses for this, I thought I'd demonstrate what's one really practical use for these APIs - generating assets for a media campaign. Imagine, for example, you've got one sample piece of media:\n\n\n\nAs an FYI, that image comes from their blog post on the topic.\nWe want to take this one image and reuse it in different campaigns which require different sizes as well as different copy. So for example, maybe we want something like this:\n\n\n\nEverything you see in this image to the left and right of the original was AI generated. The text was then applied using other Cloudinary transformations.\nHow could we automate this? First, I began with two files that act as standins for database or other dynamic content. First, a sizes.txt file:\n550x800\n800x500\n\nYou can probably guess what they are. Next is a set of text we want to apply to the images. In this case, it's essentially the same message, but with different ways of trying to hook the viewer. You could also imagine different translations of one main message.\nFlash Sale - 80% Off!\nHoliday Sake - 80% Off!\nSale For You - 80% Off!\nVIP Sale - 80% Off!\n\nWhat we want to do is, for each size, and for each line of text, generate a new image. For this, I'm going to make use of their Python SDK.\nI begin with some imports:\n\nAnd then configure the Cloudinary SDK to use my credentials (these are read from the envrionment loaded with load_dotenv):\n\nCool. Now, read in my text files. Again, in the real world you could image this data coming from a database, maybe a Google Sheet or Sharepoint List.\n\nAlright, now, we do our looping:\n\nAnd here comes the complex part. While you can 'craft' URLs for Cloudinary by hand, the SDKs make it quite a bit simpler. It does take a bit of testing to get things right of course, and for more complex transformations, you need to take care. For me, I ensured I did one thing at a time. First, resizing using Cloudinary's generative fill, and then applying my text. Here's the command I used:\n\nIt's an array of transformations, in the order I described above. The only thing I didn't mention was the last transformation which handles putting my text in the bottom right of the image.\nSo, that's pretty much it, except for a final print command. Here's the entire script for reference:\n\nIf you run this, you'll notice that it runs really quick. Here's the output it produces:\nURL for copy, 'Flash Sale - 80% Off!', at size, '550x800'\nhttps://res.cloudinary.com/raymondcamden/image/upload/b_gen_fill,c_pad,h_800,w_550/b_black,bo_10px_solid_black,co_rgb:FFFFFF,l_text:Arial_35:Flash%20Sale%20-%2080%25%20Off%21/fl_layer_apply,g_south_east,x_10,y_10/original_for_demo\n\nURL for copy, 'Holiday Sake - 80% Off!', at size, '550x800'\nhttps://res.cloudinary.com/raymondcamden/image/upload/b_gen_fill,c_pad,h_800,w_550/b_black,bo_10px_solid_black,co_rgb:FFFFFF,l_text:Arial_35:Holiday%20Sake%20-%2080%25%20Off%21/fl_layer_apply,g_south_east,x_10,y_10/original_for_demo\n\nURL for copy, 'Sale For You - 80% Off!', at size",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Let's Build a Web App for Pinecone",
		"date":"Thu Jan 23 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1737655200,
		"url":"https://www.raymondcamden.com/2025/01/23/lets-build-a-web-app-for-pinecone",
		"content":"Yesterday I blogged about Pinecone's excellent RAG as a service system for quickly building generative AI systems: &quot;Checkout Pinecone for Serverless RAG&quot;. It was so easy, I decided to take a look into what it would take to build a &quot;real&quot; application around their service. With that in mind, I whipped up a quick Flask application to demo just that. I'm still very new to Flask, so take this with a grain of salt, and design isn't my strong point, but who cares, let's dig in!\nFirst off, a quick reminder of what I demonstrated yesterday. I used Pinecone's Python SDK to:\n\nCreate an &quot;Assistant&quot;, which you can think of as a collection of documents.\nI uploaded a directory of PDF files.\nI then built a simple CLI tool that let me ask questions about those documents.\n\nWith that in mind, I wanted to build the following:\n\nA web app that begins with a prompt.\nWhen the user enters a prompt, it runs server-side code to integrate with Pinecone.\nThe results are rendered in the web page.\n\nNow, this is where things get cool. Since Pinecone returns citations, we can actually let you load, and view, the PDF, and even go to the page in question. I demonstrated that last year: &quot;Adding PDFs to Your Webpage without JavaScript&quot;\nHere's a screen shot of the application in action. I apologize if it's a bit hard to read...\n\n\n\nOn the left side you can see the result from Pinecone, and beneath it, a list of citations. The main file is clickable, as well as the pages, and if you click a specific page, it loads that particular page.\nThe Server Side\nSo, how was this built? From the Flask side, this is the entirety of the code that handles routing and such. Since my application has two routes, its ludicrously short:\n\nThe first route just loads my homepage. We'll get to that in a bit. The second route waits for a POST from the frontend code that includes the prompt. Interesting thing about that code in the route - you can only use request.json if the caller uses application/json in the Content-Type header. So I probably should have some error checking there. (In the future. Honest.)\nThe call out to Pinecone is done in a simple Python class:\n\nIf you need to, check yesterday's post for more about the code, but even if you didn't read that post, it's probably simple enough to tell what's going on, which is a testament to Pinecone's SDK design.\nAlright, let's turn our attention to the front end.\nThe Client Side\nThe HTML is relatively simple - I've got an input field and then various divs to handle data that will be loaded via JavaScript:\n\nI'm using a bit of CSS too and you can see that at the repository link I'll share at the end.\nNow for the JavaScript. First, a bunch of setup stuff. I've said this before, but when I have variables referring to DOM nodes, I like using a dollar sign in front (kinda reminds me of jQuery).\n\nThe next bit handles the button click:\n\nThat's quite a few lines of code, but it's honestly all just DOM manipulation. Get the prompt, disable the button, call out to the API, and then work with the results. If any of that doesn't make sense, just ask below! The important bits are in the citation area. You'll notice I use a class, pdflink, for those links, and have an event handler attached to them. Let's look at that code.\n\nYeah, so kind of lame, but basically, get the URL, just the filename, and update the iframe to point to it. As you can see in the comment, I should have the static prefix come from Flask somehow, as it's possible to rename that and Flask provides a utility just for those purposes, but it was good enough.\nI'll also note, when you get results from Pinecone, the citations include a link to a cloud version of the file. You could use that, but I felt it was more 'real world' to assume I'd have mine own copy of the documents as well.\nIf you want to see the complete example, you can grab the source here: https://github.com/cfjedimaster/ai-testingzone/tree/main/pinecone/webdemo. I want to reiterate, I'm new to Flask, and Pinecone, but all of this together was less than an hours work, which is dang impressive (for both Flask and Pinecone). Let me know what you think!\n",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "pinecone"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Checkout Pinecone for Serverless RAG",
		"date":"Wed Jan 22 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1737568800,
		"url":"https://www.raymondcamden.com/2025/01/22/checkout-pinecone-for-serverless-rag",
		"content":"In my quest this year to expand my AI knowledge outside of Gemini, I was recently introduced to Pinecone. Pinecone provides the ability to create and setup a vector database via serverless, which by itself is darn handy, but it also provides a super convenient wrapper called their Assistant that makes it even easier. I've played with this a bit over the past few days and thought I'd share some of what I found. Before I do that though, check out their pricing information to see if it makes sense. What made me happy is that the &quot;Starter&quot; level absolutely let me kick the tires and test things out which I really appreciate. To do this day I've yet to do any real demos with ChatGPT because there's no free developer tier.\nI started my look at Pinecone going through the database quickstart, which has you creating vectors from ad hoc data. As I've only really worked with files and RAG, seeing this, raw (not the best term) was helpful. The guide then has you create the index, upset the vectors (upsert is a fancy way of saying, &quot;add this information to the storage system if it doesn't already exist&quot;, just FYI!), create a vector of a query, and then execute it.\nAll of this was 100% new to me, and honestly, I didn't understand it completely, but it mostly made sense and considering I did it all from a bit of Python code, it was pretty impressive.\nI then took a look at Pinecone Assistant, which takes all of the above and simplifies it quite a bit. It lets you create an assistant, associate files with it, and then query it. There is an excellent quick start which breaks down the steps into:\n\nCreating the assistant\nUploading files\nSending queries\n\nFrom what I can tell, this can only be done via their Python SDK, but looking at the reference REST information, I don't think it would be difficult at all in Node.js or other languages. Also, and this is cool, their dashboard lets you do all of the above via the web, so at minimum, you could handle the setup and seeding aspect via the web and not code if you want. Here's an example assistant based on an Adobe security PDF I uploaded. I was able to ask the question right in the tool, see the response, and note that it backs up the responses with references:\n\n\n\nOk, so what would a 'real world' (ish!) type implementation look like? I built a demo with two scripts. The first script is responsible for setup and in theory, is only run once, and again, could have been done on the web as well, but I was curious as to how much work it would be to create a script for this process I could, in theory, check into source, use in the future, etc.\nI began by importing my libraries, and defining the name of my assistant:\n\nNext, I ask for a list of assistants, see if mine exists, and if not, create it:\n\nI probably should have moved my instructions into a variable as well so it's easier to update. Ok, for the next step, I want to see if the assistant is actually ready. It does take a few seconds to setup, and their SDK/API makes it easy to check, so I shouldn't be lazy, right?\n\nI assume that makes sense, but I want to call out that last line. In a bit, I'm going to need to inspect what's in the assistant, so that last line grabs a reference to it.\nAlright, the next part simply looks for PDFs in a folder, in this case, the local folder:\n\nNow that we have a list of PDF files, we can compare the local list to what's in the assistant, and upload as necessary:\n\nNow, at this point, I stopped in my script, but correctly noted:\n\nIt wouldn't be too hard to go ahead and loop over each file and ensure it's ready, but the upload/processing happened so quickly, it felt like overkill. At the end, I'll share links to the complete file.\nFor the next bit, I just wrote a little script that let me pass prompts at the CLI:\n\nAfter my setup code, here's how simple it is to pass the query:\n\nIf you scroll down to the sample output in step five in the quickstart, you can see a sample of what's returned. It's quite extensive, but what I care about is the actual result:\n\nAnd the citations, which includes both the file name and the specific pages. This part is so important to help verify the results. (As a quick aside, don't forget you can link to a specific page in a PDF, I demonstrated this here).\n\nMy PDFs consist of three Shakespeare plays, so I first tried this:\n\nAnd got:\n\n\nIf you are into romance, you might enjoy reading \"Romeo and Juliet.\" This play is about two young lovers, Romeo and Juliet, who come from feuding families, the Montagues and the Capulets. Despite their families' hatred for each other, they fall in love at first sight and quickly decide to get married. Their love story is filled with passion, secret meetings, and tragic events, making it one of the most famous romantic tales in literature.\n\n\nAnother option is \"A Midsummer Night's Dream,\" which also has romantic elements. This play involves four young Athenians who are caught in a complicated love tangle. Lysander and Demetrius both",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "pinecone"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Turn Your Weather into Poetry with GenAI",
		"date":"Thu Jan 16 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1737050400,
		"url":"https://www.raymondcamden.com/2025/01/16/turn-your-weather-into-poetry-with-genai",
		"content":"I was struggling to get some real, productive work done (I just got back from a trip in Europe) and I thought I'd tackle something that's been in my queue for a little while now. This is, yet again, one of my &quot;bad ideas&quot; that you should probably not ever use in production, but it was fun, it made me happy, and you get to see the kind of silliness I do when I'm trying to distract myself.\nThe idea was simple - given a forecast, how well could generative AI transform it into something more fun, like poetry? As I usually do, I opened up Google's AI Studio and tried it there first. I began with a system instruction:\nGiven a forecast, rewrite it in poetry. Return just the poem and nothing more. \n\nThe second sentence there is important as in my initial tests, Gemini would return a few examples in different styles.\nI then passed a simple forecast focused on the condition, chance of precipitation, and the low and high temps:\nThe forecast for today is clear, with a 0 percent chance of rain. \nThe high temp will be 61.66 and the low 40.06\n\nThe result won't win any awards, but was kinda magical:\nA flawless sky, a sunlit stage,\nNo rain descends to turn a page.\nSixty-one and two-thirds climb,\nThen forty whispers, losing time.\nDryness reigns, from dawn till night,\nA day of sun, a glorious sight.\n\nCool! So let's build this!\nStep One - The Workflow\nOnce again I turned to Pipedream to build the workflow, starting with a scheduled trigger to run every day at 6AM. I figured that would be a good time to run as the end of this workflow will result in an email to myself with the poem.\nStep Two - Defining my Settings\nI knew I was going to have code that needed to get a forecast for my location. I could have hardcoded my location values, but I decided I wanted the workflow to be a bit more abstract. Node.js steps in Pipedream support the idea of 'props', step level values that can be defined in the UI and referenced in code. Python steps do not. So, I created a code step, defineSettings, that would act in the same manner, basically returning my configuration for the workflow:\n\nIn case you're curious, that location is for Lafayette, Louisiana.\nStep Three - Get the Forecast\nFor my weather, I used the excellent, and free, Pirate Weather API. It's API lets you get a huge amount of weather data, but for my purposes, I just needed a daily forecast. The response is still pretty huge, even with stuff filtered out. You can see an example of a complete response if you want. My code here was a grand total of 2 real lines:\n\nNote that I'm returning the first result which should be the current day's forecast. Here's what that data looks like:\n\nStep Four - Translate to English\nIn theory, I could pass this raw data to Gemini, and in theory, it could possibly grok what's going on based on the key names and values, but I wanted to make it more clear, so I added a new step that essentially translates the data above into English, focused on the type of day, precipitation and temperatures:\n\nThat's pretty wordy code-wise, but as an example, this is a possible result (not based on the JSON shown in Step 3 though):\nThe forecast for today is Snow, with a 39.0 percent chance of snow.\nThe high temperature will be 36.01F and a low of 24.35F.\n\nStep Five - Call GenAI\nThe next step is where the magic happens. I create an instance of my Gemini model with the system instruction and pass the summary from before:\n\nAs this is a scheduled task, I could have switched to the Pro model to possibly get better, and slower, results, but I was happy with what Flash provided. The result is the poem itself, so that's all I need to do here.\nStep Six - Email!\nThe final step was trivial as I used Pipedream's built-in &quot;Email Yo-Self&quot; (my name, not theirs) step. I set the subject to &quot;Your Forecast&quot; and the text is just the result from the previous step. I did a quick run and got:\n\n<img src=\"https://static.raymondcamden.com/images/2025/01/poem1.jpg\" alt=\"The sun shines bright, a cloudless sky, \nNo rain today, the heavens lie.\nSixty-one degrees, the warmth will gleam,\nThen forty low, a cooler dream.\" class=\"imgborder imgcenter\" loading=\"lazy\">\n\nPretty cool! Now, it just so happens we are about to have some kind of ice storm in the next few days. It's all over the news, people are in complete panic, dogs and cats living together, mass hysteria, etc. So I tweaked step 3 to go a few days in the future and got this lovely result:\nA veil of white, a whispered threat,\nThirty-nine percent, the chance is set.\nThe snow descends, a chilling grace,\nOn frozen ground, a frosted space.\n\nThirty-six degrees, the sun's weak plea,\nThen twenty-four, where winter's free\nTo paint the land in hues of frost,\nA day of cold, whatever the cost.\n\nAnd that's it! If you want to see the complete code (as Pipedream stores it), you may find it here: https://github.com/cfjedimaster/General-Pipedream-AI-Stuff/tree/production/weather-forecast-in-poetry-p_vQCakG2. Tomorrow, and in the next ",
		"tags":[
	        
            "generative ai",
            
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Next CodeBreak - Let's AI!",
		"date":"Wed Jan 15 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1736964000,
		"url":"https://www.raymondcamden.com/2025/01/15/next-codebreak-lets-ai",
		"content":"Hey folks! One of the things I promised to do better with the Code Break show was scheduling. With that being said, I'm here to announce the next event and I've got about 4 already on the calendar after that. Hopefully this year I can get a bit more consistent with schedule. That being said, we'll see. Life is crazy at times. ;)\nOk, with that out of the way, my next session is Tuesday, January 21, at 12PM CST. I'll be talking about AI, specifically Generative AI, with a focus on Google Gemini, but also client-side stuff as well (with Chrome's built-in support and Transformers.js). I imagine this topic will cover two or more sessions. We'll see how much I get covered.\nIn the meantime, you can find more details and RSVP here: https://cfe.dev/talkshows/codebreak-01212025/\n",
		"tags":[
	        
            "misc",
            
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Simple Blog Example in Flask",
		"date":"Mon Jan 13 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1736791200,
		"url":"https://www.raymondcamden.com/2025/01/13/simple-blog-example-in-flask",
		"content":"As part of my efforts to improve my Python knowledge, I've been looking at the Flask framework for a way to build Python-backed web apps. I've only been looking at it for a short time, but I'm really impressed with how simple it is. In some ways, it reminds me a lot of when I first saw Express. Before that, I wasn't sure I was going to like Node.js as it felt like a lot of work to build a simple app, but Express handled a lot of the boring parts. The same applies to Flask. To get an idea of how easy it is, here's the basic &quot;hello world&quot; from the quickstart:\n\nOnce you've imported Flask and defined an app, you can then begin adding routes using decorators (the @app.route portion above) with the logic defined beneath it. In the example above, a simple HTML string is returned, but to use templates, you just switch to:\n\nIn this case, Flask with look in a templates folder for the file you specify, and make use of the Jinja template language. Having never seen Jinja before this week, I had no trouble picking it up. All these various template languages have their own particular syntax and quirks and as far as I can tell, Jinja supports everything you could possibly need so it won't be a problem.\nSo as I said, I'm real excited about Flask, and I thought I'd do what I usually do when learning a server-side framework, build a super simple &quot;blog viewer&quot; app. I'm saying &quot;blog viewer&quot; versus &quot;blog&quot; as I don't want to bother with a proper admin and CRUD editor for data, just something that:\n\nRenders the last X blog posts, ordered newest first\nRenders one blog post based on a URL parameter\n\nGiven this simple set of requirements, here's how I built it.\nThe Flask Application\nFirst, my application with a grand total of two routes defined:\n\nThe first route is the home page, and I used a simple Blog Python class to handle fetching my posts. (I'll show that at the end.) Note how render_template lets you pass arbitrary data to the template. You'll see that in use in a moment.\nFor the second route, note the use of a variable portion. This is how Flask handles matching dynamic URL paths. You can validate in a few ways or keep it free form. In my case, I just wanted to match as a string to everything after the /post/ portion. This portion, called a slug, is passed to my Blog class to fetch one particular post that then gets passed to the template.\nThe Home Page\nLet's look at the first template, index.html:\n\nThe first line demonstrates layouts in Jinja. By extending my layout, I can define blocks, or variable content, that will get 'stuffed' into the template. In this case, my template supports a title and content. I used Jinja's looping construct to both filter to ten posts, sort by date, and reverse. As I type this, I literally realized a mistake - can you see it?\nI'm first filtering by the first ten items in the array and then sorting, which means I will possibly get the wrong set of posts. I just changed it to apply the numerical filtering at the end and it worked!\n\nThat's a tad bit messy, but works. I only used 2 for a second and switched to 10 in the code I'll share in the repository.\nNext, turn your attention to the links. Flask extends Jinja to add a url_for function (also available in your app code too of course) which abstracts away URL creation. This is useful in case you ever change your URLs in the future and don't want to update your HTML everywhere. You can read more about this in the URL Building part of the Flask docs. Basically this code will look at my app code, find the post route, and figure out the URL to spit out.\nMy layout uses a bit of Bootstrap, which honestly I've not used in a while and it's overkill here, but you can see how the blocks defined above are inlined in:\n\nAs an aside, it's possible to include default text in a block and a template can either add to, or replace, that default text.\nPosts\nFor the second view, I just render a basic blog post like so:\n\nThat's it. You could imagine a lot more here of course, comments, categories and tags and such, but this works for now.\nBlog Data\nThis isn't related to Flask itself, but for my blogs, I went with a simple class that read in a JSON file of data. I do a bit of manipulation including creating a slug for URLs and a proper date value for sorting.\n\nI had considering firing up a quick database but this was a heck of a lot faster.\nDoes it Work?\nOf course it does! ;) Here's a quick view of my lovely UI:\n\n\n\nAnd one more showing a post:\n\n\n\nIf you would like to see the complete source code, you can see it here: https://github.com/cfjedimaster/pythondemos/tree/main/flask/flaskblog\nOne big question I have now is - how easy would it be to get this published? I asked about this on LinkedIn and got some suggestions, but I haven't yet had a chance to really investigate that yet. I'd love to hear your suggestions below, so leave me a comment!\n",
		"tags":[
	        
            "python",
            
            "flask"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (1/11/25)",
		"date":"Sat Jan 11 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1736618400,
		"url":"https://www.raymondcamden.com/2025/01/11/links-for-you-11125",
		"content":"Welcome to the first Links For You of 2025. I'm currently writing this from, I kid you not, the Danube in Austria. My wife and I are a bit over halfway through a European vacation (one we planned before Adobe decided to give me an early Christmas gift of a layoff). So far, it's been absolutely glorious, and I plan on writing about the experience later this month. On with the links!\nCSS Light and Dark Themes Made Easy\nFirst up is the wonderfully named, &quot;Come to the light-dark() Side&quot;, by Sara Joy. This looks at how native CSS can make light and dark themes incredibly easy to implement. I had no clue it was so easy to implement in CSS, but as I've said before, CSS has improved so much over the years it feels impossible to keep up with all the features. Sara's article discusses how to tap into the user's native OS settings via CSS and has plenty of easy to understand examples.\nLinking with Text Fragments\nFor a while now I've seen sites making use of text fragments - links with embedded &quot;highlight this part when you load&quot; bits. But I never really took the time to actually look into the specification. This post by Ahmad Alfy does a great job explaining the syntax for the links and demonstrates all the various ways you can use it. As an example, the link below will load his article and highlight the first subheading:\nhttps://alfy.blog/2024/10/19/linking-directly-to-web-page-content.html#:~:text=What are Text fragments?\nSomething Wicked This Way Comes\nWhy not end our list of links with something super depressing, but sadly, probably pretty accurate. My buddy Brian Rinaldi shares his predictions for 2025 over on his personal blog and I can pretty much say I think he's 100% accurate. It may be my own current state of mind, but I don't think this is going to be a great year for us in tech, or outside of tech either. I would be incredibly happy though if Brian and I were wrong.\nJust For Fun\nHow about a little snow in your web pages? Zach Leatherman provides you a solution with the simple &lt;snow-fall&gt; web component as shown below:\n\n\n\n\nYou can read more about it here: https://www.zachleat.com/web/snow-fall/\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Bluesky AI Sentiment Analysis Dashboard",
		"date":"Fri Jan 03 2025 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1735927200,
		"url":"https://www.raymondcamden.com/2025/01/03/building-a-bluesky-ai-sentiment-analysis-dashboard",
		"content":"As the &quot;Great Social Network Wars&quot; carry on (my term, not anyone else), I'm finding myself more and more enjoying Bluesky. I do more posting on Mastodon, but Bluesky reminds me a lot more of early Twitter. Threads is... ok, but has felt too corporate. I can't even remember the last time I checked it. Earlier this week, I was poking around the Bluesky API and was incredibly happy to discover that their Search API does not require a key and supports CORS, which means a simple client-side application could make use of it. In the past I had built similar tools for Twitter, back when it had a decent API, and I thought it might be fun to build something for Bluesky, specifically, a way to monitor sentiment of keywords in real time. Here's what I created.\nWhat the App Will Do\nAt a high level, the app lets you:\n\nEnter a keyword to check\nOn a schedule, get recent posts for that keyword\nFor each post, analyze the sentiment of the text\nGet and return an average\nOptionally let the user delete the keyword from the dashboard\n\nFor my app, I kept it incredibly simple, and ugly, and there's a number of UI/UX things that could be improved, but let's look at how I got it together.\nThe Search API\nThe first thing I did was play a bit with the Search API. It contains multiple different arguments but at minimum, requires a search query.\nAs a minimum example, this will return posts with my name:\nhttps://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=Raymond+Camden\nThe top level result is an array of posts. Here's two as an example:\n\nThe API supports pagination parameters, but for my usage, a default set of 25 items felt like a good enough sample size. As you can see, quite a bit of data is returned, but for each post, you can get to the text via the record.text key. I did add one parameter to my search code, and that was adding lang=en, to focus on English. Modify or remove that if you need to. Here's a minimal code sample in JavaScript:\n\nGetting Sentiment\nTo perform the sentiment analysis, there's a large variety of options here, but I really wanted to stick to client-side code only. For me that would come down to two options, Transformers.js or Chrome's new built-in AI functionality. I first covered Transformers.js a few weeks ago, Using Transformers.js for AI in the Browser, and I really liked how easy, and usually quick, sentiment analysis was done. With that in mind, I decided on Transformers.js.\nThe App\nOk, so as a warning, this isn't terribly pretty, but let's take a look at the app. In HTML, it's rather simple, a place to enter keywords, a status div, and a results div:\n\nMost of the work is done in JavaScript, and while I'll share the complete demo below, let me share the pertinent bits. The code to handle adding a topic is basic DOM manipulation, adding a string to an array of topics called, topics. In my startup routine, I do handle loading in and storing my core Transformers.js model:\n\nThe important part is the actual analysis which is done on a schedule. That core function is below:\n\nI basically fire off calls to my analysis function and store the resulting promise in an array, and when done, pass the results off for rendering.\nHere's how I get the sentiment:\n\nBasically, hit the Bluesky search API, and for each result, I call my classifier object and add the result to a total I can do an average on. Each result contains a label, POSITIVE or NEGATIVE (in theory, NEUTRAL is possible too, but I never saw it). Each result also has a score, which is always positive, but I flip it negative so that in theory, my average will range between -1 and 1. I also return a bit of metadata in the result like the orignal topic, how many items were found, and when it was generated.\nThe last bit, the rendering, is fairly simple. The only real oddity here is that it's possible for someone to remove a topic while analysis is happening, so I did a quick check to remove that if it happens.\n\nI've embedded the complete application below, but you can also open up the live demo here: https://codepen.io/cfjedimaster/live/jENaEMV.\n\n  See the Pen \n  BS Search Panel by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEverything Wrong...\nThat's a scary heading. ;) So, there's quite a bit that could be improved here to make this a nicer dashboard. I had considered using Shoelace to make it prettier, and that would be great I think. Also, I'd like to add a proper list of topics being checked so you can see them all the time, remove, add, etc. Right now if you add X as a topic, you won't actually see it till the first result is returned. Users may think it's broken, so that's not good.\nBut - my biggest question is - does anyone find this useful? I'd absolutely be willing to put some love into this and launch it as a proper web app, but I'd like to know first if folks would actually use it. ;) Leave me a comment below!\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Onwards to 2025...",
		"date":"Mon Dec 30 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1735581600,
		"url":"https://www.raymondcamden.com/2024/12/30/onwards-to-2025",
		"content":"For a while now I've had a tradition here where I end my &quot;blog year&quot; with a wrap up post looking back at how the year went and figuring out what I want for the upcoming year. This is, honestly, a post just for myself, but as usual, I'm always open to what people think, so feel free to leave me a comment below.\nCareer and Conferences\nThis year marked my (nearly) fourth year at Adobe, but as I said earlier, my time has ended. While I'm not happy with the timing, I have to be honest with myself and say it was time to move on. I am incredibly proud of what I accomplished at Adobe. I grew quite a bit as a developer evangelist/advocate, and I can look at what I did for the Acrobat Services team with a great sense of pride. However, I had been trying to increase my impact and help other organizations within Adobe, and had run into somewhat of a brick wall. I love my role and can't wait for the next role so I can continue to grow.\nThis year I gave 17 presentations, 12 virtual and 5 in person. For a while I was tracking all my CFP submissions, acceptances, and rejections, but honestly, it got too depressing. I had numerous rejections. But from what I hear, so did most of my colleagues, so I'm trying hard to not be negative about it. I did stop with the tracking (it was grunt work I didn't enjoy), but also try to keep in mind what topics seem to get the strong positive reactions. It should come as no surprise that the AI topics are hot as heck right now.\nCurrently, I've gotten one rejection and one acceptance for 2025. I'm probably not going to submit as much until I can nail down my next role. That being said, I'm always open to being asked to speak, so if you've got an event, and can handle travel if it's in person, just ask!\nWriting\nWithout a doubt, I'm incredibly pleased with my writing efforts over the past year. Over the year, I posted here 144 times, which is a pretty good clip I think. My goal is one a week and I blew that away. I published on Frontend Masters multiple times, and I even co-wrote a book (although it was 'just' an update).\nFor 2025... more of the same. My blogging will be very low in January as my wife and I are taking our first cruise, but outside of that, I'll have the same goal - a post a week and continued paid writing engagements for other publications. I don't say this a lot, but the fact that I get paid to write, the fact that I can call myself a &quot;professional writer&quot;, is a huge thing to me.\n2025\nSo, job one, find a job. Job two, stop playing with Python and really dedicate myself. I've been saying that for years, but with so much Gen AI being done in it, I've got a great excuse to continue to improve my skills there. (I even applied at a certain Python-related company, but got rejected before an interview.) I'm going to try to stop writing any server-side JavaScript and save it for my web platform projects and such.\nAbove all - my intent is continue to grow, love my wife and kids, cherish my friends, and appreciate what I have. Oh, and I'm going back to therapy as soon as possible. Anxiety is still a killer for me, and it's gotten worse the past few months, so it's time, but I'm waiting until I've settled the job situation.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Links For You (12/28/24)",
		"date":"Sat Dec 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1735408800,
		"url":"https://www.raymondcamden.com/2024/12/28/links-for-you-122824",
		"content":"Welcome to the last Links For You for 2024. Believe it or not, I started this series way back in April of 2022, and I don't know about you, but it's been one of my favorite features of my blog. I love sharing cool links (and music videos!) with readers, and I hope yall have enjoyed it as well. This is my second to last post for the year so there's still a bit more content coming, but for now, let's get into the cool stuff!\nAn Alpine.js Bluesky RSS Reader\nFolks know I love Alpine.js, and this first post is a great example of why. Andy Jarrett demonstrates how he built a RSS reader of his Bluesky profile using Alpine.js. It's relatively simple to do so, and one of the reasons I think Alpine.js is so great is that it's a lightweight addition to your web page, versus heavier options like React and Vue.\nJavaScript's Nullish Coalescing Assignment Operator\nI'll be honest with you. I can't always remember all the &quot;special&quot; operators JavaScript has. Once again, this is a good reminder about why I tend to fail code tests in interviews. (And I'm fine with that.) In this post, Trevor Lasn explains the nullish coalescing operator (??=) and how you can use it in your code.\nThis is explained better in the post, but to be clear, this operator is not the same as the nullish coalescing operator. One is always going to assign a value and one (the nullish coalescing assignment operator) will only assign if the value is currently null. Take my word for it, just read the post.\nDestructuring in JavaScript\nLast up is another JavaScript post, this one focused on destructuring: &quot;A guide to destructuring in JavaScript&quot;. This is a fairly intensive guide to destructuring filled with loads of examples. I thought I knew this topic pretty well but I definitely ran into a few things I did not know, so once again, this is well worth your time.\nJust for Fun\nLast but not least, how about a freaking music video built in CSS and HTML? Yes, just when you thought you couldn't fall behind in how awesome CSS has become, here's a great example!\n\n  See the Pen \n  CSS Music Video - No Images - Pure Code. by Ben Evans (@ivorjetski)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Automating Object Detection with Google Gemini GenAI and Pipedream",
		"date":"Mon Dec 23 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734976800,
		"url":"https://www.raymondcamden.com/2024/12/23/automating-object-detection-with-google-gemini-genai-and-pipedream",
		"content":"For my last technical post of the year (although I can't promise I'll stop blogging!), I wanted to share an interesting workflow I built using Google Gemini and Pipedream. The idea was somewhat simple - how difficult would it be to build a &quot;general purpose&quot; workflow to look for objects in images and trigger an alert if certain things were found. Here's what I was able to build.\nStep One - Image Input\nIn my mind, I imagined this workflow would be tied to some service that was either streaming in video or generating still images. You could image a security camera posting new pictures every 30 seconds or so, or some other system that takes a picture at a regular interval. In order to simplify things for this particular demo, I built the workflow to listen for a POST that includes an image. In Pipedream, that meant a URL trigger (Pipedream gives you a unique URL) and I used Postman to test.\nFor the trigger, I selected &quot;Return HTTP 204 No Content&quot; as a response, as the service sending in the data shouldn't need to wait for the workflow to finish, nor care what happens. It's just sending in the picture.\nOn the Postman side, I did have to do one small tweak. As documented, if your payload is over 512KB (easy with images), you will get a 413 Payload Too Large error, unless you pass in a header, x-pd-upload-body: 1. That's easy enough on the Postman side.\nStep Two - Get the Image\nThe next step is a code step that attempts to get the image sent to the service. I look for it in the trigger data, and if it exists, download it to /tmp, after a few sanity checks on the content type. Here's the Python code I used for this step:\n\nPart of this came right from the Pipedream docs which were real helpful. Note that the step ends with returning the file.\nStep Three - Upload to Gemini\nThe next step handles uploading the file to Gemini via the Files API. This is a temporary file storage system provided by Google to let you build multimodal prompts with Gemini. Here's that code:\n\nTwo things to note. First, notice the comment on top. This isn't always required, but helps Pipedream grab the right package from PyPi. Again, this is documented: Use PyPI packages with differing import names\nThe last thing to note was a bit more trickier. I had attempted to return the result from the upload_file operation, but it wasn't a simple object and couldn't be serialized by Pipedream. Returning the name though let me handle this later in the workflow, as you'll see in a bit.\nStep Four - Item Detection\nAlright, now for the magic. We need to ask Gemini to try to identify the items in the picture. To handle this, I did a few things:\n\nI used a system instruction to specify what I wanted Gemini to do.\nI used JSON schema to lock down the result to an array of strings. I had not used JSON Schema in Python before, but I did my initial testing in AI Studio and it's handy Get Code button actually generated exactly what I needed!\nI used the Files API to get the file I uploaded in the last step. Now... I could have skipped this step if I just combined action with the previous one. I try to keep my Pipedream workflows as simple as possible, but in this case, it may have been better to simply combine this and the last step. Obviously, this was more a 'philosophical' decision on my part and you should feel free to do it the way that makes sense to you.\n\nHere's the complete code for this:\n\nSteps Five and Six - Check for My 'Target'\nThe fifth step is the most important. In this step, I take a list of things I care about, the &quot;targets&quot;, and see if any of the targets match what was found by Gemini:\n\nNote that it's possible to match multiple items in one picture and the code correctly handles that.\nThe next step simply exits if nothing is found. So, going up to what I said earlier about keeping my Pipedream workflow simple, this too could have been in the previous code block. I just felt it was nicer on it's own:\n\nStep Seven and Eight - Emailing the Result\nOk, if we get to this part of the workflow, we've got a match. For notification purposes, I went the easy route, using an email. This is done in two steps. The first step creates an HTML string that is then passed to the final step, a 'built-in' Pipedream step that emails the owner of the workflow. This is handy as no email API is required.\nHere's the HTML string I came up, and it's pretty boring. I could have included a date and time stamp, but I figured the email itself would have that. I could have even looked for GPS metadata in the picture and included that, but I kinda figure you would know where the images are coming from.\n\nOne small thing I don't like about this. You can see I include the image in the email. This is using the fact that Pipedream uploaded the image to temporary file storage. However, the link will not last long, which means the email itself will be broken eventually.\nIn this case, I'd probably use a 'real' email API, like Sendgrid, and base64 encode the image s",
		"tags":[
	        
            "generative ai",
            
            "python",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Summarizing with Transformers.js",
		"date":"Wed Dec 18 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734544800,
		"url":"https://www.raymondcamden.com/2024/12/18/summarizing-with-transformersjs",
		"content":"Earlier this month I took my first look at using Transformers.js, a JavaScript SDK around multiple different models hosted by Hugging Face. My initial experiments worked pretty OK I think. The sentiment analysis felt pretty good, and the object detection (with a cat demo of course), worked pretty good as well. I was curious how well summarization would work, and while I'm not quite as impressed as I was before, I thought I'd share what I found. (And it's 100% possible I'm not tweaking the right knobs to get better results, so if you see a way to improve my results, leave me a comment!)\nA Basic Test\nIf you remember from the first post, usage can be fairly simple. Import the library:\n\nThen get your pipeline:\n\nGetting a summary just requires passing text:\n\nThis returns an array of objects where each item has a summary_text value. A trivial implementation of this is below.\n\n  See the Pen \n  Transformers.js Test - Summarize by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIf you test this, you'll notice right away this is a bit... &quot;beefier&quot; than the previous results in that it definitely seems to make the CPU work a bit harder. The results can be hit or miss. So, I tried with this input, some text from the Wikipedia for Milan Kundera.\nMilan Kundera was born on 1 April 1929 at Purkyňova 6 (6 Purkyně Street) in Královo Pole, a district of Brno, Czechoslovakia (present-day Czech Republic), to a middle-class family. His father, Ludvík Kundera (1891–1971), was an important Czech musicologist and pianist who served as the head of the Janáček Music Academy in Brno from 1948 to 1961.[8][9][10] His mother Milada Kunderová (born Janošíková)[11] was an educator.[10] His father died in 1971, and his mother in 1975.[10]\n\nKundera learned to play the piano from his father and later studied musicology and musical composition. Musicological influences, references and notation can be found throughout his work. Kundera was a cousin of Czech writer and translator Ludvík Kundera.[12] In his youth, having been supported by his father in his musical education, he was testing his abilities as a composer.[13][14] One of his teachers at the time was Pavel Haas.[15] His approach to music was eventually dampened due to his father not being able to launch a piano career for insisting on playing the music of modernist Jewish composer Arnold Schoenberg.[14]\n\nAt the age of eighteen, he joined the Communist Party of Czechoslovakia in 1947.[16] In 1984, he recalled that &quot;Communism captivated me as much as Stravinsky, Picasso and Surrealism.&quot;[17]\n\nHe attended lectures on music and composition at the Charles University in Prague but soon moved to the Film and TV School of the Academy of Performing Arts in Prague (FAMU) to study film.[18] In 1950, he was expelled from the party.[13] After graduating, the Film Faculty appointed Kundera a lecturer in world literature in 1952.[19] Following the Warsaw Pact invasion of Czechoslovakia in 1968, he lost his job at the Film Faculty.[20] In 1956, Kundera also married for the first time, the operetta singer Olga Haas, the daughter of the composer and his teacher Pavel Haas and the doctor of Russian origin Sonia Jakobson, the first wife of Roman Jakobson\n\nAnd after ten seconds, got:\nMilan Kundera was born in 1929 in Purky��žova, a district of Czechoslovakia. His father, Ludvík Kundero, was an important Czech musicologist and pianist. He was a cousin of Czech writer and translator Ludvísk Kundersa.\n\nNot sure what to think of the encoding issue there. That feels like probably my fault, and not the library, but it's something to consider. I also noted that the Summarize button took 3 seconds to become disabled, despite being run first.\nI then tried my About text, which is now out of date, but:\nMy name is Raymond Camden. I'm a married father of eight living in beautiful Lafayette, Louisiana. I am a Senior Developer Evangelist working at Adobe. Most of my time is spent writing, researching, or presenting. When I'm not behind a computer, I'm an avid Xbox/Playstation player, enjoy movies, and read like crazy.\n\nI've been lucky to have been invited to speak at many conferences over the years. If you would like me to speak at your conference or organization, please contact me. I can cover pretty much any topic you see my blog about, but feel free to request just about anything. I love presenting on topics I'm not yet familiar with as it gives me a chance to learn something new.\n\nI'm somewhat of a Star Wars nerd - but don't tell anyone else I told you that.\n\nIf you find this content useful (currently at 6579 posts), please consider visiting my Amazon Wishlist to show your appreciation. Since Amazon will often not tell me who purchased a gift for me, drop me a line to let me know!\n\nAnd got this:\nRaymond Camden is a Senior Developer Evangelist working at Adobe. He's a married father of eight living in Louisiana, Louisiana. He is an avid Xbox/Playstation player, enjoy movies, and read like crazy. If yo",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding PDFs to Your Webpage without JavaScript",
		"date":"Tue Dec 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734458400,
		"url":"https://www.raymondcamden.com/2024/12/17/adding-pdfs-to-your-webpage-without-javascript",
		"content":"Edited at 4:03PM to add a small note to the end. In my time at Adobe, one of the products I evangelized was the PDF Embed API, a JavaScript library for adding PDFs to a web page. I still recommend this library of course, but I was thinking this morning about how you could get similar results without JavaScript. Remember, you are allowed to build a web page and not ship any JavaScript. It's ok, I won't tell.\nBefore looking at what I built, some context to why someone would use a library like Adobe's PDF Embed:\n\nBrowser's have great built-in PDF support, natively, but the display is typically the entire page, which means you lose the context of the rest of your site.\nThere's limited to no interaction between the PDF and your code, so if you want to do reporting (&quot;Most users read only X pages of the document&quot;) or interaction (&quot;on page load, go to page X and highlight word Y&quot;), you're out of luck.\n\nThe Simple Solution\nThat being said, is there a simpler solution to getting a PDF into a web page, in context with the rest of the page? Absolutely - an iframe. This is probably obvious, but I don't necessarily see this often &quot;in the wild&quot;. Here's a simple example:\n\nI've got two main elements on the site. First, a direct link to the PDF, which honestly isn't needed but I kept it there in case I wanted to hit the PDF directly, and then an iframe pointing to the same thing. I'm able to size the iframe, in this case with attributes, but CSS could have used as well.\nAlso note that iframe supports lazy loading which means if this element were on a larger page, farther down the viewport, the PDF wouldn't even load at first. However, note that the &quot;how far away until load&quot; logic is a bit different in Chromium browsers. I noticed it was still loading for me even off screen, but if I added a good chunk of padding, it eventually worked as expected. This comment on Google's support forums suggests this is expected. I did a similar test in Firefox and the 'window' to get the PDF to not load was somewhat smaller. Just keep in mind, YMMV here, but as it takes all of two seconds to add the attribute, I would do so.\nFinally, also note that CSP will play a part here, but my assumption is that you're going to be displaying your own PDFs. If you intend to iframe another site's PDF document, test first!\nYou can see this demo here: https://cfjedimaster.github.io/webdemos/iframe_pdf/test1.html\nNote that you want to set the dimensions of your iframe based on the contents of the PDF. The one I used could probably have used a thinner, taller iframe. I point this out to just remind folks that design is not my strongest skill. (Or second strongest...)\nGetting Dynamic\nAs I mentioned, one of the things that Adobe's PDF library gives you is more control over the experience, and the ability to use JavaScript to manipulate the PDF a bit. Believe it or not, there's support for some of this just by using a tweak to the URL. It's impossible to find on Adobe's site anymore, but I found a copy of the Acrobat docs for 'Parameters' which work with hash marks. Ie, this URL:\nhttps://static.raymondcamden.com/enclosures/cat.pdf\nGoes right to the PDF. This URL, however, goes to page 2:\nhttps://static.raymondcamden.com/enclosures/cat.pdf#page=2\nIf you look at the documentation, there's a number of different options, but support is a bit hit or miss. Setting the page seems to work consisntently. Turning off the toolbar didn't work in Safari. Search, which seems really useful, but only worked in Firefox. If you want to use one of these options, again, it's harmless, but test first, and be ok with it not working. Here's a simple example of embedding a PDF and starting on page 2:\n\nAnd here it is:\n<iframe src=\"https://static.raymondcamden.com/enclosures/cat.pdf#page=2\" \n\twidth=\"100%\" height=\"700px\" loading=\"lazy\">\nTurning off the toolbar only seemed to work in Chromium for me, but, it does give a bit more focus to the PDF if you are ok removing user controls:\n\nAnd the result:\n<iframe src=\"https://static.raymondcamden.com/enclosures/cat.pdf#toolbar=0\" \n\twidth=\"100%\" height=\"700px\" loading=\"lazy\">\nNote that while you can easily use JavaScript to change the src value of an iframe, for me, this never worked, even if I included a bit of random junk at the end via new Date(). You could remove the iframe and add a new one, but that feels a bit heavy-handed to me. That being said, it did lead me to my next demo.\nA PDF Viewer\nGiven that we can change the src of an iframe, it would be possible to build a simple, inline, PDF viewer for multiple documents. I'll begin with a list of two PDFs and a default one visible in the iframe:\n\nNext, I'll use a bit of JavaScript to handle the clicks:\n\nBasically, grab all the links to PDFs, and for each, use an event handler to bypass the normal link and instead update the iframe. You can demo this here:\nhttps://cfjedimaster.github.io/webdemos/iframe_pdf/test4.html\nYou could get more fancy. I could upd",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "The Twelve (Generative) Days of Christmas - 2024 Edition",
		"date":"Mon Dec 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734372000,
		"url":"https://www.raymondcamden.com/2024/12/16/the-twelve-generative-days-of-christmas-2024-edition",
		"content":"Last year I did a fun little experiment where I asked a few different generative models to generate\nimages based on the classic Twelve Days of Christmas song. For those unfamiliar, the song is about a series of gifts given over twelve days:\npartridge in a pear tree\ntwo turtle doves\nthree French hens\nfour calling birds\nfive golden rings\nsix geese a-laying\nseven swans a-swimming\neight maids a-milking\nnine ladies dancing\nten lords a-leaping\neleven pipers piping\ntwelve drummers drumming\n\nTo be clear, this was done for fun, nothing more. Also, the prompts were literally just the lyrics, nothing more (with some exceptions, see the details below). In a 'real world' example if you wanted to generate images for the song, your prompt would be (should be) far more descriptive. As an example of why this is important, many of the Bing results look like Easter-related pictures, I'm guessing due to the bird input. Since this is for fun, I just went with it.\nBefore getting into results, some details:\n\nInitially, I did not alter the text in any way, except to sometimes swap out the numerical word (&quot;nine&quot;) with a digit (9). Pretty much every service I used had trouble with showing X items of something. I decided to not fight it and just go with it. Honestly, I was a bit surprised this continued to be an issue in 2024\nNearly every service blocked eight maids a-milking. I'm not really sure why. Eventually, I found that this prompt generally worked &quot;eight young women working with cows on a dairy farm&quot;.\nIn general, I picked the best image out of the options I got, but obviously, that was my personal opinion.\n\nThis year I tested with Bing, Firefly, Leonardo, and Meta. For each of the results, you can click through for the original, larger version.\nBing\nBing made some absolutely beautiful results. As I mentioned above, none very &quot;Christmas-y&quot;, but that's expected due to the brevity of the prompts. Day four, in particular, was funny as heck.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFirefly\nThis is where I'd usually say that I work for Adobe and the results may be biased, but hey, that's not a problem anymore!\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nLeonardo\nThis was the first time I used Leonardo, and while it was pretty cool, it also automatically took my prompts and rewrote them in a much more verbose manner. What's weird is - I can't see a way to disable that. I'm probably missing it in the UI, and I appreciate the thought, but if it's not something you can control, I'm not sure I'd use it. I'm pretty sure it's an option I just can't find, but keep in mind if you decide to give it a spin.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nMeta\nAnd last but not least... Meta. They get a prize for their drum-human-mashup result at the end.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Links For You (12/14/24)",
		"date":"Sat Dec 14 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734199200,
		"url":"https://www.raymondcamden.com/2024/12/14/links-for-you-121424",
		"content":"Good morning folks, as I shared yesterday, this week has been a bit rough as I found out my job was eliminated at Adobe, but the outpouring of support, and links to jobs, has been overwhelming. You people are pretty darn good, you know what? I'm confident I'm going to be able to land a good job, but at the same time, it's going to be a heck of a lot less stressful once I actually do. On top of that, I've got a head cold, which is annoying af as the kids say, but, I'm alive, surrounded by people who love me, well fed and warm, so all things considered, I'm pretty dang lucky. Let's get to the link.\nAI in the Browser - A Playlist\nA month or so ago, and I'm having trouble finding the link, Google hosted a one day conference on AI in the browser, covering things like Transformers.js, Tensorflow, and their own efforts to add AI natively to Chrome. The playlist for the conference is available and it's got some great content. Each one is relatively short and can be watched over a lunch break, and I definitely recommend checking it out. I'm only about half way through myself, but plan on finishing it soon.\n\nA-Maze-ing JavaScript\nNext up is a fun one, generating random mazes with JavaScript, by Paul Hebert. His post goes into the details of generating mazes, something that's fascinated me for years, although the last time I did anything in this space was with ColdFusion way back in 2009: Generating mazes in ColdFusion. Hebert's post does a great job of breaking down the steps and explaining the code.\nYou can play with a finished demo below:\n\n  See the Pen \n  Random Maze Generator by Paul Hebert (@phebert)\n  on CodePen.\n\n\n\nCooking with Eleventy\nLast up is look at adding Cooklang support to Eleventy: Adding Cooklang Support to Eleventy Three Ways. As the title says, Robb demonstrates three different ways to add Cooklang, a recipe markup language in Markdown, to Eleventy. As I've said before, one of Eleventy's many strengths lies in it's customizability, and this is a superb example of that.\nI currently use Saffron for my recipes, and while it's a very nice app, I'm almost at the limit of the free tier, and while I think the app has value, I'm don't think I'm going to pay the subscription fee to upgrade (if I could do a one time purchase I'd consider it). I've been thinking for a while now about moving my recipes to a simple web site. I talked about using Saffron's output way back in 2022, Use Your Saffron Recipes in the Jamstack, but I think I may look at converting Saffron's output to Cooklang instead. When I have time. Ahem.\nJust For Fun\nOne of the reasons my wife and I use Spotify so much is music discovery. We'll put on a favorite song, let it finish, and often Spotify will riff into things we like, and things we've never heard of. A few days ago, Spotify suggested &quot;Indie Frequency&quot;, a playlist of new indie songs from black artists. Sadly, I don't think I recognized more than one artist on this list. Happily, it was damn good.\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "In Search of My Next Role",
		"date":"Fri Dec 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1734112800,
		"url":"https://www.raymondcamden.com/2024/12/13/in-search-of-my-next-role",
		"content":"This week I discovered, unfortunately, that my position at Adobe has been eliminated. I'm incredibly proud of what I've achieved during my time at Adobe, but now need to find my next opportunity. If you've ever gained anything from one of my posts, or presentations, I'd absolutely love a recommendation or referral for a position in developer relations. I'm also looking for a role where I could mentor, or lead, a team of developer advocates/evangelists. So, if you know of a role, or have a role yourself, please reach out!\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Christmas Code Break - Next Tuesday",
		"date":"Tue Dec 10 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1733853600,
		"url":"https://www.raymondcamden.com/2024/12/10/christmas-code-break-next-tuesday",
		"content":"Hello friends. The next, and final (for the year!) Code Break will be Tuesday, December 17th, one week from today. I've got some surprises in store and would love to use this last session to answer questions from my audience. If you've got a question you would like me (and others - oh wait, that's part of the surprise!) to answer, leave me a comment below and I'll try to get to it. I hope to see you there!\nRSVP here: https://cfe.dev/talkshows/codebreak-12172024/\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Classifying Documents with Generative AI",
		"date":"Mon Dec 09 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1733767200,
		"url":"https://www.raymondcamden.com/2024/12/09/classifying-documents-with-generative-ai",
		"content":"Generative AI and documents is a fairly common topic these days, typically in the form of creating summaries or asking questions about the documents. I was curious how generative AI could help in terms of classification. Way back in January of this year, I blogged about using Google's Gemini API to classify images based on whether they were a photo, screenshot, or meme: &quot;Using GenAI to Classify an Image as a Photo, Screenshot, or Meme&quot;. That actually worked well and I thought perhaps it could work with text as well. Specifically:\n\nYour organization gets an influx of documents, lets say many per day...\nAnd you would like to categorize them for sorting/processing later\n\nBefore playing with this, I made a basic assumption, that being that while I thought generative AI could probably give a category to anything, it would probably work a heck of a lot better in cases where your inputs fall into a certain set list of categories.\nFor example, a company's HR team could have resumes coming in with categories being useful for the types of jobs applicable to the candidate. Legal firms could categorize documents based on the type of case (real estate, marine law, etc).\nGiven this assumption, I created a test. For my input, I decided to use Shakespeare plays, and while we probably won't see many more of those, it does lead itself to a simple classification:\n\nComedies\nTragedies\nHistories\n\nLet's look at the code for how a classification system could be built.\nThe Code\nFirst, I'll begin with my imports. I'm still doing most of my GenAI work in Node.js, but obviously this could be done in Python, or any language with the REST API.\n\nOne thing I'll note here is that I'm using Gemini 1.5 Pro, not Flash. With the assumption that this code is a backend process, we want the AI to take it's time, we aren't needing to rush a response back to a human.\nNext, we'll define a JSON schema to tell Gemini how ot shape the response. Again, this is an automated process so JSON makes sense:\n\nNote the use of an enum to restrict the results. I also asked for 'reasoning', thinking perhaps that it could be logged someplace for review later by a human.\nI follow the schema up with a system instruction:\n\nAnd then create my model:\n\nThe final bit is the function itself:\n\nIt uses the Gemini Files API to upload the document (and assumes it is a PDF, but you could make it more generic) and then runs the simple prompt on the document. That's literally it.\nFor a test, I whipped up a run on 3 plays:\n\nMy 3 PDFs were &quot;A Midsummer Night's Dream&quot;, &quot;Romeo and Juliet&quot;, and &quot;Henry IV - Part 1&quot;.\nHere's what I got back for &quot;A Midsummer Night's Dream&quot; (I added some line breaks in the reasoning for easier reading:\n\nHere's what I got for &quot;Romeo and Juliet&quot;:\n\nAnd finally, &quot;Henry IV - Part 1&quot;:\n\nIf you would like the complete code for this demo, you may find it here: https://github.com/cfjedimaster/ai-testingzone/tree/main/doc_classification As always, I'm interested in what you've done in this space, so leave me a comment below!\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Transformers.js for AI in the Browser",
		"date":"Tue Dec 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1733248800,
		"url":"https://www.raymondcamden.com/2024/12/03/using-transformersjs-for-ai-in-the-browser",
		"content":"Two weeks ago I had the pleasure of attending, and speaking at, connect.tech. One of the cooler presentations I saw was from Danielle Maxwell where she discussed using\nAI in the browser and introduced me to Transformers.js. I'd heard of this before, but wasn't quite aware of how easy it was to use. While this isn't necessarily going to replace a &quot;real&quot; GenAI server, it does feel compelling enough to something to consider. As my readers know, I've been playing with Chrome's attempt to bake this in as well, and while that's not quite ready for real use yet, Transformers.js feels like something you could play with right now.\nHow to get started?\nUsing Transformers.js makes use of what they call a 'pipeline' API. You will import the general API into your code and then select a pipeline based on your use case. The docs list a set of tasks that covers things like:\n\nGeneral question answering on text\nSummarizing\nTranslation\nImage classification and detection\nAudio classification\n\nAnd more. Again, check their docs for a full list. Let's look at an example of how easy it is to get started.\nDetecting Sentiment\nTo get sentiment on text, you can get the sentiment-analysis pipeline like so:\n\nThen you can pass a string to the classifier object:\n\nThis returns an array of results (for me, an array with one result always) where each result is an object with a label and scope:\n\nIn a real application, you could use this to provide realtime feedback on user input. I could imagine this being useful for customer service reps and such to help ensure they're being positive in their responses. I built a simple Alpine.js demo you can see below. To test, just start writing and go for something mean, or happy, you decide.\n\n  See the Pen \n  Transformers.js Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nYou probably don't need to show the score as I do, but could instead try using it as a filter, i.e. if the score is too low, don't bother reporting what it found.\nHow much does this impact the browser in terms of downloading the bits required for analysis? I put up a copy of the code from CodePen here, https://cfjedimaster.github.io/ai-testingzone/transformersjs/test1.html, and looking in devtools, I see 111 MB used in storage. That's not inconsequential, but also not terribly bad on a decent connection. I'd absolutely ensure that this was used in a progressive enhancement manner, ie, don't require a result to let the user submit text, but if it can be loaded and used, it would be a helpful addition.\nThis is a useful reminder that the &quot;Application&quot; tab of your dev tools (in Chrome/Edge at least, but other browsers report this as well), you can dig into these details:\n\n\n\nObject Detection in Images (for Cats, of course)\nFor the next demo, I built a demo that modified the example from the &quot;Building a Vanilla JS Application&quot; guide. This is a great tutorial (you can test out the demo as well) and a good example of another powerful feature, finding objects in images.\nIn their guide, they explain how this pipeline can find objects and optionally be customized for how confident it is before adding a result and whether or not the bounding boxes are pixels or percentages of the source.\nNow, their demo goes on to actually add visible bounding boxes and labels to the source, but let's try something simpler - using the device camera to determine if we've taken a picture of a cat.\nFor my demo, I made use of Alpine.js. My front-end code is just a button to activate the camera (or file picker on desktop) and a place for results:\n\nThe JavaScript is a bit more complex. I'll skip over some of the Alpine.js stuff and focus mostly on the Transformers.js stuff. In the Alpine.js init method, I wait for the pipeline to load the code I need:\n\nOnce this is done, the user can actually click the button. What happens when you take a picture (or select an image)? First, I get the selected image from the event:\n\nI then set up a file reader to get the bits:\n\nInside the onload method is where the real work happens. First, get the actual &quot;data&quot; url value and assign it to the DOM for a thumbnail:\n\nI can then ask the detector to... well, detect:\n\nThis returns an array of objects consisting of a label (what it thinks it is), a bounding box and a confidence score. Here's a source image:\n\n\n\nAnd here's the result:\n\nI'd argue that the book results are invalid, but perhaps sensible? The cat is perfect of course.\nOk, so given that result, I used the following code to look for cat or cats:\n\nI probably could have done that in one line, not two, but this is why I can't pass the Google tech screen.\nOk, so given that, you can point your phone (or pick a file) at a cat and it will see if a cat was found. Shockingly, no cats were in my office, so I had to get off my butt. First, a negative test:\n\n\n\nAnd then a dog:\n\n\n\nAnd here's two positive results (pardon the audio UI on the first shot):\n\n\n\n\n\n\nFor folks curious, the first cat is Wednesday,",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Next Code Break - December 5th",
		"date":"Mon Dec 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1733162400,
		"url":"https://www.raymondcamden.com/2024/12/02/next-code-break-december-5th",
		"content":"Hey folks, just a quick note. My next Code Break show will be this Thursday, December 5th, at 12PM CST. I apologize for the randomness of these sessions. My goal in 2025 is to get them a bit more consistent. We'll see how well that works out. ;)\nTo RSVP for the next show, head over to https://cfe.dev/talkshows/codebreak-12052024/. I'll be doing more building on a blog with Eleventy. I'm also hoping to have another session later in the month!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (11/30/24)",
		"date":"Sat Nov 30 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1732989600,
		"url":"https://www.raymondcamden.com/2024/11/30/links-for-you",
		"content":"Hello folks - I'm a week or so behind on my schedule for these posts, but the last few weeks have been incredibly hectic. I had 5 or so (I've lost track honestly) online presentations and one in-person event at connect.tech, and of course, this week was Thanksgiving. I'm also behind on scheduling for my &lt;Code&gt;&lt;Br&gt; show, which I hope to fix up later today. I wish I could look forward to things get less busy, but the next few weeks will be busy as well! I've got two upcoming presentations on Gen AI coming up! Ok, enough whining, let me share some links!\nAvoid Amazon's Echo Show\nIn theory you can stop reading at the title, Please don't buy an Echo Show, but it's something I've been saying to folks as well. The excellently named &quot;crash the arcade&quot; author talks about his Echo Show device went from a valued device in his home to an advertising spewing piece of e-waste. For a long time, I've had a huge amount of respect for Alexa as a developer platform. Heck, I gave multiple presentations on the platform and shipped some public Alexa skills. I had my own Echo Show next my bed and loved it... until it also started displaying ads - a foot from my bed. To be clear, these don't show up at night when the device switches to a low-light mode, but it infuriates me. I get even more angry knowing that Amazon tablets have both an ad supported price and a 'no ads' price. I could get behind that - totally! But there isn't an option for that on the Show, and it definitely wasn't an option when I purchased the device.\nI'll echo the author's suggestion of considering the Google Nest Hub, we've got one in the kitchen and love it.\nAdvent of Code\nEvery year I recommend folks check out the Advent of Code. This is a twenty-five day coding challenge that goes from fun to insanely difficult, but the nice thing is that you are free to do as many or little as you like. Each day, a core challenge is released in two parts. Typically the second part is a simple-ish modification of what you built in the first part. For the past few years, I've used AoC as a way to practice my Python. There's a dedicated subreddit where you can find solutions, and honestly, I don't consider that cheating at all. If you feel a bit guilty and can't get past a challenge, try finding a solution in another language and 'translating' it to your language of choice.\nTiny Static Map\nLast up is a little library that creates static map images, Tiny Static Map. I'm a little torn on this one as Google's Static Map API uses just image urls. This library still requires JavaScript, but, if you want to drop a map on a page and not have any interactivity, it's an option I suppose. Usage is pretty simple:\n\nI'm curious to see if perhaps this could be modified to not draw to the DOM, but return something that could be saved to the file system. In that case, it could be a useful addition to a static web site.\nJust For Fun\nI don't know about you, but I'm really excited we get a new Star Wars show this Monday. On the off chance you haven't heart of it, check out the trailer for &quot;Skeleton Crew&quot; below. I love the Goonies vibe!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Generative AI to Parse Web Pages into Data",
		"date":"Wed Nov 27 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1732730400,
		"url":"https://www.raymondcamden.com/2024/11/27/using-generative-ai-to-parse-web-pages-into-data",
		"content":"A few months back, I took a look at using JSON-LD to turn a recipe web page into pure data: Scraping Recipes Using Node.js, Pipedream, and JSON-LD. This relied on a recipe actually using JSON-LD in the header to describe itself, which is pretty common for SEO purposes. Still, I was curious as to how well generative AI could solve this problem. In theory, this could be a good 'backup' in cases where a site wasn't using JSON-LD and a general exploration of 'parsing' a web page into data. I'll be using Google Gemini again, but in theory, this demo would work in other services as well. Here's what I found.\nConverting a Web Page into Structured Data\nIn order to turn a web page into structured data, I needed a few different things. First, remember that Google's Gemini service supports the ability to use JSON Schema to tell the API how to return a result. (You can find my exploration of that feature here: Using JSON Schema with Google Gemini).\nThe code for this isn't difficult, it just becomes part of your request, but crafting the schema correctly can be a bit of work. As I suggested earlier this year, use the JSON Schema website for help and examples.\nAs I'm working with recipes, I defined my schema as such:\n\nThis could be fleshed out more, for example, with a duration properly. I also could have attempted to coerce the ingredients into an array of objects containing the name of the ingredient and quantity. As always, take my blog posts as a starting point and if you build on it, let me know!\nThe next issue I ran into was actually getting the HTML. Gemini can't be told to go fetch a URL, but my code can. I initially attempted to take the HTML and simply append it to the prompt, but this caused issues. So, I took another approach - simply saving the HTML and uploading it to Gemini for a multimodal prompt. As a reminder, multimodal is just a fancy way of saying &quot;prompt with an associated file or files&quot;, and again, I've got a blog post for that to help you: Using the Gemini File API for Prompts with Media\nGiven a string of HTML, here's a simple implementation:\n\nAs the comment says, you should absolutely not use a hardcoded path, but rather something dynamic like a UUID. My demo doesn't even clean up the file, but I assume that's a trivial change if folks want to use my code. You may be wondering - can you skip the file system? Unfortunately no, not with the Node SDK. If you switched to using the REST API, you absolutely could and do a direct push, but that's quite a few more steps and not worth the effort, but it is possible.\nNext, I designed my system instruction:\n\nAnd my prompt, which is pretty boring:\nGiven the HTML content, attempt to find a recipe.\n\nUsing this recipe, https://www.allrecipes.com/recipe/10275/classic-peanut-butter-cookies, here's what I get back:\n\nI put this into a simple web app where you could enter a URL, hit parse, and get the simpler version. This is a screenshot from the original, the complete page, where I cut out about 80% of the screenshot and it's still... a lot. Also notice the actual recipe isn't displayed in this portion.\n\n\n\nCompared to my web app version:\n\n\n\nI know which version I prefer. So, if you want to see the full code, you can find everything up at my repo: https://github.com/cfjedimaster/ai-testingzone/tree/main/recipe_scraper Unfortunately I can't run this live, but folks are free to take my code and run with it. My code is built to power a web app, but you could just as easily take the core logic and put it in a serverless function instead.\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Your Favicon for Monitoring Long Processes",
		"date":"Mon Nov 25 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1732557600,
		"url":"https://www.raymondcamden.com/2024/11/25/using-your-favicon-for-monitoring-long-processes",
		"content":"A week or so ago, I was doing some tests on Google Colab and noticed something interesting. The notebook I was using was one that took one to two minutes to process. Before I'd start the process, the favicon looked like so:\n\n\n\nAfter kicking off the workflow, the favicon changed like so:\n\n\n\nTo be honest, I had not noticed it earlier, but I only fairly recently started using a somewhat 'slow' notebook so it's possible I just didn't need it before. Realizing how it's being used now, I thought it was an excellent user experience feature and looked into how to use it in my own applications.\nChanging the Favicon\nOk, so this part is stupid easy. Assuming you've got a favicon specified:\n\nThen you can use simple DOM manipulation:\n\nThis is obvious, I guess? But I tend to only think about DOM manipulation for things that are inside my BODY tags. I've done stuff outside of there before, for example, dynamically adding a &lt;script&gt; tag, and heck, I did a post on a similar topic back in 2010 (forgive the poor formatting, Using JavaScript to update the browser window title when the user is away), but I honestly didn't think it would be this easy.\nThe Demo!\nI hopped on Glitch to build a quick demo. First, I added a button to a page, and then used a bit of JavaScript such that clicking the button would kick off a five second delayed process:\n\nNotice I disabled the button while the process (a setTimeout) is working. That's just plain good UX. To modify it to add the favicon change, I first added two lines to point to my 'working' favicon and the current one:\n\nAnd then literally added two more lines to doSlow:\n\nYou can test this yourself here: https://gamy-lying-stork.glitch.me/. For my demo, the 'regular' favicon is blue, and the 'working' one is red.\nI've got it embedded below, but you'll want to view it in your own window to see the changes:\n\n  <iframe\n    src=\"https://glitch.com/embed/#!/embed/gamy-lying-stork?path=script.js&previewSize=0\"\n    title=\"gamy-lying-stork on Glitch\"\n    allow=\"geolocation; microphone; camera; midi; encrypted-media; xr-spatial-tracking; fullscreen\"\n    allowFullScreen\n    style=\"height: 100%; width: 100%; border: 0;\">\n  \n\nLet me know what you think!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Intl is your Superhero",
		"date":"Thu Nov 21 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1732212000,
		"url":"https://www.raymondcamden.com/2024/11/21/intl-is-your-superhero",
		"content":"Last week I had the pleasure of presenting at the Mid-Michigan ColdFusion Users Group on the topic of Intl, the web platforms internationalization spec. I gave this presentation again this week at connect.tech as well. I greatly enjoyed working on this deck as I've been using Intl for a while, but had not had the opportunity to look into every nook and cranny.\nThe slide deck may be found here https://github.com/cfjedimaster/intl-is-your-superhero/ and viewed online at https://cfjedimaster.github.io/intl-is-your-superhero/decks/main/.\nThe demos are all up on CodePen within one collection, which you can peruse here: https://codepen.io/collection/oEKOwR\nAnd then finally, if you love hearing the sound of my voice, you can watch the recording:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Counting Words with Intl.Segmenter",
		"date":"Wed Nov 20 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1732125600,
		"url":"https://www.raymondcamden.com/2024/11/20/counting-words-with-intlsegmenter",
		"content":"Yesterday, I gave my presentation on Intl, the browser's built-in support for internationalization. I've been using this for a while now, but while researching the spec for my presentation, I ran into multiple cool aspects of it I wasn't aware of. One feature I thought was particularly interesting was the Segementer object. MDN's description is nice and succinct:\n\nThe Intl.Segmenter object enables locale-sensitive text segmentation, enabling you to get meaningful items (graphemes, words or sentences) from a string.\n\nIn particular, I thought the ability to get words would be an interesting use case. In the past, I've either done a lame split on &quot; &quot;, or used regex and word boundaries, but this is problematic in multiple languages. Again, quoting MDN:\n\nIf we were to use String.prototype.split(\" \") to segment a text in words, we would not get the correct result if the locale of the text does not use whitespaces between words (which is the case for Japanese, Chinese, Thai, Lao, Khmer, Myanmar, etc.).\n\nOk, so given that, how can we use the feature to count words?\nFirst, you need to create an instance of the Segmenter:\n\nIn the code above, I've specified the browser's current language for the locale (which is the default, but I like specifying it) and used a granularity value of word.\nOnce I've got that, it's then trivial to use:\n\nThe result is an iterator, but we can quickly change that to an array and log it:\n\nThe result looks like so (note, I removed a bit of data to keep the sample shorter):\n\nNotice how the results include both words, and the spaces between them, but each result also signifies if it is a word (or word like). We can use this to do a count using reduce:\n\nYou can see this in action below:\n\n  See the Pen \n Segmenter by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nA bit of warning about this approach. I tested with this input:\nThe properties defined in the format specifies the location-path and the alt-text.\n\nAnd the result I got was 14, not 12. This is because of the two hyphenated words, location-path and alt-text, which are considered two words each, and honestly, I think that makes sense, and I'd want it to be counted that way, but it did surprise me. Let me know what you think below!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Executing Dynamic Code in a Reveal.js Presentation",
		"date":"Tue Nov 12 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1731434400,
		"url":"https://www.raymondcamden.com/2024/11/12/executing-dynamic-code-in-a-revealjs-presentation",
		"content":"Please take what follows with a Titanic-sized grain of salt and do your best not to do what I did, but despite that, I thought this little hack was interesting and I figured I'd share it anyway. I typically use Reveal.js for my presentations, especially when talking about the web platform, as it makes it easy to do slides and demos, all in my browser.\nUsually when I want to embed live code in a slide, I just use a CodePen embed. While this works well, sometimes it feels like overkill for real short code samples. I wondered if it would be possible to execute code directly in the slide itself such that I could show a one-liner in the slide, and then the result after. This is what I came up with.\nFirst, consider a slide with some code on it:\n\nIn this slide, I create a new Date object and then use Intl.DateTimeFormat to display it. I wanted the final result, which would have been in the console, to show up in the slide.\nI decided on a data-attribute that would contain the code for this such that the result of executing the code would be one value only:\n\nI then added a paragraph tag to handle the result:\n\nThe fragment class there tells Reveal to not display it until I hit the right arrow. Therefore I'd get the code sample first:\n\n\n\nAnd when I hit the right arrow:\n\n\n\nHow did I handle it? Reveal.js has support for multiple event handlers, including on a slide change. Here's what I did:\n\nBasically, if the DOM for the slide had a data attribute named execute, use eval on the string and update a paragraph with class result inside the slide.\nOverkill? Probably. But this particular presentation had multiple date examples and I really wanted it to show &quot;live&quot; results. If for some reason this abomination interests you, you can find it in my slide deck here: https://github.com/cfjedimaster/intl-is-your-superhero\nAnd to be clear, don't use eval.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (11/11/2024)",
		"date":"Mon Nov 11 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1731348000,
		"url":"https://www.raymondcamden.com/2024/11/11/links-for-you",
		"content":"The last seven days have been... difficult. I don't think I need to go into why (even for my non-American readers), but I am doing my best, as are a lot of people, to take things day by day. Honestly, as a white hetero man, I'm not so much worried for myself, but I'm deeply concerned about my family and friends who are LGBTQ+ and other minorities. I don't talk a lot about my children here as I want to ensure their privacy is maintained, but seven of my kids are adopted and Asian and my usual worry for them has risen quite a bit.\nNormally, my links here are almost always tech-related, but today I'm going to do something different. The links below are resources that, I think probably folks in the groups I described above already know about, but if you're like me, it could help you help others, which is always a good thing. I promise to bring back the tech links next time, but this is too important to wait.\nLGBTQ Resource List\nEarlier today I asked on Bluesky if any of my followers had any links/suggestions for resources for people who are LGBTQ. Unfortunately, I didn't get any responses to that, but I did find a good list of links at glaad.org that may be of use. The list covers categories from youth to aging to legal and more so:\nLGBTQ Resource List\nRAINN\nThe Rape, Abuse, &amp; Incest National Network first came to my attention when Tori Amos was their spokesperson in the 90s. They operate a sexual assault hotline and have programs meant to help prevent abuse and help survivors. At some point years ago they used ColdFusion and a developer there reached out for help. I was able to do something simple and small for them and I was glad to help out where I could.\nFind out more at https://rainn.org/\nStop AAPI Hate\nI first heard of AAPI (Asian American and Pacific Islanders) back when Trump was first elected. This organization was founded to help stop racism and discrimination against both groups in America.\nFind out more at https://stopaapihate.org/\nOne More...\nUsually, I end these posts with something fun, or cool music, but I thought this would be more appropriate. Honestly, I had no idea Kate McKinnon could play, or sing.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Translation with a Web Component and Chrome AI",
		"date":"Thu Nov 07 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1731002400,
		"url":"https://www.raymondcamden.com/2024/11/07/adding-translation-with-a-web-component-and-chrome-ai",
		"content":"Edit on March 25, 2025 As expected, these APIs have changed quite a bit in the past few months. The code in this article is out of date now, but, I updated the Glitch shared at the end to work with the newer APIs.\nA few days ago, I blogged about using Chrome's built-in generative AI features (which are still super duper too early to even consider for production) to add on-device translation capabilities to a web app. It got me thinking, what if we could do translation automatically via a web component? If for some reason it failed, that would be fine as the original text would still be there, but in cases where it could work, it would be automatic. Here's what I built.\nFirst, I whipped up a quick HTML demo of the text I'd like translated:\n\nThen I built my web component. It begins by seeing if window.translation even exists:\n\nNext, it checks to see if it can detect the language being used. I originally thought I'd force the user to add a sourceLanguage=something attribute to the web component, then I remembered that one of the new AI APIs is a language detection one - so let's use that!\n\nI then use this API to sniff the language of the source text:\n\nThe API returns an array of languages it thinks might be right, sorted by the one it is most confident about. Each result contains the detectedLanguage key and confidence. In theory, you could do some sanity checking here such that if the highest confident match is too low, you stop working.\nNext, I detect the language the user would like. Naturally, I look at their IP because as we know, if a user travels to a different country, they immediately know how to read and write the language spoken there. Naturally.\n\n\n\nSigh. No, of course not, despite so many freaking web sites out there doing this stupid, asinine behavior. The browser already tells you what language the user prefers in the navigator.language property. There's actually two properties you can check, the one I just shared as well as navigator.languages which gives you an array of languages in order of what's most preferred.\nHere's the code I used:\n\nFollowed by this for testing purposes:\n\nNext, a quick sanity check:\n\nNow the work begins. First, let's define an object representing our source and target:\n\nThen we ask the browser, can you dig it?\n\nIf we've gotten this far, the last step is to do the actual translation:\n\nAnd finally, update the text inside:\n\nIf you want to see the complete code, and try a demo yourself, you can find it on my Glitch below, but keep in mind you need to be using Chrome Canary and have gone through the documented steps to enable this feature.\n\n\n  <iframe\n    src=\"https://glitch.com/embed/#!/embed/mature-glorious-celestite?path=index.html&previewSize=0\"\n    title=\"mature-glorious-celestite on Glitch\"\n    allow=\"geolocation; microphone; camera; midi; encrypted-media; xr-spatial-tracking; fullscreen\"\n    allowFullScreen\n    style=\"height: 100%; width: 100%; border: 0;\">\n  \n\nFor those curious, this is the translated text I got:\n\nLe Congrès n'adoptera aucune loi concernant l'établissement de la religion, ou leur interdisant le libre exercice ; ou écarter la liberté d'expression, ou de la presse ; ou le droit du peuple de se rassembler paisiblement et de demander au gouvernement une réparation des griefs.\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automatically Posting to Bluesky on New RSS Items",
		"date":"Tue Nov 05 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1730829600,
		"url":"https://www.raymondcamden.com/2024/11/05/automatically-posting-to-bluesky-on-new-rss-items",
		"content":"\nEdit on November 25, 2024: So this post turned out a bit more popular than I expected. :) While working with folks in the comments, two things came about. First, folks needed things spelled out a little bit, so with that in mind, I made a quick Youtube video: https://www.youtube.com/watch?v=_yp9U-QJgOM. Secondly, a user was running into issues with my code, and it turned out, my own meta tags here for og:description and og:image were using name instead of property. This seemed to work ok in most situations, but wasn't proper. So, I've updated the code here, and the GitHub repo for the workflow, to use that. Thanks to @toothless-666 for helping me debug and @benmillett for pointing out various things as well. You can see the full discussion in the comments below. \n\nHey folks - just a quick warning. This post is kind of a mashup/update of two earlier posts. Back almost two years ago I talked about this process but used Twitter and Mastodon: &quot;Automatically Posting to Mastodon and Twitter on New RSS Items&quot;. Earlier this year I first talked about using the Bluesky API, with a very appropriately named post: &quot;Using the Bluesky API&quot;. As I said, this post is going to mash up bits from both, and include new things I've not covered before, but for those of you who have been around here for a while, some of this may be repetition.\nFor this solution, I'm using Pipedream. I've blogged for years now and love it. Their free tier will support what I'm showing below so you should feel free to give it a try. There are many alternatives out there, but Pipedream has some great features that I think make it stand out. You'll see that especially in the first step below. But, keep in mind if you've already got a platform you would want to use, as long as you can handle the execution on new RSS items, you could probably just skip to the last step and copy and paste from my code.\nOk, enough preamble, let's take a look at how this can be built.\nStep One - Firing on New RSS Items\nThe first thing our workflow needs is the ability to fire on a new item added to a RSS feed. This requires setting up a schedule, parsing the RSS feed, and most importantly, recognizing when a new item has been added. Luckily, Pipedream has this built in.\nWhen creating a new workflow, you'll be prompted for the trigger. Type 'rss' to filter to the RSS app:\n\n\n\nSelect it, and then pic: &quot;New Item in Feed&quot;:\n\n\n\nThis requires, at minimum, the RSS feed you want to monitor. The timer, which just means how often it checks, defaults to every 15 minutes, which honestly is overkill. My own workflow checks every 4 hours, but once a day, of maybe every 6 hours, would be more sensible.\nOnce you've done that, that's literally it - the workflow will check the feed automatically and recognize when a new item has been added.\nStep Two - Generate the Message\nWhen an RSS item triggers the workflow, you get information about the item of course. What you need to figure out then is what you want to post. You could just post the title and URL, but since my account also has random other posts from me (always super important stuff), I wanted to distinguish the automated posts.\nPipedream lets you add arbitrary code posts, so my new action in the workflow is a Node.js one. The code is relatively simple:\n\nBasically what I said above, title and link, but I prefixed it with &quot;New post from my blog:&quot; to help it stand out.\nStep Three - Posting to Bluesky\nOk, so working with Bluesky is mostly simple. Mostly. The docs are pretty good. Initially, you create an instance of the Bluesky 'agent' and login:\n\nThe imports here are slightly different from the docs in order to get it working on Pipedream. If you want more info on why, see my earlier post in February.\nFor identifier, I used raymondcamden.com which is my username on Bluesky. Posting is super easy as well - this is right from the docs:\n\nHowever, there's one oddity that will trip you up. If your text contains URLs, they will not be automatically hot linked. Instead, you have to use what Bluesky refers to as 'rich text', and again, this is documented. It requires just a tiny tweak:\n\nOk... so that works. You can see a sample here:\n\nNew post from my blog: &quot;Next Code Break - Blogging with Eleventy&quot;\nhttps://www.raymondcamden.com/2024/11/04/next-code-break-blogging-with-eleventy\n  &mdash; Raymond Camden (@raymondcamden.com) November 5, 2024 at 12:56 PM\nBut I wanted the usual 'social media preview' card you see attached in the Bluesky app. Once again, this was nicely documented: Website card embeds. Basically, you add an embed key to your post that includes, at minimum, a URL, title, and description. The description of my post is not in my RSS. In order to get it, I made use of Cheerio, a Node.js library that gives you jQuery like features with raw HTML. I fetched the HTML and got the description like so:\n\nBy the way, the use of $$ as a variable is a twist on the Cheerio docs. They use $, but Pipedre",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Next Code Break - Blogging with Eleventy",
		"date":"Mon Nov 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1730743200,
		"url":"https://www.raymondcamden.com/2024/11/04/next-code-break-blogging-with-eleventy",
		"content":"Hey folks - my next &lt;Code&gt;&lt;Br&gt; will not be this Tuesday as I'll be presenting at API World (assuming American gets me there today) so I've pushed back the livestream till Thursday, November 7th. Usual time - 12PM CST. You can read more about the event here:\nhttps://cfe.dev/talkshows/codebreak-11072024/\nI'm going to be discussing Eleventy and building a basic blog. I've wanted to cover Eleventy on my show for a while but was waiting for 3.0 to come out. Now that it has - I can't wait to introduce it to folks. Hope to see you there!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Chrome AI for Translation",
		"date":"Tue Oct 29 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1730224800,
		"url":"https://www.raymondcamden.com/2024/10/29/using-chrome-ai-for-translation",
		"content":"I've done a couple blog posts now on Chrome's efforts to bring generative AI to the browser. It's still somewhat of a rough process (remember, you can sign up for access to test and learn more at the intro post from the Chrome engineers), but it's getting better over time. One thing I mentioned in my last post (&quot;Using Chrome AI to Rewrite Text&quot;) was how the Chrome team is shipping focused APIs for specific purposes, not just general Q and A. In that previous post, I demonstrated an example of the Rewriter API. As yet another example of this, you can now test out on device translation.\nAs with everything else I've shared in this space, you should consider this real early in terms of implementation, but once you get past some of the hurdles enabling the feature (something that's going to be far easier later in the development cycle), translation is shockingly simple.\nAt the top level, you've got a window.translation object. If it exists, you can then check for the type of translation you want to do, i.e. from what language to another. Currently, if your code request to translate from, let's say English to French, this will spawn a download process for that support. In the real world (i.e., when this is more readily available), I could see perhaps doing this on a user's first visit if you anticipate making use of the feature as they make use of the site. In other words, you're going to wait to think ahead a bit and 'pre-load' the support before it can actually be used.\nTo check for the ability to translate, you can use canTranslate:\n\nNote that you are specifying a source and target, so if for some reason you need to go back and forth, you would need to do two calls and reverse the values.\nIf translation is ready, you get readily, otherwise, after-download. For a non-valid pair, you get no. You can actually check for an event to monitor the download if you wish so in theory, it's possibly to immediately enable the feature on your site when ready.\nIf the result from canTranslate is good, you can then create a translator:\n\nSo - there's a bit of complexity involved in the setup, but not too bad I think. And once you have the translator object, the API is just:\n\nHow about a real world use? In the CodePen below (which, admittedly, probably won't work for you so I'll share screenshots), I've made use of the Adobe PDF Embed library to display a PDF in a web page:\n\n\n\nTo the right of this, I added a simple message: &quot;Select text in the PDF and I'll translate it to French.&quot;\nI then used the Embed API's features to listen for selection events. Here's the important part:\n\nWhich then calls out to the translate function:\n\nI selected &quot;It is never too early or too late to start planning your legacy. We have the perfect life insurance plan for you to make it easy to start securing your family's financial future. &quot; and got:\nIl n'est jamais trop tôt ou trop tard pour commencer à planifier votre héritage. \nNous avons le plan d'assurance-vie parfait pour vous permettre de commencer à \ngarantir l'avenir financier de votre famille.\n\nAnd as I obviously remember all my high school French that seems... um... ok I guess? :) Honestly, the best translation would be one at the time of publication, hiring professionals to ensure it's done right, carries over the context of the original text and so forth. But being able to do this real-time, on device, in a second (according to my unscientific timing) is really freaking cool.\nYou can check out the complete demo below:\n\n  See the Pen \n  PDF Selection Translate with Nano by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLet me know what you think with a comment below!\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (10/28/2024)",
		"date":"Mon Oct 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1730138400,
		"url":"https://www.raymondcamden.com/2024/10/28/links-for-you-10282024",
		"content":"This post was meant to go out on the weekend, but I got sucked into video games, baking cookies, and, well, watching the Saints suck. I've mentioned this already I think, and I'm pretty sure I talked about it on my stream, but my anxiety which had taken a bit of a back seat for nearly a month has come raging back in. November is going to be kind of a crazy month for me - I've got two trips, six presentations total, and a major holiday. I'm also just a teeny bit worried about, oh you know, the entire country going to hell, but for today, today I'm just going to focus on tackling things one by one. And with that... your lnks.\nConverting HTML to Image in Node.js\nFirst up is a simple little Node package that converts HTML to an image called... node-html-to-image. This uses a headless browser to convert HTML into an image. Here's an example from the readme:\n\nI tried this and - of course - it worked:\n\n\n\nIt's also got built-in Handlebars support which is pretty cool. An example (again, from the readme, slightly modified):\n\n\n\n\nThere's many more options so check it out here: https://github.com/frinyvonnick/node-html-to-image\nThe History of Regex in JavaScript\nRegex holds a special place in my heart, as I started my web development career writing CGI scripts in Perl, where I first learned of the power (and pain) of regular expressions. This historical guide to regex in JavaScript is a great look at the history of regex support on the web. Kudos go to Steven Levithan for the work in compiling this!\nRegexes Got Good: The History And Future Of Regular Expressions In JavaScript\nA Look at a Super Fast Website\nThis site recently 'hit the airwaves' so to speak when it was noticed that it was a) extremely fast and b) built without a framework, which, frankly, I'm shocked is even allowed anymore. (To be clear, I'm joking.) Wes Bos did an incredibly detailed breakdown into why the site performs well, all down by using devtools, which is a great reminder of how useful they can be in introspecting other web sites than your own. This thirteen minute video is 100% worth your time.\n\n  \n    Play Video\n  \n\n\n\n\nJust For Fun...\nAnd last but not least, how about I leave you with some music. I'm a huge fan of the &quot;Above &amp; Beyond&quot; Group Therapy playlist. Every few weeks, they put out a great trance set that I thoroughly enjoy. They also have many albums that are pretty great as well. &quot;Flow State&quot; is a much slower, more relaxed album from them I'll listen to when stressed. Today, this week, this is going to be on repeat. One of the tracks is a spoken word track that usually surprises me when it comes on and helps me redirect and relax...a bit. I've linked it below, but definitely check out the full album if you can.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Parallel Requests to Improve Web Performance",
		"date":"Fri Oct 25 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1729879200,
		"url":"https://www.raymondcamden.com/2024/10/25/using-parallel-requests-to-improve-web-performance",
		"content":"Yesterday I blogged about a change I made to my bots page and in it, I mentioned how the performance wasn't necessarily as good as it could be. I had made the decision to go from server-side and build-time for the page to a purely client-side solution. At the end of the post, I asked folks to let me know if anyone would like to have me work on that performance issue, and, honestly, it kept popping up in my head so I figured I should tackle it. Before I begin talking about what I changed, let me review what I had done, and what the issues are.\nThe Current Solution\nYou can go to the bots page yourself, but in general, this is the process:\n\nGiven a list of bots...\nFor each one, get the RSS feed for the bot (a network request)\nParse the XML into a result and return it\nRender the result\n\nThe net result is that as you view the page, you see nothing at first (minus the layout and initial paragraph of course), and as each one is processed, it's appended to the DOM one at a time.\nIf we assume roughly 5-10 seconds for each, and I've got 8 bots, that can take up to 80 seconds to complete, a veritable lifetime in web terms. It also means the user sees nothing for up to 10 seconds. If we assume the user spends 5 or so seconds looking at the result, they are waiting a bit for each one as it comes in.\nI wanted to create a 'clean room' type environment for this post so I created a CodePen.\n\n  See the Pen \n toot test by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nYou can't see it in the embed, but if you click the link and view it on CodePen itself, you can open the console and see I've added timing information. In my last test, I saw a total of nearly 60 seconds.\nFirst Attempt\nThe most obvious solution to this - I assumed - was to simply run all the requests in parallel. Given that BOTS is my array, I did this:\n\nThat fires off a request for each bot one right after the other without waiting. I can then wait for them all to finish and render the results all at once:\n\nYou can take a look at it in action below:\n\n  See the Pen \n toot test 2 by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nSo this is better, right?\nWell...\nI'm actually not 100% sure. If we assume 5-10 seconds per request, with Promise.allSettled, I have to wait until the longest one is complete before I render something. It's possible that this solution won't show content for a longer time than the first solution. In theory, the time to first content being rendered is the same, but of course, you get it all though so you can more quickly scan the results.\nSo... yes, this is better, but something occurred to me.\nSecond Attempt\nThe issue I had with the previous version is that if one request goes bad, the entire result is held up. What if instead, we render results as soon as they come, still firing them off in parallel? The order of my results does not matter, so here's an even nicer solution:\n\nI've rewritten my logic to do everything (fetch and display) in a new function:\n\nI'm still using an array of promises so I can 'clean up' when done, specifically the 'loading' message (and my debug timing code):\n\nYou can see this solution here:\n\n  See the Pen \n toot test 3 by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nAs I said, order doesn't matter so this works just fine, but I could shift things around a bit if necesssary. Ie, tell bot X it has to be in position X on the page, but as that's not necessary I didn't bother.\nAnyway, this was fun. As I said, it was interesting realizing that doing the requests in parallel wasn't necessarily better at first. I'd love to know what folks think, so feel free to let me know what you would do. (Oh, and don't forget you can easily fork CodePens!)\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Getting and Displaying a Mastodon Post in Client-Side JavaScript",
		"date":"Wed Oct 23 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1729706400,
		"url":"https://www.raymondcamden.com/2024/10/23/getting-and-displaying-a-mastodon-post-in-client-side-javascript",
		"content":"I've got a few pages here that are primarily built for my own use. One of them, my bots page, is a list of all the dumbsuper useful bots I've built for Mastodon (and Bluesky). The idea on this page is to show the latest post from each bot. The bots page makes use of two different shortcodes written in Liquid to do this.\nThe first uses the RSS feed of the bot to get their last toot ID:\n\nTo render this post, I then use code from Bryce Wray that fetches the data for the post and renders it out nicely. I won't share the entire code block, but you can peruse it in my repo here, https://github.com/cfjedimaster/raymondcamden2023/blob/main/config/shortcodes/stoot.js.\nThis is done like so:\n{% capture &quot;lasttoot_nps&quot; %}\n{% lasttoot &quot;botsin.space&quot;, &quot;npsbot&quot; %}\n{% endcapture %}\n{% stoot &quot;botsin.space&quot;, lasttoot_nps %}\nBasically, run the shortcode that outputs an ID, and then pass it to the renderer.\nYou can see this in action below, which will be my latest post on Mastodon and rendered at build time.\n\n            \n                \n                \n                    Raymond Camden\n                    @raymondcamden@mastodon.social\n                \n            \n            Expert JavaScript tip: aysnc fat arrow functions aren&#39;t the same as async fat arrow functions\n                7:15 PM • May 8, 2026&nbsp;(UTC)\n            \n        \nSo... this worked but proved to be a bit problematic locally. It ended up adding quite a bit of time for my local build due to constantly fetching multiple RSS feeds and then post data items. My &quot;solution&quot; locally was to just ignore that file in my .eleventyignore file. Problem solved, right? But lately, I saw a few other issues with it in production.\nWith that in mind - I thought - why not use a client-side solution? The biggest issue would be getting the RSS feed. Usually, almost always, RSS feeds don't have the proper CORS setting to let client-side JavaScript do this, but on a whim, I did a quick test with one of the bots and... it worked! I quickly then checked the Mastodon API for getting details of a post, and it worked as well.\nOk, so I massively updated my bots page to no longer use short codes and do everything on the client. First, I just listed them out:\n\nThat's a lot of bots. I've got a problem.\nFor each bot, I first get their last toot:\n\nThe code for getLastToot does XML processing, which isn't as bad as I remember in JavaScript:\n\nI convert the bot's main URL to the RSS url, fetch it, and then grab the important bits, which includes part of their profile (title, avatar, etc), and the most recent item.\nNow, I made some concessions here on how much to fetch, specifically I don't care about polls, but do care about images, since nearly every bot I have is an image poster.\nIn the end, the code returns a simple JavaScript object. Here's one example:\n\nI want to call out one specific part of the code here:\n\nFor one of my bots, I was getting escaped HTML, and as I wanted to turn that into 'real' HTML, I needed a way of doing that. Initially I used a simple replaceAll on a few entities. I asked on Mastodon, and got some good answers, but this one from Lukas Stührk worked well:\n\n            \n                \n                \n                    Lukas Ehlers Stührk\n                    @ls@discuss.systems@mastodon.social\n                \n            \n            @raymondcamden is it in the context of a browser? Or do you have a DOM library available? Then you can create a DOM node, assign the string to the node’s innerHTML property and then read the node’s textContent property.\n                4:12 PM • October 18, 2024&nbsp;(UTC)\n            \n        \nThis ended up being implemented like so:\n\nThe last part entailed displaying the toot. For that, I took part of Bryce's code, simplified it, and used a combination of an HTML template and JavaScript. Here's the template:\n\nAnd the JavaScript:\n\nAnd outside of a few other miscellaneous things, that's it. You can see the complete code if you just head over to the bots page and view source. I'll say it still takes a while to render, and in theory, I could multithread the code to get the most recent post and details and in theory, it would finish a lot quicker, but as this is - again - mostly just for me, I'll probably keep it simple. (Or, if one person leaves a comment like, &quot;hey Ray, I'd like to see that change&quot;, then I'll probably do it).\nAs always, if this code is useful to you, let me know please!\np.s. Ok, everything that follows is not related to the technical aspect of the post at all, and is 100% personal opinion. If you are only here for the code, no problem and I completely understand if you stop reading! That being said, I'm not a bot myself and I've absolutely got personal feelings and I'm going to share them here. I've been a Twitter user for a very long time. Since Musk took over, I've been less and less happy with the environment there. I've really curtailed my posts t",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Take a Code Break Tomorrow",
		"date":"Mon Oct 21 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1729533600,
		"url":"https://www.raymondcamden.com/2024/10/21/take-a-code-break-tomorrow",
		"content":"Just a quick note to my faithful readers out there - tomorrow, October 22nd, at 12PM CST (Cool Standard Time), I'll be hosting my next episode of &lt;Code&gt;&lt;Br&gt;:\nhttps://www.youtube.com/watch?v=EbJuB7irJMw\nIn the previous stream, I talked about charting with JavaScript, specifically using Chart.js. In this followup, I'll attempt to use another library so we can compare and contrast. I don't know about you, but that sounds like a lot of fun. (I really enjoyed the last stream.)\nAnd with this being the last stream before Halloween - I may even dress up!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding AI Insights to Data with Google Gemini",
		"date":"Thu Oct 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1729188000,
		"url":"https://www.raymondcamden.com/2024/10/17/adding-ai-insights-to-data-with-google-gemini",
		"content":"Yesterday, Elizabeth Siegle, a developer advocate for CLoudflare, showed off a really freaking cool demo making use of Cloudflare's Workers AI support. Her demo made use of WNBA stats to create a beautiful dashboard that's then enhanced with AI. You can find the demo here: https://wnba-analytics-ai-insights.streamlit.app/\nI found this incredibly exciting. I last looked at Cloudflare's AI stuff almost an entire year ago (&quot;Using Cloudflare's AI Workers to Add Translations to PDFs&quot;), and I haven't quite had a chance to try it again, mostly because I've been focused on Google Gemini for my Generative AI work.\nFrom an API/usage perspective, Cloudflare's Workers are easy as heck (although I recently had an issue with them that turned out to be a very unique edge case), and you can see this in her code behind the dashboard here: https://github.com/elizabethsiegle/wnba-analytics-dash-ai-insights/blob/main/app.py. Scroll down to the generate_insights Python method and you'll see it's a simple POST with a prompt to get the results.\nAs I said, this was exciting as heck to me. Last week, my Code Break episode was focused on charting in JavaScript. In that session, I made use of Chart.js to create charts for a simple set of sales data. This sales data was a hard coded set of totals for four products over 12 months. You can see an example chart I built here: https://cfjedimaster.github.io/codebr/charts1/chartjs2.html. In case you don't want to click, here's the chart:\n\n\n\nInspired by Elizabeth's example, I wanted to take this chart, and see if Google could get insights from it. Here's what I built.\nVersion One\nFor the first version, I started off with an HTML page making use of the same chart as shown above, but with an added empty div to share insights:\n\nOn the client-side, the JavaScript is mostly related to Chart.js, but at the end, I've added a call to my server-side code to get insights and render it to the page:\n\nSo far, nothing special. Do note that I'm passing the same sales data I used in the chart to my server. This is a sample of that data, just the first three months:\n\nAlright, the fun part comes at the server. I'll share a link to the complete source in a bit, but here's the Gemini aspect:\n\nIn this case, my system instruction does all the heavy lifting. I'm literally just passing my JSON to Gemini as is... and it works really well. I honestly thought I'd have to rewrite it, perhaps in simple text with tabs and such to line it up, but it didn't need any help at all.\nHere's the result:\n\n\n\nI recognize that text may be a bit hard to read, so here's a copy:\n\nHere are some insights from your sales data:\n\nDonuts are on a constant rise: Donut sales show a clear upward trend throughout the year, ending the year with more than double the sales they had in January. This suggests a strong positive response to donuts, perhaps due to seasonal preference or successful marketing.\nApples see growth, Bananas decline: Apple sales demonstrate overall growth across the year, peaking towards the latter months. Conversely, banana sales have steadily declined, particularly in the last quarter. Investigating external factors like pricing or availability could explain these opposing trends.\nCherries fluctuate, but remain relatively stable: Cherry sales don't show a strong upward or downward trend. They peak mid-year and experience some dips, but generally remain within a certain range, suggesting consistent demand.\nPotential Seasonality: There's a noticeable dip in sales for almost all items in April, followed by an upswing in May. This could be due to seasonal factors influencing consumer behavior, or perhaps external events impacting that specific period.\nConsider Donut Promotions: Given the consistent success of Donuts, further promotions or exploring variations of Donuts could capitalize on their popularity and drive even greater sales.\n\n\nSweet! You can find the source here: https://github.com/cfjedimaster/ai-testingzone/tree/main/chartdemo\nVersion Two\nSo, I was just going to stop there, and then I recognized something. When I built the HTML for this demo, I had copied in a template that made use of Shoelace, my favorite UI library built with web components. I wasn't actually using any of them in them in my code so the smart thing would have been to remove the dependencies. I didn't. Instead, I looked at the Shoelace site to see if perhaps I could render the insights a bit nicer.\nWhile looking, I came across their Carousel component, and I thought it would be nice to display the insights, one at a time, in larger text to be a bit more... bold? I'm not a designer, and I don't play one on TV, but I figured it was worth a shot.\nOn the client side, I modified my code a tiny bit, making the assumption I would get an array back from the server:\n\nConverting my insights into an array was trivial - I simply made use of JSON schema in my call to Gemini:\n\nI still sent the same prompt, the only change was to my &quot;config&quot; ob",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "JavaScript Clipboard Stuff",
		"date":"Mon Oct 14 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1728928800,
		"url":"https://www.raymondcamden.com/2024/10/14/javascript-clipboard-stuff",
		"content":"Forgive the somewhat vague title, but I wanted to point folks to a series of articles I've had published on the Frontend Masters blog the past few weeks. I started writing for them recently, and while I note my &quot;external writing&quot; on my About page, I wanted to specifically call out this series. Over three articles, I discuss reading and writing to the clipboard with JavaScript as well as working with paste events:\n\nReading from the Clipboard in JavaScript\nWriting to the Clipboard in JavaScript\nHandling Paste Events in JavaScript\n\nThat last article is an updated version of a post of mine from July, but it's got some new material in it so I definitely recommend checking it out.\nHeader Photo by Jason Rosewell on Unsplash",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (10/13/2024)",
		"date":"Sun Oct 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1728842400,
		"url":"https://www.raymondcamden.com/2024/10/13/links-for-you-10132024",
		"content":"Happy Sunday and Happy Saints Are Winning As I Write this day. Before I get into the links, some administrative. At the bottom of my posts, I've got share links. A few days ago I removed the share to Twitter link as I've gradually removed myself from that platform. I was on there a few weeks ago desperate for some technical help, but in general, I'm pretty much done with the platform. I've added a share to Bluesky (where you can find me as @raymondcamden.com) but unfortunately, it isn't quite working yet. I followed the directions for 'Action Intent Links', but even the example on their docs isn't working. I'm going to keep the share link for now, but may remove it. And now the Saints are losing. Sigh.\nOh... and I literally just checked again - the share link is working on the docs. And I tested here, it is working as well... as long as you are logged in. And the Saints got the lead back!\nStatic Maps with SVG\nThis is a cool post demonstrating how to build static, SVG-based maps on an 11ty website. In the post, he takes a location value from his frontmatter and passes it to a shortcode. He then makes use of the D3 library, geojson data for countries, and from this can generate a map. You can see an example on top of this blog post, Mumbai at Street Level.\nHacking Cars in JavaScript\nI love seeing cool uses of web APIs, and this one just takes cake. Charlie Gerard writes about how they used WebUSB and additional hardware to intercept data being sent from a keyfob. It's a fairly detailed article, but as I said, I love eeing the web platform help out here: Hacking cars in JavaScript (Running replay attacks in the browser with the HackRF)\nCheerio 1.0 Released\nI haven't used Cheerio in a few years, but when I did, it was incredibly helpful. Cheerio lets you use jQuery-style APIs in server-side code. When working with HTML, perhaps content loaded remotely with a fetch call, having the power of jQuery methods makes it much easier to get what you want out of the source.\nThe 1.0 release announcement details the long road to the release, new docs and new methods of working with the library.\nAs I said, it's been a hot minute since I used it, but it's a great tool and I'm happy to see the update!\nJust For Fun...\nAs I wrap this up (and the Saints are winning going into halftime), Lunar Pilot is a web-based game where you write code to help a lunar module safely land on the moon. You use JavaScript - of course. I love this comment in the editor:\n\nAmen. In games, fun should always trump realism.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Generating Illustrated Stories with AI",
		"date":"Fri Oct 11 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1728669600,
		"url":"https://www.raymondcamden.com/2024/10/11/generating-illustrated-stories-with-ai",
		"content":"A few months ago, I built a little demo that I simply forgot to actually talk about here. A coworker was building something similar and it reminded me to take a look at the code, update it, and actually share it. This is a pretty cool example of integrating multiple different APIs to create a final product, in this case, a short story with pictures. Here's an example:\n\nHow was this built? At a high level:\n\nGoogle's Gemini AI is used to generate a short story.\nAdobe's Firefly Services is used to generate the images.\nAdobe's Acrobat Services is used to turn the text into a PDF.\n\nThat's the high level, now let's get into the nitty-gritty.\nGenerating a Story\nTo create a story, I used a simple prompt at first, &quot;Write a four-paragraph story about a magical cat, appropriate for a young reader.&quot; And that works, but needs a bit of work though. In order to generate images in the next section, I was worried a paragraph of text would be a bit too big for a prompt. So I actually ask Gemini to create a summary for each paragraph.\nIn order to get just the right shape for my content, I use JSON schema:\n\nNote I specify exactly 4 paragraphs for the story as well the keys to use for the text versus the summary. My script takes the story idea from the command line:\n\nAnd then passes it to my function calling Gemini:\n\nSo given this:\nnode --env-file=.env process.js &quot;Write a story about cats that play music.&quot;\n\nI get:\n\nGenerating the Images\nThe next part is rather easy. Given that I have a short summary for each paragraph, I can loop over the paragraphs and ask for an image from Adobe Firefly:\n\nHere's the textToImage function which makes use of the image generation Firefly API, specifically the latest v3 model.\n\nNormally when you use Firefly, you take the results and save them, but we can use the shortlived URLs for our next step.\nStitching it Together with Document Generation\nNow for the final part. I've got my text. I've got URLs for my images. To create the final document, I'll use the Adobe Document Generation API to create a PDF. This API lets you use Microsoft Word as a template, pass in data, and generate a PDF. In this case, our Word template is super simple:\n\n\n\nIn this template, the tags say to loop over each paragraph and output the text and image URL. I could absolutely do more here. I could make the Word template prettier for example. But it absolutely gets the job done.\nI won't share the code here in the post (although I link to everything at the end), but in general, Acrobat Services REST APIs are incredibly simple. In this case I:\n\nGot my access token with my credentials\nUploaded the Word template\nCalled the API and referenced the template while also passing my data\nPolled to see when the creation job was done\nDownloaded the result\n\nI blogged about the Acrobat Services REST APIs when they were first released in 2022, so you can check that link for more information.\nMore Thoughts\nYou can see the complete code base here, https://github.com/cfjedimaster/fireflyapi/tree/main/demos/story, but note you'll need various credentials in order to get it to work.\nThis is - obviously - not perfect. Probably the biggest issue is that any 'character' created by Firefly in one image may not look the same in a followup image. Being able to carry over a result like that isn't yet supported by the API. You can use object composition, but that implies the existence of existing media, which we don't have in this case. Another possible option would be using the &quot;style reference&quot; feature of Firefly, which lets you upload a source image to use as a style guide. In theory, my code could use the first image as a style reference for the following three images. I may give that a shot next week.\nAnd as always, I'd love to hear your opinions as well, so leave me a comment below with your thoughts, suggestions, and so forth.\n",
		"tags":[
	        
            "generative ai",
            
            "javascript",
            
            "adobe"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Custom Markers with Leaflet",
		"date":"Wed Oct 09 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1728496800,
		"url":"https://www.raymondcamden.com/2024/10/09/custom-markers-with-leaflet",
		"content":"As I continue to dig into Leaflet, I was recently asked about custom markers based on data, so for example, some locations for a store may use one icon while others use another. I did some digging, and while it turns out Leaflet has deep support for customizing markers, it does take a little bit of work. Here's what I found.\nFirst off, this is the default marker:\n\n\n\nOut of the box, this is it. Period. I can appreciate the library wanting to keep its size to a minimum, but I was a bit surprised. That being said, the library provides really flexible support for creating your own markers. The first thing I found was the tutorial, Markers With Custom Icons. In this tutorial, they describe the process of creating an instance of the icon class and specifying resources for the icon and shadow. Technically, this looked incredibly simple, and again, flexible, but the immediate issue I ran into was... ok, where do I find more icons?\nI did some more digging and came across this open-source project, leaflet-color-markers, which provides a set of markers in the same style as the original, but in a variety of colors. Their read.me demonstrates the usage like so:\n\nNow... I'm hesitant to suggest making use of raw.githubusercontent.com, as I've always heard that it wasn't meant to be used in production applications. That being said, for today I will, and keep in mind that you can take the asset (for example, marker-icon-2x-green.png) and download it to your website.\nI tested this myself and it worked fine, but I wanted the smaller size icons, not the one demonstrated above. I simply copied the URL (https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png), but then the four size/anchor properties stopped working well.\nThat made sense - I mean, I had a different size icon, and my first thought was, well, I'll just keep tweaking numbers till I get it right, but then I went back to the Leaflet docs, specifically for Icon, and noticed this part:\n\nIn order to customize the default icon, just change the properties of L.Icon.Default.prototype.options (which is a set of Icon options).\n\nOn a whim, I simply entered L.Icon.Default.prototype.options in my console, and got the following:\n\nWoot. So with these defaults, I was able to figure out how to combine that project's custom colors with the 'regular' size markers and set the right values.\nI'm not quite done yet. One more cool aspect of the Leaflet tutorial on custom markers was how they demonstrated that you could define your own custom icon class, and then make new instances with only slight tweaks. This is taken right from their tutorial:\n\nI took this approach, and merged it with the icons from the GitHub project, and came up with this snippet:\n\nHere is an example using it:\n\nThis was all maybe twenty or so minutes of digging, so not too bad at all. Here's a complete CodePen:\n\n  See the Pen \n Leaflet with Custom Icons by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nA Sample Application\nLet's demonstrate this with a (kinda) real-world scenario. For the next map, I'll call a function that returns a list of stores and whether or not they are currently open:\n\nAnd with the same custom markers I demonstrated above, I'll use them like so:\n\nIt is fairly simple, and you can imagine multiple different icons in play based on the data for your locations. Here's the demo:\n\n See the Pen \n Leaflet with Custom Icons by Raymond Camden (@cfjedimaster)\n on CodePen.\n\n\nI think I said this in a previous post, but I love how easy Leaflet is, and when I do run into something that isn't quite as easy, I feel like it always provides a way to get to what I need. That's a sign of a good library!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Geocoding with Leaflet",
		"date":"Fri Oct 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1728064800,
		"url":"https://www.raymondcamden.com/2024/10/04/using-geocoding-with-leaflet",
		"content":"When I first started talking about Leaftlet, I mentioned how it was &quot;only&quot; a map library, and by that I mean, only able to present a view/wrapper around tiles representing map data. There's a heck of a lot of services that Google Maps, HERE, and so forth, add on top that won't be present, things like routing, geocoding, and more. Considering the fact that Leaflet is, again, &quot;only&quot; a client-side JavaScript library, that's just a fact of life. But I've been thinking about how I could integrate Leaflet with such services, and I thought I'd share a demo of just that - adding geocoding to Leaflet.\nWTF is Geocoding?\nSimply put, geocoding is converting an address in text to a precise location. So for example, a city like &quot;Seattle, Washington&quot; can be geocoding to latitude 47.61 and longitude -122.33. A location like &quot;3901 Johnston St, Lafayette, LA 70503&quot; (a local restaurant that is amazing) can be geocoded to 30.198, -92.055.\nReverse geocoding lets you supply a latitude and longitude and the API will tell you what's there. So for example, 38.9002898,-76.9990361 map to 508 H St NE, Washington, DC 20002.\nDo you need this?\nBefore considering even adding geocoding to a client-side application, ask yourself if you really need it. In a few of my Leaflet demos, I've used the example of stores for a business. If your business had 5, 10, heck, 50 locations, most likely that's a pretty static set of locations not changing often. Open up your browser, go to any number of geocoding websites, enter each one by one and copy and paste the results into a database, local file, or parchment. There's no need for every visitor to your web site to hit some third party API to locate a store that's the exact same location for every other visitor to your site.\nOk, but what if you have five thousand? In that case, consider automating it, but do it locally. Write a script that goes through your data, geolocates based on an address, and then stores it.\nNow, in that use case, you will want to check the usage rules for your API provider. Some prevent this and that's kind of sad imo.\nBut the main takeaway from this is before you even consider using my code below, ensure you actually need to first.\nI can't believe I'm being practical. On my blog. I should check my temperature perhaps.\nGeocoding via API\nIn preperation for this post, I did research, and by research I googled for &quot;geocoding API&quot;, because I was specifically looking for something outside of Google and HERE. I found a pretty good one, geocodio. It's got some great pluses, with only one minus. On the plus side:\n\nGreat free tier, with no need to add a credit card unless you want to do batch processing.\nA simple API, one of the friendliest I've seen. (And I'll share an example below.)\nThe ability to IP lock your key for additional safety.\nYou are &quot;legally&quot; allowed to save the results of geocoding. See what I mentioned above about some services preventing you from doing that.\n\nOn the minus side however...\n\nThey only geocode in the US and Canada, although they can reverse geocode in Mexico.\n\nThat's pretty signficant, but if you know you're working in that area, I have to say all the plusses really make this a great service. Their free tier supports 2,500 lookups a day, which is pretty high. Also, when I first ran into this limitation, I had missed it in the docs and reached out for support. They got back to me within an hour, which is always a good sign.\nTheir geocoding API (and they include reverse and batching as well) supports a number of options both how to search and how to handle the results. As a basic example, this will attempt to geocode my city:\nhttps://api.geocod.io/v1.7/geocode?q=Lafayette+Louisiana&amp;api_key=YOUR_API_KEY\nThis returns:\n\nThat's quite a bit, and you can both limit the number of results as request even more fields like congressional and school districts. Check out that doc I linked to in the previous sentence as it's quite extensive.\nBut what I really like is that it supports a format attribute that takes one value, simple.\nhttps://api.geocod.io/v1.7/geocode?q=Lafayette+Louisiana&amp;format=simple&amp;api_key=YOUR_API_KEY\nThis returns:\n\nThat's... excellent! Let's use that!\nLeaflet and Geocoding\nI whipped up a super quick Leaflet demo that while probably isn't terribly realistic, shows the integration of geocoding with maps. For my demo, I simply used a text field you can enter an address into. Beneath it is the HTML for the map.\n\nNow for the code. First, the map, centered on the US:\n\nNext, recognizing changes to the search field and kicking off a geocode request:\n\nNote that this handles removing a previous marker, and then adding a new one for the result. The last bit is a nice little function I found on a blog post that moves and centers on the marker:\n\nI've not seen those APIs before, but it immediately made sense. I modified the code a bit from what the blog showed to add the 'fly' effect.\nAnd that'",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "ColdFusion Component for Google Gemini",
		"date":"Thu Oct 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727978400,
		"url":"https://www.raymondcamden.com/2024/10/03/coldfusion-component-for-google-gemini",
		"content":"This week I had the pleasure to present on Google Gemini at the ColdFusion Summit. If you weren't able to make it, I do plan on giving the talk again on the ColdFusion Meetup sometime later this year.\nAfter the presentation, I took my 'rough and ugly' code that called Gemini and decided to wrap it up in a nice ColdFusion component. This allows for (hopefully) easier use. For example:\n\nAnd that's it. The result variable will contain two keys, a raw value that is exactly what Gemini returned, and a text value that narrows down into the text response.\nMultimodal prompts are also somewhat simple:\n\nAnd you can use multiple files at once:\n\nThere's still more I'd like to add, specifically support for safety settings and chat, but for now, you can find the code here: https://github.com/cfjedimaster/gemini.cfc\nI am, admittedly, somewhat rusty with ColdFusion, so I'm happy to take PRs that simply update my syntax to more modern conventions I may have missed. Just let me know!\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Eleventy 3.0 Released (and in use here!)",
		"date":"Wed Oct 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727892000,
		"url":"https://www.raymondcamden.com/2024/10/02/eleventy-30-released-and-in-use-here",
		"content":"This is just a quick note to let my readers know that Eleventy 3.0 has been released. This is a huge update and folks using it should read the full release notes here: Eleventy v3.0.0: Possums ❤️ ESM.\nI've been running a beta for a while. You can see the issues I ran into here: Upgraded to Eleventy 3.0 (Beta). I upgraded to the final version yesterday and ran into one issue, a template that output to a path without an extension. This was very clearly detailed in the error I got:\n[11ty] Problem writing Eleventy templates:\n[11ty] The template at './src/webfinger.liquid' attempted to write to './_site/.well-known/webfinger' (via `permalink` value: '.well-known/webfinger'), which is a target on the file system that does not include a file extension.\n[11ty]\n[11ty] You *probably* want to add a file extension to your permalink so that hosts will know how to correctly serve this file to web browsers. Without a file extension, this file may not be reliably deployed without additional hosting configuration (it won’t have a mime type) and may also cause local development issues if you later attempt to write to a subdirectory of the same name.\n[11ty]\n[11ty] Learn more: https://v3.11ty.dev/docs/permalinks/#trailing-slashes\n[11ty]\n[11ty] This is usually but not *always* an error so if you’d like to disable this error message, add `eleventyAllowMissingExtension: true` somewhere in the data cascade for this template or use `eleventyConfig.configureErrorReporting({ allowMissingExtensions: true });` to disable this feature globally.\n\nAnd since I did want it write without an extension, I added the value suggested:\n---\npermalink: '.well-known/webfinger'\neleventyExcludeFromCollections: true\neleventyAllowMissingExtension: true\n---\n\nAnd that was literally it. I did take the opportunity to upgrade two remaining serverless functions to ESM and that took about an hour or so, mostly due to non-Eleventy issues. I didn't have to update them but I was tired of the errors.\nNow that 3.0 is shipped, I'm planning on doing an intro to it on my &lt;Code&gt;&lt;Br&gt; show later this fall.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You (9/28/24)",
		"date":"Sat Sep 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727546400,
		"url":"https://www.raymondcamden.com/2024/09/28/links-for-you",
		"content":"Hello dear readers. I wish I could say I've got a restful weekend in front of me, but today we're driving to New Orleans to pick up our eldest who has been in Germany for a year, and tomorrow we fly to Las Vegas for the Adobe ColdFusion Summit. Both are things I'm quite happy about, but it's going to be a lot. I'm currently sitting, drinking coffee, and watching &quot;Grey's Anatomy&quot;, a good guilty-pleasure show.\nBefore we get into the links, a few quick reminders. If you aren't already a subscriber, use the form below or here to sign up for my mailing list. Right now it's just tied to my posts and as a subscriber, you'll get an email notice for each post.\nIf you are already a subscriber, thank you! If you want to support me (and this site) in other ways, please consider buying me a coffee or visiting my Amazon Wishlist, or as I call it, the list of things I'd like but absolutely do not need. ;)\nAlright, let's get to it!\nA Look at Japanese Web Design\nThis is an absolutely fascinating look at how different countries culture's impact their web design, in this case, Japan. I've seen web sites from Japan before and absolutely noticed the differences, but Phoebe Yu does an  incredibly deep dive into the why of those differences.\n\n  \n    Play Video\n  \n\n\n\n\nFree APIs\nNext up is a giant list of free APIs: https://github.com/public-apis/public-apis. Maybe I'm weird, but I love playing with random APIs, and free APIs make that play even easier. This repository contains a huge list of APIs categorized into things like Business (that's me, I'm all business), Dictionaries, Geocoding, and more. And since I pitched my newsletter above, I'll point out I discovered this gem via the excellent weird wide web hole newsletter run by Salma Alam-Maylor. I definitely recommend subscribing.\nIn the Beginning There Was... BASIC\nI'm going to go out on a limb and suggest that, probably, I'm older than many of my readers. I got my start with computers in the early 80s on an Apple 2e (or Plus, I can't remember which we had) my mom had gotten from her employer. I played a lot of games on that machine, but at some point, I wanted to actually do things with the hardware, and that meant coding. BASIC was incredibly approachable, something I greatly appreciate now as a developer advocate. I wrote, probably, thousands of programs on that Apple, many of which were copied by hand from magazines like Family Computing.\n&quot;Back to BASIC—the Most Consequential Programming Language in the History of Computing&quot; by Clive Thompson does a great job capturing my feelings about the language and the impact it had on my life.\nWhile I don't want to go back to BASIC (ignoring that silly experiment from earlier this year), the language will always have a special place in my heart.\nAnd now for something interesting...\nThis video was shared with me by my friend Todd Sharp and the subject manner may not be something you think is interesting - refrigeration. But in thirty minutes, the presenter (sorry, I can't seem to find their name and if they aren't being obvious about it, I won't hunt), talks about refrigeration and specifically thermoelectric cooling. This is absolutely not something I'd watch if Todd had not shared with me, but honestly, this is a master class in how to make a technical topic interesting. It's informative and fun at the same time. Enjoy.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Chrome AI to Rewrite Text",
		"date":"Thu Sep 26 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727373600,
		"url":"https://www.raymondcamden.com/2024/09/26/using-chrome-ai-to-rewrite-text",
		"content":"Earlier this month, I discussed how Chrome's upcoming built-in AI support was adding new features specifically tailored to certain use-cases. In that post, I looked at the Summarizer API. For today, I decided to take a look at the rewriter API.\nAs a quick reminder, this is very early on, and if you want to try this yourself, you should hit the sign-up form and read the intro post from the Chrome folks first. Obviously, everything I'm going to show below probably will, almost certainly will, change before shipping.\nOk, with that out of the way, let's talk rewriting, specifically, how the Chrome API operates. Given a set of source input, the API can shorten, or lengthen the input, as well as help modulate the level of formality of the text. Another example would be to, well not &quot;dumb down&quot;, but simplify text for audiences of different technical abilities.\nOne of the reasons this excites me is the specific use-case of shortening content. I feel like this is something I've hit many times in the past. I'll be using a form on the web, enter some text, and then be told I need to shorten my input. (By the way, if your form doesn't provide real-time feedback about this, that's a UX issue you need to correct. See my post from last year on how I did this with Alpine as an example.)\nAs a way to see this in action, I built a quick demo that provides a text area for input and then provides a shorter version of your text:\n\n\n\nIn case it is difficult to read in the screenshot, my input was my typical bio:\n\nRaymond Camden is a Senior Developer Evangelist for Adobe. He works on the Acrobat Services APIs to build powerful (and typically cat-related) PDF demos.  He is the author of multiple books on web development and has been actively blogging and presenting for almost twenty years. Raymond can be reached at his blog (www.raymondcamden.com) or via email at raymondcamden@gmail.com.\n\nThrilling, right? You would definitely hire me as your next GenAI Evangelist I bet! After using the built-in AI service to shorten it, the result was:\n\nRaymond Camden is a Senior Developer Evangelist for Adobe. He's the author of multiple books on web development and has been blogging and presenting for nearly 20 years. He can be reached at his blog (www.raymondcamden.com) or via email at raymondcamden@gmail.com.\n\nI got to say, I think this is the most impressed I've been so far with these proposed APIs. For the heck of it, I took this output and fed it back in. The result was:\n\nRaymond Camden is a web development evangelist for Adobe. He's the author of several books, blogs, and presents regularly. You can contact him at his blog or email.\n\nTo be honest, that's dang good. And because I'm crazy, I did it again:\n\nRaymond Camden, Adobe web development evangelist, writes books and blogs and gives presentations. You can contact him at his blog or email.\n\nAgain, great result. It would be stupid for me to do it again. So I did it:\n\nRaymond Camden writes books, blogs, and gives presentations. You can contact him at his blog or email.\n\nAt this point we are down to 17 words. Let's do it again:\n\nRaymond Camden blogs, writes, and presents. Contact him at his blog or email.\n\nJust 13 words. And yes, I did try again and it wasn't able to make it shorter. In theory it could have gone with &quot;Mr. Camden&quot;, but honestly, I'm pretty pleased with the iterations.\nLet's look at the code. I'm going to 'paraphrase' a bit from the Alpine.js bits. After you determine if the browser supports it, you can then create a session:\n\nI feel like sharedContext would normally be more tuned to a particular kind of content. So for example, if this were helping writers on a site dedicated to cats, the context could be modified to discuss that. For length, you have values for shortening, lengthening, or keeping it as is. There is also a 'tone' setting that lets go for more formal and casual.\nOnce you have that, this is all you need to do:\n\nAnd that's it. Incredibly simple. I will share the CodePen below so you can see the full code, but 99% of it is Alpine.js and, again, it won't work for you outside of Chrome Canary. That being said, I am curious what you think. Leave me a comment!\n\n  See the Pen \n  window.ai - shorten by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n```",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Dynamically Showing and Hiding Markers in Leaflet",
		"date":"Tue Sep 24 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727200800,
		"url":"https://www.raymondcamden.com/2024/09/24/dynamically-showing-and-hiding-markers-in-leaflet",
		"content":"This was originally going to be an example of using Leaflet with Alpine.js, but while working on that demo I discovered an interesting aspect of Leaflet that was a bit more difficult than I thought it would be - hiding and showing markers. Here's how I approached the problem, and as always, if you know of a better way, leave me a comment below and share!\nApproach One\nFor my first demo, I decided to start simple. First, I built a function to return a static set of data:\n\nNote that each store has a name, a boolean on whether or not they're open 24 hours a day, and a location. For my first demo, I wanted a way to show and hide stores that were not open 24 hours a day. You could imagine this being on a company website and a user needing to find a store open late.\nWhen I first Google for how to hide (and show again of course) markers, I found that there wasn't a method for that. (Not technically, you'll see more in the second approach below.) I ran across one StackOverflow answer that was helpful: https://stackoverflow.com/a/61590840/52160\nIn that answer, the user suggests making use of a &quot;FeatureGroup&quot; that can added and removed from a map. That seemed like a good possibility, but I felt a LayerGroup was better suited. The docs describe this feature as:\n\nUsed to group several layers and handle them as one. If you add it to the map, any layers added or removed from the group will be added/removed on the map as well.\n\nOk, so given that I want a simple &quot;off/on&quot; toggle for showing stores that are open 24 hours, here's how I built that. First, the HTML:\n\nNot much there, but you can see the div for the map and my checkbox filter. In thecode, I begin by getting my stores and initializing my map:\n\nNext, I create two LayerGroups:\n\nNow I loop over my stores, and based on their properties, add them to the appropriate group:\n\nFinally, I add both groups to the map:\n\nAlright - so now, I add an event listener to my checkbox, and add the hide/show logic:\n\nAnd... that's it. Pretty simple actually. I can say that I could have gotten away without making the open24Group as it's never removed, but I kept it in there for now. You can try this yourself below:\n\n  See the Pen \n  Leaflet - Show/Hide Markers 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nApproach Two\nSo, the first approach works great for one filter, and a boolean filter at that. But what happens if you have 2? Or more? This approach breaks down unless you start creating a heck of a lot of different groups to accommodate all the possible combinations of filters, and that's only going to work if they are all boolean filters. What if wanted a free form text field filter, perhaps on the name of the store?\nI then realized that there may be an even simpler solution, at least in cases with a 'reasonable' amount of markers. While there isn't a 'hide' marker method, you can simply remove it from the map! Let's look at an example. First, my updated HTML with a name filter:\n\nMy JavaScript is a bit different now obviously. I'll share the entire thing then break it down:\n\nI now create my markers and store them in my, um, stores. You can see that in the forEach loop. I then have two event listeners, one for the checkbox and one for the input field. Both run filter which handles the logic of looping over every store and determining if the store's marker should be removed or added back.\nThis really seemed to work well, and you can test it below:\n\n  See the Pen \n  Leaflet - Show/Hide Markers 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs I said, this seems fine for a &quot;reasonable&quot; amount of markers. I'm not sure I'd use this approach with thousands of markers, but that's a consideration for another blog post probably.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Google Gemini's File API with ColdFusion",
		"date":"Mon Sep 23 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1727114400,
		"url":"https://www.raymondcamden.com/2024/09/23/using-google-geminis-file-api-with-coldfusion",
		"content":"I promise, I'm not turning this back into a ColdFusion blog, but as I prepare my presentation next week at Summit and update my Google Gemini code for some ColdFusion demos, I ran into a particularly gnarly bit that I wanted to share in a post. For the most part, I've had no issues using Gemini's REST APIs in ColdFusion, but the File API ended up being more difficult.\nIf you go the documentation for uploading, and use the 'Shell' language tab, you can see an example like so:\n\nThat's... a lot. :) I don't really do bash scripting at all, but I was able to guess as to a lot of what was being done here. It's a two step process - first create the file object which in turn returns a URL you can use for uploading the actual bits.\nWhen I began replicating all of this in ColdFusion, I started off as simple as possible, and didn't think the headers shown where that important. I was wrong - they were critical. I eventually got things working and turned it into a nice little UDF:\n\nAll you need to use this is a path to a file and the function will handle the rest. (Oh, and an API key of course. The UDF reaches out to the Application scope for it which is bad, so feel free to modify the code make that an argument.) I'll note that the Files API also allows for giving a display name to a file. For my code, I just used the filename, but you could make that an optional argument to allow for nicer, friendlier names if that makes sense.\nIn case you're wondering why this is necessary, and for folks who are planning on attending my session next week I ask that you stop reading now so you won't be spoiled (grin), this is how you can handle basic multimodal prompts with Gemini. For example, I can upload an image and then run prompts against it. Here's a script that does just that - scan a directory and ask Gemini to describe each image:\n\nThe function, promptForFile, shows how you can append the file as part of the prompt. Gemini can handle multiple files in one prompt as well, but for this demo I'm just showing how well Gemini can identify what's in a picture.\nThat's all for now. I'm thinking after this conference, I may try to wrap up my code in a simple component for folks to use. As always, you tell me if that would be useful for you!\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Using Asynchronous Content in Leaflet Popups",
		"date":"Tue Sep 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1726596000,
		"url":"https://www.raymondcamden.com/2024/09/17/using-asynchronous-content-in-leaflet-popups",
		"content":"Today in my &lt;Code&gt;&lt;Br&gt; stream (I'll share a link to the video at the bottom), I spent some time digging into Leaflet and worked on a demo that made use of the National Parks Service API. This is a fun API I've used many times in the past, especially at my last job at HERE. For the stream today, I wanted to build the following:\n\nCreate a map that loads a geojson file of NPS parks. The geojson file contains the code and name for each park.\nOn clicking one of the markers, use the NPS API to get more information about the park.\n\nIn general, I've found everything in Leaflet to be stupid easy, but this particular aspect turned out to be a bit more difficult, which of course made for a fun stream. I got it working, but I want folks to know I'm not 100% convinced that the solution shown here is the best. As always, if you've got a better idea, I'd love to hear more and you can leave me a comment below. Ok, let's get started.\nThe First Approach\nLet me begin by showing the code that handled the geojson initially:\n\nBasically, load my geojson from the server, parse it, and after the Leaflet map is initialized, you can add it with the geoJSON method. I freaking love how simple that is.\nThe first change I made was to bind a popup to the layer:\n\nIn this case, the function I wrote there is called every time you click and will return the Name value from the properties. As I said above, only the code and name are available, but we're going to fix that with a quick API call.\nFrom the NPS docs, you can retrieve park information with one quick call:\n\nThe API returns an array of results in the data key and since I know I'll always be getting one, it's trivial to return just the park info. With that function written, I then turned to incorporating it into the popup.\nI first tried something like this - and to be clear, I didn't check the docs so I fully didn't expect this to work:\n\nOn clicking, I got an error, so it was clear that Leaflet expected a synchronous response from the function.\nThe Second Approach\nSo, at this point, I actually did check the docs, and nothing really seemed helpful. I googled around and found this on Stackoverflow: Unable to successfully bind an async function to a Leaflet marker popup\nThe answer shared there shows returning a DOM element in the bindPopup function that gets updated later in code. Here's their solution:\n\nThat seemed... like a possible solution. So I gave it a shot:\n\nRemember that getParkData is the wrapper around the NPS API. I didn't use await on the call as I needed to return the DOM element as shown above and just update it later.\nThis... actually worked well. I was concerned however about race conditions. What happens if you click one marker and then quickly click another. As far as I can tell... it just works. Either Leaflet 'kills' the DOM element for the first marker so the later code does nothing, or it 'works' but as the popup is hidden, it doesn't impact the current popup.\nAs I said... I'm rather unsure about this, but it seems to work well. The code may be found here, and you try it yourself here: https://cfjedimaster.github.io/codebr/leaflet/nps.html\nI think there's still improvements that could be made here. For one, caching the calls to the API in browser storage for example. The NPS API responds quickly, but I do know last week it was running a bit slower. Some quick sessionStorage caching would really help. Also, the popup shows up small and blank. I could possibly setup the park name initially and show some &quot;loading...&quot; type messages while it works.\nThat being said... any thoughts? I really think that there's possibly a better way and I'd love to see it if so!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Parsing Markdown in ColdFusion",
		"date":"Mon Sep 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1726509600,
		"url":"https://www.raymondcamden.com/2024/09/16/parsing-markdown-in-coldfusion",
		"content":"Welcome to my third, yes, third, ColdFusion post in 2024. Is it a trend? Who knows. That being said, I'm doing some prep work to update my presentation on Google Gemini in preparation for my talk at Adobe's ColdFusion Summit later this month, I'm updating my Node.js demos to ColdFusion and ran into an interesting issue - converting Markdown responses from Gemini to HTML. Edit: I realized I gave my function at the bottom a dumb name. I updated the code to reflect a better name on 9/18/2024\nMy first quick Google searches didn't really mesh well with what I expected, so I asked on the CFML Slack and James Moberg pointed out a few options, but suggested I focus on Flexmark (which was backed up by another person on the Slack).\nI was directed to a blog post by, of course, Ben Nadel: &quot;Using Flexmark 0.32.24 To Parse Markdown Content Into HTML Output In ColdFusion&quot;. It was a bit out of date but was enough to get me going. Here's how I built my, admittedly quick and dirty, solution.\nStep One - Get the Jar\nThe Flexmark library is a Java package that looks to be incredibly customizable and conplex. The install instructions expect you to use Maven or another Java tool, but I figured I just needed to get the right jar. This took me a minute to figure out. I ended up Maven at the latest release for the &quot;all&quot; package, which led to the file listing here: https://repo1.maven.org/maven2/com/vladsch/flexmark/flexmark-all/0.64.8/\nOn this page, I downloaded flexmark-all-0.64.8-lib.jar.\nStep Two - Load the Jar\nNext, I added it in my Application.cfc like so:\n\nI'd probably not put the jar in the root of my demo, but this isn't for production or anything.\nStep Three - Use the Code\nSo for actually using it, I didn't follow Ben's code, but rather the simple Java code referenced in the GitHub repo. This is what they had:\n\nAnd from this, I wrote up a quick demo:\n\nYeah, not the best variable names, but, it worked perfectly well. I took this scratch code and built a simple UDF:\n\nI feel like this would be better as a CFC cached in the App scope so I'm not re-creating the Java objects on every call, but I'll leave that for others to do. :)\nI tested it like so, and it worked perfectly well:\n\nI hope this helps! It will be in my repo for the presentation once I check it in, but let me know if you have any questions.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Links For You (9/15/2024)",
		"date":"Sun Sep 15 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1726423200,
		"url":"https://www.raymondcamden.com/2024/09/15/links-for-you",
		"content":"Happy Half Way Through September... which isn't a thing I guess, but, I'm just in awe at how much faster time goes in the fall compared to summer. With the kids in school, and activities, time just seems to fly by. In roughly two weeks I'll (and my wife) will be in Vegas for the Adobe ColdFusion Summit. If you'll be there too be sure to say hi. Before then I'll be speaking online at the JavaScript Global Summit next week. All of these upcoming conferences is just enough to make me forget I got a rejection today. ;) Lets focus on the positive, right, like some fun links for your enjoyment!\nCSS One Liners\nFirst up is a great collection of CSS one liners by Alvaro Montoro. As the title says, these are a set of simple CSS tweaks you can do to improve your web site. Even better, he shows &quot;before and after&quot; pictures of the changes so you can see exactly what they accomplish.\nAlt Text Like a Dragon\nOk, maybe that's a bit over the top, but, the article, Dungeons &amp; Dragons taught me how to write alt text, is an incredibly good look at how alt text should be written. I think most folks (including myself) tend to focus on just ensuring we write something, but Eric Bailey  explains in detail what kind of information should go into your alt text (as well as what shouldn't).\nDeep Dive into Location\nNext up is a fun little article, The Cake User Location is a Lie!!!. In it, Austin Gil discusses the various ways to get a user's location, and what problems each of those approaches will have. Now, I will say I've got some problems with the article. (I'm mentioned in it based on my feedback. :) Specifically, I'm an incredibly strong opponent of basing a site's language on the user's location when browser's share (via a header) what language they prefer. That being said, this article is still absolutely worth your time.\nAnd now for something you will hate...\nThis is a really bad but really fun video with Babymetal. I don't think it's bad, but this is the kind of thing you will either enjoy or absolutely, positively hate. Either way, turn your sound up and give it at least 30 seconds before you quit!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using AI to Roast Your Photos",
		"date":"Thu Sep 12 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1726164000,
		"url":"https://www.raymondcamden.com/2024/09/12/using-ai-to-roast-your-photos",
		"content":"Chalk this up as another of my &quot;this is probably not a good idea, but it's fun&quot; blog posts. A few weeks back my buddy and ColdFusion Evangelist Mark Takata shared a fun little thing he did with GenAI - using it to roast himself. That immediately set me off on a quest to see just how much fun I could have with the idea. Now, to be clear, I do not like mean people. But having a disembodied set of code routines roast me? Sounds perfect.\nBack in December last year, I built an experiment where I used the device camera on a mobile web app and asked Google Gemini what kind of cat breed was in the picture: Using Generative AI to Detect Cat Breeds\nThat experiment worked really well actually. The only real issue I ran into involved the size of the image I was sending to the API. When the Gemini API first came out, you could only do multimodal prompts by converting your image to base64 and including it in the prompt. That had a max file size limit that was easy to reach with images taken on a good mobile camera. I ended up doing client-side resizing in JavaScript to get around it.\nNow, however, Gemini has a proper &quot;Files&quot; API. I first wrote about this back in May: &quot;Using the Gemini File API for Prompts with Media&quot;\nI decided to take that existing front end app, remove the resize portion, and hook it up to a new back end. How did it work?\nOmg really, really, well:\n\n\n\nBrutal. And I love it. Alright, so how about the code...\nThe Front End\nI'm not going to share all of the code on the front end as it's pretty much the same as before. I will remind folks that you can easily add camera support via the web on mobile like so:\n\nWhat's nice is that on desktop, that just reverts to a file picker. (And to be clear, you can ask for a web cam on desktop too, but this was all I needed for the moment.)\nOn the JavaScript side, I'm using Alpine, and basically I just read in the data to base64 and pass it to the server:\n\nI don't have any error checking here because I like to live dangerously.\nThe Back End\nIn the previous post I made use of Cloudflare Workers, but I'm a little ticked at them currently so I avoided that and just wrote some Node.js code. I'll share a link to the entire codebase at the end, but here is, roughly, what it does:\n\nThis first block has my imports and sets up the objects I'll use later. Of special note is the si variable which is my system instruction.\nThe Node.js code listens for the call to /roast and then runs this function with the data:\n\nI convert the base64 back to binary and save it. That file is then uploaded to Gemini and then I run my prompt, which is fairly simple since most of the 'definition' for how the results should work is done in the system instruction.\nYou can find the full code here: https://github.com/cfjedimaster/ai-testingzone/tree/main/camera_roast\nMore Examples\nSo I played around with this a bit, of course. I shared it with my brother, Alex, who took this great pic:\n\n\n\nThe result was:\n&quot;Oh honey, put the camera down. The only thing appealing in this photo is that crisp blue siding in the background. Did you roll out of bed, sweat for a few hours and think &quot;Time for my close-up&quot;? The forced sadness expression? Get outta here. This belongs in a 'how-not-to-selfie' textbook.&quot;\nAnd here's another result, this from a random picture I had saved already:\n\n\n\nGemini loses a point though for identifying the tribble as a cat.\nAnyway, it would be interesting to see what the service says if it wasn't set to &quot;roast&quot; mode. I don't know enough about photography to judge how well the responses would be, but if folks want to fork my code and give it a shot, let me know what you find in a comment below.\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using the Chrome AI Summarizer (Early Look)",
		"date":"Tue Sep 10 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725991200,
		"url":"https://www.raymondcamden.com/2024/09/10/using-the-chrome-ai-summarizer-early-look",
		"content":"I've looked at Chrome's on-device GenAI development a few times now, and as a feature it is moving pretty fast. In fact, that first post and my follow up both don't work anymore due to the API changing. I'm fine with that as I knew it was a bleeding edge feature, but I just want to warn folks ahead of time that everything you see here may, no, will change, probably a lot. As before though, I'm keep getting more and more excited about the possibilities here. I'm still not certain this will see the light of day (in mainline Chrome) or expand out to other browsers, but it's quite interesting.\nMost recently, Google has added three new APIs to the feature:\n\nA summarization API\nA language detection API\nA writer and rewrite API\n\nMy understanding of these APIs is that they are basically &quot;directed&quot; hooks into the LLM bundled in the browser. You can already use a freeform prompt for the above, but by having a specific API for these needs, you can get better results out of the model. That seems sensible, especially as prompt writing itself can be somewhat of an art and anything that makes that simpler will be useful. (As it stands, I'd like to see this in the Gemini API as well.)\nFor today, I'm looking at the summarization API. The docs are a bit sparse at this time. The link I just shared there walk you through the setup process, which as I explained in my previous posts is a bit of a thing.\n\n\n\nFollow the directions, closely, and be prepared to wait a bit for the model to download. I've had numerous conversations with Chrome folks and they all know this needs to be improved.\nThe API overview shows you an idea of the simplest use of this - beginning with initialization:\n\nAnd then use (for this code sample I'm stealing from the docs, I greatly reduced the input text for brevity):\n\nIn that same documentation page, there is a list of caveats that I believe is mostly out of data. For example, it mentions that the options you pass to the summarizer object are ignored, but I didn't see that myself. It also says you have to destroy and recreate the object for each call to .summarize, and again, I'm not seeing that.\nMore information about the API cay be found here: Writing Assistance APIs Explainer. As they clearly warn on top:\n\nThis proposal is an early design sketch by the Chrome built-in AI team to describe the problem below and solicit feedback on the proposed solution. It has not been approved to ship in Chrome.\n\nSo take that and all my warnings above to heart.\nIf you scroll down to Detailed design, you'll find definitions for the options you can pass, which include:\n\ntype: What kind of summary do you want? Includes &quot;key-points&quot;, &quot;tl;dir&quot;, &quot;teaser&quot;, and &quot;headline&quot;\nformat: Supports &quot;plain-text&quot; or &quot;markdown&quot;\nlength: Supports &quot;short&quot;, &quot;medium&quot;, &quot;long&quot;\n\nDemo\nSo how about a demo? And again, keep in mind that this code probably won't work next Tuesday. I thought a good usecase for this would be summarizing the content of RSS feeds. You could imagine a RSS feed reader built for the web (hey, someone should do that!) that provides summaries of the entries.\nTo enable that, I first made use of a serverless function to handle RSS parsing for me. Last year, I built a generic RSS parser on Cloudflare. I say &quot;generic&quot;, but the serverless function is limited to a very small set of allowed RSS URLs. (To be honest, Cloudflare's free tier is so freaking generous I could probably get rid of that. If someone asks nicely in the comments, I will.)\nThat API takes a RSS feed, parses the XML, and returns an array of entries. I started off with a simple bit of HTML that makes use of Alpine.js directives:\n\nBasically, enter a URL, hit a button, and it kicks off the process. Now let's look at the code.\n\nI'll skip over the Alpine stuff as that isn't critical. You'll note I check for window.ai as well as it actually being ready to use. If so, I create my summarizer object. I went with tl;dr and medium. The sharedContext property isn't really documented but seems to direct the summarizer about what kind of content is being summarized. I'm honestly not sure.\nNow, the crucial bit came when I actually summarized the text. I noticed quite early that the HTML in my RSS content was not working well and messing up the summaries. In my initial approach, I removed HTML tags and removed code blocks. Once again, Thomas Steiner helped out with a fascinating and potentially better way of doing that:\n\nWrite to a div\nGet the innerText\n\nThat worked really well, but I still ended up doing a regex replacement on code blocks as well.\nSo how well did it work?\n\nLet's Map Traffic Incidents... Again\nLink: https://www.raymondcamden.com/2024/09/06/lets-map-traffic-incidents-again\nSummary: In 2010, the author wrote a Proof of Content 911 Viewer using Yahoo Pipes and ColdFusion to scrape data from a local police department's website and display it on Google Maps. This w",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Let's Map Traffic Incidents... Again",
		"date":"Fri Sep 06 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725645600,
		"url":"https://www.raymondcamden.com/2024/09/06/lets-map-traffic-incidents-again",
		"content":"This blog has been around for a while (twenty one years currently) so it isn't too uncommon for me to revisit old topics and demos and rebuild them. I think today's post may be something of an outlier though. Way back in 2010, early 2010, I built a Proof of Content 911 Viewer that wrapped a local police department's web site, lafayette911.org.\n\n\n\nNote the cute disclaimer at the bottom of the site saying you have to ask permission to link to it. Tell me you don't know how the internet works without telling me you don't know how it works.\nAnyway, back in 2010 I used Yahoo Pipes (pour one out for a cool as heck web service) to scrape the data and store it in a database. This was done via a scheduled ColdFusion script. I then used ColdFusion's Google Map wrapper to display it.\nThat was fun. But even more fun was the fact that I forgot I had built it and six months later I had a crap ton of cool data: Update to my 911 Viewer\nFast forward seven years and I updated my code to make use of IBM OpenWhisk, the first serverless platform I really got into: Collecting 911 Data with OpenWhisk Cron Triggers\nHop forward again and in 2020, I demonstrated how to use Pipedream's event sources features to fire off workflows when a new traffic incident occurs. I like Pipedream for a lot of reasons but their ability to design custom ways to fire off workflows is incredible.\nIt may be that I'm obsessive, but recently I decided to take a look at it again. As it turns out, the last version of my code no longer works, so it was time to update it again.\nGetting Raw Data from Lafayette911\nWhen viewing Lafayette911, you have to dig a bit to see where the actual data is coming from. Opening up Devtools shows a POST request to https://lafayette911.org/WebService1.asmx/getCurrentTrafficConditions. I whipped up a quick script to confirm:\n\nThe result is within a d property of the object so realData just helps me get to it quicker. This is how it looks right now. As a note, we're having bad weather here so the number of results isn't surprising:\n\nCool. Now, let's map it!\nMapping the Data with Leaflet\nI knew that mapping this with Leaflet wouldn't be difficult, but I was missing something important. The data returned from Lafayette911 isn't geocoded. It's just a street and city address. In order to geocode this, I was going to need to use another service.\nI decided to make use of the Google Maps REST API. Give you have a key, this is a fairly easy process:\n\nNow, this isn't free, but in theory, my little demo should be well within the free tier. Hopefully anyway. That being said, I knew I'd need some kind of caching in place which meant - I needed to build a 'real' server.\nFor this I turned to Glitch. I've built some fun little projects there before and I thought this might be a good chance to do so again. One of their starter projects is a simple Fastify Node server so I began with that.\nI had never seen Fastify before, but I was able to read the code easily enough and modify it do what I needed. That's a great sign imo. Do check the Fastify website though as from what I can see, it looks to be a pretty nice Node server. (My main experience in that area is Express, and I haven't really used Express in probably a decade.)\nSo, given that I've got a real server, my plan was:\n\nCreate a route to simply load the HTML for my map (and CSS/JavaScript)\nCreate a route to get the data from Lafayette911\nFor each address, check a local RAM based cache for a geocoded address and if not there, ask Google for it.\n\nHonestly the RAM based cache should be more persistent, but again, this is just a demo for fun.\nHere's the entirety of the Fastify stuff, and again, even if you've never seen it, I bet you'll have no trouble understanding what's going on:\n\nNow let's look at the front end. I've got a div to hold my map, and here's the code that creates the map:\n\nBasically, ask the server for traffic incidents and add them to the map. For each incident, add a popup describing the issue.\n\n\n\nIf you want to see it yourself, and keep in mind, the data will change as time goes on, just visit: https://spectacular-large-battery.glitch.me/map\nWant to see the code? Just go to https://glitch.com/~spectacular-large-battery\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using PDF Content with Google Gemini - An Update",
		"date":"Thu Sep 05 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725559200,
		"url":"https://www.raymondcamden.com/2024/09/05/using-pdf-content-with-google-gemini-an-update",
		"content":"Way back in March of this year, I took a look at using Google's Gemini APIs to analyze PDF documents (&quot;Using PDF Content with Google Gemini&quot;). At the time, the Gemini API didn't support PDF documents, so I made use of our (Adobe) PDF Extract service to get the text content out from the document. This &quot;worked&quot; but was possibly less than ideal as my &quot;glom all the text together&quot; approach didn't really represent the PDF well. The PDF Extract API returns information about text context (like if it is a header for example), but my method ignored that. I'm happy to share that Gemini now supports PDF files natively. Let's take a look at how this works.\nUploading PDFs\nTo begin, you need to provide your PDF to Gemini. This is done via the Files API. I blogged about this a few months ago and it's a rather simple process. You can upload files up to 2 gigs with a limit of 20 per project. These files are stored temporarily, but last for 48 hours so you can absolutely upload, do some &quot;stuff&quot;, and then either delete them via an API call or let them expire naturally.\n\n\n\nThat aspect of the code hasn't changed, but I'll share the general function here.\n\nSummarizing the File\nOnce the file is uploaded, you then just include the reference in your prompt. Again, this is no different than what I showed in that earlier post, but here it is in action:\n\nAnd that's literally it. For an incredibly exciting document relating to Adobe's security policies, I get:\n\nThe document \"Adobe Vendor Security Review Program White Paper\" outlines the process and requirements for vendors that handle Adobe data. Here is a summary:\n\nAdobe Vendor Security Review (VSR) Program:  A program managed by Adobe Information Security that evaluates third-party vendors' compliance with the Adobe Vendor Information Security Standard.\nVSR Process:\n\nAdobe business owners initiate the VSR process by submitting a request with information about the vendor and the data being handled.\nAdobe sends a questionnaire to the vendor, covering security controls.\nAdobe Information Security analysts review the questionnaire, perform a gap assessment, and assign a risk level.\nA risk analyst discusses any gaps with the business owner and provides remediation suggestions.\n\n\nData Classification:\n\nAdobe uses a four-tier data classification system to define the sensitivity of data and establish handling requirements:\n\nAdobe Restricted: The most sensitive data, requiring limited access and strict controls. Examples: cardholder data, social security numbers, bank account numbers, passport information.\nAdobe Confidential: Data that would cause significant harm if disclosed. Examples: salary information, product roadmaps, financial data.\nAdobe Internal: Data that is sensitive within Adobe, but not as critical as Confidential or Restricted. Examples: operational planning documents, internal communications.\nPublic data: Information that is openly available.\n\n\n\n\nVendor Engagement:\n\nVendors must undergo VSRs annually or biannually, depending on the data classification they handle.\nVendors must comply with the most restrictive classification if data falls under multiple classifications.\nVendors must handle all data according to the Data Classification and Handling Standard.\nAdobe may take disciplinary action if data is handled incorrectly.\n\n\n\nThe VSR program is a critical component of Adobe's information security strategy, ensuring that third-party vendors comply with Adobe's security standards and protect sensitive data.\n\nSummarizing is just one thing you can do of course, I also tried a prompt for categorization:\nReturn a list of categories that define the content of this document. \nReturn your result as a comma-delimited list.\n\nUsing the same upload reference, I got this:\n\nInformation Security, Data Classification, Vendor Security, Security Review Process, Vendor Information Security Standard,  Vendor Risk Assessment, Policy, Security Management, Data Handling, Legal Obligations, Privacy Assessment, Data Retention, Due Diligence, Auditing, Compliance\n\nThis seemed to work well, but I'd be curious to know if you could restrict the returned categories to a certain set. I haven't tested that yet, and of course, you could keep a 'sanitized' list in code and only use results that match.\nHere's the entire script for this demo (and I'll link to the repo at the end):\n\nWorking with Multiple Documents\nOf course, the benefits can get even better if you want to work with multiple documents at once. In order for that to work, you just upload more items, and refer to them in your prompt. For example:\n\nThat's the uploads, and here they are in use in a prompt:\n\nThis is what I got comparing Hamlet and Romeo and Juliet:\n\nBoth *Hamlet* and *Romeo and Juliet* are tragedies written by William Shakespeare. They both share common themes but also exhibit significant differences.\nSimilarities:\n\nLove and Death: Both plays explore the themes of love and death, with romantic love le",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Next <Code><Br>, and Vote For My AI Demo!",
		"date":"Wed Sep 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725472800,
		"url":"https://www.raymondcamden.com/2024/09/04/next-codebr-and-vote-for-my-ai-demo",
		"content":"Greetings, programs. This is just a quick note about my &lt;Code&gt;&lt;Br&gt; show. Usually the show goes on every two weeks, but with the holiday this week I pushed it off to September 17th. You can find the details and RSVP here: &quot;Parks and Recreation - Using Leaflet and the NPS&quot;\nAs the link says, this will be another stream involving Leaflet, which I've been having a hell of a lot of fun with the last few weeks. I hope to see you there!\nAnd then on a completely different topic, I've entered the Gemini API Developer Competition and honestly, I've probably got no chance to actually win, but I'd love for you to take a look at my entry, Content Assistant, and give it a vote if you like it. I'm also ok with you voting for it if you don't like it. ;) To be honest, it's an incredibly simple demo, but it felt like a decent example of the use of gen AI. Watch the video and tell me what you think.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using CSV Data with Leaflet",
		"date":"Mon Sep 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725300000,
		"url":"https://www.raymondcamden.com/2024/09/02/using-csv-data-with-leaflet",
		"content":"As I continue to play with, and really freaking enjoy Leaflet, I thought it would be interesting to show a demo of using CSV data with it. This also coincides with an interesting dataset I got from the Data is Plural newsletter, a collection of datasets covering just about any topic you can imagine.\nA few weeks back, they shared ancient shipwrecks covering the years from 1500 BC to 1500 AD. I know, that's a bit random, but I thought it was kinda cool. The dataset covers near two thousand unique shipwrecks and includes information, at times, about the cargo that was being carried. I thought this would be fun to map, and here's how I did it.\nWorking with CSV\nDisregarding the map, the first thing I needed to do was parse the CSV. I turned to a solution I've used many times in the past, Papa Parse. Papa Parse is a JavaScript library for - wait for it - parsing CSV files. It's worked perfectly for me in the past, and did so mostly this time, with one small issue.\nI wrote a function to handle parsing my data so I could use it with Leaflet. I began with this:\n\nFrom the top, I provide the URL (hosted as an asset on CodePen), and specify the following arguments:\n\ndownload:true - this is how you tell Papa Parse that the first argument is a URL to be fetched\nheader:true - this tells Papa Parse to consider the first row as headers and to map the results to use those names\n\nThe final argument simply lets me make use of my Promise so I can make this an async function.\nThis worked... kinda. It failed to properly parse because our data has a line before the header line. Here's the first five lines:\n,,,,HARVARD MAPS/DARMC DATA,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,OXREP DATA,,,,,,,,,,,,,,,,,,,,,,,,,\n,DARMC_X,DARMC_Y,DARMC_OBJECTID,NAME,NAME2,Latitude,Longitude,Geo_Q,F2008_Wreck,F2010_Wreck,Geo_D,Start_Date,End_Date,Date_Q,Date_D,Depth,Depth_Q,Year_Found,Year_Found_1,Cargo_1,Cargo_Type1,Cargo_2,Cargo_Type2,Cargo_3,Cargo_Type_3,Other_Cargo,Gear,Estimated displacement,Comments,Length,Width,Size_Detail,Parker_ref,Parker?,Bibliography AFM State 2008,Bibliography_2,OXREP_locid,OXREP_Site_Name,OXREP_Wreck_Name,OXREP_Earliest_Date,OXREP_Latest_Date,OXREP_Dating_comment,OXREP_Wreck_ID,OXREP_Strauss_ID,OXREP_Parker_Number,OXREP_Sea_area,OXREP_Country,OXREP_Region,OXREP_Min_depth,OXREP_Max_depth,OXREP_Depth,OXREP_Period,OXREP_Reference,OXREP_Comments,OXREP_Stone_cargo_notes,OXREP_Other_cargo,OXREP_Hull_remains,OXREP_Shipboard_paraphernalia,OXREP_Ship_equipment,OXREP_Estimated_tonnage,,\n33,-316258.6733,6959356.392,34,Ellesmere, ,52.872,-2.841,ca,0,0, ,-500,500, , , ,silted,1864, , ,, ,, , , ,Paddle &amp; bowl found with the boat.,0.412,,3.35,0.73, , ,0,,S. McGrail 1978.,,,,,,,,,,,,,,,,,,,,,,,,,,\n912,1805231.076,5322124.705,913,Krava, ,43.066667,16.216667, ,416,430, ,-400,-200, , , , , , ,amphoras,&quot;Dr2-4, pear-shaped&quot;, ,, , , ,, ,,0,0, ,558,1,, ,1921,Krava,Krava,-400,-200,C4th-3rd BC,7970,,558,Adriatic,Croatia,Vis,,,,Classical/Hellenistic,&quot;N. Cambi in Amphores Romains et Histoire Economique, Dix Ans de Rechereches (Siena, 1986) 1989, 323-5; M . Jurisic in D. Davison, V. Gaffney and E. Martin (eds.) BAR 2006, 175-192&quot;,The earliest known wreck in the Adriatic,,Grindstones and handmills,,,,,,\n738,1421178.833,4602997.051,739,San Vito, ,38.166667,12.766667, ,809,847, ,-400,500,?, , , , , ,amphoras,, ,, , , ,, ,,0,0, ,1025,1,, ,1413,San Vito,San Vito,-400,400,,8405,,1025,,,,,,,,,,,,,,,,,\n\nSigh. So, the easiest solution (and honestly the one I did first) would be to just edit the file and remove that first line. But I was really curious to see if Papa Parse had another way of handling this. Maybe my CSV isn't a flat file per se but the result of an API call. I could still &quot;edit&quot; in JavaScript, but as I said, I was curious to see if the library could handle it.\nTurns out - it could. But it couldn't. What do I mean? Papa Parse documents a configuration option, skipFirstNLines, which seems perfect. I tried that... and nothing changed. I did some Googling and turns out, it's a bug.\nSigh (again). Luckily, in the bug report there was a simple workaround using the beforeFirstChunk option. I used that fix below:\n\nHopefully the library corrects this soon. I don't see a PR for it yet, so we'll see. That being said, I now have a generic function to translate my CSV into data.\nUsing Custom Data with Leaflet\nOn the Leaflet side, the work here was trivial. Given a latitude and longitude, and some information for a label, here's a generic bit of code that will add a marker:\n\nThat's literally it. Given how easy it is, here's the complete application:\n\nI create my map (centered on Europe), add my tiles, and then simply loop through my CSV data. There's a lot of columns, but I decided to show the name and a list of cargo. Not every item has a name, or any cargo, but for those that do I'll be able to show it.\nYou can play with the demo below:\n\n  See the Pen \n  Leaflet3 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSome Thoughts\nNow, i",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (8/31/2024)",
		"date":"Sat Aug 31 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725127200,
		"url":"https://www.raymondcamden.com/2024/08/31/links-for-you-8312024",
		"content":"For my American readers, I hope you are able to enjoy a relaxing three day weekend. I've got big plans to do absolutely nothing and do quite a bit of it. My September is going to be busy so I'd like to try my best not to think about that for the next few days. Let's get to the links!\nOne Million Checkboxes - You Won't Believe This...\nI use a note app to keep track of the links I want to share in these posts and generally, I share in a FIFO manner - the oldest added links first. But this... this link comes from a post shared recently on Mastodon and frankly was way too cool to put off. I don't even want to tell you about this link, I just want you to read it. Trust me.\nThe Secret Inside One Million Checkboxes\nOperating System Selector Web Component\nOne of my pet peeves are web sites that share documentation for one operating system only, typically OSX. Eleventy is a great example of doing it the right way - showing examples for OSX, Linux, and Windows. My friend Simon MacDonald on the Begin team shared how they support this using a component that handles the toggle as well as remembers your previous setting: OS Selector for Documentation Sites\nA Rant about Front-end Development\nRant is putting it nicely to be honest. This post by Frank Taylor is... a lot. Before clicking, prepare yourself for many bitter truths and even more curse words. Honestly I agree with pretty much all of it so I encourage you to read it (I mean, I'm sharing it here so of course I do), but just be ready for a rough ride.\nAnd now for something sweet\nThis is gloriously wonderful video of Deborah Woll explaining Dungeons and Dragons to Jon Bernthal. It's a near perfect explanation (although to be fair I've never played myself and I hate that I haven't yet) and seeing Jon grasp just how cool it is makes the video absolutely heartwarming.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a General Purpose GeoJSON Viewer with Leaflet",
		"date":"Fri Aug 30 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1725040800,
		"url":"https://www.raymondcamden.com/2024/08/30/building-a-general-purpose-geojson-viewer-with-leaflet",
		"content":"Last week I shared my initial experiences with Leaflet and I thought I'd share a small demo I built with it - a general purpose GeoJSON viewer.\nGeoJSON and Leaflet\nAs I mentioned at the end of my last post, GeoJSON is a specification for encoding ad hoc geographic data. Here's an example:\n\nGeoJSON can encode points, lines, polygons, and more, and support a properties section that can have anything in it. Leaflet makes it easy to use GeoJSON. Here's the example I used in that last post:\n\nThat's literally it. Given how easy this, I thought I'd build a demo where the data was provided by the user.\nThe Application\nMy application is built with simple vanilla JavaScript, no Alpine even, and lets you drop a file into the browser to load the information.\n\n\n\nMy code waits for DOMContentLoaded and then registers event handlers for dragdrop support:\n\nWhen you drop a file, I then use a bit of code to read it in.\n\nThe loadGeoJSON function handles adding the data to Leaflet:\n\nThis is pretty much the same code as before, except that my popup uses a basic dump (stringify) of the properties key. Note that this will not work for all files, especially if there's a lot of data there. I could get fancier with my output there and perhaps add a max height with overflow. That being said, here is how it looks after adding America's parks to it (and clicking one feature):\n\n\n\nYou can test it out here (full screen): https://codepen.io/cfjedimaster/full/GRbxVVR\nAnd here's the full code:\n\n  See the Pen \n  Leaflet geojson viewer (v2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Example using Azure's Node.js SDK for Signed URLs",
		"date":"Wed Aug 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1724868000,
		"url":"https://www.raymondcamden.com/2024/08/28/quick-example-using-azures-nodejs-sdk-for-signed-urls",
		"content":"Way back in June (wait, that's only two months ago?) I wrote up a blog post showing how to use the AWS SDK for Signed URLs: &quot;Quick example using AWS Node.js SDK V3 for Signed URLs&quot;. The idea for this was to cover a very specific set of functionality I needed to use along with Adobe's Firefly Services. Specifically my needs are:\n\nCreate a readable URL for a cloud storage asset\nCreate a writable URL for a cloud storage asset\n\nAnd on top of that - also I needed to upload directly to cloud storage. I worked with Azure Storage Blob SDK and came up with the following functions. Honestly, use this with a grain of salt as it &quot;worked for me&quot;, but I can't make any promises about how reliable/safe/etc this code is. That being said, I'd love any comments or suggestions.\nImports and Connecting\nOnce I installed the SDK, I began by importing what I needed:\n\nNext, I loaded in my credentials as well as an account and container name. So to be clear, for credentials it's an Azure key that I got from my portal and a connections string. The account name was also from the portal, and finally the container name is the 'bucket' where I'm working. I feel like the connection string could be constructed dynamically, but I hard coded it. All of these values are in my environment:\n\nAnd finally, I created my client objects:\n\nCreating Read URLs\nTo create readable URLs, I used two functions.\n\nNote that getSignedDownloadUrl chains to createSASReadString and doesn't modify the duration, I could update that. And honestly, looking at this now, I think it should be one function. When I was building this, I thought I'd be reusing createSASReadString a few times but I don't think I did. You could easily wrap those two together and I may do so in the future.\nUsing it then is as simple as:\n\nNote that I'm passing in my auth stuff. In that previous blog post the methods I wrote used the global s3 objects which is &quot;bad&quot;, but is simpler as well. I thought the approach above was a bit more generic and pure.\nI don't want to get that caught up in it though - feel free to modify what I build. ;)\nCreating Write URLs\nOn the flip side, here's the method to create writable URLs. This can be handed off, for example to the Photoshop APIs, and used for outputs.\n\nUsing it looks like so:\n\nUploading to Azure\nNormally I didn't have to worry about uploading to Azure. If I made an upload URL and the API used it, then I didn't need to worry about it. But I was curious how it would work. My 'usual' upload code failed because Azure requires a special header. Here's the function:\n\nThat x-ms-blob-type is the special header you need. Also note I've hard coded an image content-type. You could make that an argument or get the value dynamically.\nUsing it just requires the URL, which you get from the previous method, and a file path:\n\nThat's it. I hope this helps because this post is the post I wish I had found when I started. ;)\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Mapping with Leaflet",
		"date":"Fri Aug 23 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1724436000,
		"url":"https://www.raymondcamden.com/2024/08/23/mapping-with-leaflet",
		"content":"If you missed my &lt;Code&gt;&lt;Br&gt; episode earlier this week you missed (imo) a great episode. I'll share a link to the video at the end, but I spent the session raving about how great the Leaflet JavaScript library is for maps. I had it on my list to look into for a few months now and while having a layover recently I took the time to dig into it. I was - blown away.\nI've got a lot of experience over the years working with maps on the web. I really dig Google Maps, both the JavaScript library and APIs, and I spent some time as a developer evangelist for HERE helping others learn about their offerings. I'm obviously a bit biased but I really dug their offerings as well.\nThat being said, I was incredibly impressed with just how simple Leaflet is. Their quickl start has you up and running within minutes. As I played with it and wondered, &quot;how do I do X&quot;, every time I googled I found an answer and typically - it was pretty trivial.\nI don't want this post to be a rehash of their guide, you really should check it out, but I thought I'd show a few quick samples just to give you an idea of the level of effort required to work with the library.\nNow before I get started, let me point out that Leaflet is &quot;just&quot; the top level framework for working with map data. You have to bring in your own tiles. Their quick start demonstrates this. Also, you won't find features like routing, geocoding or reverse geocoding, and so forth. You could absolutely mix in those APIs from other providers of course, but if you ended up using Google Maps or HERE, I'd probably just suggest using their front-end code as well. The point is, you've got options.\nWith that being said, let's consider a few examples.\nStore Maps\nFor a few example, a real world use case could be plotting stores on a map for a company. Those stores could come from a database/backend API/etc, but honestly, if you are a small company with a few locations, you can just hard code em. In a few months or years when you open a new store, first off, congrats, and secondly, you can just add one more line of code.\nLeaflet requires one JavaScript and CSS resource:\n\nHTML wise, like other mapping solutions, you provide a div and simple css:\n\nNow for the JavaScript. We need to center logically, in this case the region where our stores are:\n\nThen add our tiles, ie the actual map:\n\nNow let's add 3 markers for stores:\n\nAnd how about some info about each store?\n\nAnd that's literally it. You can see it below:\n\n  See the Pen \n  Leaflet1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nStore Maps - But an API\nReal developers know that hard coding stuff is lame-sauce, and if you are a 10X Unicorn, you would quickly build up a serverless function backed by a database to put those stores in a persistence system that could power the universe.\nLet's pretend we built an API:\n\nAnd now our code becomes:\n\nYou can see this below. It looks the same, but I get to charge Senior Developer rates.\n\n  See the Pen \n  Leaflet Blog 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nGeoJSON\nOne of the things I enjoyed most about my time at HERE was discovering GeoJSON. GeoJSON is a JSON style that supports ad hoc mapping data. It's incredibly flexible and used in many applications. I grabbed a copy of America's national parks as a GeoJSON file and this is how easy it is to use in Leaflet:\n\nSimple, right? You can see this below. Clicking on a marker will show you the park's name:\n\n  See the Pen \n  Leaflet Blog 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWatch Me Build Maps...\nSo, if you want to see more, check out my CodeBr episode below. I plan on covering this more in my next session, and I've got a few more blog posts planned as well. If you've built things with Leaflet, please let me know and write a comment below.\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Another Web Component - Table Compressor",
		"date":"Tue Aug 20 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1724176800,
		"url":"https://www.raymondcamden.com/2024/08/20/another-web-component-table-compressor",
		"content":"Earlier this week I was browsing a site that showed a tabular list of data. It initially showed something like ten rows and had a clickable item that showed the rest of the data. I thought I'd whip up a quick web component that mimicked this functionality.\nMy thinking was that you would wrap a regular HTML table (much like my table sorting component) and the component would truncate and add the 'click to expand' logic. Now, to be clear, this still means the user is downloading the entire set of data, but visually it would take up less space until the user selects to show the rest of the data.\nLet me share the component here and then I'll explain how it works:\n\nFrom the top, I begin by looking for a rows attribute. If not specified it defaults to 50. The real work begins in connectedCallback.\nFirst I check for a table and if one isn't found, just leave.\n\nI then see how many rows we have, and if less than our desired cut off point, just leave:\n\nNext, I create my 'click to expand' portion. I could make the text here something you pass in via an attribute, but left it hard coded for now. Also note the 'hack' of colspan there. I found out that specifying a number larger than the columns in the table seems to have no side effects. Also, according to MDN, the max value for colspan is 1000. I had no idea.\n\nNow for the fun part. I needed to hide the rows over my desired cut off point. Turns out, you can do this in CSS because CSS, at least in the last decade, is freaking awesome. Here's how I did it:\n\nThe final part of this method adds my click to expand bit:\n\nThe final final bit is the click handler to reveal the hidden table rows and hide the clicker (I could remove it from the DOM I suppose):\n\nActually using it is simple - just wrap your table!\n\nYou can play with it below, and folks think it's worthwhile, I'll add it to NPM as well.\n\n  See the Pen \n  Compress Table by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Real-Time Sentiment Analysis on Device with AI in Chrome",
		"date":"Mon Aug 19 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1724090400,
		"url":"https://www.raymondcamden.com/2024/08/19/sentiment-analysis-on-device-with-ai-in-chrome",
		"content":"Last week, I took a look at Chrome's GenAI implementation in the browser. What I found was interesting, and rough, but possibly something that could be incredibly useful in the future. I've been thinking about it more the last few days. First off, if this feature interests you, there is an early preview program you can sign up for that gives you access to a mailing list containing docs, announcements, and more information. I've done that and it was a relatively quick process. There are already cool things I've seen there that I want to dig into soon, but today I wanted to share a little experiment. This too does not work terribly well yet, but I think the idea has merit and, as always, I'd love to know what folks think. Leave me a comment below with your thoughts.\nSentiment Analysis\nOne of the things I've done with GenAI in the past is use it for sentiment analysis. In fact, one of my very first demos using Gemini (PaLM back then) was to create a sentiment report of recent forum posts. This got me thinking about what kind of sentiment analysis could be done on the client-side, possibly providing feedback of your input before you submit a response or create a post.\nMy initial tests didn't work so well:\nGive me a sentiment score for the following test, with 0 being very negative, \nand 1 being very positive: I like cats. I love cats. If I could have a \nthousand cats, I would.\n\nSentiment Score: 0 The sentiment expressed in the statement is overwhelmingly \npositive and conveys a strong emotional connection to the topic. The user \nindicates their love and fondness for cats, using phrases like &quot;like&quot; and \n&quot;love.&quot; The sentiment also extends to the hypothetical scenario of having \nthousands of cats, emphasizing the emotional attachment and yearning \nassociated with the possession of these animals. Overall, the sentiment \nscore for the given statement is set at 0\n\nThe reasoning was dead on, but it didn't properly score the response. Here was my second attempt:\nGive me a sentiment score for the following test, with 0 being very \nnegative, and 1 being very positive: I do not like dogs. They smell. \nThey stink. They fart. I just really don't like dogs.\n\nSentiment Score: -1 The sentiment expressed in the statement is \noverwhelmingly negative and conveys strong emotional revulsion towards \ndogs. The user consistently uses phrases that describe the negative \ncharacteristics of dogs, such as &quot;smelly,&quot; &quot;stinky,&quot; and &quot;farting.&quot; \nThe negative and emotional language employed indicates a strong \ndislike for dogs, resulting in a sentiment score of -1.\n\nAgain, it parsed the text very well but didn't properly handle the scoring aspect. I then took another approach - why not simply ask for a sentiment summary of one word? In theory, it could help you quickly see the tone of what you're writing:\nSummarize the sentiment of the following text in one word\n\nGiven that and the two inputs above, the first returned:\nLove\n\nAnd the second returned:\nDislike\n\nBy the way, I feel like I have to say, I absolutely love dogs. Maybe not quite as much as cats, but I love my big stinky dog.\n\n\n\nCool, so let's build it!\nThe Demo\nFor my demo, I went with Alpine.js, primarily for one reason - an easy way to debounce calls to get the sentiment analysis. While the calls are all within the browser itself, it does take a second or two to process and I figured a simple debounce call would help reduce the work. Here's my HTML:\n\nThe debounce part can be seen in the x-model attribute on the textarea. Here's the JavaScript I used:\n\nBasically, I just have a hard-coded prompt where your input is appended and sent to the built-in model. I realize most folks can't test this yet, so here's a screenshot of this in action:\n\n\n\nAnd here's a more positive result:\n\n\n\nObviously, those inputs were just tests, but you could imagine a customer support response form perhaps, or just a forum post in general using this to provide feedback. I'd like to see the browser API mimic Gemini's where you can use system instructions and JSON schema to 'force' a particular response type. You can find the complete demo below:\n\n  See the Pen \n  window.ai test (sentiment analysis) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo, not even close to being really ready for production, but what do you think?\n",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (8/18/2024)",
		"date":"Sun Aug 18 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1724004000,
		"url":"https://www.raymondcamden.com/2024/08/18/links-for-you",
		"content":"Woot, welcome to football season! Ok, preseason, but I am beyond excited to watch my Saints play later today and, hopefully, look halfway decent. My wife and I are both big football fans and the start of the season is always a happy occasion. This past week I traveled to Atlanta to give a lab on Adobe's Firefly Services APIs which went well, and I get to do it again next week in New York City. I hope your week goes well, and here are some interesting links for you to enjoy.\nMigrating from Netlify to Cloudflare\nIn this post, Sia Karamalegos discusses how she migrated her site from Netlify to Cloudflare. This was done primarily due to Cloudflare releasing a new service to block AI crawlers, something she felt important enough to go through the work of a migration. Cloudflare is pretty impressive and I've really dug their serverless support, and while I have no plans of migrating my site, Sia's guide is well done and could be helpful to others.\nPipedream Adds If/Else\nReaders know I've been a huge fan of Pipedream for years now. They are, to me, the best Low Code solution out there. One aspect they were missing though was more advanced workflow controls like IF/ELSE conditions. Well, now it's here, well, almost here. The feature is available for testing but not quite done yet. You can find out more and see a cool demo below:\n\n  \n    Play Video\n  \n\n\n\n\nPersonally, I really like how they implemented this, especially the support for collecting the results at the end of each branch.\nHandling Rounding Errors in JavaScript\nFor your last link, here is a deep look at handling rounding errors in JavaScript for financial applications. This isn't something I've ever had to worry about and always figured I'd research it when I need to, but I'm really happy I read this article as it was fascinating.\nAnd Now For Something Completely Different\nThis is - easily - the most silliest and interesting form I've seen. I don't want to say anymore as it's just fun to experience it:\nhttps://codepen.io/ksenia-k/pen/xxoqXbJ\nThank you to the excellent Frontend Focus mailing list for sharing this link first.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Setting Dynamic Objects Keys in JavaScript",
		"date":"Fri Aug 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1723831200,
		"url":"https://www.raymondcamden.com/2024/08/16/setting-dynamic-objects-keys-in-javascript",
		"content":"It's always a good day when you get pleasantly surprised by JavaScript, even more so when you find that it's a feature that's been around for a while (ES6, which according to Google was standardized in June of 2015). Earlier today I saw some syntax that didn't look quite right to me. Here's a simple example of it:\n\nSpecifically, the thing that surprised me was this portion:\n\nIf you console.log the code above, you get:\n\nAnd then it makes sense. This syntax allows you to set a dynamic key in an object literal, much like:\n\nApparently, this has been around for nearly ten years and I never noticed it. Or, more likely, maybe I saw it and it didn't click in my head what was going on.\nOfficially you can refer to this as 'Computed keys in object literals' and can read more in Dr. Axel's Exploring JS book here: 30.7.2 Computed keys in object literals\nThanks to Dr. Axel, Tane Piper, and Caleb for all chiming in on Mastodon when I asked about this.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Quick Look at AI in Chrome",
		"date":"Tue Aug 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1723572000,
		"url":"https://www.raymondcamden.com/2024/08/13/a-quick-look-at-ai-in-chrome",
		"content":"Google announced a while back their intent to experiment with generative AI in the browser itself. Personally, I think this could be a really good idea, but I'm really unsure as to how many other vendors would support it. With Edge being a Chromium product, and Microsoft being pro-GenAI, it seems like a safe bet it would support it. Safari and Firefox... I honestly feel like they probably never would. That being said, initial support landed in Chrome Canary (the bleeding edge version of Chrome) and I thought I'd take a quick look. Here's what I found.\nSetting it Up\nTo test this new feature, you need either Chrome Canary or Dev. Enabling support requires tweaking two flags and restarting. This is all covered in a good blog post by Om Kamath, &quot;Google Chrome's Built-In AI Is A Game Changer.&quot;. You can also find a guide at Chrome AI.\nNow, here comes the rub, and this is a very early feature so keep in mind this may be completely different if you are reading this sometime after I posted. Both guides have you enable two flags and restart the browser.\nYou are then asked to go to chrome://components and look for Optimization Guide On Device Model. Here's where I ran into issues. I was never able to get this to show up.\nUntil... I kid you not... I simply forgot about it for a few hours, looked back at the Chrome window in the background, and saw that the line item had shown up. Also, I couldn't get it to ever work in Canary, but it did work in Chrome Dev.\n\n\n\nI had some help from Thomas Steiner on Mastodon. He mentioned that this long time to show up issue was known and that it should get better, but it's something to keep in mind if you wish to test this yourself.\nTesting\nI've not been able to find able to find a proper JavaScript spec for it yet, but the core object you'll use is window.ai. From what I see, it has three methods:\n\ncanCreateTextSession\ncreateTextSession\ntextModelInfo\n\nIf you first check for window.ai, you could then use canCreateTextSession to see if AI support is really ready. Ie, it's on a supported browser and the model has been loaded. For fun, this doesn't return true, but... readily. I'm kinda hoping that's a temporary string.\ntextModelInfo returns information about the model, which is probably obvious, but here's what it gave me:\n\nFinally, the thing you probably care about the most is createTextSession. It supports options, but I've not been able to find out what those are.\nTesting this is rather simple:\n\nWhich for me returned:\nI am a large language model, trained by Google.\n\nThere's also a promptStreaming method for working with a streamed response. I haven't tested that yet.\nHow well does it work?\nWell... I did some testing in console and it didn't work great. It felt really easy to get an error or a bad response. For example:\nawait model.prompt('What is the nature of the galaxy?')\n' The nature of the galaxy'\n\nAnd:\nwait model.prompt('Explain how light works. Answer like a high school physics teacher.')\n' Light'\n\nThis may - and I don't have any reason to guess why - be the nature of testing in console. When I built a simple web app, it actually started to work quite a bit better, so... maybe? With that being said, let's look at a quick demo.\nDemo One - Just a Prompt\nI started with a basic Alpine.js demo with just a prompt. I used this HTML:\n\nYou can see a form field for your prompt, a button, and a place to dump it. (Note that I'm seeing window.ai return text with line breaks, which will not be rendered properly in HTML. Something to keep in mind.)\nAnd a bit of JavaScript:\n\nAnd honestly - this seemed to work ok. If you've gone through the steps to enable this feature, you can test it below.\n\n  See the Pen \n  window.ai test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nDemo Two - Summarizing\nI followed up the last demo with a slightly more advanced one - a textarea you can drop text in and have it summarized. In this case, the prompt is hard coded. Again, here's the HTML:\n\nAnd the JavaScript:\n\nHonestly, this seemed to work well. I passed in the first paragraph of this blog post and got:\nGoogle has expressed an interest in experimenting with generative AI in the browser, and it looks like Microsoft and the Chromium-based Edge browser could support it. While other browsers like Safari and Firefox might not be as open to such developments, Google's move into this area signifies potential support for generative AI in the future.\n\nNot bad. Here's the demo:\n\n  See the Pen \n  window.ai test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nDemo Three - PDFs!\nYou love PDFs, right? I know I do. For the heck of it, I built a quick demo using Adobe's PDF Embed JavaScript library. One of the things it allows for is handling selection events. So what if you could select some text and get a summary?\nHere's a screenshot of this in action - first, the PDF on the left with my selection:\n\n\n\nHopefully you can kinda read the selection. This was the output:\nThe Bodéa Prime Plan is our most straightfor",
		"tags":[
	        
            "generative ai",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automating Background Removal with Firefly Services",
		"date":"Thu Aug 08 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1723140000,
		"url":"https://www.raymondcamden.com/2024/08/08/automating-background-removal-with-firefly-services",
		"content":"As a quick FYI, if you would rather skip reading my text and jump to a video, I've got one at the end of this post. Be my guest to scroll down and watch that instead. One of the most interesting aspects of Adobe Firefly Services is what it enables in the automation space. I think it's fair to say that these automations will still be followed up by a human checking, tweaking, and adjusting results, but if the APIs can save a significant amount of time, that's got to be a great benefit. Let me demonstrate one simple example of this - removing background images at scale.\nThe Remove Background API\nThe Remove Background API is part of the Photoshop API family and handles the job of figuring out the main subject of a picture and removing the background around it.\nThis API, like the others in the Photoshop set of operations, requires cloud storage. For my testing, I'm using Dropbox, but you could use AWS S3 or Azure as well. Note that if you aren't using cloud storage for your assets, this does mean you will need to, at least temporarily. So for example, you could copy a file to a specific folder in your cloud storage, do the processing, and then download (and probably delete) the output.\nHere's a sample body showing the minimum amount of values:\n\nPhotoshop APIs also return a Job when you begin the process. This Job is a URL you can check for the latest status of the operation. Here's an example:\n\nVersion One - Node.js\nFor the first version, I'm going to demonstrate a script that uses a specific input and output. I'm using Node.js, but obviously, any language that could make an HTTP request would be fine. (Hey, someone write a Perl script, please?)\nI'm going to start off with the main part of the program, and then flesh out the functions these lines are calling:\n\nFrom the top, I begin by getting a readable link to my input on Dropbox and then a writeable link to the output location.\nAfter that, I exchange my Firefly credentials for an access token, create the &quot;remove background job&quot;, and then poll the job.\nProcess-wise, that's the entire thing. Some error handling would surely be nice, but who has time for that these days?\nOk, let's hop back up to the top:\n\nFirst thing I do is read in my credentials and initialize the Dropbox Node SDK. Speaking of Dropbox, the next two functions are how I generate the URLs:\n\nNext up is the Firefly authentication routine - all Firefly services, whether GenAI related, Photoshop, or Lightroom, use the same auth:\n\nNext up is the API call to generate the job. Outside of authentication, this only cares about the input and output:\n\nFinally, here's the code that handles polling the job URL:\n\nAnd that's it. Given this input:\n\n\n\nThe output is this:\n\n\n\nIf you want the entire script, you can find it here: https://github.com/cfjedimaster/fireflyapi/blob/main/demos/remove_bg_scale/demo.mjs\nVersion Two - Automation via Pipedream\nFor the automated version, I'll be using Pipedream once again. My automation is a grand total of six steps.\n\n\n\nI begin with a trigger that fires when Dropbox gets a new file. This is built-in to Pipedream so literally all I had to do was select the folder.\n\n\n\nThere are three important bits above:\n\nNotice that the folder is /RemoveBGProcess. Unfortunately, I could not select the exact folder I wanted, /RemoveBGProcess/input. I don't know if that's an issue in the Pipedream trigger or the Dropbox API. But I handle that next so it isn't a big deal.\nBecause I couldn't specify the exact folder, I set Recursive to true so it would pick up files added to input.\nBecause I'm going to need to read the files, I set Include Link to true.\n\nThe next step of my process is a code step that handles the issue I noted above:\n\nNow I need to generate the path for my output. Luckily, this is just replacing input with output:\n\nNow that I know the path for my output, I need to generate a writable link to it. Pipedream doesn't have a built-in action for it, but it does have a &quot;Run any Dropbox API&quot; step. This handles authentication for me and lets me just write code against their REST API:\n\nThat covers the Dropbox aspects and next, I switch to Firefly and the Photoshop API. First is a step to get my access token:\n\nFinally, I kick off the job:\n\nNow, my automation ends here and you could rightly say I should have code to handle checking the results. I could add one more code step with my 'poll' and 'delay' methods. For now, I'm keeping it nice and simple.\nThis workflow can be found here: https://github.com/cfjedimaster/General-Pipedream-Stuff/tree/production/remove-backgrounds-at-scale-p_gYC1GJp\nThe Made for TV Version\nIf you want to see the above workflow in action along with the Node script as well, enjoy this yummy video below:\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "generative ai",
            
            "adobe"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Upgraded to Eleventy 3.0 (Beta)",
		"date":"Mon Aug 05 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1722880800,
		"url":"https://www.raymondcamden.com/2024/08/05/upgraded-to-eleventy-30-beta",
		"content":"Like I assume most of my fellow Eleventy users have been doing, I've been closely following the updates on Eleventy and its progress towards 3.0. As my blog is a fairly large site, I wasn't in a rush to upgrade to an Alpha release, but the recent beta release convinced me it was time to take the plunge. The TLDR is that... it took me roughly an hour (most of which was by choice, I'll explain below) and it's been smooth sailing. Want to know more? Keep on reading...\nThe Upgrade Helper\nPer the release notes, I made use of the upgrade helper which is a plugin you install along with, of course, upgrading your site to Eleventy 3.\nThis worked well and flagged my only issue, using EJS, which is removed from the core of Eleventy and requires a plugin:\n\n\n\nThat screenshot probably isn't too readable, but it clearly pointed out the EJS issue. I added the EJS plugin (and filed an issue to remove the files later) and it corrected the issue.\nNote that I made one small mistake though. The docs for the upgrade helper say:\n\nAnd to be honest, I ignored the comment. It's important. If you don't have it last, it won't recognize that you've installed the EJS plugin.\nSwitch to ESM\nEven though it is not required, I thought, why not switch to ESM as well? That was mostly easy as I've found Node to be really quick and clear when you screw this up. The only real issue I had was a script I had copied from someone else that I thought wasn't going to work for... I don't know. I think just because it wasn't my code. But I went through them all, updated them all, and it worked just fine.\nThere's one exception to this. My serverless functions were a mix of CJS and ESM code. I only used ESM in the Netlify functions that used their new 2.X version. I figured, well, assumed, that since the serverless functions don't run in the same context as the rest of the site I didn't have to upgrade them. I figured I should, and did a few, but I still have two left.\nRight now when I run netlify dev I get a warning, but it builds just fine locally and in production:\n\n\n\nFinishing up the last two serverless functions is on my TODO.\nMisc\nUpdating to Eleventy 3 also fleshed out a weird thing - I had a filter defined that I imported but didn't export. And I never used it. All I can think of is that it was a filter I forgot to remove sometime in the past, and for some reason, it started throwing an error in the 3.x version. Fine by me - I just deleted it from existence.\nThe last and final issue I ran into was on Netlify. Eleventy 3 requires Node 18. In my site settings at Netlify, I had 20 specified. Running a build though got me a pretty quick error saying my Node version was too low. Turned out I had an environment variable specifying the version. Again, I probably did that a few years back and just forgot. I nuked that too.\nThat's it really - not too difficult and I'm real excited about this version. I plan on hosting a &lt;Code&gt;&lt;Br&gt; session on Eleventy once 3.0 is finalized.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You (8/3/2024)",
		"date":"Sat Aug 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1722708000,
		"url":"https://www.raymondcamden.com/2024/08/03/links-for-you",
		"content":"The past few weeks have been a bit crazy, although mostly crazy good. Since the last one of these posts, my family and I went on vacation in San Antonio for a few days, and currently, my wife and I are in New Orleans for a short break. Last night we went to the Postmodern Jukebox concert which was easily one of the most fun concerts I've been to. I'll share one of their clips at the end, but let me just say that they were a real treat live.\nCSV for Eleventy\nFirst up is a great example of why I love Eleventy - its flexibility. This post, &quot;Eleventy - Add CSV data file support&quot; describes how you can use CSV files in Eleventy to drive your content. Eleventy's Data feature is incredibly powerful and this post by Rob O'Leary gives a great example of that.\nAutomating Redirects with Eleventy\nIf I've shared one Eleventy link, why not another? Flamed Fury, who wins the award for then coolest domain/name I've seen in a while, wrote up a post on creating a .htaccess file automatically with Eleventy: Automate 301 Redirects In 11ty. This also serves as a great time to remind folks that while his example uses Nunjucks, you can always recreate their logic in any of the other supported Eleventy languages, or just include it. Eleventy is fine with that. I normally recommend folks stick to one language in their project, there are times when it's really helpful to be able to mix in another language. I do this myself with EJS (although I'm removing those pages soon).\nExploring JavaScript\nRepeat readers of this series know that I've often shared links to Dr. Axel Rauschmayer. He has consistently, for years now, done incredibly deep explorations into JavaScript. He has shared the latest version of his book, Exploring JavaScript for free on his website. You can, and I suggest you do, also purchase the book as well to show your support, but that is completely optional. (He even provides a helpful chapter to read before you buy to help you with that decision.) I especially like the &quot;New in&quot; section which breaks down updates by release.\nAnd last but not least...\nOk, so I mentioned above I'd share a Postmodern Jukebox track for you all to enjoy, so here's one of their latest. As I said, they are spectacular live!\n\n  \n    Play Video\n  \n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Help Me Solve an Alpine.js Mystery",
		"date":"Thu Aug 01 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1722535200,
		"url":"https://www.raymondcamden.com/2024/08/01/help-me-solve-an-alpinejs-mystery",
		"content":"Yesterday I wrote up my experience in building a simple CRUD interface using Alpine.js, and in doing so, ran into an interesting issue. While it would be best to read the entire previous article, let me try to break down the issue... or at least as how I saw it.\nMy Alpine app had a variable, cats, that was an array of objects. I looped over them and displayed them in a simple table:\n\nNotice I'm using a variable, cat, to represent each element of the array. Here's one example of using it:\n\nThe application also made use of a cat variable. This was intended to be used in the edit form. I had logic that would let you select a cat to edit, load the information for that cat and assign it to the variable, and display a form. In that, I used x-model. Again, here's one small example:\n\nSo far so good? Well, the issue I saw was that when I clicked to edit and the form appeared, the input fields were all blank. The binding in x-model wasn't working.\nHonestly, I had no idea what was wrong. There wasn't an error in the console, it just wasn't binding right. Than it occurred to me, what if the cat value in the for loop was overwriting/conflicting with my 'regular' Alpine.js variable? To be fair, that seems sensible. While looping over the array you can still access the rest of your variables, so this made sense, and changing my loop variable to catOb, it began working.\nWoot. High fives all around.\n\n\n\nSo today my plan was - build a simpler version of this issue to demonstrate it - blog about it - and just do my best to remember to avoid conflicts when naming my loop iterator variable.\nExcept... my simple demo didn't have the error.\n\n\n\nSo... this is what I did. I forked my CodePen from yesterday and reverted the loop variable to cat to recreate the problem. It did. I then removed as much as possible, most of the functionality, so I still had the problem. For the life of me, I couldn't reproduce the issue in my new CodePen... until I realized I had not done one thing in my recreation. Let me show what I have.\nFirst, the HTML, which again is a simplified version:\n\nAnd here's the JavaScript:\n\nIt's a bit messy, I apologize. The idea in init was to kinda mimic what I had before. An initial view of data, and then an edit interface. Hence the setTimeout. When it fires, my form field shows up and correctly renders a new cat:\n\n  See the Pen \n  trying to repro alpine issue i saw by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFinally, I decided to not automatically show the cat, but more closer mimic my previous demo. I added:\n\nI also removed my setTimeout. In theory, this does the exact same thing, except it waits for user input. But, look what happens:\n\n  See the Pen \n  trying to repro alpine issue i saw by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nCrazy, right? Cats and dogs living together. It's anarchy.\nHonestly, I've got no idea why the user interaction version shows the bug while the &quot;delay 5 seconds&quot; one does not. And honestly, this is all solved by just using a unique variable. But... I want to know why! So, can you help? Leave me a comment below please.\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a CRUD Web App with Alpine.js",
		"date":"Wed Jul 31 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1722448800,
		"url":"https://www.raymondcamden.com/2024/07/31/building-a-crud-web-app-with-alpinejs",
		"content":"One of the things I try to encourage here is for my readers to reach out with their questions. That rarely happens, but when it does, I try my best to answer as soon as possible. In this case, I got a great question from a reader back in May and then... life happened. Sorry, Nicholas, but hopefully this isn't too late. His question was pretty simple - could I build an example of using Alpine.js for a CRUD interface?\nFor folks who may not know the term, CRUD refers to:\n\n(C)reate\n(R)read\n(U)pdate\n(D)elete\n\nYou've probably seen a hundred interfaces like this. You have a list of content with links to edit one, delete one, and a link to add a new instance of that content.\nWhen that reader reached out to me, I agreed to take a look at this, with the stipulation that I'd &quot;fake&quot; the server-side calls. My intent is to demonstrate client-side stuff, not boring old server-side code. (I'm kidding. Mostly.) With that in mind, note that I will not be discussing the &quot;proxy&quot; JavaScript methods that fake the server logic. You'll be able to see them if you wish, I separated them from the rest of the code, but the precise implementation of them is really not important.\nAlso, this brings up a really important point. When I talk about Alpine, one of the things I discuss is where Alpine is most appropriate. Obviously, this is a matter of opinion, but I generally say Alpine is best for progressive enhancement of a page, not for building a web &quot;app&quot;. To me, and again, I'm sharing my opinion here, I generally view an &quot;app&quot; as anything that has two or more &quot;views&quot;, or distinct UI components to it.\nCRUD certainly implies two views typically - that list and editing view I mentioned above, but it feels simple enough that I figured Alpine would probably be fine for this, even without building a 'router' or something similar.\nThat's a lot of preamble, let's get to it. I built this out in stages, so I'll share each stage one at a time.\nPart One - Listing Content\nIn the first iteration, I focused on two things - setting up my Alpine app to support two views, listing and editing, and then displaying my current list of data.\nFirst, the HTML:\n\nNotice my app is split between two template tags that check for either listView or editView being true. In the list view, I iterate over my data (cats) in a simple table. Here's the JavaScript, again, minus my server-related calls:\n\nI want to call out one aspect in particular. When I first wrote this, I used one variable, view, that I set to a string. In my HTML, I then had:\n\nThat certainly worked, but I didn't like the logic embedded in the HTML. Slightly more code in JavaScript for cleaner markup seems like a good tradeoff to me.\nYou can test this version here:\n\n  See the Pen \n  Alpine Crud (1) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nTechnically the R in CRUD usually refers to reading one item, at least how I understand it, but in this case, I considered it &quot;Read All&quot; and felt like it was a good stopping point.\nPart Two - Deleting Content\nFor the next iteration, I added delete support. In my HTML, I just added a new table column with a button:\n\nAnd here's the JavaScript:\n\nNotice I added a new method, readCats, so I didn't have to repeat the logic. After a delete is run, I call that so my list of items updates.\nOnce again, that was a good stopping point, so here's that version:\n\n  See the Pen \n  Alpine Crud (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPart Three - Editing Content\nNow for the big change - editing support. In this iteration, I had to do multiple different things. I began by adding an edit button:\n\nNotice I change my for loop to use catOb, not cat. I'll get back to that at the end of this section. Now let's look at the JavaScript:\n\nSo, a few things to note here. First, editCat makes a call to the server to get the cat record (to be honest, my getCats already returns all the data, but in the real world, the 'get all' logic may only return some properties) and then fires off a new method to set the view. I made this a method as it's slightly complex - switching the true/false values for two variables.\nBack in HTML, I've got this code now to render a form:\n\nThe cancel button simply resets the view back to the list, while save calls the server method, waits for the result, and then also resets the view. I'm not handling server-side errors here and that is something to consider.\nOk, so a quick note. Notice how I use a variable, cat, to represent the data that's represented in the form? I had issues with it and I couldn't understand why. It was basic x-model stuff but it refused to work. Turns out, the cat I initially had in my x-for was in the same 'state' as my Alpine.js variables. I honestly didn't know that was an issue until today. I'm going to do a blog post on just this issue sometime later.\nAnyway, here's the demo:\n\n  See the Pen \n  Alpine Crud (3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPart Four -",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Next Code Break - August 6",
		"date":"Tue Jul 30 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1722362400,
		"url":"https://www.raymondcamden.com/2024/07/30/next-code-break-august-6",
		"content":"Hey folks, earlier last month, I mentioned a tweak to my &lt;Code&gt;&lt;Br&gt; schedule, and originally I was going to have one today, but that has been pushed out to August 6th. You can find out more here: Hacking Arduino Hardware as a Noob Pt. 2\nMy plan is to continue showing Arduino/web integration and I've got an interesting bug to share. (I love it when I make mistakes that are good lessons!) I hope to see you there!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "An Online Mastodon Archive Viewer",
		"date":"Sun Jul 21 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1721584800,
		"url":"https://www.raymondcamden.com/2024/07/21/an-online-mastodon-archive-viewer",
		"content":"Here's a quick write-up of something I actually built a week or so ago... and forgot to share on the blog. Instead, I shared a way to integrate your Mastodon with Eleventy. In that post, I mentioned how the account Fedi.Tips was looking for ways to let people view their archived Mastodon data. I followed their guide on getting my export and started work on a simple viewer.\nIf you don't care about the &quot;how&quot;, and have your archive ready, just head on over to https://tootviewer.netlify.app and upload your zip.\nThe Archive\nWhen you get your Mastodon archive, it will be a zip file containing the following:\n\nactor.json: A file related to the user, yourself probably.\navatar.jpg: You're current profile picture.\nbookmarks.json: A list of your bookmarks. (I don't use this feature myself.)\nheader.jpg: The header image in your profile.\nlikes.json: A list of your liked toots.\nmedia_attachments: All media you have attached to posts. There is a file subdirectory and then subdirectories numerically named with further subdirectories many levels deep with the actual data.\noutbox.json: Finally, a JSON file of your toots.\n\n\n\n\nI decided to focus mainly on actor.json and outbox.json, figuring toots would be the most critical thing folks would want to examine.\nThe App\nAt a high level, the application consists of:\n\nAlpine.js to help with the architecture.\nShoelace for visual components\nJSZip for JavaScript parsing of the zip file. Honestly, I didn't need this, I could have asked users to unzip first, but I wanted it to be as easy as possible.\n\nI'm not going to over every line of code (I'll share a link to the repo at the end), but here are some interesting bits.\nIngesting the Data\nThere re two ways to select your archive, either via a simple input[type=file] or drag and drop. I covered how to do this in-depth here: &quot;Using Drag/Drop in Alpine.js with PDF Embed&quot;\nOnce you have a handle to the file, I then begin the process of parsing the zip in a function named loadZip. I begin with a bit of validation:\n\nThe validateArchive function does simple sanity checking on the entries of the zip:\n\nGoing back to loadZip, I can then read my two files (again, I'm only concerned with toots and your profile):\n\nNotice that I do a filter on your toots to focus on items related to writing toots. The data also seemed to include things like announcements of accounts I followed and such, so this filter removed any noise.\nFinally, I store all of this in localStorage. This made testing a heck of a lot easier as I could reload and skip uploading the zip, but if I do decide to work with media, I'll need to switch to IndexedDB instead.\nThe UI\nThe UI is still very much a work in progress, but I focused on showing your profile first:\n\n\n\nYou'll notice I did not render your avatar and header. I absolutely could have, and heck, may add that in a few minutes as I literally just thought of a nice way of doing that and storing it in local storage.\nBeneath the profile view is the most important bit, your toots:\n\n\n\nYou'll notice on top there is a quick filter and pagination. I've got nearly two thousand toots so pagination was required and the search lets me find anything by keyword. As I mentioned, I'm not handling media yet so you won't see attached pictures in the view here, but each individual toot is linked to the original for quick view in the native Mastodon view.\nThe Code, and What's Next\nIf you've got ideas or suggestions, please head over to the repo (https://github.com/cfjedimaster/tootviewer) and let me know. I'm open to any PRs as well. As I said above, I think I'll quickly add your profile pics to the UI, but outside of that, I think the main thing I want to tackle is supporting media in the toot display and perhaps a general media browser. (You may remember posting a particular picture but have forgotten what you typed about the picture.) I hope this helps folks out!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Caching Input with Google Gemini",
		"date":"Fri Jul 19 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1721412000,
		"url":"https://www.raymondcamden.com/2024/07/19/caching-input-with-google-gemini",
		"content":"A little over a month ago, Google announced multiple updates to their GenAI platform. I made a note of it for research later and finally got time to look at one aspect - context caching.\nWhen you send prompts to a GenAI system, your input is tokenized for analysis. While not a &quot;one token per word&quot; relation, basically the bigger the input (context) the more the cost (tokens). The process of converting your input into tokens takes time, especially when dealing with large media, for example, a video. Google introduced a &quot;Context caching&quot; system that helps improve the performance of your queries. As the docs suggest, this is really suited for cases where you've got a large initial input (a video, text file) and then follow up with multiple questions related to the content.\nAt this time, speed improvements aren't really baked in, but cost improvements definitely are. If you imagine a prompt based on a video for example, your cost will be X let's say, where X is the token count of your text-based prompt and the video. For cached data and Gemini, the cost is instead: &quot;Token count of your prompt, and a reduced charge for your cached content&quot;. Honestly, this was a bit hard to grok at first, but a big thank you to Vishal Dharmadhikari at Google for patiently explaining it to me.\nYou can see current cost details here:\n\n\n\nThe docs do a good job of explaining how to use it, but I really wanted a demo I could run locally to see it in action, and to create a test where I could compare timings to see how much the cache helped.\nCaveats\nAgain, this is documented, but honestly, I missed them both.\n\nYou must use a specific version of a model. In other words, not gemini-1.5-pro but rather gemini-1.5-pro-001.\nGemini has a free tier in which you can create a key in a project that has no credit card. This feature is not available in the free tier. I found the error message a bit hard to grok in that case.\n\nOk, with that in mind, let's look at how it's used.\nCaching in Gemini\nMy code is modified slightly from the docs, but credit to Google for documenting this well. Before getting into code, a high-level look:\n\nFirst, you use the Files API to get your asset in Google's cloud. Note that this API changed from my blog post back in May.\nSecond, you create a cache. This is very similar to creating a model.\nThird, you actually get the model using a special function that integrates with the cache.\n\nAfter that, you can run prompts at will against the model.\nHere's my code, and honestly, it is a bit messy, but hopefully understandable.\nLet's start with the imports:\n\nNext, some constants. By the way, I'm not using const much anymore, so when you see it, it's just code I haven't bothered to change to let.\n\nNext, I defined my system instructions. This will be used for both model objects I create in a bit.\n\nNow my code handles uploading my content, in this case, a 755K text version of &quot;Pride and Prejudice&quot;:\n\nAt this point, we can create our cache:\n\nNote that this is very similar to how you create a model normally. It's got the model name, system instructions, and a reference to the file.\nThe cache object returned there is the only time you have access to the cache. There are APIs to list, update, and delete caches, but you can't get a reference once the script execution ends.\nTo get the actual model you can run prompts on, you then do:\n\nAs an example:\n\nAnd that's it really. I've got a complete script that demos this in action and it shows a comparison to a non-cached model. It reports on the timings, which again, at this point do not show the cached stuff being quicker, but it also reports the usageMetadata and that shows the impact of the cached token count against your total. Here's an example with the cache:\n{\n  promptTokenCount: 189940,\n  candidatesTokenCount: 591,\n  totalTokenCount: 190531,\n  cachedContentTokenCount: 189925\n}\nwith cache, duration is 52213\n\n{\n  promptTokenCount: 189935,\n  candidatesTokenCount: 251,\n  totalTokenCount: 190186,\n  cachedContentTokenCount: 189925\n}\nwith cache, second prompt, duration is 19117\n\nAnd here's the report when the cache isn't used:\n{\n  promptTokenCount: 189939,\n  candidatesTokenCount: 790,\n  totalTokenCount: 190729\n}\nwithout cache, duration is 29005\n\n{\n  promptTokenCount: 189934,\n  candidatesTokenCount: 181,\n  totalTokenCount: 190115\n}\nwithout cache, second prompt, duration is 11707\n\nAgain, the timing above shows that with the cache, the timings were actually slower, but cost-wise, something like 99% of the cost is reduced. That's huge. If you want the complete script (and source book), you can find it here: https://github.com/cfjedimaster/ai-testingzone/tree/main/cache_test\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Web Component to Generate Image Color Palettes",
		"date":"Tue Jul 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1721152800,
		"url":"https://www.raymondcamden.com/2024/07/16/web-component-to-generate-image-color-palettes",
		"content":"Chalk this up to something that is probably not terribly useful, but was kind of fun to build. A few weeks ago I came across a site talking about the colors used in the Fallout TV show. I grabbed a screenshot of how they rendered it:\n\n\n\nUnfortunately, I didn't make note of the site itself and I can't seem to find it anymore. I really dug how it showed the palette of prominent colors directly beneath the image itself. Using this as inspiration, I looked into how I could automate this with a web component.\nTo get the color palette, I turned to a library I've used many times before, Color Thief. Given an image, it can return either the most dominant color of an image or return an array of values representing the palette of the image.\nI began with the HTML, which in this case simply wrapped a random image returned from Unsplash at a specific height and width:\n\nIn retrospect, maybe using a random image was a bad idea, as every reload showed something different, but once I had the code working it, well, worked, so I kept it as is.\nNow for the code. To be honest, I didn't spend much time thinking about the color palette, rather, I was more concerned about how to 'rewrite' the image HTML in a way to look like the screen grab above. I knew CSS Grid could do it, so I went that route. I built a simple CodePen that only handled the layout.\n\n  See the Pen \n  Pallete Demo 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIt was... nearly perfect. You can see a bit of whitespace after the image and before the images beneath. Honestly, if someone were to fork my CodePen with a fix, I'd definitely appreciate it! But with CSS in hand, I proceeded to the web component version.\nHere's the code:\n\nThe script has two async processes it needs to wait for. First, load the external Color Thief library. Next, see if the image is loaded. As it's possible it's already loaded, my code has to handle that case as well.\nBut once past that, I can get the color palette and then rewrite the HTML contained with the tags with my CSS and new divs holding the colors. I try to avoid the shadow DOM where possible, but obviously, this code will possibly 'clash' with existing CSS. I could perhaps use some better-named classes (for example, div.color_palette_web_component).\nHere it is in action:\n\n  See the Pen \n  Palete Demo 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAnd since what you see above is random, I captured two examples:\n\n\n\n\n\n\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You (7/13/2024)",
		"date":"Sat Jul 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720893600,
		"url":"https://www.raymondcamden.com/2024/07/13/links-for-you",
		"content":"Hello from the incredibly hot Pacific Northwest. This week I discovered that not every part of the PNW looks like Seattle. I'm in Bend, Oregon, which is incredibly beautiful, but also just as hot as back home. On the flip side, the humidity is basically zero and the mornings and evenings are incredibly nice. My wife and I are up here for a few days as a quick break, and last night we saw a great concert featuring Ratboys, The Head and The Heart, and the Decembrists.\n\n\n\nNow to your links - enjoy!\nSlash Pages\nFirst up is not a directory of horror fansites, but rather, a list of the many different types of &quot;slash pages&quot;. These pages follow a similar URL scheme (like /now or /about) and typically, a common form of content. I've had a /now page for a while now and use it as a way to show what media I'm currently consuming. The Slash Pages guide lists all the other different types of pages like this that are being used by folks. Some I knew, many I didn't.\n&quot;Stand Alone&quot; Web Components\nNext up is a great list of &quot;stand alone&quot; web components compiled by Chris Coyier. He defines &quot;stand alone&quot; as:\n\nA \"stand alone\" Web Component is a Web Component that provides some design or functionality but that has little by way of dependencies, strong opinions, or heavy design.\n\nHis list is an incredibly good set of examples of useful but simple-to-deploy web components that you can make use of right now. Here's one of my favorites.\n\n  See the Pen \n  &lt;sparkly-text&gt; by Chris Coyier  (@chriscoyier)\n  on CodePen.\n\n\nZipping with JavaScript\nLast up is a good guide to working with zip files and JavaScript: &quot;Generating ZIP Files With Javascript\n&quot;. This article makes use of JSZip, a library I used myself a week or so ago for my Mastodon archive viewer, and focuses on creating zip files. The author, Josh Martin, mentions some of the pain points of the library, specifically the docs, which I myself ran into as well.\nAnd last but not least...\nSince I mentioned the Decembrists above, how about a great video from them?\n\n  \n    Play Video\n  \n\n\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Scraping Recipes on the Web - Now with Display and Print",
		"date":"Fri Jul 12 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720807200,
		"url":"https://www.raymondcamden.com/2024/07/12/scraping-recipes-on-the-web-now-with-display-and-print",
		"content":"A few weeks back I wrote up the process of building an API that looks for JSON-LD on a web page containing recipe information, parses it, and returns it as pure data. You can (and should before continuing on) find that post here: Scraping Recipes Using Node.js, Pipedream, and JSON-LD. When I first shared this, someone (I forget your name, but thank you!) asked the natural follow-up question - can we then render this to HTML or PDF? The answer is, of course, I just had to stop being lazy and build a proper web app. I fired up Glitch and created the following little demo.\n\n\n\nIt isn't the prettiest demo, but it gets the job done - converting a recipe site that's 90% adds/commentary to just the basics. To give you an idea of the change, the total network load on my app after loading the recipe is 140kb. 128 of that is just the image.\nOn the real site, which is still loading crap, clocks in at 6.7MB. Wow.\nSo, here's what I did. I began with minimal HTML. Up until a few seconds ago, I had a hard-coded form value to make testing easier, but just now I moved it to an HTML comment:\n\nThe JavaScript code handles listening for the click event, calling the API I built in the last post, and then rendering the results. Note that I do not use 100% of the result, which is fine, and I used my best design skills for the render. I assume 99% of you can do better:\n\nThe only real interesting part honestly is that last bit handling the print. Initially, I was going to add a 'Download to PDF', but I figure opening a print dialog lets you choose between printing it as is or saving it as a PDF. One thing I don't like is the print button using onClick, but as I don't have it visible until you get a good result, I didn't want to worry about adding the event handler and removing it, or remembering I already added it and so forth. I figured it was a quick and dirty way to get the job done.\nYou can play with this yourself here: https://display-recipe.glitch.me/. The code is all available at Glitch here: https://glitch.com/edit/#!/display-recipe.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Generic Generative Template Language in Google Gemini",
		"date":"Wed Jul 10 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720634400,
		"url":"https://www.raymondcamden.com/2024/07/10/creating-a-generic-generative-template-language-in-google-gemini",
		"content":"I've been a fan of 'random text' for some time. &quot;Random text&quot; is a bit vague, but to me the idea of using code to generate random stories, or even snippets, is fascinating. Back in April, I blogged about how I created short dragon-based stories. It took a generic string:\n\nA #adjective# dragon lives #place#. She #verb# her hoard, which consists of a #number# of #thing#, #number# of #thing#, and #number# of #thing#. She feels #feeling#.\n\nAnd created a story by replacing the pound-wrapped tokens with real words. I used a couple of different tools to build this, but the core one was a cool little Node library named random-word-slugs. It's a powerful random word library that can get pretty specific in terms of focusing on a particular type of word. For example, here's the code I use to generate an adjective for the dragon focused on three aspects - color, appearance, and personality:\n\nI was curious if I could take this approach and build it with generative AI. Here's what I found.\nVersion One\nFor my first version, I opened up Google AI Studio, started a new prompt, and used these system instructions:\n\nYou are a text parser. Given a prompt with variables wrapped in {{ and }}, you will replace that with a value based on the contents of the text inside the brackets. So for example, {{noun}}, means to replace it with a random noun, and {{animal}}, means to replace it with a random animal.\n\nWith this in place, I then did a test:\n\nThis is a story about a {{noun}}. It was colored {{color}} and liked to eat {{food}}. On the weekend, it liked to do {{activity}}.\n\nWhich gave me:\n\nThis is a story about a tree. It was colored blue and liked to eat pizza. On the weekend, it liked to do skydiving. \n\nNot bad. Then I decided to push it a bit:\n\nI like to eat {{food}}. But sometimes when I eat it, I feel {{sad feeling}}. My favorite holiday is {{holiday}}. My favorite song is {{song}}.\n\nI was curious how well it would handle sad feeling and holiday. I got this:\n\nI like to eat ice cream. But sometimes when I eat it, I feel guilty. My favorite holiday is Christmas. My favorite song is Bohemian Rhapsody. \n\nThis was kind of shocking how close it was to my own personal answers. ;) I changed sad feeling to happy feeling and got this:\n\nI like to eat pizza. But sometimes when I eat it, I feel happy. My favorite holiday is Thanksgiving. My favorite song is Imagine. \n\nFinally, I decided to really push it:\n\nI like to eat {{food}}. But sometimes when I eat it, I feel {{weird feeling}}. My favorite holiday is {{holiday}}. My favorite song is {{song by The Cure}}.\n\nIt had no problem with specific song token:\n\nI like to eat sushi. But sometimes when I eat it, I feel disoriented. My favorite holiday is Halloween. My favorite song is Friday I'm In Love by The Cure. \n\nPretty impressive. But then I decided to push more...\nVersion Two\nIn the past, I worked with a library that could generate a random word and then remember it for reuse later. Tracery had a feature where it could select a random word, but then remember the chosen word for use again later. I decided to see if I could get that working with Gemini. I updated the system instructions like so:\n\nYou are a text parser. Given a prompt with variables wrapped in {{ and }}, you will replace that with a value based on the contents of the text inside the brackets. So for example, {{noun}}, means to replace it with a random noun, and {{animal}}, means to replace it with a random animal. You also support storing and remembering values. If the string inside the brackets contains a colon, the value after the colon is the name of a variable. So for example, {{noun:itemX}} means to select a random noun and insert it into the result, but also store the value in a variable named itemX. If I use {{itemX}} again, you will use the previous value.\n\nAnd again, it worked really dang well. I started with:\n\nMy first name is {{first name:identity}}. Say hi to me, my name is {{identity}}.\n\nAnd got:\n\nMy first name is Sarah. Say hi to me, my name is Sarah. \n\nI then tried:\n\nMy first name is {{first name:fname}}. My last name is {{last name:lname}}. I write my formally as {{lname}}, {{fname}}. My favorite color is {{color}}.\n\nI tried this twice and got:\n\nMy first name is David. My last name is Smith. I write my formally as Smith, David. My favorite color is blue. \nMy first name is Emily. My last name is Jones. I write my formally as Jones, Emily. My favorite color is green.\n\nAs a last test, I tried the Dragon example above, replacing pounds with double brackets, and it did an admiral job:\n\nA fiery dragon lives in a cave beneath a mountain. She guards her hoard, which consists of a thousand of gold coins, five hundred of precious gems, and a hundred of magic artifacts. She feels content. \n\nI see two issues here. One, I should remove of. Second, I'd be willing to bet the place token will consistently be things relevant to dragons. I did a few more tests and it definitely seemed that way:\n\nA sleepy dragon lives i",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Cat Herder V1 Released!",
		"date":"Tue Jul 09 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720548000,
		"url":"https://www.raymondcamden.com/2024/07/09/cat-herder-v1-released",
		"content":"It's been a few weeks since I blogged about Cat Herder, my latest web game, but over the holiday break I plugged up the last few features missing and decided it was time to &quot;release&quot; it, and by release, I mean set the version number to 1 and see what happens next.\nSince my last post in June, I've made a few small changes here and there, but the biggest updates in this last release revolve around the cats, and how you get more of them. As I mentioned in my previous posts, I wasn't really sure about making cats &quot;purchasable&quot;, that just didn't feel right. Instead, I went with a system that kind of works like levels in a RPG.\nThe more purrs you get (which are the currency), the more cats join your home. This was based on a formula I found on Stack Overflow that felt like it had a good natural progression of XP to level.\nMy implementation was two-fold:\n\nFirst, in the heartbeat function, simply see if I've earned enough for a new cat:\n\n\n\nI defined nextCatCost based on the SO answer:\n\n\nAs the comment says, it's a bit of a reverse. Given I have X cats now, or I'm X level in the RPG, determine the cost of level X+1. That constant, 0.04, again comes from the SO thread on the topic.\nThe last big(ish) change was to remove the explicit display of cat happiness and replace it with a random string. I 'group' happiness by either really sad, kinda sad, neutral, happy, and really happy. I defined a set of strings for these values:\n\nNote the of $name. In the HTML, I added:\n\nAnd then defined that function like so:\n\nRelatively simple, and to be honest, some of the differences between, for example happy and very happy, aren't terribly clear, but that's also kind of the point, right?\nSo, that's it for now. I've already got an idea for my next game, but I'll probably return to this from time to time, like I've done with IdleFleet.\nCheck out Cat Herder now and let me know what you think! Below is a video version of what I described above.\n",
		"tags":[
	        
            "javascript",
            
            "games"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "(Don't) Add BASIC Support to Eleventy",
		"date":"Sun Jul 07 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720375200,
		"url":"https://www.raymondcamden.com/2024/07/07/dont-add-basic-support-to-eleventy",
		"content":"So yesterday I wrote up the process of adding the Squirrelly template language to Eleventy. It was, essentially, five minutes of work due to how well Eleventy supports adding custom languages. After writing it up, publishing it, and running some errands, a really bad and silly idea came to me... what if I added BASIC support to Eleventy?\nWay back in the Stone Age, my first computer language was Applesoft BASIC on an Apple 2e (or +, not sure). Just look at this rad machine and imagine it paired with a monochrome green monitor:\n\n\nBy Bilby - Own work, CC BY 3.0, https://commons.wikimedia.org/w/index.php?curid=11119727\n\nI wrote a lot of programs on that machine, most typed by hand from the pages of various programming magazines like &quot;Family Computing&quot;. BASIC wasn't a very powerful language and I didn't even have a proper editor, but it's where I got my love of programming and therefore it absolutely occupies a warm place of my heart.\nBecause of this, I've played around with some modern implementations of it, including jqbasic, a JavaScript interpreter. With that, I built things like a &lt;applesoft-basic&gt; web component and even a severless API. I wouldn't recommend actually using either of those projects nor would I recommend implementing what I'm about to show - BASIC support in Eleventy.\nAs I described in yesterday's post, you need two functions within your Eleventy configuration file. One to make Eleventy recognize and process the file extension:\n\nAnd then the call to implement the actual logic:\n\nMost of the code there is the configuration to the JavaScript library to help it work in &quot;headless&quot; mode. Ie, just process the code, don't wait for input, and return the output. And surprise surprise, it works:\n10 print &quot;&lt;h1&gt;Hello&quot;\n20 print &quot; Word&lt;/h1&gt;&quot;\n30 goto 100\n40 print &quot;never gonna show up&quot;\n100 print &quot;&lt;hr&gt;&quot;\n110 print &quot;&lt;p&gt;BASIC to HTML&lt;/p&gt;&quot;\n120 x$ = &quot;moo&quot;\n130 print x$\n\nWhen Eleventy runs, this outputs:\n\nYou can even include frontmatter as Eleventy will parse it first:\n---\nlayout: main\n---\n\n10 print &quot;&lt;h1&gt;Hello&quot;\n20 print &quot; Word&lt;/h1&gt;&quot;\n30 goto 100\n40 print &quot;never gonna show up&quot;\n100 print &quot;&lt;hr&gt;&quot;\n110 print &quot;&lt;p&gt;BASIC to HTML&lt;/p&gt;&quot;\n120 x$ = &quot;moo&quot;\n130 print x$\n\nWhat doesn't work is passing data to the BASIC template. You've got two main ways of using data in BASIC programs. Either define it using lines of code or use READ/DATA. The former would only work if I required developers to start at, say, line 100, and then I'd copy all the data to lines of code starting at line 1 and then incrementing. BASIC supports numbers, strings and arrays, but not, as far as I know, an object of key-value pairs. So stuff like site.name wouldn't work. Again, as far as I know. READ/DATA would require having an ordered set of data that you knew of ahead of time and would just end up being a mess.\nI'm sure it's possible, but, I decided I was being silly enough so I stopped where I was.\nThe code you absolutely positively do not want to use may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/basic\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Add Squirrelly Support to Eleventy",
		"date":"Sat Jul 06 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720288800,
		"url":"https://www.raymondcamden.com/2024/07/06/add-squirrelly-support-to-eleventy",
		"content":"I'm supposed to be on vacation but writing about Eleventy two days ago has got it fresh on my mind, also, I can't pass up an opportunity to use &quot;squirrelly&quot; in a blog title. I subscribe to three or four different email newsletters related to web development. It's fairly normal to see the same link shared among a few of them. Most recently an example of this was the Squirrelly library. This is, yet another, JavaScript template language and I thought I'd take a look at it in my spare time. Given that Eleventy makes it easy to add other template languages, how long does it take you to add support for it?\nStep One - Make Your Eleventy project\nTechnically this isn't even a step, any folder can be processed with the eleventy CLI, but assume you've got a new or existing one you want to add Squirrelly to.\nStep Two - Install Squirrelly\nThis is done via npm:\n\nStep Three - Add Support to Eleventy\nNow for the fun part. Given an Eleventy configuration file, first, include Squirrelly:\n\nNext, let Eleventy know to process files using the template. This doesn't tell it how to, just to pay attention to it and include it in the output: You can use any extension you want and I went with sqrl as it matched the variable I used to instantiate the library.\n\nNow to tell Eleventy how to actually support the library's template language. For this, I used Eleventy's docs for custom templates and Squirrel's introductory docs:\n\nThe compile function is passed the input of the template and returns a function that accepts the compiled data that is available to every template. To be clear, this is the 'usual' Eleventy data which comes from multiple sources, is combined, etc.\nThat's it. Done. Less than five minutes perhaps. Here's the complete Eleventy config file I used for my testing:\n\nLet's build a .sqrl template:\n\nI literally just copied over sample code from their docs and confirmed that page data (see the front matter on top) and global data worked and... yeah, that was it.\nIf you want this sample code to start off testing Squirrelly, you can find it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/squirrelly\nIf you want to learn more about Squirrelly, check out the site here: https://squirrelly.js.org/\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Web Version of Your Mastodon Archive with Eleventy",
		"date":"Thu Jul 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720116000,
		"url":"https://www.raymondcamden.com/2024/07/04/building-a-web-version-of-your-mastodon-archive-with-eleventy",
		"content":"A couple of days ago Fedi.Tips, an account that shares Mastodon tips, asked about how non-technical users could make use of their Mastodon archive. Mastodon makes this fairly easy (see this guide for more information), and spurred by that, I actually started work on a simple(ish) client-side application to support that. (You can see it here: https://tootviewer.netlify.app) This post isn't about that, but rather, a look at how you can turn your archive into a web site using Eleventy. This is rather rough and ugly, but I figure it may help others. Here's what I built.\nStart with a Fresh Eleventy Site\nTo begin, I just created a folder and npm installed Eleventy. I'm using the latest 2.0.1 build as I'm not quite ready to go to the 3.X alpha.\nStore the Archive\nI shared the guide above, but to start, you'll need to request and download your archive. This will be a zip file that contains various JSON files as well as your uploaded media.\nMy thinking is that I wanted to make it as easy as possible to use and update your Eleventy version of the archive, so with that in mind, I created a folder named _data/mastodon/archive. The parent folder, _data/mastodon, will include custom scripts, but inside archive, you can simply dump the output of the zip.\nExpose the Data\nTechnically, as soon as I copied crap inside _data, it was available to Eleventy. That's awesome and one of the many reasons I love Eleventy. While the data from the archive is &quot;workable&quot;, I figured it may make sense to do a bit of manipulation of the data to make things a bit more practical.\nTo be clear, everything that follows is my opinion and could probably be done better, but here's what I did.\nFirst, I made a file named _data/mastodon/profile.js which serves the purpose of exposing your Mastodon profile info to your templates. Here's the entire script:\n\nSo, I started this file with the intent of removing stuff from the original JSON that I didn't think was useful and possibly renaming things here and there and... I just stopped. While there are a few things I think could be renamed, in general, it's ok as is. I kept this file with the idea that it provides a 'proxy' to the archived file and in the future, it could be improved.\nFor your toots, the Mastodon archive stores this in outbox.json file. I added _data/mastodon/toots.js:\n\nThis is slightly more complex as it does two things - filtered to the Create type, which is your actual toots, and then sorts then newest first. (That made sense to me.) Again, there's probably an argument here for renaming/reformatting the data, but I kept it as is for now.\nRendering the Profile\nWith this in place, I could then use the data in a Liquid page like so:\n\nRight away you can see one small oddity which I could see being corrected in profile.js, your join date is recorded as a published property. I really struggled with renaming this but then got over it. Again, feel free to do this in your version! That dtFormat filter is a simple Intl wrapper in my .eleventy.js config file.\nDitto for attachment which are the 'extra' bits that get displayed in your Mastodon profile. You can see them here:\n\n\n\nWith no CSS in play, here's my profile rendering on my Eleventy site:\n\n\n\nThat's the profile, how about your toots?\nRendering the Toots\nI just love the word &quot;toot&quot;, how about you? I currently have nearly two thousand of them, so for this, I decided on pagination. My toots.liquid file began with:\n---\npagination:\n    data: mastodon.toots\n    size: 50\n    alias: toots\n---\n\nThat page size is a bit arbitrary and honestly, feels like a lot on one page, but it was a good starting point. My initial version focused on rendering the date and content of the toot:\n\nAt the end of the page, I added pagination:\n\nWhile not terribly pretty, here's how it looks:\n\n\n\nNot shown is the list of pages, which at 50 a pop ended up at thirty-seven unique pages. I don't think anyone is going to paginate through that, but there ya go.\nSupporting Images\nOne thing missing from the toot display was embedded attachments, specifically images. In the zip file, these attachments are stored in a folder named media_attachments with multiple levels of numerically named subdirectories. A toot may refer to it in JSON like so:\n\nNot every attachment is an image, but I turned to Eleventy's Image plugin for help. It handles everything possible when it comes to working with images. Using a modified version of the example in the docs, I built a new shortcode named mastodon_attachment to support this:\n\nBreaking it down, it looks at the src attribute and if it's an image, uses the Image plugin to create a resized version as well as return an HTML string I can drop right into my template. I went back to my toots.liquid template and added support like so:\n\nThe name value of the attachment ends up being the alt for the image, and currently, I just ignore non-images, but you could certainly do something else, like link to it perhaps for downloading at least. Her",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with Pasted Content in JavaScript",
		"date":"Wed Jul 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1720029600,
		"url":"https://www.raymondcamden.com/2024/07/03/working-with-pasted-content-in-javascript",
		"content":"This began as me wanting to build an Alpine.js application that handled pasted input, but I realized before I looked into handling this with Alpine, it made sense to start with basic vanilla JavaScript at first. I've worked with the clipboard before, mainly storing information to it, but this was the first time I looked at handling input from the clipboard. The web platform handles it rather nicely, but as with most things, there are a few interesting things you need to be aware of. Here's what I found.\nListening To the Event\nThe first thing you need to do is actually listen to the event. While you probably listen on a part of a DOM, it made the most sense to me to listen at the window level:\n\nEasy peasy.\nHandling the Event\nNow comes the fun part. When the event is fired, your event object gets a clipboardData object. This object is used both for reading and writing to the clipboard. You can, if you choose, prevent the default paste behavior with the usual e.preventDefault(). This will impact pasting into form fields for example.\nThere are then two ways you can access the data from the paste. The first involves the getData method which as you would expect, gets data from the clipboard. The MDN docs say you have to specify the type of the data, giving text/plain and text/uri-list as examples. Let's look at how this works.\nIf you do:\n\nYou get the text of the pasted content. What's interesting is that if you copy HTML, you get a plain text copy of the HTML. That's expected, but could be handy if you wish to disregard any formatting. If you want the HTML, you can do:\n\nInterestingly enough, if you take this result and add it to the DOM, it looks great. For example, consider this super simple HTML/CSS block:\n\n  See the Pen \n  Scratch by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nNote the use of a background color for the body as well as a specific P color. If I select the text from that, and paste it into my app where I'm testing the API, it actually renders with the styles:\n\n\n\nIf you console.log that result, you can see the clipboard contained a lot more HTML (I added line breaks for readability):\n\nThat's text, but what if you paste a file? For example, on my desktop, I can select one or more files, copy, and then paste. In that event, the clipboardData object contains a files array, and this works just like an input[type=file] DOM element. You can read and work with the files for whatever purposes you desire.\nSo for example:\n\nThe previewImage function is just a utility to read and display the image in the DOM:\n\nTaken together, you could check for .files to look for binary data, and then run getData to see if you've got anything there. Or vice-versa. Whatever makes sense for your application.\nTo see this in action, I built a little CodePen. When you paste in text, it should render to the DOM the plain text and HTML version of it. If you copy and paste an image specifically, it should render it.\n\n  See the Pen \n  Paste1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nYou could, absolutely, handle non-images and more than one file. You would need some way to render it in the DOM and that would be an exercise for the reader. (I am on vacation after all. ;) But again, this is just like an input[type=file]. Once the user gives you the file, you can work with it. That also includes using it in a API call via fetch or a form post.\nWhat about Word and Acrobat?\nNow for something interesting. As a quick text, I opened up a Word document. When I copied a block of text from it, the clipboard contained an image and text. This means the simple if/else block I used above may not be appropriate, or, it may make sense to reverse the order and look for data before files. You can also get text/html from a Word paste if you feel like seeing an abomination.\nAcrobat did not send along a file object, but did support both plain text and HTML well. Hit up the CodePen above and let me know what you see.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jun 29 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1719684000,
		"url":"https://www.raymondcamden.com/2024/06/29/links-for-you",
		"content":"Technically I'm on vacation, but I love these link-sharing posts and decided I could tear myself away from... doing nothing. As a quick reminder, if you enjoy these posts, and this blog, please consider visiting my Amazon wishlist or buying me a coffee, which, to be clear, isn't really buying me a coffee, just a handy little donation portal. I'm also open to sponsorship and if that's an interest, drop me a line. Ok, enough of the PBS Pledge Break, let's get to the links.\nWeb Components Gotcha with connectedCallback\nI've been enjoying working with web components the past year or so, and in general, they aren't difficult to work with, but like the web platform in general, there are some unexpected gotchas you may run into. In his post, Custom Elements; Unconnected Callback, David Bushell talks about an issue you may run into with the connectedCallback handler and the content contained with a pair of tags. If you are developing web components, absolutely check out this article to avoid issues in the future.\nAn Introduction to JSONPath\nHere's a cool introduction to a cool technique, parsing JSON with path expressions: How to use JSON Path. JSONPath lets you query against complex JSON data and parse out the bits you want. JSONPath isn't new (apparently it was first discussed in 2007), but it's not something I see a lot of people talking about so I was happy to see a post discussing it. You may also find JSONata interesting as well. Adobe uses it for its Document Generation API.\nI Promise this is an Excellent Promise Resource\nThis will be, I believe, the fourth time I've featured Josh Comeau on this series, and that's because he continues to make incredibly well-done technical blogs. He's done it again, this time with a kick-butt introduction to JavaScript Promises, Promises From the Ground Up. If you've never seen his work, and are the world's best expert in Promises, do yourself a favor and click anyway. He is a master craftsman in technical writing.\nAnd last but not least...\nOne of the things on my bucket list is to make time to take drum lessons. It's been something I've been kicking around in my head for a few years and I absolutely plan on it, but want to wait till my young ones are a little bit older. It's also one of those things where if I somehow end up on my deathbed, I'm not going to be kicking myself for not getting around to it. I want to do it, but I'm also fine waiting. With that in mind, I've been watching drumming videos on YouTube and really enjoying watching people play. I came across this video and... well the camera work is horrible. Like, really, really bad. That being said, try your best to ignore that and focus on the skill of the player, and the great sound.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "CodeBr Break",
		"date":"Fri Jun 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1719597600,
		"url":"https://www.raymondcamden.com/2024/06/28/codebr-break",
		"content":"I'm probably worrying too much about this, but as the schedule is changing slightly for &lt;Code&gt;&lt;Br&gt; in July, I just wanted to let folks know. Normally the next session would be Tuesday, July 2nd, but with Adobe being shut down that week I'm doing nothing. Absolutely nothing. Well, ok, that's a lie, I'll be doing stuff, but hopefully nothing by my office.\nThe schedule for July will be:\n\nJuly 16th - Hacking Arduino Hardware as a Noob\nJuly 30th - Something else I'll figure out when I need to - do you have an idea?\n\nThis will be the longest break between shows so I wanted to sure folks were aware. Right now, the schedule isn't up on the website, but it will be in a day or so.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Storing Recipes in IndexedDB",
		"date":"Thu Jun 27 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1719511200,
		"url":"https://www.raymondcamden.com/2024/06/27/storing-recipes-in-indexeddb",
		"content":"The last two sessions of my show, &lt;Code&gt;&lt;Br&gt;, were taken up talking about one of my favorite web platform features, IndexedDB. This is a topic I've covered many years on the blog (I even wrote a book on it back in 2016) so I thought it would be a good topic for the show. (I will include links to those episodes at the end of this post.)\nIn the first session, Sue, one of the folks watching the live stream, suggested I use recipes as an example of data to persist in the browser. I thought this was perfect as recipe data can get quite complex. You can see an example of that in the post I wrote earlier this month on scraping recipes. For the context of the live stream, I decided to keep things a bit simple. My recipe data looked like so:\n\nA string property for the name.\nA string property for ingredients, using a textarea in the frontend so you could write them all out.\nA string property for directions, again using a textarea.\n\nHere's how that UI looked:\n\n\n\nAs you will see if you watch the video, the UI was simple and worked well for quickly entering data. It's absolutely not the best, but it got the job done.\nThe last thing I did in the second session was add search. This was done by using an index on the recipe's title and using a fuzzy string match that basically said, if you entered &quot;foo&quot;, look for &quot;fooa&quot; to &quot;fooz&quot;. This worked... kinda. I didn't realize till later that if I had a recipe with the exact name &quot;foo&quot;, then my search for &quot;foo&quot; wouldn't work. I fixed that in the version I'll show in a bit. I also didn't correctly handle case. Sigh. But ignoring that, here's how that filter worked:\n\nSo ignoring the issues I found later, at the end of the second live stream, I had a basic, if not pretty, recipe database stored on the client. Today I decided to take a look at how I could make this a bit more complex and better suited for actual recipes.\nImproving the Recipe Object\nI began with two important changes - changing both ingredients and directions to an array of data. For ingredients, I thought it made sense to have an array of objects with each element consisting of a name and quantity value. For directions, just an array.\nNow, on the IndexedDB side, this is a non-issue. Period. You just pass your data as you see fit and it's stored. It was much more of an issue on the UI/UX side. I decided to take the easy way out. I kept the textareas in and added a bit of text:\n\n\n\nIn case it's a bit hard to read, for ingredients I now say:\nIngredients: (Enter one ingredient per line. Use the format: Name, Qty)\n\nAnd for directions:\nDirections: (Separate steps with a blank link.)\n\nThe direction bit is ok, and honestly, I'd probably not even say anything, just handle it. The ingredient thing is absolutely not something I'd put into production. It's too brittle. But again, my desire here was to focus on the database portion, not the actual UI/UX of this app.\nMy code to handle this looks like so:\n\nFor directions:\n\nEasy peasy. As I said, IndexedDB is fine with you passing an object that has objects under it. I spent more time on the UI than the DB.\nMy next change was to add support for durations. Recipes need to say how long it takes to make them so this is an important bit of info. I simply added a new number field to my form and included it in my code when saving values.\nImproving Search\nWell, here's where things went a bit insane. Given that I had title search working (with a few issues), I decided to add not one, but two more search options - max duration and ingredients. Let me walk you through these changes.\nFor the title search, as I mentioned, I had an issue with case and matching the exact string. I fixed this in two ways:\nYou can't do a case-insensitive search in IndexedDB. So I decided to make a new property of my recipe object, title_lower. I then modified my index to use that property:\n\nTo handle the &quot;foo&quot; doesn't match &quot;foo&quot; issue, I modified my range to start with the string and go up to z:\n\nTo support searching durations, I added an index for that:\n\nAnd then, an index for ingredients. Ingredients is an array, but you can make an index on that by using the multiEntry flag:\n\nSo far so good. Now is when things get a bit intense. IndexedDB doesn't really support complex searches. You can easily search along one index, but not two or more at once. You can make an index on multiple properties, but that isn't (as far as I know) going to support a search with just one property being defined. I could definitely be wrong here (and please, fork the code and let me know!), so here is how I decided to attack it.\nFirst, I added the filters on top:\n\n\n\nI won't bother showing the code here, but I've got JavaScript listening for all three fields and calling out to get recipes when there is an input event fired on them.\nWhen I do a filter, I've got an object containing the values from the DOM called filter. It contains values, or blanks, for title, ",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Content Assistant with Gemini and GenAI",
		"date":"Mon Jun 24 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1719252000,
		"url":"https://www.raymondcamden.com/2024/06/24/creating-a-content-assistant-with-gemini-and-genai",
		"content":"One of the use cases for generative AI that I've discussed before is the idea of using the tool to aid in the writing process. I'm not talking about creating content so much as creating suggestions and providing feedback about the content you yourself have created. This past weekend I worked on a &quot;general purpose&quot; tool with this in mind and thought I'd share it to get your feedback. (&quot;You&quot; being the smart readers of this blog who keep me honest when I show something stupid. ;)\nThe Application\nThe application itself is rather straightforward. It asks two questions - first, what are you trying to accomplish with your writing and what are the desired results? The idea is that you (most likely) have some sort of plan for the content. If it's developer documentation, this could simply be &quot;To educate a developer audience about how our tool X lets you do Y.&quot; For a school setting, it may be the exact assignment from the teacher.\nThe second question is simply a place to dump your content.\nHere's the UI, built nicely with Shoelace, my new favorite web component-based UI library:\n\n\n\nEntering your data and hitting the submit button fires off the call to the server and returns the analysis. I used this as a test for requirements:\n&quot;I want to write a paper that changes the hearts and minds of my readers so that they are more open to adopting cats, and making cats as part of their lives. It should be both emotional and practical.&quot;\nAnd then pasted in some content I got from Gemini (yes, I used GenAI to generate content I asked GenAI to evaluate) and got this in response:\n\nThis is a good start to your draft, and it already shows your passion for cats! You've included both emotional and practical elements, which was one of your goals. Here's a breakdown of what's working well and areas for improvement:\n\nStrengths:\n\nPassionate Tone: Your love for cats is evident throughout the piece, which is crucial for connecting with readers emotionally.\nStrong Opening: You immediately grab the reader's attention with a compelling statement about the special nature of cats.\nGood Balance: You strike a decent balance between emotional appeals (unconditional love, special bond) and practical advantages (independent nature, low maintenance).\n\nAreas for Improvement:\n\nSpecificity: While you mention &quot;playful antics&quot; and &quot;unwavering loyalty,&quot; try to provide more specific examples or anecdotes. These will make your arguments more relatable and impactful.\nAddress Concerns: Consider acknowledging and gently refuting common concerns people might have about adopting cats (e.g., allergies, scratching furniture). This shows you understand both sides.\nCall to Action: While you encourage adoption, make your call to action stronger. Tell readers how to take the next step (visit a shelter, research online).\nVaried Sentence Structure: Some sentences have a similar structure. Try adding variety for a more engaging read.\n\nSuggestions for Improvement:\n\nAnecdote: Consider opening with a heartwarming anecdote about a cat you've known or a touching adoption story.\nSpecificity: Instead of just &quot;gentle purrs,&quot; describe the feeling of a cat purring on your lap. Instead of &quot;low maintenance,&quot; mention that cats groom themselves.\nAddress Concerns: You could write, &quot;While some worry about allergies, many hypoallergenic breeds exist, and shelters can help you find the perfect match.&quot;\nCall to Action: Conclude with something like, &quot;Visit your local shelter today and meet your new best friend. You won't regret opening your heart and home to the love of a cat.&quot;\n\nRevised Snippet Example (with suggestions incorporated):\n&quot;Imagine the soft rumble of a purr vibrating in your lap as a sleek, furry head butts gently against your hand, seeking affection. This is the magic of a cat's companionship, a feeling that transcends words and melts away stress. While some worry about allergies, many hypoallergenic breeds exist, and shelters can help you find the perfect match. Don't let these concerns prevent you from experiencing the joy of a feline friend.&quot;\nRemember, writing is a process! Keep working on it, and you'll have a persuasive and heartfelt essay encouraging cat adoption.\n\nThat's pretty detailed. Now let's look at the code.\nThe Server\nI'm going to skip the frontend code as it's literally, &quot;take the form values and throw em at the API&quot;, so instead we'll focus on the server, and even there, let's just demonstrate the Gemini calls. I wrote a function that takes in the two values entered by the user and then wraps it in a prompt. I also used system instructions (in the too-short named si variable below) to guide the responses.\n\nThe net result is a block of text that is passed to the front end. Since it typically includes Markdown, I used a Markdown processor to display it nicely. Code-wise, this is nothing more than &quot;take the input and feed it to Gemini&quot;, so te",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "PDF Embed Web Component Available Via NPM",
		"date":"Wed Jun 19 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718820000,
		"url":"https://www.raymondcamden.com/2024/06/19/pdf-embed-web-component-available-via-npm",
		"content":"Earlier this month, after being motivated by Thomas Steiner, I went through the not-really-a-hassle process of publishing &lt;table-sort&gt; to NPM. (Table-Sorter Available Via NPM\n) Today I've done the same for another web component, &lt;pdf-embed&gt;.\nThis component wraps Adobe's PDF Embed API, which, honestly, isn't an API, but a JavaScript library to embed PDFs inline with the rest of your document.\nGiven this HTML:\n\nYou get:\n\nPDF Embed itself has many different customizations, not all of which are available via the web component, but the best part is that this can be used in a progressively enhanced manner. As you can see in the example above, if the library doesn't load, you still have a way to direct users to your PDF.\nYou can find it on NPM here: https://www.npmjs.com/package/@raymondcamden/pdf-embed\nThe repo is here: https://github.com/cfjedimaster/pdf-embed\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Your Buttondown Email Stats to Your Website",
		"date":"Mon Jun 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718647200,
		"url":"https://www.raymondcamden.com/2024/06/17/adding-your-buttondown-email-stats-to-your-website",
		"content":"I've been using Buttondown for almost a full year now (I blogged about the setup here). After having a few issues with Mailchimp and my newsletter, I was pleasantly surprised by how easy Buttondown was and how quick it was to set up. I ended up paying for it as I knew I'd end up paying for some solution and Buttondown worked great and wasn't expensive.\nFor a while now, I've had a custom-built stats page on my blog that's primarily built for me. It reports on multiple different parts of my site and its biggest use is to let me quickly judge how well I'm keeping to my publishing schedule (a post a week). One stat it didn't have that I've been checking manually is a count of how many subscribers I've got to my newsletter. Here's how I added that support.\nFirst, I took a look at the API documentation and more specifically, the Listing subscribers endpoint. The sample code for this was incredibly simple:\n\nThis returns a paginated set of results, but what I care about is the count property. I can basically ignore the actual data and just return that. By default, this endpoint returns all subscribers, even those that haven't actually verified their subscription, but that can be modified by passing a type=regular query string in the call.\nWith that working, I created a new Netlify serverless function:\n\nPretty trivial, right? I named this get-stats and you can see the path uses that as well, and I'll admit, that's pretty generic. But it occurred to me that I may have more 'small' stats like this in the future and I could simply add to the function. Notice how the result object returns the stat in buttondownCount. In theory, I can just add more crap there as I, well, get more crap.\nThe very last bit was a small amount of client-side code to fetch this and if you're curious, just view the source on that stats page yourself, but I noticed this weekend when adding it that I'm still using Vue.js. I'll be changing to Alpine.js sometime this week.\nAnyway, I hope this helps, and don't forget, you can sign up for my newsletter using the simple form below. The newsletter simply sends you a notification when I release new blog posts. Like this one. :)\n",
		"tags":[
	        
            "serverless",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jun 15 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718474400,
		"url":"https://www.raymondcamden.com/2024/06/15/links-for-you",
		"content":"Happy Saturday to everyone, especially those who like me, skipped yard work and finished a video game instead. Tomorrow is Father's Day and I hope every Father, Step-Father, Not a &quot;Real&quot; Father but Does the Dang Job Father, and so forth have a wonderful day. I'm the proud father of eight and am so happy that my children bring me so much joy, and teach me patience. Let's get to the links!\nAs a reminder, the point of this series is to share cool stuff with you, my favorite reader. I typically post three links every two weeks. If you've got something you would like me to share, just drop me a line and I'll take a look!\nA Year of the 11ty Bundle\nThis share is a bit old, as the post came out a month and a half ago, but the 11ty Bundle site is now a year old. Built as a one-stop shop of Eleventy resources, this is a great way to both learn about Eleventy and keep up with the latest demos, posts, and more. Huge thanks to Bob Monsour for creating and maintaining this resource, and you can read more about the year in review here: &quot;The 11ty Bundle continues - A year in review&quot;\nEleventy Filters\nLet's keep a good theme going, shall we? Next up is a cool list of Eleventy filters written up by Chris Burnell. This is a great collection of filters along with examples of how to use them. My favorite is numberStringFormat.\nThe 11ty International Symposium on Making Web Sites Real Good\nWell heck, might as well make it an Eleventy week? Last up is the live-streamed Eleventy conference, &quot;The 11ty International Symposium on Making Web Sites Real Good&quot;. I love that name. It's over six hours of content and multiple different sessions. Check out it out below:\n\nOne More Thing...\nI mentioned above I put off yard work to finish a game. That game was PowerWash Simulator which if you're wondering, yes, it is a game where you use a power washer to clean stuff. I know, sounds lame, right? I downloaded as part of Xbox's Game Pass program (more on that below) and quickly got addicted. It's incredibly simple. There are no time limits. No health bars or lives. You can't lose. All you do is clean. Either an environment like a house or park, or a vehicle. It's incredibly simple and best of all, incredibly satisfying. I can't describe how calming this game is and I can't remember a game like it. Obviously, this is going to be a hit or miss for most folks, but I absolutely suggest checking it out.\nI first got XBox's Game Pass service three or four years ago. It's basically Netflix for games where you can download and play any game from the library. Each month, games are added, and some taken away, but it's a pretty extensive selection of games. Best of all, I have discovered so many really fun games. Heck, it's actually kind of rare for me to buy games now as I can typically find something on Game Pass to occupy me for a few weeks at a time. If you've ever worried about how much you'll actually enjoy a game, Game Pass gives you a safe way to spend time with a game without the commitment.\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Scraping Recipes Using Node.js, Pipedream, and JSON-LD",
		"date":"Wed Jun 12 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718215200,
		"url":"https://www.raymondcamden.com/2024/06/12/scraping-recipes-using-nodejs-pipedream-and-json-ld",
		"content":"It's pretty well known now that most, if not all, recipes on the internet are 90% crap and 10% actual recipe, at best. Obviously, there are outliers of course and obviously, if you are sharing your recipes online you are free to do so as you see fit, but speaking for myself, when I click a link to a recipe my assumption is that I'm going to have to &quot;work&quot; to figure out where the actual details are amongst the humorous backstories and other tidbits that don't actually tell you how to make what you're trying to make.\nThat's why I love apps like Saffron. Not only is it incredibly minimal and laser-focused on recipes, it has an incredibly good &quot;recipe importer&quot; service that can translate nearly all URLs into a core set of ingredients and directions. I recommend it, both the website and mobile application.\nRecently I was thinking about what it would take to build my own version of that service. Not the complete recipe hosting app but just the &quot;url to details&quot; aspect. Turns out, one of the creators (Ben Awad) actually blogged about the topic in 2020: &quot;Scraping Recipe Websites&quot;.\nThe blog post details how 75% of the time, he was able to automate the process, and outside of that, had to build a somewhat complex workaround for the remaining percentage. I decided to focus on the &quot;easy&quot; part, specifically parsing recipe metadata included in the HTML with JSON-LD. It's a flavor of JSON supporting linked data and can be embedded on a site like so:\n\nSpecifically, the metadata follows standardized structured data schemas supported in Google Search and this is why when you search (on Google at least), you can see &quot;cards&quot; summarizing results on top. Google is able to create those summary cards based on the data contained within the page in JSON-LD.\n\n\n\nOne of those formats supports Recipes, which means a page containing a recipe can actually support a data-centric version of it, and in theory, all we need to do is get that from the page and go to town. Here's an example taken from Google's docs:\n\nFor the most part, I think you can look at this and figure out what each part means easy enough, but the docs do provide a great level of detail about each part. For example, if you look at duration and see that it is &quot;PT1M33S&quot; you may be confused, but the docs tell us this is a duration in ISO 8601 format.\nGiven that we're got docs, in theory, it shouldn't be too hard to automate this, right? Right???\nStep One - Getting the JSON-LD\nI began by writing code to attempt to find the script tag with embedded JSON-LD in HTML. I tried via regex... and then gave up. Thankfully, I found this great StackOverflow post that detailed how to do it with Cheerio. I've used CHeerio a few times in the past. It's basically jQuery for Node, allowing you to use familiar jQuery syntax with a pure HTML string.\nBased on the SO post, I whipped up this:\n\nFor the most part it follows the SO post, but as the long comment suggests, in my testing I saw two types of results. Either a simple array where I could return the first element or an object where the @graph element contained what I needed. I think this logic isn't 100% solid, but it seemed to work well in testing.\nThe net result from this is a parsed object containing whatever was in the JSON-LD element. (As the else block and comments state, when @graph is used, I can filter out to Recipes immediately, while in the if block we don't actually do that. I definitely think this could be addressed better.)\nStep Two - Getting the Recipe\nThe next block of code checks to see if the data was a recipe, and if so, handles simplifying the results. This part gets even more complex. First, the main function:\n\nAs you can see, I'm doing things like renaming keys from &quot;recipeSomething&quot; to just &quot;something&quot;. I'm also rewriting some values.\nFirst, to translate duration I use a handy package named TinyDuration. This parses the duration value and returns an object containing each unit (years, months, etc) and value. I want to convert this into a simple string so I used this function:\n\nI freaking love Intl (and I've been trying to get conferences to accept a talk on it!) and the ListFormat function handles automatically creating things like &quot;2 hours and 33 seconds&quot; versus &quot;15 minutes&quot;.\nFinally, instructions are simplified as well. They are either an array of strings or an array of objects, including images and things that I just strip out:\n\nResults\nSo, did it work? Yep! Mostly anyway. Here's a set of recipe URLs and their results:\nClassic Peanut Butter Cookies\nURL: https://www.allrecipes.com/recipe/10275/classic-peanut-butter-cookies\nResult:\n\nIced Pumpkin Cookies\nI make this once or twice each fall. They are absolutely delicious.\nURL: https://www.allrecipes.com/recipe/10033/iced-pumpkin-cookies/\nResult:\n\nThe API\nTo wrap this all up, I built a Pipedream workflow that lets you pass in a URL and get the result in JSON. As a",
		"tags":[
	        
            "javascript",
            
            "pipedream",
            
            "serverless"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using JSON Schema with Google Gemini",
		"date":"Tue Jun 11 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718128800,
		"url":"https://www.raymondcamden.com/2024/06/11/using-json-schema-with-google-gemini",
		"content":"Back about a month ago, I wrote up a post on how to generate JSON results using Google Gemini, &quot;JSON Results with Google Gemini Generative AI API Calls&quot;. While you should read that post first, the process basically boiled down to:\n\nSetting the response type of the result to JSON. Without this, Gemini will return JSON but encoded in Markdown.\nUsing a System Instruction to give directions on the &quot;shape&quot; of the JSON, i.e., use this key and that key.\n\nWhile these techniques work well, recently yet another feature was added that makes this even better, JSON schema support. JSON Schema is an abstract way to define the shape of JSON and can be really useful in validation. The website provides examples and documentation of how to build your schema. It can be used to define the shape of JSON results as well as signify property types and what is required versus what is optional. Note that this feature is not available in Flash models, only Pro.\nIt can be somewhat gnarly. For example, here's the schema that defines the JSON results from the Adobe PDF Extract API: https://developer.adobe.com/document-services/docs/extractJSONOutputSchema2.json\nWhat's nice though is that while a text description of a JSON result is nice, a schema description should be even more precise in terms of directing Gemini. The docs provide a Python example, but let's consider how this could be done in Node.\nFirst, remember the last demo in my earlier blog post showed a &quot;comic book recommendation&quot; agent and used a system instruction to shape the results:\nYou are an expert in comic book history and return suggested comics \nbased on a user's desired kind of story. \n\nYour response must be a JSON object containing four to five comic \nbooks. Each comic book object has the following schema:\n\n* name: Name of the comic book or series\n* publisher: The publisher of the comic book\n* reason: A brief reason for why the user would like this book\n\nThe first thing we'll do is simply the instructions:\nYou are an expert in comic book history and return suggested comics \nbased on a user's desired kind of story. \n\nNext, I'll define a JSON schema for the results:\n\nThis matches what I had in the previous version, although it doesn't limit the results and I'm fine with that. I could add that back in the system instructions if necessary.\nThe last step is actually using it, which can be done using the generationConfig object:\n\nDo note that I actually parse the JSON schema before sending it, which feels a bit silly as the SDK is just going to stringify it anyway. Putting it all together, here's a complete rewrite of that previous demo that now uses the schema:\n\nThe results are pretty much the same as before. Note that I changed the prompt to ask about fantasy stories involving cats.\n\nBy the way, that first recommendation is absolutely spot on. You can find the complete source here: https://github.com/cfjedimaster/ai-testingzone/blob/main/gemini_json/test_system_instructions_4.js\nBuilding an API with the API\nFor fun, I wondered if it would be possible to use the Gemini API to build... an API. A few days ago my stepson asked a Google Home device about the &quot;animal of the day&quot;, which was apparently something it supported because it (sadly) responded saying it no longer had that feature. I was curious if I could recreate this in Gemini.\nI began with this schema and system instruction:\n\nI'm defining the JSON to support three keys in a basic object - name, description, and link. I then updated my function to use a hard-coded prompt:\n\nAnd the results look like so (I ran it a few times):\n\n\nYou can find the complete source here: https://github.com/cfjedimaster/ai-testingzone/blob/main/gemini_json/test_system_instructions_5.js\nObviously, it wouldn't (probably) be cost-effective to build an API like this, but it's a possible solution. As always, let me know what you think by leaving me a comment below.\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Table-Sorter Available Via NPM",
		"date":"Mon Jun 10 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1718042400,
		"url":"https://www.raymondcamden.com/2024/06/10/table-sorter-available-via-npm",
		"content":"This is just a quick post to say my &lt;table-sorter&gt; web component is now available via npm! My thanks go to Thomas Steiner who suggested I take my little CodePen demo and actually publish it. You can find it at NPM here, https://www.npmjs.com/package/@raymondcamden/table-sorter, and install it in your project like so:\nnpm install @raymondcamden/table-sorter\n\nAnd holy crap - 79 downloads already? That's pretty cool. You can find the repo here, https://github.com/cfjedimaster/table-sorter/, where I've got a few issues (again, thanks to Thomas) for future updates.\nAs a reminder, this web component progressively enhances a table so that users can click to sort the table in different ways. You literally just wrap an existing table:\n\nYou can check out an online demo here: https://cfjedimaster.github.io/table-sorter/demo.html\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Game Dev Diary - Cat Herder - Part 2",
		"date":"Fri Jun 07 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1717783200,
		"url":"https://www.raymondcamden.com/2024/06/07/game-dev-diary-cat-herder---part-2",
		"content":"Welcome to my second game diary for [Cat Herder, which I'm subtitling - &quot;Rise of the Machines&quot;. This update is all about the 'machine' aspect of the game. Let me explain.\nRight now each cat (well, you can only have one unless you cheat) has three needs you must manually take care by clicking the right button to match the mood. The idea of the machine feature is that they will automatically handle this for you, providing you have enough of them and the right type.\nThe first thing I needed to figure out was how to enable this feature. I decided on a one time check to see if you have 75 or more purrs (the currency of the game). This is a one-time check because as you spend purrs, I didn't want to take them away if you went below that threshold. In code, the UI does this:\n\nAnd in code, it's actually two things. First, a boolean variable:\n\nAnd then a getter:\n\nOnce this returns true, I open a panel built with a Shoelace sl-details web component, wrapping three sl-card components:\n\n\n\nI generated those images with Adobe Firefly. Each machine handles one particular need - to be ignored, fed, and petted. There's a couple mechanics at play with machines.\nFirst, the cost is based on the total of all machines together, following a simple bit of math:\n\nI'm actually displaying the cost of machines only in cheat mode now, but may move that to the button itself so you can see. Those buttons all check to see if you can afford to buy one using logic like so:\n\nAnd with code like so:\n\nCurrently, all three machines have the exact same logic, and I could rewrite it to a canBuyMachine method, but I'm keeping them separate for now in case I change my mind about them all sharing the same cost.\nNow, as to how the machines work, it's a bit interesting, and remember, none of this is really told to the player, they have to figure it out. The logic is - if you have X cats, for a machine to help it, you must have X of that type.\nSo for example, imagine you have 2 cats, and one of each machine. Cat 1 wants to be ignored and cat 2 wants to be fed. Even though you have, in theory, an idle feeding machine, it won't help the cat. Basically, the Nth element of each machine is tied to the Nth cat. Here's that logic:\n\nThe above lives within my main heartBeat method which is run every second.\nI think the next thing I'll build is the 'gain cat' mechanism, which I'm still figuring out. I kinda want them to randomly show up, as thats how I've gotten most cats in my life, but I'm not sure yet.\nWant to play it? You can do see here: https://catherder.netlify.app. The GitHub repo is here: https://github.com/cfjedimaster/catherder\nFinally, I recorded a video version of this as well - enjoy!\n\n",
		"tags":[
	        
            "javascript",
            
            "games"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Game Dev Diary - Cat Herder - Part 1",
		"date":"Mon Jun 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1717437600,
		"url":"https://www.raymondcamden.com/2024/06/03/game-dev-diary-cat-herder---part-1",
		"content":"Over a year ago, I released my first &quot;idle clicker&quot; game, IdleFleet. IdleFleet is a simple &quot;space merchant&quot; game built with Alpine.js. I've worked on it off and on since the initial release (which was in Vue.js by the way) and still have updates I want to add, but a few weeks ago I started work on a new game I'd like to share with you, Cat Herder.\nThe Game\nCat Herder is very much in its early stages so while I'll (eventually) link to it, you can't do much with it now. The basic idea is that you have one cat... to start with that is - and have to keep it happy. Cats have three basic moods:\n\nWanting to be left alone\nWanting to be fed\nWanting to be petted\n\nThe UI displays the cat's current mood and provides buttons to respond to the cat's needs:\n\n\n\nIf you think it's crazy to have a button for &quot;ignore&quot;, well, no one said being a cat owner was easy. The &quot;H&quot; in the UI above is temporary and represents the numeric value of the cat's happiness. You give the cat what it wants, the number goes up. Do the wrong thing, and it goes down.\nTo make things more fun, a cat's mood will change at random intervals. Right now it's a very quick interval so I can see it working, but will be slowed down a bit later.\nGiving the cat what it wants raises the happiness and at a certain level, you earn &quot;purrs&quot; which is the game's currency. If you don't make the cat happy enough before it changes its mood, you lose all happiness. Sorry, welcome to being a cat owner.\nWhen you have multiple cats, it gets a bit overwhelming, which is kind of what I'm going for in the game:\n\n\n\nWhat you can't see in the screenshot is that the cats' moods are all changing at different intervals and the boxes are sizing kind of randomly. I thought about fixing the layout but realized - the random resizing actually made the game more challenging. Also, cats.\nThe next step is to add purchases. The game will offer three machines - an auto feeder, an auto petter, and a box (for cats that want to be ignored). The idea is that one machine services one cat's needs, so you need to cover all three types, multiple times, to handle your cats, and you'll probably not be able to keep up, which again, is kind of the point. Because... cats.\nI also plan on letting you purchase cats, but I also expect to randomly drop them in from time to time because sometimes we adopt cats, and sometimes they adopt us.\nYou can play the game, although there really isn't a point yet, here: https://catherder.netlify.app\nThe Code\nI'm building the game with two technologies, Alpine.js of course, and Shoelace for my UI. Shoelace is a really easy to use web component library that works just fine with Alpine. Here's how I'm rendering the cats:\n\nOn the JavaScript side, there really isn't a lot going on yet. Cat names are not using the random word libraries I've used in the past but just a simple array of prefixes and names:\n\nStuff like this I'll move out into a different file eventually to make it more manageable. Cat moods are an array of strings for how it's displayed as well as a string for the action that my click event matches:\n\nThe game has an internal 'heartbeat' that will iterate over each cat and check to see if it's time for a mood change:\n\nAnd that's mostly it. You can peruse the complete code here: https://github.com/cfjedimaster/catherder I'm not quite ready for PRs yet as it's early, but I would take ones that add to the list of cat prefixes and names.\nFinally, if this was all too much to read, why not watch the video version???\n\n",
		"tags":[
	        
            "javascript",
            
            "games"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jun 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1717351200,
		"url":"https://www.raymondcamden.com/2024/06/02/links-for-you",
		"content":"Hello from what feels like a rainforest down in Louisiana. We've had what feels like weeks now of not just rain, but strong rain and storms, and the hurricane season has only officially just begun. Thankfully we've got a whole home generator but I'm not looking forward to this year's storm season. (One of many reasons my wife and I are moving as soon as the youngest finishes school.) Here are some links to help add a bit of sunshine to me, and hopefully your, day.\nThe HTML List Padding Problem\nHere's a problem I didn't know actually existed, but as soon as I started reading, immediately realized it could be an issue. Lists (&lt;ul&gt; and &lt;li&gt;) include a bit of inherent padding when rendering the markers in front of each list item. For numeric lists (&lt;ol&gt;), this padding ends up not being big enough for very long lists. In this post, Making room for long list markers with subgrid, Noah Liebman describes how to use CSS subgrid to solve it. Now, you may say that having a big list is a problem itself, but don't forget HTML lists have a start attribute that can change the default starting number. You may use pagination to break up a large list but still want the numbers to be correct, and Noah's blog post could help out with potential layout issues.\nECMAScript 2024 Proposal - Promise.withResolvers()\nDr. Axel Rauschmayer is probably the most knowledgeable JavaScript expert on the planet, and even better, is incredibly willing to help others. (I've reached out to him many times over the years and he's always helped me.) In one of his latest posts, he digs into the proposal for Promise.withResolves(), which could provide more power to the already really useful Promise feature. I've read his post twice now, and I think I get it, but honestly will probably need to write up my own demo to wrap my head around it.\nState of HTML 2023\nOver the past few months, a survey was filled out by over twenty thousand developers focused on HTML. While there's been a State of JavaScript and State of CSS for a while now, this was the first developer survey focused on HTML. The State of HTML survey covers all aspects of HTML. While there's a lot to digest here, it could be a good way for technical leaders to get a barometer on how folks feel about and how much they are using various aspects of the web platform. I found the web components section very interesting.\nAnd last but not least...\nAnother music treat for you, this one from Men I Trust, and I believe I've got Brian to thank again for this track.\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Tracking Gemini Models with Pipedream",
		"date":"Fri May 31 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1717178400,
		"url":"https://www.raymondcamden.com/2024/05/31/tracking-gemini-models-with-pipedream",
		"content":"APIs, tools, consumer features, and heck, pretty much every aspect, of generative AI is changing at an incredibly rapid pace. I mostly focus on just Google Gemini and even that is pretty difficult to keep up with. Recently, Linda Lawton shared that she actually uses an automation script to keep track of the models currently available in Gemini. I thought that was a great idea and decided to see if I could build something similar using Pipedream. Here's what I came up with.\nGetting Available Models via API\nNormally, if I wanted to know what models I had available, I'd go to AI Studio and just look, or check the docs. While that's fine usually, there's actually an API method that can do this programmatically, list_models.\nCalling this method returns an array of available models, along with information about their features. Here's a partial list of the results:\n\nBasic Node.js code to get this data is as simple as:\n\nAutomating the Process\nTo automate the process, I turned to Pipedream. My first step was to set up a schedule. I set it as weekly as I figured that was enough, but heck, I may need to increase it if things continue to progress as quickly as they have been.\nMy second step was a call to get models. It's basically the code above, but here it is in a Pipedream step:\n\nNext I needed to figure out - how am I going to tell what changed? I decided I'd use Pipedream's Data Store feature. It's a basic key/value system and can handle arrays of objects as shown above. Pipedream has a built-in step where I can specify my data store name, a key, and a default value:\n\n\n\nTo figure out changes, I decided on a simple approach. While I figured it was possible for a model to change something deep, like the default temperature, I figured that would break backwards compatibility and probably would never happen. Instead, I decided to focus just on the model name. I would do two things:\n\nLook in my cached list of models and see if any were removed.\nLook in my list of models from the API and see if they weren't there before.\n\nThe first step looks for models missing:\n\nAnd the next step looks for new models:\n\nAt this point, I have two arrays - one for missing models and one for new models. Pipedream has a 'condition' step that lets you apply a basic logical check and end the workflow if it evaluates to false. I went with this check:\n{{steps.findNewModels.$return_value.length &gt;= 1 || steps.findMissingModels.$return_value.length &gt;= 1}}\n\nAt this point, if the workflow is still running, I need to inform me, and I figured a simple email would suffice. I built a code step to generate the text:\n\nAnd then a built-in Pipedream step that emails to myself, with the result of the above as the HTML body.\nThe final step simply updates the data store, and again, Pipedream makes this easy with a built-in step:\n\n\n\nThe first time I ran this, there were no values in the cache so everything was new. I did a quick little hack to remove one from the cache so I could see a slightly more realistic email with one model added:\n\n\n\nThe Workflow\nIf you're a Pipedream user and want the source, you can find it here: https://github.com/cfjedimaster/General-Pipedream-AI-Stuff/tree/production/monitor-available-models-p_OKCQw5x And if you aren't a Pipedream user, you should absolutely check it out!\n",
		"tags":[
	        
            "generative ai",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Update to My Table Sorting Web Component",
		"date":"Wed May 29 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1717005600,
		"url":"https://www.raymondcamden.com/2024/05/29/update-to-my-table-sorting-web-component",
		"content":"Just a quick note. Last year, I blogged a demo of a web component that lets you wrap an existing HTML table and progressively add table sorting. I'm rather proud of that demo and was actually planning on doing a quick video about it, but while testing I encountered two small bugs that somehow missed my earlier rigorous testing. (And by rigorous testing I mean a few minutes of clicking around.)\nSpecifically, the issue is in the &quot;when clicking to sort, notice if we sorted this column before and if so, reverse the sort&quot; area:\n\nIn the function above, i simply refers to the index of the column that is being sorted. My thinking at the time was - the default is ascending, but if you are clicking the same column as last time, reverse it.\nThere are two bugs here:\n\nOne, I'm using sortDir which doesn't even exist. I must have renamed it to sortAsc and missed it. That was an easy fix.\nThe second issue was harder to find. I clicked to sort a column a few times, then clicked another column a few times, then came back, and noticed the second click wouldn't properly change the direction. Why? Because I never revered sortAsc to true on a new column.\n\nSo the fix looks like this:\n\nI'm going to edit the older blog post now and correct the samples, but if you just want to see the finished version, here it is:\n\n  See the Pen \n  PE Table for Sorting (2) - Edited by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding Recommendations to my Blog with Algolia",
		"date":"Mon May 27 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1716832800,
		"url":"https://www.raymondcamden.com/2024/05/27/adding-recommendations-to-my-blog-with-algolia",
		"content":"I've been using Algolia for my site's search functionality for a few years now and it works great, especially once the free tier expanded to cover the size of my content somewhat better. In that time, I've mainly just stuck to basic search functionality and haven't really touched any of the more advanced features. This weekend I took a look at one I've been meaning to play with for some time, Recommendations.\nMy thinking was, of course, a way to recommend/suggest content related to the current blog post you may be reading. This distinction is important because as I looked at the Recommendations marketing and documentation, the content is heavily focused on product recommendations. I.e., the typical &quot;you are looking at product X, and these 3 items are often purchased with it&quot; type scenario. That makes perfect sense, but I will say that initially, I assumed what I wanted to do wasn't possible, ie, just straight content recommendations. That may be on me for perhaps skimming the docs a bit quickly, but I share this just in case others have the same reaction as well.\nRecommendations are covered by the incredibly generous free tier, but oddly, at least in my look at the pricing page, I don't see that specifically called out. (I've sent my contacts at Algolia feedback on this and certainly, it could just be me missing the obvious.) I was told that the free tier includes 10k &quot;requests&quot; including search. Now, my search page barely gets any traffic, I think I'm the one who uses it the most, but my site itself gets quite a bit of traffic and if every page load is making a call for recommendations, that can quickly add up.\nI decided to implement recommendations on my blog with:\n\nA Netlify serverless function to proxy the calls to Algolia.\nNetlify Blob's as a simple caching system.\n\nAs an FYI, Algolia's client-side JavaScript API absolutely supports recommendations and I initially built that locally, but removed it once I realized I'd probably blow away my free tier usage.\nHere's how I built it.\nEnabling Recommendations for My Content\nThe first step is to actually enable recommendations which can be done in your Algolia dashboard by - clicking &quot;Recommendations&quot;. Yeah, I know, obvious. However - this brings you here:\n\n\n\nBeneath this and not in the screenshot was a table named, Existing models, which was blank with no way to add to it. From what I could tell, I needed to select one of the options you see above, but had no real clue due to what I mentioned above - the heavy focus on a product use case. Luckily I had help from an Algolian, Juff (sorry buddy, don't know your real name, but thank you) who told me to use the &quot;Alternative recommendations&quot; model.\nThis leads you to this UI:\n\n\n\nThe first question, data source, was easy enough, I selected the index for my blog. You can skip the events (for a content-based example like I'm doing), and then add the &quot;key object&quot; attributes, which for my content was my content and title attributes.\nThe final step is to hit that Start training button and then go take a quick break. This takes a little while. I didn't time it exactly but given the size of my content (nearing seven thousand blog posts), it felt like a reasonable amount of time. I want to say it was less than thirty minutes or so.\nWhen done, you get a really nice visualization and even a bit of sample code as well:\n\n\n\nAll in all, relatively painless, but I do wish the &quot;content use case&quot; was more obvious.\nImplementation\nAs I mentioned above, it's relatively straightforward to get recommendations in JavaScript. I had to add a new script tag (I'm using a 'lite' version of the search SDK), and then a bit of code like so:\n\nI do a bit of manipulation to get the proper object ID. My Algolia content is identified by the URL with no trailing slash at the end. Once I have that though, I just call the getRelatedProducts method and that's it. The queryParameters bit there is used to reduce the load of data going back and forth, but all in all, it took just a few minutes.\nAnd then I promptly ripped it out. I scaffolded a new Netlify function and wrote the following:\n\nFrom the top, I import what I need and initialize variables and such. The function itself starts off by looking for the URL in a query string variable. If it exists, I check the cache. If it exists in the cache, and most importantly, is less than a day old, I return the cached version.\nOtherwise, I hit the Algolia REST API. I do a bit of manipulation on the results to make it simpler (date, url, and title), cache it, and then return it.\nFor my blog post on dynamically creating variables in Postman, here's the result:\n\nHonestly, only the first one feels really on target, but as I've tested with other entries, in general, I feel like I'm getting decent results.\nThe last part, and the one that took me the longest, was figuring out how and where to render it. I decided to append a gray box after the &quot;Support&quo",
		"tags":[
	        
            "javascript",
            
            "algolia"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating Visualizations in Postman",
		"date":"Fri May 24 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1716573600,
		"url":"https://www.raymondcamden.com/2024/05/24/creating-visualizations-in-postman",
		"content":"Earlier this week, I blogged about a cool Postman feature where you could use scripting to take the result of one API call and use it as a variable that is then used by a second call. For APIs that first require you to exchange credentials for an access token, this is a super useful way to make that process easier. Today I'm following up on that with another useful application of scripting - visualizations. Once again, I've got my coworker Ben to thank for showing me this. Let me show you an example.\nWhen working with Firefly Services and the text to image API, you get a nice JSON response back containing information about the results as well as links to your images. Here's an example where I used the prompt, &quot;Cats writing enterprise software demos.&quot;:\n\nIn Postman, I can ctrl click (or CMD click on Mac) to open those results in my browser. Easy peasy. However, Postman supports a &quot;Visualize&quot; tab where you can craft your own visualizations of the data. Here's an example of a script that does that:\n\nI've got a string on top for layout, and in case you don't recognize it, that's Handlebars, an HTML templating language that Postman supports. I simply pass the template and my JSON response and... that's it. I get this nice output:\n\n\n\nBoth images are there, but the second one is beneath the fold in the app. You'll notice I'm using a bit of CSS to shrink the images, and obviously if I wanted to, I could have made them smaller. I could have laid them out left to right instead of vertically. I just whipped up something quick and simple. But the nice thing is, I can see the results without having to leave Postman.\nAlso, make note of this:\n\n\n\nThat little icon there lets you reload the visualization, which means you can make changes to your script and template and test without making another API call.\nWhile this is a great way to render out results from an API that generates images, there are other good uses for the feature as well. The Pirate Weather API is a great free weather API. This call, https://api.pirateweather.net/forecast//30.216,-92.033?exclude=minutely,hourly,currently, returns a weather forecast for my area. (The {{pirateweather}} part is replaced with my API key in Postman.)\nThe API returns a ton of data, even with me specifically excluding parts. Here's the result for that call:\n\nStill here? Thank you. Given all that data, you may want to summarize it a bit. Here's a script I wrote to do that:\n\nI take all that data and create a smaller summary showing the date, weather, and high and low temps. This renders out nicely like so:\nDate: 2024-05-24T05:00:00.000Z\nWeather: Clear\nHigh: 89.11F\nLow: 77.63F\n\nDate: 2024-05-25T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 90.23F\nLow: 75.15F\n\nDate: 2024-05-26T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 88.85F\nLow: 79.28F\n\nDate: 2024-05-27T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 90.81F\nLow: 75.56F\n\nDate: 2024-05-28T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 90.44F\nLow: 70.85F\n\nDate: 2024-05-29T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 88.64F\nLow: 71.5F\n\nDate: 2024-05-30T05:00:00.000Z\nWeather: Partly Cloudy\nHigh: 87.11F\nLow: 71.89F\n\nDate: 2024-05-31T05:00:00.000Z\nWeather: Clear\nHigh: 87.71F\nLow: 75.94F\n\nI think this feature would both be useful for showing other folks a high-level view of API results and heck, even just useful for yourself if you need to focus on a small portion of the data.\nAs cool as this is, I do want to share a warning. While I was building these examples, I made mistakes. That's natural. But I noticed sometimes Postman responded very badly to them, to the point where I had to close it and re-open it. I've got no idea why, I wasn't doing things like creating infinite loops, but for some reason, it would just stop responding well and only a restart of the app would help. I also noticed it sometimes &quot;lost&quot; edits my code. When I saw Postman start acting up, I'd copy the code over to Notepad, restart, and paste it back in. I'm sure it was my fault but... keep it in mind.\nAs before, I've got a video version of this as well. Enjoy!\n\n",
		"tags":[
	        
            "postman"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Dynamically Creating Variables in Postman",
		"date":"Wed May 22 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1716400800,
		"url":"https://www.raymondcamden.com/2024/05/22/dynamically-creating-variables-in-postman",
		"content":"This may come as a shock to you, but sometimes, I don't read the documentation for the tools I use. Sometimes, I don't even look at all the various menu items and UI stuff for the tools I use. I know I'm probably the only one who does that and I apologize for letting down my faithful readers.\n\n\n\nI've used Postman for probably over ten years now. I don't use it terribly often as I can normally whip up a quick API demo in Node in minutes, but I'll use Postman from time to time. As you can probably guess by how I started this post, my use of Postman was very rudimentary. Heck, I've only recently realized the benefit of organizing requests via various collections and using different environments for variables.\nRecently, I was introduced to a very cool, and very simple feature that is incredibly useful. My thanks go to my coworker Ben Vanderberg for showing me this tip (and the follow-up coming tomorrow).\nMany APIs require two calls in order to work properly. The first call takes a set of credentials, usually an ID value and a secret, and exchanges them for an access token. Then in the next call the token, and sometimes the ID value again, are passed to authenticate the operation.\nSo for example, here's a Postman request to authenticate and get a token for Firefly Services:\n\n\n\nIn the request above (and I apologize if that's too small to read, let me know), the client_id and client_secret values are both pointing to variables defined in my environment.\nNow, in the next request, I need to pass the client_id and the token from the previous call.\n\n\n\nYou'll notice that the variable, accessToken, has a reddish color, which is Postman telling me it isn't defined.\nThis is where I'd usually run that first request, copy the value, come into this request, paste, and run. That felt kinda lame, and bugged me (although apparently not enough to bother reading the docs, shame again on me), but as the access token would last a while, I'd do my testing and just be done with it.\nThis is where Ben's tip really came in to help me. Every request in Postman has a &quot;Scripts&quot; tab and allows you to write code for both before and after the request. Postman has a JavaScript API that lets you work with the request and actually update the application itself. That API even lets you... define variables. All in like two lines of code:\n\nThis basically says - parse the response from the call as JSON and create an environment variable named accessToken. And literally, that's it. Now when you run the first call, the post-request script will automatically create that variable for you and the second request will be able to use it.\nThat is so dang useful I've actually found myself using Postman a lot more, and tied together with the tip I'm going to share tomorrow, I'm definitely turning into more of a Postman user.\nNow, one quick note. Currently, the JavaScript SDK does not support a way to create secret variables, which means if you open the environment it will be in plain text. There is a two-year-old bug report on this so most likely it isn't being changed soon. In my experience, I saw that if I set the new variable to secret, the next time my authentication request ran, it did not toggle it back to being exposed, so you can manually change it one time if you want.\nIf this was news to you, let me know in a comment below, and enjoy the video version of this post!\n\n",
		"tags":[
	        
            "postman"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using the Gemini File API for Prompts with Media",
		"date":"Tue May 21 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1716314400,
		"url":"https://www.raymondcamden.com/2024/05/21/using-the-gemini-file-api-for-prompts-with-media",
		"content":"Using media in your prompts (what's called 'multimodal') with the Gemini API is fairly simple in small cases. You can encode your input with base64 and pass it along with your prompt. While this works well, it's got limitations that may be quickly hit - most specifically a file size limit of 20 megs. A few months ago, I shared a demo of using your device's camera to detect cat breeds. With today's cameras taking incredibly detailed pictures, I hit that limit right away and had to write some code to resize the image to a smaller size. Luckily, the Gemini API has a better way of handling that, the File API.\nThe File API\nThis API provides a separate method of adding media to a prompt by using a separate upload process that returns a reference you can then use in your prompt. These references are stored in Google's infrastructure and are automatically deleted after 48 hours, which means a long-running process can make use of the media without having to re-upload the file every time. (And do know that if you wish, you can programatically delete the file as well.)\nSwitching to the File API also provides other benefits, including a much larger file size (2GB). There is a limit per project of 20GB however so if you know, or are worried, you may hit this limit you might want to use that delete capability I mentioned above.\nSupported file types include images, audio, text, and video. The documentation tells you every precise file format but for the most part, everything you expect is there. I will note though that under the list of plain text formats, PDF isn't supported. Of course, PDF isn't &quot;plain text&quot;, but I still felt like I should point this out.\nUsing the File API\nAs a simple example, you can use the File API like so (in Node):\n\nThe result looks like so:\n\nObviously you wouldn't run this alone (at least I don't think so typically), so let's consider a full example.\nUsing a File Result with a Prompt\nA trivial example of using media with a prompt would be a &quot;what's in this picture&quot; use case. For that, I'd start with some basic input processing:\n\nThe function processImage is responsible for wrapping the File API and prompt handling. Here's that block:\n\nThis code was partially taken from sample code in AI Studio and modified a bit by me, but you can see in processImage the first call is to uploadGemini with the path and mime type. Once you get that result, it becomes one more part to the data sent to generateContent along with a hard-coded prompt.\nSo given this input:\n\n\n\nThe result is:\nThe picture shows a black Labrador Retriever dog lying on a white floral \nblanket on a red couch. The couch has two light blue pillows with white \nflower and rabbit designs. The dog is wearing a collar with a tag and has\nits legs stretched out in front of it. The dog is looking at the camera.\nThe couch is situated on a wooden floor. \n\nThe result is pretty much perfect... and... I hate to say this... but I never noticed the rabbit outline on the pillows before. This is literally five or so feet from where I usually sit in the living room.\nFile API versus Base64\nSo given that you have two options for using files with multimodal prompts, which is best? Honestly, and this is the consensus I'm seeing with other Gemini users, it feels like it's just plain safer to always use the File API. It's only a few more lines of code, and it lets you simply not worry about the file size. Again, for the most part, you want to remember that 20 gig limit per project as a max cap. My recommendation is to just use the File API going forward.\nShow Me the Code!\nYou can grab the code from my repo (https://github.com/cfjedimaster/ai-testingzone/tree/main/gemini_files_api), or copy it below. Remember, you'll need your own key of course.\n\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing Google's New Gemini Flash Model",
		"date":"Wed May 15 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1715796000,
		"url":"https://www.raymondcamden.com/2024/05/15/test-googles-new-gemini-flash-model",
		"content":"I'm currently at Google I/O waiting for the next session to start and decided to take a quick look at the latest Gemini model to be released, Flash 1.5. As the name implies, this is a 'speedier' model built to return responses quicker than other models, with the tradeoff that the results may not be as good. Like most things in life, there's going to be tradeoffs. Gemini's Pro 1.5 model will definitely be slower but will return better results. When and how you choose is... well that's a good question, right? I decided to build a tool so I could play with this myself. The idea is to let me enter a prompt and have it run both Flash and Pro models and see both the result as well as how long it took. Here's what I built, and what I saw in my testing.\nModels - in General\nMy mental model for generative AI, at least from the perspective of the developer writing code to use these services, has very much been similar to how I looked at databases when I did more backend work. Most programming languages make it easy to work with databases. However, writing good SQL is a far different story. When I first began to realize how important my prompts were, I drew parallels to the importance of writing good SQL as well. The API is simple - the use of the API is complex.\nIt is quite a bit more of a stretch, but I kinda look at models like databases. Different databases have different strengths and weaknesses. In the case of Pro and Flash, the tradeoffs are quality of results and speed of execution.\nThis is probably not the best analogy, but it's helping me come to terms with working with generative AI. (And please, leave me a comment below with your thoughts on this as well!)\nThe Application\nThe web app I built was very simple - basically a text box for the prompt:\n\n\n\nClicking that button fires off two requests - one to get the results for Flash, and one for Pro. I let both run at the same time and render the result for each as soon as it comes in. Usually, I expect Flash to always come back first.\n\n\n\nIn the example above, the difference isn't huge, 3 seconds to five seconds, but the prompt was ridiculously small. I did another test with this prompt:\nThe following text is a play. Read the play and create a \nsummary consisting of a one sentence title and then \nthree paragraphs covering the major themes of the \nplay (with citations):\n\nAnd added a plain text version of &quot;Hamlet&quot;. In this case, the differences with much larger - Flash took 13.8 seconds to return while Pro took 35 seconds. Here's the Flash result:\n\n\nHamlet is a play that explores the themes of revenge, madness, and the corruption of power. Hamlet, the protagonist, is driven to madness by his father's murder and his uncle Claudius's subsequent marriage to his mother, Gertrude. Fueled by the ghost of his father’s vengeful plea, Hamlet grapples with his own moral compass and his duty to exact justice.\n\n\nThe play centers on Hamlet’s internal struggle, where he contemplates the consequences of his actions and the potential for a moral downfall should he act on his vengeful desires. He ponders the question of whether it is better to “suffer the slings and arrows of outrageous fortune” or to “take arms against a sea of troubles” (III.i.55-57). Ultimately, his quest for revenge consumes him, leading to a tragic climax where multiple characters, including himself, meet their demise.\n\n\nThe play also explores the corrupting nature of power, as Claudius’s actions to seize the throne through murder showcase the ruthless lengths one can go to for personal gain. The play suggests that power can lead to a disregard for moral principles and a loss of empathy for others. Claudius, “with witchcraft of his wit, with traitorous gifts,” betrays his brother, lustfully covets the queen, and ultimately orchestrates a plan to eliminate Hamlet (I.v.41). The play's ending, with Fortinbras taking the throne, suggests that the corrupting influence of power is cyclical and inescapable.\n\n\nNow compare it to Pro:\n\n\nShakespeare's Hamlet explores the corrosive nature of revenge and its devastating impact on individuals and the state. Hamlet, driven by the ghost's demand for vengeance (I.v.9-11), becomes consumed by his mission, neglecting his studies (II.ii.113-119), alienating his loved ones (III.i.184-189), and feigning madness (I.v.170-172). This pursuit of retribution leads him down a dark path, culminating in a series of tragic deaths, including Polonius (III.iv.21), Ophelia (IV.vii.168-183), and ultimately, himself (V.ii.333-336).\n\n\nThe play also highlights the destructive effects of ambition and corruption. Claudius, blinded by his desire for power, murders his brother, marries his widow, and usurps the throne (I.v.75-80). This act throws the kingdom into turmoil, breeding suspicion, paranoia, and ultimately, chaos (I.iv.90-92). Claudius’s actions further demonstrate how the pursuit of power can lead to moral decay, as he manipulates those around him, including Laertes (IV.vii.105-121",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Mon May 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1715623200,
		"url":"https://www.raymondcamden.com/2024/05/13/links-for-you",
		"content":"Yes, yes, I know it's not the weekend and that's when these posts are supposed to go out, but I was super busy being lazy and didn't even realize it had been two weeks since my last post. It happens. Today I'm (weather permitting) hopping on a plane to attend my first Google I/O and I can't wait to dig more into Gemini at the sessions there. If you're attending, let me know and come say hi!\nSelf-Closing Tags\nHTML has been, for better or worse, pretty fault-tolerant since the beginning. Honestly, this is for the best because, at the end of the day, I'd rather my browser be practical and try its best to show me something than simply give up if there's one error in the document.\nIn this post, &quot;The case against self-closing tags in HTML&quot;, Jake Archibald makes the case against self-closing tags and honestly I 100% agree with him. (Although I've absolutely done it in the past, like, recent past.)\nConverting Plain Text to HTML\nThis is a pretty cool article on Smashing Magazine, &quot;Converting Plain Text To Encoded HTML With Vanilla JavaScript&quot;. If you've ever left a comment on a website where your text was 'automatically enhanced', then you've seen an example of taking plain text input and converting to HTML. Sometimes that's as simple as noticing blank lines in input and converting them to paragraphs, sometimes it's more fancy changes like converting *something* to italics (as Markdown does). In this post by Alexis Kypridemos, he discusses the various different ways of doing just this.\nLet vs Const - the Final Answer\nFinally, here's a great video that aims to give a 'final' answer on whether JavaScript developers should be using let versus const. Honestly, I'm trying to only use let myself, but I definitely will employ const, especially if I'm copying existing code. (Yes, I admit to that.) Anyway, check out the video (and transcript) and let me know what you think: &quot;Let me be&quot;\nBy the way, even if you don't really care, watch the presentation. It's short and incredibly well done.\nOne More Thing\nRecently I learned about Tina Bell, and early virtually (and virtually unheard of) pioneer of grunge. Her band, &quot;Bam Bam&quot;, was active in 1983, before Nirvana and Pearl Jam. You can learn more about her from this news piece below:\n\nAnd watch a pretty rough SD video below:\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "ColdFusion's CFOAUTH Tag",
		"date":"Fri May 10 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1715364000,
		"url":"https://www.raymondcamden.com/2024/05/10/coldfusions-cfoauth-tag",
		"content":"This will be my third ColdFusion post in the past year. I'm not saying I'm going to continue the trend, but as I find interesting use cases, I'm going to share. Today, that involves the &lt;cfoauth&gt; tag that I recently had a chance to play with.\nAbout two weeks ago, an old client of mine reached out asking for help adding an OAuth flow to their CF app. I've covered CF and OAuth in a few posts from ten-plus years ago (part one, covering Facebook, part two, covering LinkedIn, and part three, covering Google).\nThat code demonstrated the basics of using OAuth, which entailed:\n\nCreating your application on the platform you'll be authenticating again, which gives you credentials.\nGenerating a link in your CF app that will take the user to the authentication endpoint.\nWhen redirected back to the app with a code, using the code to get an access token.\n\nNow, that flow hasn't changed, and I could have simply copied over the existing code that would still work fine, but I decided I'd take a quick look at the &lt;cfoauth&gt; tag to see if it would help.\nWhile the docs are clear about the syntax, it doesn't really explain how it works practically. The most important aspect is that the tag handles the redirect flow for you automatically. What that means is - on a page with &lt;cfoauth&gt;, the user is automatically redirected. This is important to know as you'll want to use the tag on a page with no UI, one that is loaded when a user wants to login. The tag will also handle responding to the return from the auth provider by automatically getting the access token (on a good result of course).\nIn a simple CF app, you could use this like so. First, a basic Application.cfc:\n\nAll I've done here is define my Google credentials in the application scope and enabled session management. Now, on an index.cfm page, I can do this:\n\nBasically, if not authenticated with Google, link to the page that will handle it, otherwise, print out basic information from the user profile.\nHere's login.cfm:\n\nI've provided a type, my credentials, and told the tag how to store the result. I've also requested an additional scope for Google Calendar data.\nSo to be clear - when the user enters this page, the tag is going to generate the URL at Google to handle auth, and automatically redirect. That's the important bit and what I found lacking in the docs.\nWhen, and if, the user logs in, they are automatically redirected back to this URL, and the result variable will be populated. Here's an example of that result. I didn't bother obscuring the access token as it's expired:\n\n\n\nAnd here's the result back on index.cfm:\n\n\n\nWith that access token, you can now make ad hoc queries against what you have access to, and as we asked for calendar access in the scope, we can fetch it. So for example (and I wouldn't include a UDF on the page like this, but it works for a simple sample):\n\nSimple, and useful... but...\nHandling Expiration\nAccess tokens returned from an OAuth provider have an expiration, and here we see the first problem. If you look at the cfdump of the result above, you'll note I got the access_token, but the expires_in value which I know (well, 99% know) is returned by Google is not in this result set for some odd reason.\nNext, it's possible to ask Google (and I assume other providers) to return an additional refresh_token. That token can be used to refresh an access token when it expires.\nIn my research, I found that if you added access_type=offline&amp;prompt=consent to the URL when authenticating, you should get the values returned. The &lt;cfoauth&gt; supports this via the urlparams attribute, so I tried this:\n\nEasy, right? And while it changed the 'auth' flow (ie, Google noticed the change in the request), when I back to the result variable, the refresh_token wasn't there. If I had to guess, I'd say the tag is looking for a set of named variables and ignoring the rest of the result, which doesn't make sense to me.\nUnfortunately, I'd say this one flaw makes the tag unusable unless you want to force users to log in every 60 minutes (the value Google uses for its tokens). To be fair, that's probably not too big of a hurdle as folks probably won't be sticking around that long, but it's something you would need to ensure you properly handle.\nI filed a bug report for this behavior issue here: https://tracker.adobe.com/#/view/CF-4221899\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Upcoming Generative AI Talk by... Me!",
		"date":"Wed May 08 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1715191200,
		"url":"https://www.raymondcamden.com/2024/05/08/upcoming-generative-ai-talk-by-me",
		"content":"I don't normally blog about upcoming conference talks, but I'm really excited to announce my first talk on generative AI, specifically Google's Gemini will be in a few weeks at the F/ND Tech Conference. This is a free, online conference covering a wide range of topics. My talk, &quot;Adding Generative AI to your Workflow with Google Gemini&quot;, will absolutely be appropriate for beginners (as that's what I am) and hopefully provide a gentle introduction to working with Gen AI. Now, one point of warning - this is a Europe-based event so my talk will be... 5:45 AM on a Saturday morning, but admit it, wouldn't you love to wake up early on a Saturday to hear me talk about AI?\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Super Useful NPM Module - Open",
		"date":"Fri May 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1714759200,
		"url":"https://www.raymondcamden.com/2024/05/03/super-useful-npm-module-open",
		"content":"Forgive the samewhat lame title, and this will be a quick little post, but I've recently been using such an incredibly helpful npm module I wanted to share it with others. open by Sindre Sorhus (you must click that link and look at their incredible GitHub profile) is a simple, but powerful utility that... opens things.\nOk, that sounds rather obvious, but what it means in practice is that your Node code can open a resource on your computer with the associated app. (It can also open up an app by itself if you want.) I can't tell you how many scripts I've written that generate file-based results, or URL-hosted results, that I then double-click to open. This one little utility handling that for me has been incredibly useful lately.\nHow about a quick example? Imagine this script was doing a lot more, for example, using Acrobat Services to create a PDF that's stored when done. Here's how you would automatically open it:\n\nAs an aside, this works perfectly well on my Windows machine, running Node via WSL. It opened up Acrobat with the result just fine. And as one more aside, the Acrobat Services REST APIs generate a download URL when done, and in theory, I could skip downloading the PDF if I just wanted to look at a result and didn't need to keep it.\nSpeaking of URLs, when passed to open, not only will it respect your default browser, but even the most recent window. I've been doing a lot of work lately with Firefly Services, and I've used open to simplify my looking at the results.\nHere's a complete script showing Firefly's test to image API and sending the results directly to my browser:\n\nIn this case, as I had 3 results, I get three tabs opened in the browser. That's perfect. But if I wanted to compare and contrast them, I could get a bit fancier. I covered this technique back in May when I blogged about generating headers, but the idea is - generate a temporary HTML file, save it, open it, and then delete it. Here's a modification of the previous script where I do that. (To keep the listing shorter, I just shared the portion after my functions and setup.)\n\nThe uuid4() function there comes from another useful npm library, uuid, and helps me generate a unique filename.\nIn this case, my output looks like this in my browser:\n\n\n\nI'm not sure I've blogged a &quot;love letter&quot; to an NPM package before, but this one really felt like it deserved it. Check the docs for more information, as I only used the simplest options.\nPhoto by Viktor Forgacs™️ on Unsplash\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Chat Integration with Google Gemini",
		"date":"Tue Apr 30 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1714500000,
		"url":"https://www.raymondcamden.com/2024/04/30/building-a-chat-integration-with-google-gemini",
		"content":"It's been on my queue to investigate how to use Generative AI in a 'chat' interface versus &quot;one prompt and answer&quot; mode for some time and today I finally got a chance to check it out. I'll share my thoughts below, but once again I want to thank Allen Firstenberg for his help while I worked through some issues. As always, take what I'm sharing as the opinion of a developer still very new to this space. Any mistakes are my fault!\nWhat is GenAI chat?\nSpecifically, what is chat when it comes to generative AI? Nothing. Seriously. All 'chat' is taking your initial prompt, getting the result, then taking your next prompt and appending it. So for example:\n\nUser sends a prompt, &quot;what is the moon made of&quot;?\nSystem responds with &quot;cheese&quot;\nUser enters a follow-up question, but the prompt sent is:\n\nwhat is the moon made of?\n\ncheese\n\nok, but what kind of cheese?\n\nBasically, chat is just a longer, and longer prompt being sent to the system. Now, this does have the benefit of being able to ask following up questions. In the example above the query about the kind of cheese would be applied back to the earlier context of us discussing the moon.\nChat with Google Gemini\nSo with that in mind, you really don't need to do anything special except handle the history. However, this is where a SDK can help, and the Google Gemini SDK explicitly has support for chat. This example, taken from their docs, shows it with the Node.js SDK:\n\nThere are a few important things to note here:\n\nFirst, a chat can be 'seeded' with an existing conversation. But it is not required. An initial chat would start with no history.\nSee the maxOutputTokens there? I used that in my code and hit the limit super quickly. While it makes sense to limit the tokens, that number isn't sensible I believe.\n\nSo how about a demo?\nDemo One\nIf you've read any of my previous Gen AI posts here you know I typically build my demo with a super simple Node.js &quot;server&quot; that literally just serves up an HTML page and one API endpoint. It's not a full Express.js engine or anything like that, just a basic two-route web server. I'll link to the full code at the end so I won't share every line, but let's start with the front-end.\nI built a super simple chat UI using Milligram CSS that looks like so:\n\n\n\nThe HTML isn't that interesting, but I'll share the JavaScript, which handles listening for the button click, sending the input, and waiting for the response:\n\nThe only real interesting thing here is that I didn't use JSON, just plain strings, as that's all I needed. Felt a bit weird, but KISS, right?\nHere's an example of it in action - I'm going to copy and paste the text from the chat log instead of taking a screenshot so it's easier to read:\n\nYou: \"what is the moon made out of?\"\nGemini: The Moon is made out of rock and dust. The Moon's crust, or outer layer, is made up of a type of rock called regolith. Regolith is a mixture of broken rock and dust that was formed by the impact of meteoroids and asteroids over billions of years. The Moon's mantle, or middle layer, is made up of a type of rock called peridotite. Peridotite is a dark-green rock that is rich in magnesium and iron. The Moon's core, or inner layer, is made up of a mix of iron and nickel.\nYou: &quot;how old is it?&quot;\nGemini: The Moon is about 4.5 billion years old. It formed at the same time as the Earth, when a Mars-sized object collided with the Earth. The impact ejected a large amount of material into space, which eventually coalesced to form the Moon.\nThe Moon is the only celestial body that humans have visited. The first humans to walk on the Moon were Neil Armstrong and Buzz Aldrin, who landed on the Moon on July 20, 1969, as part of the Apollo 11 mission.\n\nNotice how my follow-up question, using &quot;it&quot;, is correctly handled by the API as it understands the context from the previous question. I'm just now noticing that I wrap my questions in quotes but not Gemini's responses, but honestly, I kinda like how it makes my questions stand out versus the responses. I'm not going to spend a lot of time on the UI for this. ;)\nHere's the code for the server portion:\n\nThe important bit is up on top and in the callGemini section. I basically initialize the chat once, and after that, just run sendMessage. The SDK really makes this simple. Since it did, let's kick it up a notch!\nDemo Two\nFor my second demo, I kept the same basic interface and idea - you type, Gemini responds - but added in system instructions to 'flavor' the chat interaction. Before I share how I did that - here's a sample following the same questions I asked before:\n\nYou: \"what is the moon made out of?\"\nGemini: The moon is composed of a variety of materials, the most abundant being oxygen, silicon, magnesium, iron, calcium, and aluminum. These elements come together to form minerals such as olivine, pyroxene, and feldspar. The moon's surface is covered in a layer of regolith, which is a fine-grained material create",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Apr 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1714327200,
		"url":"https://www.raymondcamden.com/2024/04/28/links-for-you",
		"content":"Good afternoon, readers, I hope you are having as good a day as I am. Today I started playing &quot;Midnight Suns&quot; (PS5) and so far, it's a heck of a lot of fun. Yesterday was one of those days that was so good, I almost got a bit paranoid that something bad was going to happen. (It didn't - the day ended great.) I hope it's a sign of how good the week will be. As a quick aside, there will be no &lt;Code&gt;&lt;Br&gt; this week - it will return next week. See you then!\nCally - Calendar Component\nFirst up is a cute (can you call a web component cute???) little web component that displays calendars and allows the user to select a date, or a range of dates. It's easy to use, for example, show a single calendar and allow for one date:\n\nOr - show multiple calendars and allow for a range:\n\nCally was created by Nick Williams and can be installed via a script tag or npm install if you wish. It is important to note that, &quot;The aim is not to give you a full date picker, instead only the lower-level building blocks that allow you to build your own.&quot;, which means outside of displaying the calendar and letting you select a date (or range), it doesn't do much else. I wish it would support form participation, but you can grab the value with JavaScript by just getting the value.\nTo show that, and the component in action, I whipped up a quick CodePen:\n\n  See the Pen \n  Cally by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs I said, it renders very well and is quick to use, so check it out: Cally\nFiles and the Web Platform\nIn various ways, web developers have had some way to work with user files for quite some time. But as the web platform has expanded, so have those options. Scott Vandehey wrote up a great guide on those features named &quot;The Many, Confusing File System APIs&quot;.\nHis article covers all the various options, helps explain the differences, and even offers up a PDF guide (if you're willing to sign up for a newsletter).\nEmbed the Sky...\nSorry for the overly dramatic subtitle there. I've gone back and forth about my opinion of Bluesky. For a while, I was considering dropping it, but Threads has really turned into... I don't know. Threads feels more very &quot;shiny&quot;, but also a bit &quot;lifeless&quot;, whereas Bluesky feels a bit more like how Twitter used to be. While I don't post a lot there, I do spend more time there than I used to. (If you want, you can find me at raymondcamden.com.)\nVincent Will created a nice little web component named bsky-embed that lets you embed a user's posts, a feed, or search, quickly and easily.\nAfter loading in the script tag, you can then embed a profile like so:\n\nYou can see it in action below:\n\n  See the Pen \n  bsky-embed by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne More Thing...\nLet's end as I've done the past few weeks with a music video you may enjoy. Or not. Either way, give it a listen?\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Automating Movie Recommendations with Generative AI and Pipedream",
		"date":"Fri Apr 26 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1714154400,
		"url":"https://www.raymondcamden.com/2024/04/26/automating-movie-recommendations-with-generative-ai-and-pipedream",
		"content":"For the past few months or so, I've started tracking my movie watching with Letterboxd. I'm not doing a lot of reviews, mostly just logging, but I find it neat to look back and remind myself of what I've watched recently. You can see my profile if you're curious, or check out my &quot;Now&quot; page as well. I thought it might be interesting to see if I could use my Letterboxd data along with Google Gemini as a way to suggest the next movie I should watch. I was able to build a quick workflow using the incredible Pipedream in a few minutes. Let me share with you how I did that.\nWhat does it do?\nAt a high level, my workflow does the following:\n\nIt runs, automatically, once a week on Sunday. That was a bit arbitrary but felt like a good day to pick.\nLook at my Letterboxd profile and pluck out the most recent film I logged.\nFrom that, generate a prompt and ask Gemini what it would suggest.\nTake those suggestions and email them to me.\n\nNow let's take a look at that workflow in detail.\nStep One - The Schedule\nThis will be the quickest step as it was one of Pipedream's built-in triggers - a CRON/schedule. All I had to was set it to weekly on Sunday.\n\n\n\nStep Two - Getting My Letterboxd Data\nWhile Letterboxd has an API, they also have RSS feeds for people's logs. You can find mine here: https://letterboxd.com/raymondcamden/rss/. In Pipedream, I added a built-in step that parses RSS feeds. I selected the &quot;Merge&quot; one even though I only had one feed and I'm not sure if there's an action I missed specifically for one feed, but it worked easily enough - I simply gave it the RSS url.\n\n\n\nAs a quick aside, and as a reminder of why I love Pipedream, I've now got a serverless workflow with a custom schedule parsing RSS and I haven't written one line of code.\nStep Three - Generate My Prompt\nNow I actually do need to write code. I'm going to get the first item from the RSS feed, which looks like so in raw XML:\n\nI wanted the title, but not the title in &lt;title&gt; as it has extra info, but rather, the value in &lt;letterboxed:filmTitle&gt;. I grab that value, and generate my prompt:\n\nNote how I both return the prompt and export the title. I need the title later in the workflow, but I wanted the main return to be the prompt. I've not returned multiple values from a Pipedream step before and I appreciate how easy it is. At the end of this step, I'll be able to reference title from the step as well as $return_value.\nStep Four - Talking to Gemini\nAt this point (if you read my blog), you've seen me demonstrate the Gemini API multiple times. In general, it's trivial code, with the truly important bits being the prompt. A few days back, I blogged about how to get JSON results from your prompt using the response type setting and system instructions. I followed the same format for my code here:\n\nThat's a big block of code, but the important portion really is the system instructions I set up in the si string. I describe how Gemini should act and how it should return its results. The net result is an array that looks like so:\n\nStep Five - Generate Email\nAs I plan on using HTML for my email, I created another code step for the sole purpose of creating an HTML string:\n\nBasically, here's what you watched last, and here's what to watch next. I spent maybe one minute making it look decent, but I could have done much more of course.\nStep Six - Email\nThe final step is trivial. Pipedream includes an &quot;email you&quot; type step where you provide a subject and content to email, and it emails the account holder, in this, case. You absolutely can use email services and they've got built-in actions for those, but I'm fine with the basic one.\nThe Results\nThe last movie I watched was &quot;Late Night with the Devil&quot;, and here's what Gemini recommended:\n\nMovie Recommendations\nThe last movie you watched was Late Night with the Devil. I asked Google Gemini what you should watch next and this is what it recommended:\nThe Vast of Night (2019)\nIf you liked the radio broadcast suspense and 1950s setting of \"Late Night with the Devil\", you may enjoy this film with a similar premise.\nTalk to Me (2023)\nIf what you liked about \"Late Night with the Devil\" was the occult horror aspect, then you'll enjoy this film that deals with similar themes.\nPontypool (2008)\nThis is another horror film that takes place largely in one location, similar to \"Late Night with the Devil\", and uses sound design as a key element of horror.\n\nOf those, I've seen two of them, and honestly, the recommendations feel spot on. While testing I got a few different recommendations including &quot;Antlers&quot;, &quot;The Blackening&quot;, and &quot;Evil Dead Rise&quot;. Again, these feel like great recommendations.\nI do think it might be interesting to grab the last two movies instead of one, and that way you would get a bit more variety in responses (assuming you watched different genres), but I figure this by itself is good enough. You can find the complete Pipedream source h",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "JSON Results with Google Gemini Generative AI API Calls",
		"date":"Wed Apr 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1713376800,
		"url":"https://www.raymondcamden.com/2024/04/17/json-results-with-google-gemini-generative-ai-api-calls",
		"content":"Forgive the somewhat alliterative title there, but today's post covers something that's been on my mind since I started playing with Google Gemini, specifically, how to get the results of your API calls in JSON. To be clear, the REST API returns a result in JSON, but I'm talking about the content of the result itself. Before I continue, a quick shot out to Allen Firstenberg who has been helping me off and on with Google Gemini stuff. Anything I get wrong though is entirely my fault. 😜\nOk, so before I go on, let's look at a typical result. Take a prompt like so: &quot;What is the nature of light&quot;. Pass this to Gemini via the API, and the result you get, once you dig down a bit from the full result which includes various bits of metadata and any safety values, looks like so:\n\nNotice it uses Markdown to format the result. If we wanted JSON, one thing we could try is to ask for it specifically. Here's a new version of the prompt: &quot;What is the nature of light? Your answer should be in valid JSON which includes an answer key with the value being your answer.&quot;\nAnd this returns:\n\nIt looks like JSON, but it's a) still wrapped in Markdown code and b), well, not valid JSON as the newlines would break parsing. You could use a bit of code to fix this, but, what if we didn't have to?\nMethod One - Response Type\nLet's first look at the fact that, at the top level, Gemini is returning Markdown, period, for every call, even when it attempts to format the results inside the Markdown. There is a way to stop that now when generating the result by using the response_mime_type argument. This is mentioned in the docs in JSON format responses and looks like so, in Node.js:\n\nThat's a lot of code, but look in generationConfig where I've specified it. Switching that to application/json will reformat the result. To test, I built a wrapper function that took the prompt and mime type as arguments:\n\nAnd then tested twice with the simpler prompt:\n\nHere's how the JSON response looked:\n\nNotice that it is an array of strings, in this case one. I rewrote my prompt to test something:\n\nAnd this gave:\n\nOne array element per sentence. But, it's valid JSON though. If we simply wanted to print this, a quick join would give us a paragraph.\nOk, but what about getting an actual JSON object as a result? Enter system instructions.\nMethod Two - System Instructions for the Win\nThe next part of the puzzle is system instructions. These are ways of telling Gemini how it should respond. Now, this has already been possible with a basic prompt. For example, consider this prompt:\n&quot;Explain how light works as if you are a hyperactive teenager who loves the Marvel movies.&quot;\nSee how I ask a question, and direct how I want my response? This gives:\n\nOMG, like, have you ever thought about light? It's totally wild!  It's like Ant-Man shrinking down and surfing on electromagnetic waves or something!  Light is made up of these super tiny things called photons, and they're, like, the Flash of the universe – they travel SO fast!  Nothing can beat them!  \nSo basically, photons zoom out of stuff like the sun, light bulbs, or even your phone screen, and they bounce off everything!  That's how we see stuff – the photons bounce off an object, say, Captain America's shield, and then bam! They hit our eyes and our brains are like, &quot;whoa, that's a sweet shield!&quot;\nAnd get this – light can act like a wave AND a particle at the same time! It's like, quantum physics or something, super complicated but crazy cool!  So, like, light can diffract and interfere with itself, like when you see a rainbow. It's basically light waves doing a synchronized swimming routine or something!\nAnd there are different kinds of light, too, like the visible spectrum – that's all the colors of the rainbow, ROYGBIV!  But there's also, like, ultraviolet light, which is what gives you a sunburn if you're not careful, and infrared light, which is basically heat vision!  So next time you're out in the sun, just remember, you're being bombarded by tiny little photon superheroes, and they're the reason you can see all the awesome stuff around you!  It's like having superpowers, but for your eyes!  🤯🤯🤯\n\nFrom what I understand, while this works, it would require you to take user input and add that bit about how to answer at the end. System instructions remove that by adding the context outside of the prompt.\nTo use system instructions with the Node SDK, you specify it when generating your model object:\n\nWhen calls to generate content are made against this model, it will use the system instructions to format the response. For fun, consider this example:\n\nNow when I ask it to explain the nature of light, I get:\nMeow. Light be not a thing, but a happening.  The sun, and stars, and fireflies, and also light bulbs, do make tiny particles called photons.  These photons zoom about super fast and bounce off things.  When they hit your eyes, you see!\n\nDo you want to hear about the time I ch",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Apr 14 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1713117600,
		"url":"https://www.raymondcamden.com/2024/04/14/links-for-you",
		"content":"Good day my fabulous readers and I hope all is well with you. I just got back from speaking at the excellent Devnexus event and despite some travel issues (thank you storms, really), I had a great time. I got to see not one, not two, but three very old friends of mine and attended some good sessions as well. I'm home for a week and then on the road again to the Adobe ColdFusion Summit in the DC area. Don't forget, this Tuesday I've got another episode of &lt;Code&gt;&lt;Br&gt; coming up. I'll be building, or attempting to, build a PWA live. Surely it will go perfectly, right?\nPretty Console Table Printing\nFirst up is a neat little library, voici.js, that makes it a bit easier to print tabular data in the console. I'm going to borrow from their quick start to show you an example:\n\nGiven the above code, you'll get this in your terminal:\n\n\n\nYou can get pretty fancy too, for example, easily picking out what columns to show, and even creating 'virtual' columns based on custom logic. If you want to see an example of how I used this recently, read my recent post on how I investigated some odd traffic issues on my site.\nA Great Example of Promise.race\nEarlier this year I took a look at various Promise methods, including Promise.race. In this post by David Bushel, he demonstrates how he used Promise.race to nicely handle an interesting use case with media files. It's a perfect example of how useful this Promise method can be.\nSpeaking of Promises...\nWhile on the topic of Promises, Lydia Hallie has a dang good and deep look at how Promises work: JavaScript Visualized: Promise Execution. She goes into incredible depth about how the internals of Promises operator and honestly, a lot of this was new to me. She also built some incredibly good visualizations. I will say I'm not a fan of animated GIFs - it's sometimes difficult to tell when the 'loop' is starting and I wish I could pause - but the ones used here are pretty well built. Be sure to check out her blog as a quick look at her past entries shows a wealth of valuable information.\nAnd last but not least...\nIn my last post, I shared a music video from a recent Spotify discovery, so I figured why not do it again? Enjoy!\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using GenAI to Help Pick Your D & D Class",
		"date":"Thu Apr 11 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1712858400,
		"url":"https://www.raymondcamden.com/2024/04/11/using-genai-to-help-pick-your-d--d-class",
		"content":"A few weeks back I wrote up my experience with generative AI as a dungeon master. That post ended up being really popular and got me thinking about other ways I could integrate D &amp; D, or other games, with Generative AI. With Gemini 1.5 now available via API, I thought it would be good to find an excuse to hit the API in a demo. So with that in mind, I'd like to introduce you to the Class Suggester.\nThe Application\nThe app begins by simply presenting some introductory text and invites you to click a button to roll for your stats.\n\n\n\nYou can hit the Roll Stats button as many times as you want. It uses the standard D &amp; D rule of rolling a six-sided dice four times and removing the lowest number:\n\n\n\nOnce you have stats, I enable another button that lets you hit it to ask Gemini to make a suggestion. Here's a screenshot of it in action:\n\n\n\nI played with this a bit, and it seemed to match well with the basics I know about D and D, and RPGs in general. It also surprised my many times, for example:\n\n\n\nI don't know what an Eldritch Knight Fighter is but it sounds cool as hell. Alright, let's look at the code.\nThe Frontend\nThe web portion of the application is just a simple Alpine.js application. Here's the relevant HTML with placeholders for Alpine data:\n\nI don't think there's anything really interesting there, but obviously, leave me a comment below if you've got a question. The JavaScript is also relatively simple:\n\nThe only real interesting part is getScore, which handles rolling the die four times and then dropping the lowest value.\nThe Backend\nMy backend is one main script, server.js, that is a lightweight Node.js web server. I'm going to skip the boilerplate part, and instead show you how it processes the incoming request.\nFirst, I've got code to parse the incoming request body and send this to the method that will integrate with Gemini:\n\nIt's simply returning that value to the caller. Now let's look at the Gemini related code:\n\nOn top, the first change (from my previous demos anyway) is selecting the new 1.5 model. In order for that to work, however, you must specify apiVersion and set it to v1beta. (And if you are reading this in the future, that's probably not required anymore.)\nThe code inside callGemini is roughly the exact same as I've shown before, the important part is how I crafted the prompt. You can see I'm describing the situation (creating a new character) and then specifying what I want. Notice this part:\nI already know what Dungeons and Dragons is, so your response should just focus on the class recommendation.\n\nWhy is this there? When I first tested my code, I was still using Gemini 1.0 Pro, and it worked perfectly fine. In 1.5, it still worked fine, but every result started off with a quick explanation of D&amp;D, which was correct, but noise since in this case, we can expect the user to already know what D&amp;D is. When I added this extra bit of prompt text, it worked well to focus the results.\nIf you want to see the complete code, you can check it out here: https://github.com/cfjedimaster/ai-testingzone/tree/main/class_selector As always, let me know what you think by leaving a comment below.\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Netlify Edge and Blob Support to Investigate Website Traffic",
		"date":"Sat Apr 06 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1712426400,
		"url":"https://www.raymondcamden.com/2024/04/06/using-netlify-edge-and-blob-support-to-investigate-website-traffic",
		"content":"For some time now, I've relied on my Netlify Analytics report to keep track of how well my site is doing, what content is popular, and so forth. I was a Google Analytics user for over ten years, but when they updated the UI, I saw red every time I tried to use it. Netlify Analytics is super simple and quick. (My only real complaint is that it's limited to 30 days, but I've got free access to the feature so I'm happy to not care about that.) I complement Netlify Analytics with GoatCounter as well. Netify's analytics show much more traffic than Goat, and I figure the truth is somewhere in the middle, and again, I'm fine with that.\nWhat's odd though is that for the past year, two old blog posts have consistently had the most page views:\n\n\n\nIgnoring hits to the home page, the top two page views are two blog posts related to ColdFusion, oAuth, and Facebook. You can read them here and here if you would like, but both are now eleven years old. There's absolutely no reason for these pages to be getting this kind of traffic. Neither of these show up on my GoatCounter report so I've got no real clue.\nMy guess is, of course, bots, but... why? I honestly have no clue. If they were trying for a DOS attack, they aren't really tring that hard. As an attempt to investigate what's going on, I decided to take a stab at using a Netlify Edge. Why an Edge Function? They can inspect an incoming request and transform the response. While I don't need to transform anything, being able to put up a function to run before a URL, log something, and continue, felt like exactly what I'd need. I began with this:\n\nThe path portion at the end matches the most popular 'weird' post and the default function simply logs out the context value and returns. This has the result of just rendering the page as is, but when testing locally, I could see the function running so I knew it was set up correctly. The context object has some interesting data in it I thought would be good for my tracking.\nFor that, I turned to another Netlify feature, Blob storage. This is a simple key/value system that lets you easily persist data for a site. Each site can have one or more stores and each has its own dictionary of values. I modified my Edge function like so:\n\nI begin by opening up a store called 'tracker'. I'm storing the HTTP referrer, user agent, ip, geo information, and the current time. I read in the existing value for log from the store, initialize it as an array if need be, and then add to the end. Finally, I store the value back in. Super easy and quick to add. I deployed this and was able to confirm data was being stored using Netlify's CLI.\nI could have stopped there, but I thought it might be nicer to have a quicker way to get the data. For that, I wrote a quick serverless function:\n\nThis simply grabs the value and returns it as JSON. You can see this yourself if you want, here: https://www.raymondcamden.com/api/log\nAgain, I could have stopped here. But then I figured, why not whip up a quick script to grab the data and dump it out to my console.\n\nThis script makes use of voici.js, a table printing library. I also make use of a user agent parser. I take my initial data and render each row with a bit of logic to make location and the browser report better. The end result is... a lot of text. I've had the Edge Function running for maybe 4 days and it's at nearly 600 records. Here's a dump if you wish to see:\n\n.gist {\n\toverflow: auto;\n}\n.gist .blob-wrapper.data {\n   max-height: 400px;\n   overflow: auto;\n}\n\n\nAnd now I know... nothing. The referrer value is either blank, my blog, or the post itself. There's nothing consistent in the location or any other value! Honestly I didn't learn a thing... except I did get to play with a Netlify Edge function and their blob support, so I'll take that as a win.\nIf anyone else has an idea of what could be causing the traffic, leave me a comment below please!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "A Quick First Look at Amazon Bedrock (with Node.js)",
		"date":"Thu Apr 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1712253600,
		"url":"https://www.raymondcamden.com/2024/04/04/a-quick-first-look-at-amazon-bedrock-with-nodejs",
		"content":"My regular readers (hello, yall rock!) know I've been playing with generative AI the past few months. I'm still a bit skeptical about the amount of hype involved around the space, but I'm slowly getting more excited as I see some of the interesting possibilities available with these tools. Most of my recent exploration has been on the Google Gemini side, but after hearing my buddy Todd Sharp talk about Amazon Bedrock on his stream yesterday, I figured it was time to check it out. (FYI, you should absolutely check out his weekly Twitch show on the AWS Twitch channel called &quot;Streaming on Streaming&quot; - Wednesdays at 3PM CST.)\nGetting Started\nSo obviously, you want to begin on the marketing page and get a high-level overview. Just now, I tried out the web-based free demo here and it is incredibly well done, appropriate for both technical and non-technical audiences, and heck, if you've never seen anything in regards to how Generative AI works, it's a very well done introduction that highlights the practical use cases.\n\n\n\nThe marketing page has a nice clear &quot;Get started&quot; button as well that leads you into the AWS console, so I'd probably recommend having AWS credentials already.\nOnce in the dashboard, you'll see this prompt, and it's important:\n\n\n\nOne of the first things that was different for me with Bedrock is that it offers a lot of models, not just ones Amazon has created, but you have to request access to actually use them. Don't let that scare you - at least for the Amazon models you can get access immediately, but you do have to request it. Here's a screenshot from the Model access dashboard where you can see I've enabled two Titan Text models:\n\n\n\nSome models require you to describe your intended use. For example, the models under the Anthropic branch. Here's what that prompt looks like:\n\n\n\nI actually did this and answered honestly - ie I just want access for blogging/video creation purposes, not production use. I did this yesterday afternoon and haven't heard back yet. You'll notice there's no indication of that in the model list above which is a bit weird, but I'm just going to assume I'm in a queue someplace. (And to be honest, I'm going to judge Amazon a bit here on how they respond. I do feel that 'exploration' is a 100% valid use case!)\nAnyway, I'd probably recommend starting with the Titan models, that's what I did.\nBefore I jump into the code, I'll point out that there is a set of playgrounds available for testing in your web browser,I picked the first playground for Chat, hit Select model, and in the popup, it correctly identified I had Titan access:\n\n\n\nWhich is cool - I could then start chatting with it. The playground also comes with examples as well. My only real complaint is that I wish it had a code export like Google's AI Studio. What Google gives you isn't complete, but it's a shell that you can quickly modify. Speaking of code...\nLets Code!\nSo when it comes to code, there's a lot you can find in the User Guide and API Reference. My issue with the user guide was that it was Python only, and while I love Python, I had it in my head that I wanted to start off with Node.\nI've used the AWS SDK a few times recently, and while I sometimes struggle to find what I need, when it works, it works well.\nAs a reminder, you can, and should, only install what you need in the SDK. For me, that was @aws-sdk/client-bedrock and @aws-sdk/client-bedrock-runtime. The first one you may not need as it's there to support working with models. I only needed it to build a script to list available models. I've got that code in my repo (which I'll share in a bit) but I don't see myself using it in the future. Instead, the latter one is how you make actual calls against the models and is probably all you need.\nAfter installing the SDK (well, part of the SDK), you import what you need. One of the things I found last year when I shared some code on working with S3 (&quot;Quick example using AWS Node.js SDK V3 for Signed URLs&quot;) was that the SDK has imports related to the 'commands' you do with the service. So imagine a database, if you knew you were only doing read operations, you would import a command specific to that and not the others. That approach is a bit different from most SDKs I think, but not a big deal once you understand that.\nI began my script like so:\n\nI've imported the main Bedrock runtime and the code that will let me invoke commands on the model.\nAs a reminder, you'll see sample code like this:\n\nThat's because the AWS SDK can support cached credentials in your user profile. That's handy, but I don't like that for my samples as I don't want to assume they're there.\nNow let's look at the code that calls the model:\n\nI begin with a hard-coded prompt. The shape of input is important because while the first three parameters seem consistent, the body depends on the model. I found this by looking in the Titan portion of the user guide. If you compare this to the Claude models, you can see ",
		"tags":[
	        
            "generative ai",
            
            "aws",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "All Your Dragons Are Belong To Us",
		"date":"Tue Apr 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1712080800,
		"url":"https://www.raymondcamden.com/2024/04/02/all-your-dragons-are-belong-to-us",
		"content":"Forgive the somewhat silly title, but it's not like I haven't been silly here before. Almost four years ago I wrote a little post about a random text-generated app called the &quot;Queen Maker&quot;: Let's Make Everyone a Queen!. The idea for that app (which lives on at queenof.netlify.app) was to use a random text library called Tracery to generate random short &quot;queen-based&quot; stories. Yeah, that may not make much sense, but read the earlier post or play with the app to see.\nThe important bit though was that it was inspired by a cool Twitter bot called Dragon Hoards. This bot, like many &quot;fun&quot; bots, is now dead, but it still makes me smile when I think about it. On a whim, I reached out to the creator peat and asked if I could recreate it. They graciously agreed and I got to work.\nIf you don't really care how it was built and just want to see it in action, you can follow it on Mastodon at https://botsin.space/@dragonhoards or at Bluesky here: https://bsky.app/profile/dragonhoards.bsky.social.\nHere's an example toot from Mastodon:\n\nHow Dragon (Hoards) Are Made\nWhile I initially wanted to give Tracery another go, I decided against it for various reasons. The library has not been updated in some time, and it would have meant a lot of data entry to make the results more interesting. I began by looking at some sample tweets and breaking it down into a format:\n\nA #adjective# dragon lives #place#. She #verb# her hoard, which consists of a #number# of #thing#, #number# of #thing#, and #number# of #thing#. She feels #feeling#.\n\nSpecifically, when looking at that, I'd need to create long lists of adjectives, feelings, things, and so forth. Instead, I decided to turn to a library I've used before, random-word-slugs. While I think the idea for this library was to make it easier to generate random code names or strings for project names, I've used it creatively in the past, most recently for IdleFleet.\nBit by bit, I broke it down. So for example, to get the type of Dragon, I wrote this function:\n\nNotice I'm asking for an adjective, but specifically one using either a color, appearance, or personality. Sample results include:\n'nice'\n'aggressive'\n'short'\n'muscular'\n'witty'\n'embarrassed'\n'adamant'\n'jolly'\n'busy'\n'clumsy'\n\nUsing this, I noticed that if I started my string with &quot;A&quot;, as in the template above, I'd have an issue with words like adamant. Luckily, there's another useful NPM module, indefinite, which handles this. I added it along with the capitlize option to get this:\n'A bright'\n'A victorious'\n'An itchy'\n'An uptight'\n'A proud'\n'A beefy'\n'A gorgeous'\n'A clean'\n'An embarrassed'\n'A plump'\n\nNext up is the location, and while random-word-slugs supports a 'place' category for nouns, I wasn't happy with the result. For this, I decided to create a hard-coded list of places. I perused the Dragon Hoards Twitter account until it seemed like I ran out of new places and wrote up this logic:\n\ngetRandomIntInclusive simply returns whole numbers in a range and the net result is a random selection from the large list above.\nI used similar logic for the verb related to the dragon's hoard:\n\nOk, so now for hoards, which was a bit more complex. I began by crafting the hoard string like so:\n\nAnd here's getHoard():\n\nIt makes use of two calls to random-word-slugs, one for a quantity, and one for a noun, being either an animal, food, or thing. Notice I use another npm package, pluralize, to turn the noun into its plural form. I noticed that some numeric quantities needed an 'of' for them to make sense. I was able to hard-code it (see ofWords) and while it feels slightly hackish, my inner lazy dragon wholeheartedly approves. Here's some results just from getHoard():\n'billions of toothbrushes'\n'incalculable actions'\n'full kilobytes'\n'many eggs'\n'little addresses'\n'abundant kangaroos'\n'scarce needles'\n'little accidents'\n'many books'\n'scarce jewelleries'\n\nThe final bit, the emotion, was simple:\n\nHere are some samples from that:\n'better'\n'careful'\n'hallowed'\n'abandoned'\n'helpful'\n'bad'\n'acrid'\n'shy'\n'abandoned'\n'easy'\n\nSetting up the Automation\nThe last part is kinda boring, not that it isn't cool, but I've covered it here quite often. Once again I went to Pipedream as it's been my platform of choice for services like this for years now. Even better, they are previewing a new version of their builder, and it's a huge improvement when building workflows. You can see a video about it below:\n\nMy workflow consisted of:\n\nA scheduled-based trigger, executing once every three hours.\nA code step containing the logic I showed above\nA step to post to Mastodon\nA step to post to Bluesky\n\nI've shared how to work with Mastodon many times already, and I discussed how to use the Bluesky API back in February. As the code hasn't changed, I won't include it here, but you can find the complete workflow here: https://github.com/cfjedimaster/General-Pipedream-Stuff/tree/production/dragon-hoards-bot-p_QPCWLwy\nEnjoy!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Mar 31 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711908000,
		"url":"https://www.raymondcamden.com/2024/03/31/links-for-you",
		"content":"For those who celebrate, happy Easter. For me, this is more 'second day after the Shingles shot recovery' day. Here are some links to start off your week. I almost wish today was tomorrow so I could sprinkle in some April Fool's jokes, but I'm sure there will be plenty of that around the internet tomorrow.\nTaking Care\nI've been a fan of &quot;The Oatmeal&quot; for what feels like a good decade or so now. I even had the privilege of attending a session Matthew Inman gave at a technical conference a while back. (Believe it or not, he is just as funny, if not more so, in real life.) This cartoon, &quot;Taking Care&quot; is not his usual comedic fair, but one based on a poem by Callista Buchen dealing with grief. I'm intimately familiar with grief and this work really hit home. I once described my grief to a therapist as a companion who was waiting to punch me in the face. I'd be happy and it would just be waiting, ready to surprise me with an attack and remind me of what I had gone through. I am so incredibly lucky now, not that I've &quot;gotten through it&quot;, as the comic says, grief is always with you, but I've been able to make my peace with it. Give this a read.\nWeb Bluetooth and BBQ\nThis one's quite interesting as I had my first encounter with Web Bluetooth a few months ago. It is &quot;not&quot; easy to work with. This article by Rik Schennink talks about how he used the spec to communicate with a BBQ thermometer: &quot;Using Web Bluetooth To Read BBQ Temperature Sensor Data&quot;. Note that this is Chrome only currently and you will 100% need to use Chrome's devtools to help you out. Rik's article is a great introduction and I wish I had it months ago.\nThe Uber Framework Guide\nI haven't yet dug deep into this, but I feel confident enough to share it as it looks incredible. The Framework Field Guide is an incredibly deep framework guide that covers Angular, React, and Vue.js. You simply pick the language you want to and start reading. At sixteeen chapters, this guide is near book level while also being completely free. I've been delaying learning React for, well, since it came out, but I'm going to give this guide a try and see if my opinion of the framework changes any.\nI'll add that this guide is part of Unicorn Utterances, an excellent site on web development that I've someone never heard of. Let's fix that.\nLastly...\nTypically I share three links on these posts, but I think I may start adding a fourth, just for music. I recently discovered Labyrinth Ear thanks to Spotify and absolutely love this track. Enjoy.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Generative AI to Check for Spam",
		"date":"Thu Mar 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711648800,
		"url":"https://www.raymondcamden.com/2024/03/28/using-generative-ai-to-check-for-spam",
		"content":"A few days ago I wrote up an exploration I did in Spam APIs, &quot;Spam APIs in 2024&quot;. After working on those demos and writing up my findings, I thought it might be interesting to see how a generative AI tool, like Google's Gemini would handle such a task.\nInitial Tests\nSo - once again - I'm a broken record on this - but I fired up AI Studio first to give it some quick tests. I began with this prompt:\n\nGiven the following text, rate how much it appears to be spam by giving it a score between 1 and 10, with 10 being the most likely the content is spam.\n\nFor my first test, I used a 'bad' sample (see my last post for a full listing of my sample data):\n\n💪 If you're looking for a lady to be in a relationship with, I could be your lady . I'm Maya, and I'm ready to match with a local dude who understands how to romantic with a girl like me. ❣ 👉Connect with me here👈 .\n\nI got this response:\n\nI would rate this text a **7/10** on the spam scale. Here's why:\nSpam indicators:\n\nGeneric introduction: &quot;If you're looking for a lady...&quot; is a broad statement that could apply to many people.\nSuggestive language: Phrases like &quot;ready to match&quot; and &quot;understands how to romantic&quot; can be interpreted as suggestive.\nCall to action with suspicious link: The text urges you to &quot;Connect with me here&quot; with a link that may lead to an unreliable or unsafe website.\nUse of emojis: While not inherently spammy, the excessive use of emojis can be a tactic to grab attention.\n\nHowever, there are some elements that make it less likely to be spam:\n\nName introduction: The text mentions a specific name, &quot;Maya,&quot; which suggests a real person might be behind the message.\nSpecificity in desired partner: Mentioning &quot;local dude&quot; indicates some degree of preference beyond just seeking any possible match.\n\nOverall, the text exhibits several characteristics of spam, but it also contains some elements that could indicate a genuine attempt to connect. Therefore, a score of 7 reflects this ambiguity.\nIt's important to remain cautious and avoid clicking on suspicious links, even if the message seems somewhat personal.\n\nThat feels pretty correct. Next, I tried it with this 'good' sample':\n\nIntroducing Polls on Buy Me a Coffee 🎉\nAleesha aleesha@buymeacoffee.com\nWed, Mar 20, 4:33 PM (4 days ago)\nto me\nHi Raymond Camden,\nJust wanted to drop in and give you the big news: Polls are now live on Buy Me a Coffee.\nTo create a poll, go to your studio, select Posts and you will see an option to create Polls.\nYou can use Polls to engage with your audience, get ideas on what you should create next, collect feedback, and what not. Excited for you to try it out.\nIf you have any feedback for us, LMK. I read every reply :)\nRegards,\nAleesha\nBuy Me a Coffee Team\n\nAnd got:\n\nI would rate this email a **2 out of 10** on the spam scale. Here's why:\nReasons it's not spam:\n\nSent from a legitimate company: The email is from Buy Me a Coffee, a well-known platform for creators to receive support from their fans.\nPersonalized: The email addresses the recipient by name.\nRelevant content: The email announces a new feature relevant to users of the platform.\nClear unsubscribe option: The email provides a clear and easy way to unsubscribe from future emails.\n\nReasons it could be considered slightly spammy:\n\nPromotional content: While relevant, the email is ultimately promoting a feature of the platform.\nInformal language: The email uses informal language like &quot;LMK&quot; which might not be appropriate for all audiences.\n\nOverall, the email seems legitimate and informative, but its promotional nature and informality slightly increase its spam score.\n\nThis also feels pretty accurate. All in all, it seems to work well, but if I wanted to automate the process, I'd need to remove the context. To do that, I changed my prompt to:\n\nGiven the following text, rate how much it appears to be spam by giving it a score between 1 and 10, with 10 being the most likely the content is spam. Your response should only contain the score with no additional text.\n\nI verified this worked, and then wrote a quick automation script:\n\nMost of this is boilerplate Gemini code, with one really important difference. Note the safety settings are all set to BLOCK_ONLY_HIGH. I found that Gemini would have an issue with some of the spam (no surprise there) and lowering the safety thresholds worked.\nAnother important change was to throttle my code a bit such that I was only calling the API two times per minute. Without this I would get 429 errors stating I was hitting the API too much. That feels a bit on the stingy side and I'm not sure I'd use this at scale, but at the same time, it's doing a lot more and heck, may even be overkill for this task.\nThe Results\nHow did it do? Pretty good I think:\n\nEvery single good test was less than 5 and every bad test was over 5. If you would like to try this code yourself, get a key, and then grab the code from here: https://g",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Automating Blog Post Headers with Firefly Services",
		"date":"Wed Mar 27 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711562400,
		"url":"https://www.raymondcamden.com/2024/03/27/automating-blog-post-headers-with-firefly-services",
		"content":"Yesterday I introduced you to Adobe's new offering, Firefly Services, and demonstrated a simple example of how to generate images from prompt using the REST APIs. Today I thought I'd share one of the little demos I've made with the API, and one specifically built to help out with my blog - generating headers.\nMy usual process for headers is to go to the Firefly website, enter a prompt, let it load, and then promptly change it to landscape and re-generate my prompt again. I always feel bad that the initial, square, images are essentially trashed. It occurred to me I could build a Node.js utility to generate the images at the exact right size and even quickly display them. Here's how I did it.\nFirst, I designed the CLI so I can simply pass in a prompt. Here's how I handled that:\n\nNext, I authenticate, and create my images:\n\nI showed both of these methods yesterday, but my parameters for the Firefly API to generate images are slightly tweaked though. First, the authentication method again:\n\nAnd here's the call to the text to image API:\n\nNote two things here:\n\nFirst, I set n to 4 so I get 4 results, not the default of 1.\nMy size is hard coded to the landscape size.\n\nOk, so that's the easy bit honestly. But I wanted to do something cool with the results. There is a really useful npm package called open that will open URLs and files. The result of the Firefly API call above will include 4 URLs and I could have simply opened all four of them in individual browser tabs, but I wanted one page where I could see them all, much like the Firefly website. While not directly supported by open yet, I got around it by generating a temporary HTML file locally:\n\nSo now what happens is, I run my prompt, and when it's done, I get an HTML page. Here's the result of using:\n\n\n\n\nAnd yes, I used the fourth image for this post. Here's the complete script, but you can also find it in my Firefly API repo: https://github.com/cfjedimaster/fireflyapi/tree/main/demos/makeheader\n\n",
		"tags":[
	        
            "generative ai",
            
            "adobe"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automate Generative Image APIs with Firefly Services",
		"date":"Tue Mar 26 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711476000,
		"url":"https://www.raymondcamden.com/2024/03/26/automate-generative-image-apis-with-firefly-services",
		"content":"Adobe Summit is currently happening in Vegas and while there's a lot of cool stuff being announced, I'm most excited about the launch of Firefly Services. This suite of APIs encompasses the Photoshop and Lightroom APIs I've discussed before, as well as a whole new suite of APIs for Firefly itself. Best of all, the APIs are dang easy to use. I've been building demos and samples over the past few weeks, and while I'm obviously biased, they're truly a pleasure to use. Before I go further, do know that while the docs and such are all out in the open, there isn't a free trial. Yet.\nBasics\nFirst, some quick basics that are probably assumptions, but, you know what they say about assumptions.\n\n\nAuthentication is required and consists of a client ID and secret value, much like the Photoshop API and Acrobat Services. You exchange this for an access token that can be used for subsequent calls.\n\n\nThe Firefly APIs, when working with media, require you to upload the resource to an API endpoint first. This is different from the  Photoshop API which requires cloud storage. This will be made more consistent in the future.\n\n\nResults are provided via a cloud storage URL that you can download, or use in further calls.\n\n\nThis is all done via REST calls in whatever language, or low-code platform, you wish.\n\n\nFeatures\nCurrently, the following endpoints are supported (and again, Firefly &quot;Services&quot; refers to the gen AI stuff, Photoshop, Lightroom, and more, I'm focusing on the generative stuff for this post):\n\nUpload - used to upload images that are referenced by other methods\nText to Image - what you see in the website - you take a prompt and get images. Like the website, there's a crap ton of tuning options, including using a reference image which would make use of the upload method described above.\nGenerative Expand - take an image and use AI to expand it. Basically, it expands an image with what it thinks makes sense around the existing image. Can use a prompt to help control what's added.\nGenerative Fill - same idea, but fills in an area instead. Can also take a prompt.\n\nCheck out the reference for full docs.\nDemo!\nOk, so I said it was easy, how easy is it?\nFirst, grab your credentials, in this case from the environment:\n\nSecond, exchange this for an access token. This is very similar to all the other Adobe IDs with the main exception being the scope:\n\nCool, now let's make an image. The text to image API has a lot of options, but you can get by with the minimum of just a prompt. I also want to get a bunch of options so I'll change the default limit of 1 to 4:\n\nA basic wrapper function could look like so:\n\nThis returns, if everything went well, a JSON packet containing links to the results. Here's an example where I reduced it to one result to keep the length down:\n\nAnd that's it. You then just need to write some code to download the bits:\n\nHere's one example from the prompt used above, and remember, there are numerous options I could have tweaked, and the prompt could have been more descriptive.\n\n\n\nGetting Started\nMost of the code above was taken from the excellent intro guide from the Firefly docs that includes both a Node and Python version and I'm not saying it's excellent because I wrote it. No, wait, I am. Ok, it's pretty good I think. Check it out for a complete file showing the 'prompt to image' process.\nI've also got a repo, https://github.com/cfjedimaster/fireflyapi, of demos and scripts, but keep in mind it's a bit messy in there. I've got some cool demos I'll be sharing soon.\nFinally, check out the developer homepage for Firefly Services as well!\n",
		"tags":[
	        
            "generative ai",
            
            "adobe"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Spam APIs in 2024",
		"date":"Mon Mar 25 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711389600,
		"url":"https://www.raymondcamden.com/2024/03/25/spam-apis-in-2024",
		"content":"I enjoy building API demos so I generally keep an eye out for interesting APIs to play with. A few weeks ago it occurred to me that I had not seen anyone talking about or sharing information about Spam APIs. I may be showing my age a bit, but it feels like spam was a much larger issue back in the early days. It was something you always heard about and worried about but not so much anymore. Much like nuclear war.\n\n\n\nI did a bit of digging and it turns out Chris Coyler had similar thoughts 4 years ago: &quot;Spam Detection APIs&quot;. I thought I'd check out a few myself and share the results. Here, in no particular order, are the APIs I tried.\nTest Data\nBefore I looked into any APIs, I gathered a bit of test data. I found five examples of 'good' text and five of 'bad'. I copied from emails in my inbox, spam, and my wife's as well. On the 'bad' side I avoided the over the top sexual ones for obvious reasons, but I did copy one that was a bit risque. I put these test strings in an external JS file:\n\nAfter the initialization, I then just did a bunch of copying and pasting. Below is one example from the good side, and one from the bad.\n\nHere's the entire data set if your curious:\n\n.gist {\n\toverflow: auto;\n}\n.gist .blob-wrapper.data {\n   max-height: 400px;\n   overflow: auto;\n}\n\n\nOOPSpam\nThe first API I tried was from OOPSpam. They get credit for having a free trial without needing a credit card, but had an incredibly (imo) small trial of 40 API calls. Obviously, a free trial should have limits but I was really surprised to see how small it was. In my testing I ensured I saved the results as at most, I'd be able to do 3 complete tests of the ten items. (I say 3 as I knew I'd be doing one or two individual tests before testing the entire data set.) If you do pay for their service, their pricing page shows a good set of ranges, and to their credit, if the lowest level is too much, they ask you to reach out for something customized to your needs. I've got some experience with APIs that have bad &quot;cliffs&quot; (free goes up to X, the first pay tier is some number WAY over X, and folks in the middle are kinda screwed) so that was good to see.\nI went to their docs and was happy with how easy it looked to be. Their API for spam checking only requires the content, but can additionally use the IP address and email of the person who created the content. It's a simple API, but oddly they use xhr in their JavaScript demos which hasn't been a recommended way of doing network calls in quite some time.\nThat being said, it wasn't hard to rewrite it in fetch:\n\nI built up a simple script that loaded in my test data, ran each test, and saved the result to the file system. All of my API tests used this format so I'll only share this once:\n\nGood results look like so:\n\nAnd here's a bad result:\n\nSeems very clear. Their endpoint docs show additional options that let you block disposable emails and even entire languages and countries.\nSo how did it do?\nOf the five 'good' samples, two were flagged as spam. Of the five 'bad' samples, three were correctly flagged. I wouldn't call that great, but maybe with tweaking using the optional arguments it could be better. Of course, with the tiny free trial it may be hard to test and see if it's going to work well for you though. (Since they ask folks to reach out who want something below the lowest priced tier, it may be worth reaching out for additional free trial credits too.)\nAPILayer Spam Check API\nNext up is the Spam Check API from APILayer. They've got a real generous free tier (three thousand calls a month) and then pretty cheap plans above that. Their API is rather simple - you pass the body and can optionally specify a threshold value that determines how strict the checking is.\nHere is the wrapper function I wrote for them. I'm not specifying a threshold so it defaults to 5, in the middle of the 1 to 10 range with 1 basically considering everything spam.\n\nAt the default threshold of 5, every single item was marked as not spam. At 2.3 (I picked that as their sample used it), everything was spam. Threshold 4 also marked everything as not spam. Ditto for 3.5.\nI then realized the result from the API was returning the score for the input, which is good as you can see how 'close' it is to your threshold, but every single input had the exact same result. I know my inputs aren't terribly long, but that just seems wrong. I'd probably avoid this one. Here's a sample result:\n\nI'll also note that this API was the slowest, by a wide margin, of the three I tested. It took about 4 seconds to run each test.\nAkismet\nLast but not least is the Akismet API. While focused on Wordpress, it can be used for general purposes as well. Pricing seems fair and while there isn't a real &quot;free trial&quot; or &quot;tier&quot;, you can select the Personal &quot;name your price&quot; tier and select 0. It will prompt you to confirm you will only use it on a non-commercial site though.\nTheir API can be a bit complex.",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using PDF Content with Google Gemini",
		"date":"Fri Mar 22 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1711130400,
		"url":"https://www.raymondcamden.com/2024/03/22/using-pdf-content-with-google-gemini",
		"content":"Back in February Google announced Gemini 1.5, their latest, most powerful language model, and while access has been open via AI Studio, API access has only been available in the past few days. I thought I'd try out the new model and specifically make use of the larger context window to do prompts on PDF documents. I discussed something similar earlier this year (&quot;Using AI and PDF Services to Automate Document Summaries&quot;) which made use of Diffbot, so I thought it would be interesting to build a similar experience with the Gemini API. At a high level, it's not too difficult:\n\nBegin by getting the contents of the PDF\nMake a call to Gemini with the contents and a prompt that asks about it\n\nI think the part I was most concerned about was how to combine the PDF contents and prompt in a way that would make sense over the API. I've got a working demo I can share and as always, comments are welcome. Let me know what I could do better.\nAlright, let's take a look.\nGetting PDF Contents\nThe first part is rather easy, but mostly because I've got a powerful API I can use, Adobe's PDF Extract service. (Reminder and disclaimer - I work for Adobe.) Given a PDF, I can pass it to the API and get structured JSON back that details every aspect of the document. I've talked about the API many times here before, so I think for this post I'm going to summarize the code instead of showing every line (the full code will be at the end), but the general process is:\n\nAuthenticate\nUpload the PDF\nTell the service to extract info\nSave the JSON\n\nAs I said, I want to save on space, so I'll show the code calling these steps:\n\nSince it doesn't make sense to extract the PDF more than once, I check for an existing export before calling the APIs. It's not a lengthy process, but it is complex and takes 4-5 seconds, and there's no real point in running it more than once.\nWhile the JSON can be pretty large, it's main component is an array of elements. Here is one example of that:\n\nSpecifically, this element represents text on the screen, and is going to be important for our next step.\nGather Text\nFrom the PDF contents extracted via the API, we can take the JSON and collect all the Text values from elements:\n\nDo note that I check to see if the .Text element exists before grabbing it. This approach is not the best. Specifically, I'm adding a line break after every bit of a text, and it's possible to have elements, like from a table, that should be in one line that will end up in multiple lines instead. There's actually much more 'process' that can be done to make the output from Extract better prepared for generative AI applications.\nCalling Gemini 1.5\nI've shared multiple examples of using Gemini from Node already and for the most part, 1.5 &quot;just works&quot; if you specify the right model, but there's one small issue currently. Previously, I've begun with code like so:\n\nWith the new 1.5 model, you need to pass an optional argument to specify the API version:\n\nIf you are reading this in the future, first note that I've always been Pro Robot Overlord and I'm a faithful meatbag servant, secondly, and most importantly, you can probably leave off that second argument.\nAlright, so with that out of the way, and knowing the code to use the SDK is pretty simple, let's instead focus on the prompt. I built a method that takes two arguments - the text of the PDF and the question about that text:\n\nGiven these two values, how do I craft the prompt? This is what I came up with, and it seems to work:\n\nAs I said, this seems to work, but I'm not convinced it couldn't be done better. For completeness sake, here's the entire method. Outside of how I craft the prompt, and the model, this is pretty much the same code I've shown before, which again drives home how important the prompt is in cases like this, and less so the code.\n\nThe last part of my code simply calls this:\n\nResults\nI did my testing with a rather boring Adobe security whitepaper. If you're having trouble sleeping, you can read it below.\n\n\n\nlet ADOBE_KEY = '33f07f2305444579a56b088b8ac1929e';\nif(window.location.host.indexOf('localhost') >= 0) ADOBE_KEY = ' 9861538238544ff39d37c6841344b78d';\n\nfunction displayPDF() {\nvar adobeDCView = new AdobeDC.View({clientId: ADOBE_KEY, divId: \"pdf\"});\nadobeDCView.previewFile({\ncontent:{location: {url: \"https://static.raymondcamden.com/images/2024/03/adobe_security_properly_ocr.pdf\"}},\nmetaData:{fileName: \"adobe_security_properly_ocr.pdf\"}\n}, {embedMode: \"FULL_WINDOW\"});\n}\n\nif(window.AdobeDC) displayPDF();\nelse {\ndocument.addEventListener(\"adobe_dc_view_sdk.ready\", () => displayPDF());\n}\n\nAnd while it was in the code above, the question is: &quot;What is the summary? Also, what are the three key takeaways?&quot;. Here's the result:\n\nSummary:\nThis document details Adobe's Vendor Security Review (VSR) program, which assesses the security practices of third-party vendors who handle Adobe data. The program aims to ensure that these vendors meet Adobe's security st",
		"tags":[
	        
            "generative ai",
            
            "pdf services"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Three Cool (to me) Node.js Features",
		"date":"Wed Mar 20 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1710957600,
		"url":"https://www.raymondcamden.com/2024/03/20/three-cool-to-me-nodejs-features",
		"content":"I've been using Node.js on the regular now for probably over a decade, but one thing I've never done well is keep up to date with its features and new additions. In general, my brain just thinks &quot;use javascript&quot; and that's all. The last time I really cared about what precisely was supported was when I was using fetch in client-side JavaScript and it wasn't supported natively in Node. Luckily I could just use node-fetch and be done with it.\nThat being said, I recognize I've not done a great job of keeping track of updates to Node itself so I try to notice it when folks mention it. Over the past week or so I've encountered not one, not two, but three really cool things and I figured I'd share them with yall. If you want to skip reading and watch a video, I've got you covered:\n\nWatch and Reload on Change\nThe first feature I want to share isn't necessarily new, it's been around since the fall of 2022, but as it's still marked 'experimental' you may have missed it. For years I made use of a tool, nodemon, that would run your Node.js code and on detecting a change in the filesystem, would automatically restart your code. I stopped using it as much when I moved away from using Express. Most of my Node.js code now are simple scripts - run them and check the output. Node now has this built-in via a simple command-line flag: --watch. So for example:\n\nHere's the contents of watch1.js:\n\nRunning this you get the following:\n\n\n\nIn the screenshot above you can see both the experimental warning and the blue line at the end saying it has completed running the script. If I modify the script, I get this:\n\n\n\nTwo things to note here - it cleared the entire terminal, which is useful, and gave useful messages about restarting and finishing. If my script took time to run, having that first message about restarting would be really useful.\nWhat makes me most happy about this is that I can use this for scripts, not just servers. I'm sure nodemon did that too, but I just didn't use it that way. So much of my workflow is - code, run, note the error and curse, edit, save, run - and this will help!\nYou can also watch other files as well. Imagine this script:\n\nWith the --watch-path argument, I can note changes to the file. Here's an example:\n\nNow if I edit either my JavaScript or the source file, it will automatically reload. Heck, you can even just watch the txt file if you want:\n\nMultiple --watch-path statements can be used and you can specify a directory as well.\nLoading Environment Variables\nNearly every project I work on has some kind of API key involved, and for that, I create a .env file and install dotenv. dotenv will read in the .env file and set key-value pairs to environment values. Turns out I may never use it again.\nNode 20.6.0 (September 2023) added support for the -env-file flag. So given this .env file:\nMILKSHAKE=brings all the boys to the yard\n\nAnd this one-line Node program:\n\nI can use the following command:\n\nTo be clear, the idea is that you use the .env file locally, but in production rely on &quot;proper&quot; environment variables. You never check in your .env files to source control.\nSo while that was added about half a year ago, more recently, Node v21.7.0 (released March 6), also added this:\n\nWhich does the exact same thing. You can pass a path to it, but by default it loads .env. So my previous one line program becomes:\n\nI love it. Now, I'm kinda torn as to what I'll use by default. I kinda like not having to add any code to my program to load .env, but I also worry I'll forget the command-line flag. The last couple of scripts I wrote have used process.loadEnvFile()`, but I'm thinking I may switch to the CLI flag instead. I don't know - I'll figure it out. Eventually.\nColorful Output\nContinuing the trend of &quot;I used to add an open source project for this&quot;, Node 21.7.0 also added support for coloring console output. The new styleText method from node:util lets you pass colors and styling. So for example:\n\nWill return:\n\n\n\nYou can also use colors in the background, and 'bright' variants:\n\nI'm going to switch to my Visual Studio Code terminal for this example as something in my terminal isn't showing &quot;bright&quot; - well, bright. I blame the terminal, not Node.\n\n\n\nI kept the first three lines in the screenshot above and hopefully, you can see that the red is, indeed, a bit brighter? Bolder? I don't know. It looks different in Visual Studio Code like I said, not so much in Windows Terminal.\nYou can employ multiple effects by wrapping the function:\n\n\n\n\nAgain, it may not come out terribly well on the blog here, but in my terminal, I can see a difference between the three reds.\nIf you aren't ready to upgrade to this version of Node, the excellent chalk package is what I used for this before.\nBut wait, there's more...\nObviously, I'm probably missing out on even more Node features, so let me know in a comment below what you've recently discovered that's been useful for you!\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Alpine.js Workshop this Friday",
		"date":"Tue Mar 19 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1710871200,
		"url":"https://www.raymondcamden.com/2024/03/19/alpinejs-workshop-this-friday",
		"content":"This is somewhat last minute but it just came together in the past 24 hours. This Friday, at 4 PM CST, I'll be giving a free, online workshop on Alpine.js: Web Development with Alpine.js. Thank you to the Ontario Tech University Google Developer Student Club (whew, that's a long name) for inviting me to present to them. Just hit the link to RSVP and you'll be invited.\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Mar 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1710698400,
		"url":"https://www.raymondcamden.com/2024/03/17/links-for-you",
		"content":"Good morning. I managed to tear myself away from Assasin's Creed Valhalla a bit to get out a &quot;Links For You&quot; post. I was supposed to do this last week (my schedule is every two weeks), but I must have forgotten. Or just been busy. Life happens, amiright??!?! Next month will be pretty busy as well. I've got two conferences I'll be speaking at, a birthday, and just normal life stuff as well. As a reminder, if you find these posts, and this blog handy, I've got multiple ways you can show your support. With my birthday coming up, you could always visit my Amazon wishlist, or become a patron (I'd use that to possibly invest in better analytics), or simply buy me a coffee. Anything and everything is much appreciated. :) Ok, enough begging, here are the links for this edition.\nWebShare Web Component\nI played around with the Web Share API almost a year ago now (&quot;Testing the Web Share API&quot;), and while I thought it was pretty neat, I haven't looked at it recently. Turns out, it's an excellent candidate for progressive enhancement and web components. Zach Leat built the very cool &lt;webcare-webshare&gt; component that lets you use Web Share when available but gracefully fall back to a simple link. You can play with the demo or check out the repo. (And as always, whenever I post to stuff like this, I love to hear about people using it so if you do decide to implement it, leave me a comment below.)\nQuick and Simple Typescript with Node Tutorial\nNext up is a quick tutorial for quickly getting started with TypeScript and Node.js: &quot;How to set up a Node server with TypeScript in 2024&quot;. Don't let the title confuse you though, the tutorial can be used for a Node &quot;server&quot; (i.e. something that keeps running, usually responding on HTTP) as well as a Node script that just runs and is done. I've only used TypeScript rarely, but this post was incredibly easy to understand and quick to test. Also, I learned something new. The &quot;watch&quot; command in Node can be used for scripts that aren't servers. It simply reruns the script for you. That's really useful and I wish I had known that before!\nCredit for this post goes to Jason Lengstorf.\nQuickly Mock a REST API with json-server\nFor the last link, here's a super useful CLI tool. json-server lets you set up a mock API using just a JSON file. The CLI reads the file and based on the data there, sets up multiple different API routes and features. Heck, it can even update the JSON file based on PUT and DELETE calls. It is super flexible and neat and can be great in cases where you don't want to setup a database and a proper server, just mock up some REST API calls quickly. Read more about it here: https://github.com/typicode/json-server/tree/v0\nBack in January, I did a quick video on it. Check it out below:\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Responding to HTML Changes in a Web Component",
		"date":"Wed Mar 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1710352800,
		"url":"https://www.raymondcamden.com/2024/03/13/responding-to-html-changes-in-a-web-component",
		"content":"While driving my kids to school this morning, I had an interesting thought. Is it possible for a web component to recognize, and respond, when its inner DOM contents have changed? Turns out of course it is, and the answer isn't really depenedant on web components, but is a baked-in part of the web platform, the MutationObserver. Here's what I built as a way to test it out.\nThe Initial Web Component\nI began with a simple web component that had the following simple feature - count the number of images inside it and report. So we can start with this HTML:\n\nAnd build a simple component:\n\nIt just uses querySelectorAll to count the img node inside it. For my initial HTML, this reports 3 of course.\nI then added a simple button to my HTML:\n\nAnd a bit of code:\n\nWhen run, it will add a new image, but obviously, the counter won't update. Here's a CodePen of this initial version:\n\n  See the Pen \n  Img Counter WC 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEnter - the MutationObserver\nThe MDN docs on MutationObserver are pretty good, as always. I won't repeat what's written there but the basics are:\n\nDefine what you want to observe under a DOM element - which includes the subtree, childList, and attributes\nWrite your callback\nDefine the observer based on the callback\nTie the observer to the DOM root you want to watch\n\nSo my thinking was...\n\nMove out my 'count images and update display' to a function\nAdd a mutation observer and when things change, re-run the new function\n\nMy first attempt was rather naive, but here it is in code form, not CodePen, for reasons that will be clear soon:\n\nSo the MutationObserver callback is sent information about what changed, and in my simple little mind, I figured, I don't care. If something changes, just rerun the count to count images.\nLook at that code and see if you can figure out the issue. If you can, leave me a comment below.\nSo yes, this &quot;worked&quot;, but this is what happened:\n\nI clicked the button to add a new image\nThe mutation observer fired and was like, cool, new shit to do, run renderCount\nrenderCount got the images and updated the HTML to reflect the new count\nHey guess what, renderCount changed the DOM tree, let's run the observer again\nRepeat until the heat death of the universe\n\nI had to tweak things a bit, but here's the final version, and I'll explain what I did:\n\nI initially had said I didn't care about what was in the list of items changed in the mutation observer, but I noticed that the target value was different when I specifically added my image count report. To help with this, I'm now using a div tag with an ID and renderCount modifies that.\nWhen a new image (or anything) is added directly inside the component, my target value is img-counter, or this, which means I can run renderCount on it. When renderCount runs, the target of the mutation is its own div.\nAlso, I noticed that the MutationObserver talks specifically called out the disconnect method as a way of ending the DOM observation. That feels pretty important, and web components make it easy with the disconnectedCallback method.\nAll in all, it works well now (as far as I know ;), and you can test it yourself below:\n\n  See the Pen \n  Img Counter WC 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nRemember, MutationObserver can absolutely be used outside of web components. Also note that if you only want to respond to an attribute change in a web component, that's really easy as it's baked into the spec. As always, let me know what you think, and I've got a strong feeling that someone going to show me a better way of doing this, and I'd be happy to see it!\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Intl.RelativeTimeFormat for Localized Relative Timings",
		"date":"Thu Mar 07 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1709834400,
		"url":"https://www.raymondcamden.com/2024/03/07/using-intlrelativetimeformat-for-localized-relative-timings",
		"content":"I've been singing the praises of the web platform's Intl object for years now, but it still continues to impress me. While I've seen it before, today I came across the RelativeTimeFormat API which looks absolutely fabulous. I played with it a bit and thought I'd share some tips.\nThe Basics\nThe RelativeTimeFormat API works like so:\n\nGiven a locale...\nGiven a difference (in either a positive or negative value)...\nAnd given a unit of time, like 'hour' (or 'hours')\n\nReport the difference in the user's desired language. So for example:\n\n3 and day, &quot;in 3 days&quot;\n-1 and day, &quot;one day ago&quot;\n18 and hour, &quot;in 18 hours&quot;\n\nAlso, you can specify whether or not to always have a numeric answer. This comes into play when the value of 1 is used. So for example, if you specify 1 and 'day', you can either get, &quot;in 1 day&quot;, or &quot;tomorrow&quot;.\nHere's an example in code:\n\nWill return 'in 2 days'. Simple, right? Let's look at the options, and remember the MDN doc is your best bet for a full description.\nRelativeTimeFormat takes a second optional argument for options. Those options include:\n\nlocalMatcher - which gives you more control over what locale is used\nnumberingSystem - lets you switch to different ways of representing numbers\nstyle - can be long, short, or narrow and modifies the output to, well, be shorter. long is the default, but in short you will see things like 'mo.' instead of 'month'.\nnumeric - I mentioned this above. It can be always, the default, or auto, and when set to auto you'll see stuff like &quot;next month&quot;, or &quot;yesterday&quot;.\n\nThe format method only takes two arguments:\n\nThe number, where a negative value represents the past\nThe unit, one of: 'year', 'quarter', 'month', 'week', 'day', 'minute', and 'second'. You can also use the plural forms.\n\nRelativeTimeFormat also has a few other related methods you may need I won't cover today, but check the docs for info.\nThe Simple Demo\nI wanted to play with this myself, so I built a quick demo in CodePen. As a quick aside, when I build demos in CodePen for my blog, I try to avoid using console as you can't see it in the embed. To make it easier to share the outputs with you, I used an empty div and this little bit of JavaScript:\n\nThis then lets me do stuff like log('here is the result', foo).\nWith that out of the way, here's the embed, and you can see two formatters in play - one using the defaults, and one using numeric=auto to show you the difference. Also, note that '0' is a valid value for the difference.\n\n  See the Pen \n  Intl.RelativeTimeFormat by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEasy, right?\n\n\n\nUsing RelativeTimeFormat in the Real World\nI think you can see this is an easy API to use, but how do you actually use it with, you know, real data? What I mean is this. Given two dates, what makes sense for the unit?\nFor example:\n\n3/7/2024 8:00 AM and 3/7/2024 9:20 AM: Do we use minutes or hours? I'd say hours, but I could see minutes being useful.\n3/7/2024 8:00 AM and 3/6/2023 2:00 PM: Do we use days? Do we use hours?\n\nAlso, I think the nature of your content also has a big impact on this decision. For a social media network, I think you would want as precise of a difference as possible: &quot;Ray's Cat posted 'X' one minute ago.&quot; Or heck, maybe even to the second: &quot;Ray's Cat posted 'X' 50 seconds ago.&quot;\nBut would that make sense for a blog, or press release? If you read this post an hour after I post it, I'm probably fine with the difference being reported as 'Today' versus 'One hour ago', or '50 minutes ago'.\nTo handle this, we need to first figure out what makes sense for our content, and then handle the technical aspect.\nLike most things in life, the 'solution' here will come down to...\n\n\n\nHere's one take on handling it.\nGiven two dates, in this case, a user selected date and right now, I'm going to do the following:\n\nGet the difference in milliseconds between the selected date and now.\nFigure out an appropriate unit. If the difference is less than a minute, use seconds. If less than an hour, use minutes. And so forth.\nThen, convert the difference, which was milliseconds, to the value that makes sense for the unit.\n\nWhew, got that?\nI began with this HTML:\n\nNote I'm using datetime-local as my input type so I can pick both dates and times. Now for the code.\nFirst, some constants:\n\nThen I added a change handler to the datetime field:\n\nAs you can see, I get the date, get the difference based on right now, and then call two helper functions. The first determines the unit to use:\n\nBy the way, I skipped quarter, but you could modify the code to support that.\nThen I used this function to change the difference to the right value:\n\nYou can play with the demo below:\n\n  See the Pen \n  Intl.RelativeTimeFormat by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThis works, but brings up yet another issue! Imagine today is March 2nd and I selected February 28th. You could say, rightly, that was 3 da",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Converting a Vue 2 App to Alpine.js",
		"date":"Mon Mar 04 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1709575200,
		"url":"https://www.raymondcamden.com/2024/03/04/converting-a-vue-2-app-to-alpinejs",
		"content":"A little over two years ago I published an &quot;idle clicker game&quot; built in Vue.js. I called it &quot;IdleFleet&quot; and was heavily inspired by games like A Dark Room, where I relied on simple text graphics and game mechanics that would change as you played. In my last &lt;Code&gt;&lt;Br&gt; session, I walked through the process of building a simple text game and brought up IdleFleet as an example. While playing the game to refresh my memory about what I actually built... I discovered I actually really liked it. I decided it would be good to give it some attention with new features and other updates, but before I could do that, I knew I needed to switch from Vue to Alpine.js.\nWhy Vue to Alpine?\nSo, this is mostly my opinion, and feel free to skip to the next section, but as much as I respect Vue, I don't find it as appropriate these days for simpler web pages and non-&quot;apps&quot;. I put &quot;apps&quot; in quotes because that means something different to different people. In general, when what you are building involves multiple different 'views' (a screen for X, a screen for Y), I generally consider that an app. A page with JavaScript for interactivity is simpler and Vue feels like overkill there. Alpine really fits the spot for these needs and that's part of the reason I've been so enamored of it the last year or so.\nAlso, and this is really now just an opinion, I kind of feel like Vue has lost some of its approachability it had in the older days. It's absolutely powerful, performant, and so forth, but I'm just finding myself a lot more comfortable with Alpine.\nOk, enough opinions, let's get into the process.\nThe Previous Code\nBefore I get started, you can browse the Vue version of the repository here: https://github.com/cfjedimaster/IdleFleet/tree/820f1bea20a33b6f9248ebdc687f9ce7c93235bf. My changes primarily revolve around two files: index.html and app.js (although I made a small change in app.css as well).\nLibrary Change\nThe first change was the easiest, and resulted in hundreds of awesome console errors - swapping out the Vue CDN (&lt;script src=&quot;https://unpkg.com/vue@2.6.14/dist/vue.js&quot;&gt;&lt;/script&gt;) for Alpine (&lt;script src=&quot;https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js&quot; defer&gt;&lt;/script&gt;).\nCloak Change\nBoth Vue and Alpine support the idea of a 'cloak' that will hide all the content of your application until the application is loaded. In Vue, you add v-cloak to your top-level container for your application and then add CSS to hide it. In Alpine, you just change this to x-cloak and rename the directive in CSS:\n\nNoting the App Container\nSpeaking of the app container, in a simple Vue app you could mark the 'area' where it would work with an ID and specify it in your Vue code with el:&quot;name of area&quot;, so for example, this in HTML:\n\nAnd this in the JavaScript:\n\nIn Alpine, you specify it in your HTML:\n\nAnd this is then referenced in JavaScript:\n\nFilter Removal\nFilters were removed in Vue 3, so I removed it and some config stuff as well:\n\nThe filter just called a function (numberFormat) defined later in the code. If your are curious, that function simply makes use of the awesome Intl API:\n\nMain Application Updating\nIn the previous version, the Vue app defined variables in a data key, had a list of functions in methods, and computed values in, well, computed. Basically the new Vue... part wrapped an object where data, methods, and computed functions were defined in separate blocks, also the init function.\nWhen you define an Alpine application, you also define a top-level object, but there's no separation, you just provide a list of key/value pairs where each value can be simple reactive data or a function. You can mix this up as much as you want.\nThat being said, I try to organize my Alpine applications by putting data on top, and then methods. I don't usually worry about splitting up computed methods versus regular methods, but due to the size of this application, I did.\nSo I start off with variables:\n\nThen have my init:\n\nThen a set of methods, where I generally tried to use alphabetical sorting, with the exception of heartBeat as it's a pretty core method to the game.\nComputed methods in Alpine are written as getters, but you don't have to specify the get keyword. I like doing so though as it makes it more obvious. Here's an example of two of them:\n\nJust like Vue, Alpine will notice when data referenced in these methods are updated and rerun their logic for display.\nThis Scope binding\nOne issue I ran into that I've seen before in Alpine, was ensuring my this scope was properly referenced. So for example, in Vue, I had this in my start-up code:\n\nIn Alpine, when doAutoShip ran it lost access to the this scope variables. I tweaked them all like so:\n\nHTML Updates - Variables\nIn Vue, you can add references to variables with brackets, so for example:\n\nAlpine requires you to use x-text or x-html, so I switched these to:\n\nIt's a bit more verbose ",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "TIL - submit() versus requestSubmit()",
		"date":"Fri Mar 01 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1709316000,
		"url":"https://www.raymondcamden.com/2024/03/01/til-submit-versus-requestsubmit",
		"content":"Today I learned (well, technically, a few days ago, the week has been a lot), that the web platform supports a requestSubmit method. Since the beginning of time (or the beginning of JavaScript), we've been able to submit forms like so:\n\nI intentionally used getElementById there as a reminder of what we had before jQuery. Given that, why do we need another method? Two important requests.\nReason the First\nWhen using submit, any validation of form fields is completely skipped. Consider this form:\n\nI've got two fields, both required, with the second field using type email. If you hit submit, the form will stop itself from POSTing and show errors, but if you submit with JavaScript, that validation is completely ignored.\nI added two more buttons to my HTML:\n\nAnd wrote some quick JavaScript to demo this:\n\nClicking the first button immediately shows that validation is ignored. Clicking either the main submit button in the form or the tester button shows validation working.\n\n\n\nReason the Second\nNot only is validation ignored with submit(), any submit handler on the form itself is completely ignored. I added this:\n\nAnd again, submit() ignores it and requestSubmit() runs it fine. I'm mostly sure I remember this aspect of submit(), but it's definitely been a while since I've thought about it.\nAnyway, everyone loves the web platform. (Except Apple.) Here's a CodePen showing this in action if you want to see for yourself. (Which is 100% why most of my blog posts exist.)\n\n  See the Pen \n  requestSubmit test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Multiple Variations of Generative AI Prompts",
		"date":"Mon Feb 26 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708970400,
		"url":"https://www.raymondcamden.com/2024/02/26/testing-multiple-variations-of-generative-ai-prompts",
		"content":"In nearly all of my recent explorations of Generative AI, I've come to realize how important prompts are. That hasn't necessarily translated me into writing better prompts all the time, but being aware of the problem is at least one step towards correcting it, right? One of the things that I thought would help me personally would be to have a tool to quickly compare and contrast different prompts. Obviously, I could just open up multiple tabs in a browser, but I really wanted something more direct and simplified for my purposes. With that in mind, let me share what I've built.\nPrompt Tester\nPrompt Tester is my incredibly imaginatively named tool to... um... test prompts. It begins with one prompt:\n\n\n\nThe idea is that you enter your initial prompt, and then click one or more times to add more prompts. When ready, you can then just click &quot;Generate Results&quot;:\n\n\n\nBefore showing some example results, let me talk a bit about how it was built.\nThe Code\nThe backend is an incredibly simple Node.js server that handles throwing up the static HTML and responding to the front end API requests. Once again, I thank Todd Sharp for sharing his initial code with me. Here's the boring simple handling portion:\n\nAnd the callGemini part, which just passes the prompt off to Google's Gemini API. In theory, this could be changed to ChatGPT or any other service.\n\nI'm proud to say that I'm now, finally, doing some basic error handling and trying to expose to the user (me) why a prompt was blocked.\nThe front end is relatively simple. I used Milligram for the core CSS (again, thank you Todd) and some vanilla JavaScript to handle adding new prompts and making the calls. Here's the HTML (minus the inline CSS):\n\nAnd then the JavaScript. Notice I used &lt;template&gt; above to handle the prompt layout. This is the first time I've used that tag and it was relatively easy.\n\nI think that's all pretty simple DOM manipulation and API calling, but definitely let me know in a comment below if something doesn't make sense. Notice I fire off all my API calls at once and Promise.all wait for them to finish.\nAlso, note I use the marked library. The results from Google typically have Markdown and this helps render them nicely in my little web app.\nAll of this isn't terribly interesting I think, but let's look at some testing. As I said, I knew the style of prompt was important. Having it presented directly to me in a tool went a heck of a long way to reinforcing that.\nSample Result - Gravity\nLet's start with a simple idea - explaining gravity, but for different age groups. Because of the large size of the results, I'm going to use a bit of CSS here to help keep things sane.\n\n\nPrompt: explain gravity to me but make it appropriate for a child in 1st grade\n\nGravity is a superpower that our planet has! It's like an invisible force that makes everything on Earth stay on the ground.\nImagine you have a ball in your hand. When you drop it, what happens? It falls down, right? That's because of gravity. Gravity pulls the ball towards the center of the Earth, and so it falls to the ground.\nGravity is also what keeps us on the ground. It's what keeps our feet planted firmly on the Earth and prevents us from floating away!\nGravity is a very important force. It keeps everything in place and helps things work the way they do. Without gravity, we would all be floating around in space, and things would be very chaotic!\nHere's a fun way to think about gravity: imagine that the Earth is a giant magnet, and everything on Earth is made of metal. The magnet pulls the metal objects towards it, and that's what keeps them on the ground.\nGravity is a very important and interesting force. It's one of the things that makes our planet such a unique and special place to live!\n\n\n\nPrompt: explain gravity to me but make it appropriate for a child in 7th grade\n\nImagine you're playing with a ball. You toss it up in the air, and it comes back down to you. This is because of gravity. Gravity is a force that pulls things toward each other. It's what keeps you on the ground and what makes things fall down.\nThe amount of gravity between two objects depends on their mass. The more mass an object has, the stronger its gravity is. That's why things like planets and stars have more gravity than things like people and cars.\nGravity is also what keeps the planets in orbit around the sun. The sun's gravity is much stronger than the Earth's, so it pulls the Earth towards it. This keeps the Earth moving in a circle around the sun.\nGravity is a very important force in our universe. It keeps things in place and makes things move. Without gravity, the planets would fly off into space, and we would all float away.\nHere are some fun facts about gravity:\n\nGravity is responsible for the tides. The moon's gravity pulls on Earth's oceans, causing them to rise and fall.\nGravity is the reason why we have an atmosphere. Without gravity, the air would just float away into space.\nGravity is what makes it",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Feb 25 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708884000,
		"url":"https://www.raymondcamden.com/2024/02/25/links-for-you",
		"content":"Good morning programs. Before I get started with the three links I want to share with yall, some personal notes.\n\nTomorrow I'm giving a free online presentation at the Adobe ColdFusion Online Summit where I'll be talking about how to use Adobe Acrobat PDF Services with ColdFusion. Join me and see how easy the APIs are,or, join me and laugh at how bad my ColdFusion skills have gotten.\nI had my second &lt;Code&gt;&lt;Br&gt; episode last week and it went great (imo). I walked through building a simple to play web-based game based on Paper, Rocks, Scissors. You can watch the recording here: https://youtu.be/15SgRdJPdoE\nI decided to give another ad network a try, ethicalads.io. I saw it on another site and loved how lightweight and simple they were. I'm not sure how well it will work, but if it offsets my only cost (the newsletter), then it's a win. As a reminder, I'm looking for sponsors for this blog, and if you or your company would like to do so, just reach out!\n\n\nsparkly-text for making the web more... sparkly\nFor my first link, I've got a fun little web component called sparkly-text. As you can imagine, this creates a completely unnecessary but very cute and fun &quot;sparkle&quot; effect on your text with a simple wrapper:\n\nCheck out the blog post/demo for an example, and thank you to Stefan Judis for sharing the cuteness!\nYou don't know JSON\nOk, maybe I don't know JSON, or surely not as well as I did. This incredibly detailed and deep look at JSON, Judicious JSON\n, covers about 900 things I didn't quite know about the spec, it's use, and so forth. Absolutely worth your time even if, like me, you've been using JSON for over a decade and figured you had a good handle on it.\nThank you to the fine folks at Socket for sharing.\nCentering Divs - But it's Awesome...\nI've said this before and I'll say it again. Josh W Comeau produces some of the best technical content I've seen in my entire career. Each post contains numerous interactive examples that really help drive the point home. I don't care what the topic is - if I see one of his articles, I'm going to it, because I'll come away more knowledgeable and inspired to improve my technical writing/presenting skills.\nIn his latest piece, he presents a deep dive into how to center a div. No, not with the &lt;center&gt; tag (yes, it really was a thing in the past), but rather with CSS and loads and loads of examples.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding a Guestbook to Your Jamstack Site (Yes, Seriously)",
		"date":"Thu Feb 22 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708624800,
		"url":"https://www.raymondcamden.com/2024/02/22/adding-a-guestbook-to-your-jamstack-site-yes-seriously",
		"content":"Don't do this. I'm serious. Or do it, I certainly don't listen to reason when it comes to building demos. I've been in web development for a very, very long time, and I've seen many trends come and go. Guestbooks were a way for folks to leave a comment on your site as a whole. I haven't seen one in ages, but some still linger. In fact, Ana Rodrigues has an absolutely lovely guestbook driven by Webmentions. And if you really want to, you can still download a Perl CGI guestbook over at Matt's Script Archive. I haven't written Perl in decades, but I absolutely loved it back in the 90s. That being said, I had a free hour yesterday, was bored, and decided, why not do something fun? The result - my new guestbook that you can visit today. Here's how you too can (but don't) add a guestbook to your Jamstack site. (My example is in Eleventy, but uses nothing specific to Eleventy.)\nThe Database\nFor my data, I decided to store information in a Google Sheet. That's a pretty lame database, but it worked easily enough. I set up a Google Sheet with four columns: Name, Comment, Date, and Approved. Name and Comment should be self-evident, but Date is a 'time since epoch' numerical value and Approved is TRUE or FALSE.\nGetting Guestbook Entries\nTo retrieve guestbook entries, I built a Pipedream workflow with the following steps:\n\nThe trigger is an HTTP trigger so I can call it via JavaScript. I'll be showing that later.\nThe next step is a built-in Google Sheet action to read data where I specified my spreadsheet, the sheet name, and a range. In my code, A2:D10000. If my guestbook gets over 10k entries, it's time to move to a real database.\nNext, I wrote a code function to do two things - filter out unapproved entries and map the result to a more readable format. By default, the result of getting my data in the previous step is a 2D array. Mapping the result makes it easier to use:\n\n\n\nThe final step just returns the data:\n\n\nYou can see the result of this yourself here: https://eoxzk4xd3lr6trv.m.pipedream.net/.\nAdding Guestbook Entries\nTo add a guestbook entry, I created another Pipedream workflow. It's also HTTP triggered of course, and does the following:\n\n\nEdited at 1:47PM I just added a new step to validate that the name and comment field was sent. Duh, I should have done that initially. If they are not passed, the workflow ends.\n\n\nFirst, it uses another built-in Pipedream action that adds rows to Google Sheets. I look for the name and comment value in the body of the HTTP trigger, set Date automatically, and Approved to false. For the most part, this just worked, but check out what I do with Name and Comment:\n\n\n\n\n\nFor both of the user-submitted content, I strip out any and all HTML. This is a safety measure to ensure nothing naughty gets in. I've already set it up such that it's set to not being approved by default, but this extra step ensures I don't have to manually clean input.\n\nNext for the fun part. I need to know when someone adds an entry, so to do that, I'm going to send me email. The email will include the name and comment, and a method to approve the entry. I'll share the code then explain more:\n\n\nThe beginning just outputs the simple values. For approval, I'm referencing the third and final workflow I'll show next. I used an environment variable for that because I want to keep the URL secret, and my Pipedream workflow is tied to a public GitHub repository so I can share stuff. Obviously, that was my choice and I could have used a private repo. To handle knowing what to approve, I used the result of the previous step that added the data. The updatedRange value looks like so: Sheet1!A9:D9.\n\nThe final step just uses the built-in Pipedream step to email me. Here's an example:\n\n\n\n\nApproving Guestbook Entries\nThe third and final Pipedream workflow is another HTTP-driven workflow with just two steps (ignoring the trigger):\n\nThe first step uses another built-in Pipedream action to update Google Sheets data. For the Cell, I specify this: {{steps.trigger.event.query.range.split(':').pop()}}. If you remember, I passed the updated cell range via query string in the email, so if I split on the colon, I get the final cell (the Approved column) and can then set it to true.\n\n\n\n\n\nThe final step simply redirects me (as I'll be the one clicking) to the guestbook:\n\n\nThe Guestbook\nOk, so far I've shown the serverless functions built on Pipedream to support the workflow. Now let's look at how it's rendered. You can visit the guestbook now, but if you'd rather not open a tab, here it is in all its glory:\n\n\n\nGiven that we're building on the Jamstack, we've got multiple different ways to build this.\n\nOne would be entirely static. In Eleventy, I could use a data file to fetch the entries and just render them at build time. As I'm the one who approves entries, I could manually kick off a build when I do, or even update my Pipedream workflow to call Netlify and request a build. For adding entries, I'd either use JavaScript, or a serverless ",
		"tags":[
	        
            "eleventy",
            
            "pipedream"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Using Generative AI to Organize Video Game Screenshots",
		"date":"Mon Feb 19 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708365600,
		"url":"https://www.raymondcamden.com/2024/02/19/using-generative-ai-to-organize-video-game-screenshots",
		"content":"Way back in January (remember January), I wrote a blog post describing how to use gen ai to improve image filenames. This worked by uploading the image to Google Gemini, asking for a short description, and using that description for a new filename. Recently I was thinking about that demo and was curious how well it would work for video games.\nAs always, I did a few quick tests in Google AI Studio. I did some quick Googling for various games and screenshots, and the results were pretty impressive. Here are three mostly modern examples:\n\n\n\n\n\n\nAnd here's a first failure, identifying this as Final Fantasy 14, not 16.\n\n\n\nIt did well for one really old game, although to be fair the name is in the picture:\n\n\n\nAlthough failed on this rather obscure one. It's Bard's Tale 2, not Betrayal at Krondor.\n\n\n\nAlso, note it didn't follow my directions to answer with just the name of the game.\nI then tried a few race games. I was really curious about this as with the high fidelity of modern racing games, it feels like it would be a difficult task. Surprisingly, it got them right:\n\n\n\n\n\n\nI then went super old school and obscure and it failed, but it was worth a shot. (The first person to identify it without using reverse image search earns 200 Nerd Points.)\n\n\n\nSo all in all... it worked reasonably well. So let's talk automation!\nAutomating the Process\nNearly two years ago, I blogged about copying Nintendo Switch screenshots to Dropbox. This made use of the fact that the Switch can post to Twitter and you can use Pipedream to scrape media from a Twitter account. As far as I know, that probably doesn't work since Twitter crapped the bed on developer access. While it did work, the images I got had random names for images.\n\n\n\nThe XBox can upload to OneDrive and actually does name the images, which is super helpful.\n\n\n\nIn case you can't read it, the filename is Diablo IV-2024_02_19-1514-24.png. It's not only got the game in the screenshot, but the date and time as well. For the purposes of this blog post, I'm going to ignore that but in a real application, I'd probably just handle the XBox images with a bit of custom code, no AI needed.\nPlaystation is a bit more wonky and a bit surprising. As far as I can tell, you can share videos on YouTube, but screenshots can only be added to the PS app or copied to a USB drive. Again, surprising.\nHere's a screenshot showing how the images are named, and as you can see, nothing relevant is included.\n\n\n\nFor the purpose of this, let's make some assumptions (nothing ever goes wrong with that, right?). We will assume that our screenshots all get stored in a Dropbox folder named, SSIn.\nI'm going to build a Pipedream workflow that will:\n\nTrigger on a new file added to SSIn.\nDownload the image.\nUse Gemini AI to determine the game.\nUse the name as a new folder in Dropbox, under SSOut, So for example, /SSOut/Galaga\nUpload the image and auto rename it based on the time. This lets us know when the screenshot was - well not taken, but at least added to Dropbox.\n\nLet's break it down:\nStep One - The Trigger\nThis part is relatively simple in Pipedream. I created a trigger based on the Dropbox action for new files. I specified the path and told it to include a link so it could be downloaded.\n\n\n\nStep Two - Download the File\nThe next step is another built-in Pipedream action, downloading a file. In this case, it gets downloaded to /tmp with the original filename.\n\n\n\nStep Three - Use AI to Determine the Game\nThe next step is a Node.js step that essentially takes the code output from AI Studio and has it work with the file in /tmp:\n\nThe only really important part here is the first argument to identifyPic, the path. Note that the path is the second result value from the download step. This definitely surprised me.\nStep Four - Create a Folder\nThe next step was also a 'built-in', creating the folder. Now, I should warn you. I'm pretty sure this step is going to throw an error if run twice with the same game. The action's configuration lets you pick a new name if it already exists, but doesn't have a &quot;don't make it if already exists&quot; parameter. I didn't get a chance to test that, but, if it is an issue, I'd simply switch to the Pipedream feature that lets you hit any Dropbox API with the right credentials, check the Dropbox API docs, and see if it's possible there, or heck, just wrap in a try/catch.\nOutside of that, note that I added a trim() to the result from Google as it had a space in front.\n\n\n\nAlso note that sometimes Gemini will return a sentence, not just a game name. For example, &quot;The game is X&quot;. I figure that will be pretty obvious in the output and a human can handle that.\nStep Five - Upload\nThe final step is to upload the picture to the new output directory. To get the name to be date based, I used a pretty long expression:\n\n\n\nDid it work? Sure did! I mean it's brittle as heck, but here's the output from a few runs. First, top-level folder:\n\n\n\nAnd here's the contents of the Valhalla one:\n",
		"tags":[
	        
            "generative ai",
            
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Google Gemini as Your Dungeon Master",
		"date":"Fri Feb 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708106400,
		"url":"https://www.raymondcamden.com/2024/02/16/google-gemini-as-your-dungeon-master",
		"content":"So this is absolutely just another example of me playing around too much, but I had to share. As I mentioned in my post yesterday, Google's AI Studio now supports uploading files and working with them in your prompt. Today I decided to give the Chat interface a try as I hadn't yet played with it. On a whim, I googled for &quot;dungeons and dragons rules PDF&quot; and... well, you won't believe what happened next. (Sorry, I couldn't resist.)\nFirst off, the most important thing to note if you want to test with PDFs, ensure that they are OCRed. Right now AI Studio does not handle that well, but it should be corrected in the future. My Google search turned up the PDF here, https://media.wizards.com/2018/dnd/downloads/DnD_BasicRules_2018.pdf, and I confirmed it was properly OCRed with a very scientific test of trying to select a few words. Note that it's from 2018 and probably a bit out of date, but I figured it was good enough.\nI uploaded the PDF to Gemini and ensured I was using Gemini 1.5. This is important as the size of the PDF (180 pages) means a rather large token size. The UI reports this nicely:\n\n\n\nFor my first test, I simply asked it &quot;what are the attributes of a player character?&quot; Here's what I got:\n\nIn Dungeons & Dragons, player characters (PCs) are defined by several key attributes:\n\n\nRace: This determines your character's physical characteristics, ancestry, and innate abilities. Examples include humans, elves, dwarves, halflings, dragonborn, tieflings, etc. Each race has unique traits that affect gameplay.\n\n\nClass: This represents your character's chosen profession and determines their skills, abilities, and progression path. Examples include fighter, wizard, rogue, cleric, bard, etc. Each class has unique features and abilities that define their role in the party.\n\n\nAbility Scores: These six scores represent your character's core physical and mental capabilities: Strength, Dexterity, Constitution, Intelligence, Wisdom, and Charisma. They influence almost every action your character takes, from attacking and defending to interacting with the world and casting spells.\n\n\nSkills: These represent specific areas of expertise your character has developed through training or experience. Examples include Athletics, Acrobatics, Stealth, Perception, Persuasion, Arcana, etc. Skills are tied to ability scores and determine your character's success in various tasks.\n\n\nBackground: This details your character's history and past experiences before becoming an adventurer. Examples include soldier, criminal, noble, sage, etc. Backgrounds provide additional skills, proficiencies, and roleplaying hooks.\n\n\nEquipment: This includes weapons, armor, tools, and other items your character carries. Equipment affects your character's capabilities in combat and exploration.\n\n\nPersonality and Alignment: These define your character's moral compass, beliefs, and behavior. Alignment is a combination of two factors: morality (good, evil, or neutral) and attitude towards society and order (lawful, chaotic, or neutral).\n\n\nBackstory: This is the narrative of your character's life before the game begins, including their motivations, goals, and relationships. A well-developed backstory adds depth and helps you roleplay your character.\n\n\nThese attributes work together to create a unique and well-rounded character that you can roleplay and guide through adventures in the D&amp;D world.\n\nI'm pretty rusty in D&amp;D, but that surely seems right! (When I shared this in a chat related to Gemini, William Ito skimmed it said it looks correct for that era - so I'm taking that as another sign it worked well.)\nI then asked, &quot;what races are available to player characters?&quot;\n\nThe most common player character races in Dungeons & Dragons are:\n\nHumans: Adaptable and versatile, with no major strengths or weaknesses.\nElves: Graceful and long-lived, with a natural affinity for magic and a resistance to charm effects. Subraces include High Elves, Wood Elves, and Dark Elves (Drow).\nDwarves: Stout and hardy, with a resistance to magic and poison, and proficiency in combat and craftsmanship. Subraces include Hill Dwarves and Mountain Dwarves.\nHalflings: Nimble and small, with a natural ability to hide and a resistance to fear. Subraces include Lightfoot Halflings and Stout Halflings.\n\nThe Player's Handbook also introduces several less common races, including:\n\nDragonborn: Proud and draconic humanoids with scales and the ability to breathe fire.\nTieflings: Demonic descendants with a natural affinity for magic and resistance to fire.\nGnomes: Inventive and curious small creatures with a natural ability to tinker and a resistance to magic.\nHalf-elves: Combining human and elven traits, they are versatile and have a resistance to charm effects.\nHalf-orcs: Combining human and orc traits, they are strong and fierce with a resistance to being knocked unconscious.\n\nAdditionally, some campaign settings and sourcebooks introduce even more exotic races,",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Google Gemini 1.5 Announced (and more new features)",
		"date":"Thu Feb 15 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1708020000,
		"url":"https://www.raymondcamden.com/2024/02/15/google-gemini-15-announced-but-not-yet-released",
		"content":"In general I don't tend to blog about stuff that isn't quite out yet, but as I've got early access (and permission to share), and as it's pretty darn cool, I thought I'd share. Plus, some of the new stuff is available to everyone, so you can try it out as well! Today, Google introduced its newest language model, Gemini 1.5. You can, and probably should, read the marketing/nicely polished intro by Google here, but I thought I'd share some highlights and examples here. I've had access to this for a grand total of four hours so please consider this my first initial impressions. As the title says, this is not yet released, but you can sign up for the waitlist now. Also, right now this is AI Studio only and not available via API.\nWhile the introductory post covers everything that was added, there are a few changes I'd like to specifically call out.\nFile handling, and Video Support\nProbably the biggest change is the ability to handle files and folders. Video support is only available in the new model so it's not publicly available yet, but files and folders can be used by anyone now. This is available within Studio right at the top:\n\n\n\nNote that files and videos can not be drag/dropped into the interface and you have to use those buttons on top. But once you do, you can then start writing prompts. I dropped a PDF copy of Hamlet and asked for a summarization as well as the themes:\n\n\n\nI haven't read Hamlet since college but honestly, I liked what it output. By the way, the output was in Markdown and I've copied it as is here so the markup came from them. Oh, um, spoiler warning for a four-hundred-year-old play.\n\n## Major Plot Points of Hamlet:\nAct 1:\n\nThe ghost of Hamlet's father appears, revealing he was murdered by Claudius, Hamlet's uncle.\nHamlet vows revenge but begins to feign madness to investigate the truth.\nPolonius believes Hamlet's madness stems from unrequited love for Ophelia.\n\nAct 2:\n\nHamlet tests Claudius's guilt by staging a play mirroring the murder. Claudius reacts suspiciously.\nHamlet confronts his mother about her hasty marriage to Claudius.\nPolonius spies on Hamlet and is accidentally killed.\n\nAct 3:\n\nClaudius, fearing for his life, sends Hamlet to England with Rosencrantz and Guildenstern, secretly plotting his death.\nOphelia, driven mad by grief, drowns.\nHamlet learns of Claudius's plot and returns to Denmark.\n\nAct 4:\n\nLaertes, Ophelia's brother, seeks revenge for his father and sister's deaths.\nClaudius manipulates Laertes into a duel with Hamlet, planning to kill Hamlet with a poisoned sword and drink.\n\nAct 5:\n\nDuring the duel, Gertrude drinks the poisoned wine meant for Hamlet and dies.\nLaertes and Hamlet are both fatally wounded by the poisoned sword.\nHamlet kills Claudius before dying.\nFortinbras, Prince of Norway, arrives and takes the throne of Denmark.\n\nThemes:\n\nRevenge: The central theme, driving the plot and exploring its complexities and consequences.\nJustice: The play questions how to achieve justice for wrongful acts and the morality of revenge.\nMortality: Characters grapple with death, grief, and the afterlife.\nMadness: Hamlet's feigned and Ophelia's genuine madness raise questions about sanity and reality.\nBetrayal: The play explores various forms of betrayal, including familial, political, and personal.\nPower: The corrupting influence of power and its impact on relationships and morality.\nAppearance vs. Reality: Characters engage in deception and manipulation, blurring the lines between truth and illusion.\nFamily: The play examines complex family dynamics and the destructive effects of betrayal and revenge within families.\n\n\nLast week I had blogged about using GenAI as your content assistant. With that in mind, I tried uploading a Markdown file right from my blog (this post) and asked for a summary. Here's a screenshot, just so you can see how files are rendered - note that the token cost of the file is included:\n\n\n\nAnd here's the summary:\n\nThis article explores the concept of \"temperature\" in generative AI and demonstrates its impact on the variety and creativity of generated text. \nThe author:\n\nExplains the concept of temperature: Higher temperature values lead to more varied and unexpected responses, while lower values result in more predictable and consistent outputs.\nCreates a temperature testing tool: This tool allows users to input a prompt and generate multiple responses at different temperature settings, providing a visual representation of how temperature affects the output.\nShares the results of the tool: By comparing the generated stories at different temperatures, the author showcases how higher temperatures lead to more creative and diverse narratives, while lower temperatures produce more focused and conventional stories.\n\nOverall, the article provides a practical and insightful exploration of temperature settings in generative AI, helping readers understand how to adjust this parameter to achieve their desired level of creativity and variety in generated text.\n\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Temperature Settings with Generative AI",
		"date":"Wed Feb 14 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707933600,
		"url":"https://www.raymondcamden.com/2024/02/14/testing-temperature-settings-with-generative-ai",
		"content":"So far most of my playing with Generative AI has been on the prompt side, with no real look at the various settings you can tweak in your calls. As I'm still very new to this, I'm trying my best to take things slowly. But every time I open Google AI Studio and see the settings on the right, I keep nagging myself to take a deeper look.\n\n\n\nYou can also see the various settings options in the default code Studio spits out:\n\nI thought today I'd take a look at temperature, as I had a very rough idea of what it implied. Temperature, at a high level, refers to how 'varied' a particular generated response can be. As an example, if I were to say, &quot;I like ____&quot;, then you could make a list of what the next word would be, rated by how likely it is:\n\nCats (VERY LIKELY)\nStar Wars (LIKELY)\nBooks (LIKELY)\nVegetables (UNLIKELY)\n\nAt the highest temperature value, there will be a lot of variety. At the lowest level, no variety. Now, from what I know, that does not mean that every response will be the same. Just very similar. This post, &quot;Creatively Deterministic: What are Temperature and Top_P in Generative AI?&quot;, does a great job explaining temperature (and other settings).\nWhile it's relatively easy in AI Studio to adjust the temperature setting you see above, what I really wanted was a tool to help me see multiple results at once. So I built one. :)\nTemperature Testing Tool\nMy tool begins with a simple prompt:\n\n\n\nThis is pretty vanilla HTML so I won't share the code here (I've got a link to everything down below). Upon hitting the button, that's where the magic begins. Here's the JavaScript:\n\nI begin with some variable declarations and a listener for DOMContentLoaded. In the variable sections, the first two are the crucial ones. Temperature values go from 0 (focus on only the highest probable response) to 1 (be more creative) and I decided to test four values in that range.\nFor each range in the array of temperatures, I do two tests. My thinking was that two results would be enough to see the variance in one particular temperature value.\nCarrying on...\n\nIn this code, I loop over the range of temperatures and then make my two sample calls. When done, I render it out to the screen.\nThe last function simply passes my prompt and temperature to the backend API. I'll share a screenshot but it's a long wall of text, this is only a small portion:\n\n\n\nThe backend is a lightweight Node.js server that basically handles outputting my HTML and responding to the POST request by the client-side JavaScript. Let's take a look at that, specifically the portion handling the Google Gemini call:\n\nThis is pretty much the exact code spit out by Studio, but wrapped into a function where the prompt and temperature can be passed in. This function has no error handling and that absolutely bit me in the butt. I'm going to be taking a look at those errors, and how to handle them, in a future blog post.\nSo what did I find?\nResults\nHere's the results of using the prompt: &quot;Tell me a story about cats.&quot;\nFirst, temperature 0.\nResult 1\n\nOnce upon a time, in a quaint little town nestled amidst rolling hills, there lived a mischievous cat named Mittens. Mittens was known throughout the neighborhood for her playful antics and her insatiable curiosity. One sunny afternoon, as Mittens was basking in the warmth of the sunbeam that streamed through the window, she noticed a peculiar sound coming from the kitchen. Intrigued, she hopped off the window sill and tiptoed towards the source of the noise. Upon entering the kitchen, Mittens discovered a group of kittens huddled around a bowl of milk. Their tiny paws were eagerly reaching for the sweet liquid, and their eyes sparkled with delight. Mittens, being the adventurous cat she was, couldn't resist joining in on the fun. With a playful leap, Mittens landed gracefully in the midst of the kittens. She purred loudly, her tail swishing back and forth, as she rubbed against their soft fur. The kittens, initially startled by the sudden intrusion, soon warmed up to Mittens' friendly presence. Together, they formed a circle around the bowl of milk, their whiskers twitching as they lapped up the delicious treat. Mittens, being the eldest and most experienced cat, took it upon herself to teach the kittens proper table manners. She showed them how to take turns and how to avoid spilling the milk. As the kittens learned and grew, they developed a deep bond with Mittens. They looked up to her as their mentor and protector. Mittens, in turn, cherished her role as the leader of the pack. She spent her days playing with the kittens, teaching them valuable life lessons, and keeping them safe from harm. Word of Mittens' extraordinary kindness and wisdom spread throughout the town. People would often bring their cats to her, seeking her advice and guidance. Mittens welcomed each and every cat with open paws, offering them comfort, support, and a sense of belonging. And so, Mittens became known as the \"Guardi",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Come Build/Play a Game with Me Next Week",
		"date":"Tue Feb 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707847200,
		"url":"https://www.raymondcamden.com/2024/02/13/come-buildplay-a-game-with-me-next-week",
		"content":"Next week I'll be hosting my second episode of the &lt;Code&gt;&lt;Br&gt; show, &quot;Shall we play a game?&quot; In this session I'm going to talk about, and build, a simple web-based game. An hour is not a lot of time to do this so it will be a bit of a race, but I think this could be really fun. Check it out next Tuesday at 12PM CST.\np.s. As &lt;Code&gt;&lt;Br&gt; continues, I'm not sure if I'll blog about every episode, but I definitely want to ensure folks are kept up to date so for now, I apologize for the noise.\np.p.s. Wait, this is my blog, I can do whatever I want. Enjoy the noise!\n\n\n\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Looking at the JavaScript Promise Collection Methods",
		"date":"Mon Feb 12 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707760800,
		"url":"https://www.raymondcamden.com/2024/02/12/looking-at-the-javascript-promise-collection-methods",
		"content":"Let me begin by saying that &quot;Promise Collection Methods&quot; is not something I've seen mentioned elsewhere, but is my own way of referring to the various methods of the Promise API that work with multiple promises. They are:\n\nPromise.all\nPromise.allSettled\nPromise.any\nPromise.race\n\nI've used Promise.all many times in the past, and I was aware of the other methods but had not taken the time to actually build a demo of them. This weekend I changed that. After spending a few hours in Sanctuary grinding my Necro character, I put down the controller and picked up the laptop. Here's what I built. As a note, everything shown here works in modern browsers, but you can check MDN for more information on compatibility if you need.\nThe Helper Functions\nBefore getting into the important code, I built some methods to help me test things out, help me display stuff, and so forth. First, I knew I was going to build this on CodePen (I'll be embedding it below) and I wanted something would visually display in the browser, so I added this HTML:\n\nAnd wrote this little utility:\n\nThis lets me then use log(&quot;like, whatever&quot;) in my code and have it rendered out to the browser instead of the developer console.\nNext, a function to handle returning promises with different times and optionally in an error state:\n\nHere's a few examples of it in use:\n\nThis will create two promises. One resolves in three seconds, the other in 1. Next:\n\nThis will reject as an error in two seconds.\nAlso, note that I use the log method here so I can see stuff resolving in real-time. This will become important later.\nFinally, I wrote a simple delay function:\n\nThis will let me stuff like:\n\nAs a quick and hacky way to delay a few seconds. In theory, I could have made makePromise make use of it, but I didn't bother updating it. Alright, let's get started.\nPromise.all\nThe all method has the following behavior:\nGiven an array of Promises, resolve when all are done, or immediately when any throw an error.\nIt resolves with an array of results that match the results of inputs. Here's my first test:\n\nLooking at the code, you can see that bob will return before alpha and charly. Here's the output:\nGood result for bob\nGood result for alpha\nGood result for charly\nResults from test with Promise.all\n[&quot;Resolve from alpha&quot;,&quot;Resolve from bob&quot;,&quot;Resolve from charly&quot;]\n\nNotice that even though the order was different than the input, the results match the input which is great.\nNow let's throw an error into the mix:\n\nI wrapped the call in a try/catch to handle the rejection. Here's the output:\nTEST TWO  - Promise.all (one bad)\nBad result for failwhale\nExpected failure in test: Fail from failwhale\nGood result for alpha\nGood result for charly\n\nNotice that my handler fires as soon as the error occurs, but the other promises are still running. You don't have to use try/catch. Since the result of Promise.all is itself a promise, you can use the catch method of Promise instead:\nlog('\\n\\nTEST TWO A - Promise.all (one bad), modified handler.');\t\nPromise.all(test).then(results =&gt; log('all good')).catch(e =&gt; log('one bad'));\n\nThis returns, as you would expect, just one bad.\nPromise.allSettled\nWhile Promise.all is good for when you're pretty sure everything is going to work out ok (and remember, a Fetch call to an API may run just fine, but the API itself may return with an error), you may find [allSettled] method a bit more flexible. It's behavior is:\nGiven an array of Promises, resolve when all are done and report on the success or failure of each Promise.\nThis means you can now rely on knowing when everything is done and handle the success or failure of each one by one. Here's an example:\n\nThe result now is a bit different:\n\nNow we get both the value from the resolve as well as a status flag. Here's an example with a failure. First the calls:\n\nAnd then the result:\n\nYou can see the failure in the second result. Woot. I think this is my favorite so far. Going on...\nPromise.any\nThe any method works like so:\nGiven an array of Promises, resolve when any of the Promises resolves, or reject if all of them fail.\nThis one's kind of interesting. It's basically the &quot;try really hard for something to work&quot; collection method. Here's a first example:\n\nIn this one, bob is the winner:\nTEST FIVE - Promise.any(all good)\nGood result for bob\nResults from test with Promise.any\nResolve from bob\nGood result for alpha\nGood result for charly\n\nNext, here's one with an error. It's the quickest, but any keeps trying:\n\nAnd the output:\nTEST SIX - Promise.any(one bad)\nBad result for bad bob\nGood result for alpha\nResults from test with Promise.any\nResolve from alpha\nGood result for charly\n\nFinally, here's one where they all fail.\n\nNotice I log e.errors - this is an additional value thrown by the method that contains an array of all the messages from the failed promises. (It's an AggregateError).\nHere's the output:\nTEST SEVEN - Promise.any(all bad)\nBad result fo",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Feb 11 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707674400,
		"url":"https://www.raymondcamden.com/2024/02/11/links-for-you",
		"content":"Happy Super Bowl Day for those who celebrate, oh wait, sorry, I mean &quot;The Big Game&quot;. I'm looking forward to both the game and the commercials this year, and I managed to avoid every single commercial preview so they should all be new to me. This week was incredibly hard at work (with the caveat that I sit on my rear, in a home office, and I'm very lucky), but difficult in that way like when you exercise and muscles you haven't used in a while hurt. I complained to my wife more than once, but also recognized I was improving some skills that needed it.\nBefore getting into the links, a reminder. If you want to sponsor these posts (twice a month) or the email newsletter in general, drop me a line. I no longer use any kind of advertising here but would welcome a sponsor to help with the (pretty minimal) costs. Just let me know!\nMaze Maker\nI love mazes and have built some in the past (check out this ColdFusion maze generator post from 2009), and I was very excited to see the release of Labyrinthos, which generates mazes, terrains, and biomes. Check out the GitHub repo for detailed docs, but here's a simple example of generating a maze:\n\nYou can see this running in the CodePen below:\n\n  See the Pen \n  Maze with Labyrinthos by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nCSS One Liners\nNext up is an incredibly good post by Stephanie Eckles on 12 CSS one-line upgrades that you can start using today. I knew a grand total of one of these features so this post was very useful to me. I wanted to point one one in particular I thought was handy but there were too many of them!\nThat's One Big PDF\nOk, this last one is more of &quot;just for fun&quot; post but as I work with PDFs at my day job, I found it really interesting. Is there such a thing as a &quot;max size&quot; for a PDF? I mean, technically we're talking about an electronic document, but they do have a rendered height and width and in theory, there could be a limit, perhaps defined in the PDF spec. (No, I haven't read it.) Alex Chan did an exhaustive look into this and like I said, the results were incredible. Check it out here: Making a PDF that’s larger than Germany\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using the Bluesky API",
		"date":"Fri Feb 09 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707501600,
		"url":"https://www.raymondcamden.com/2024/02/09/using-the-bluesky-api",
		"content":"Social media has always been... addicting and kinda gross/dangerous/etc, but lately, I'm not even sure what to think anymore. As I mentioned in my first episode of &lt;Code&gt;&lt;Br&gt;, I segregate all of my social media to the Firefox browser so it doesn't get in the way of my regular, work/research/etc browsing. I currently have tabs open for Threads, Bluesky, Mastodon, and, yeah, Twitter still.\nI find Threads to be really good for news. It works well as a replacement for Twitter for that type of content. Mastodon works really well for technical content. Bluesky is a bit more unusual for me. I've considered dropping it at times, but then I'll see some great content there. I honestly don't know if I'll be using it a year from now, but for now it's still got a place in Firefox.\nAll that being said, yesterday an account posted a link to the Bluesky community showcase which then led me to the core docs and I was really surprised at how simple their API was. Here's their initial example of just connecting:\n\nAnd here's an example of posting:\n\nThat's super simple. Obviously, there's a lot more you can do with the API, but if you want to build a simple bot, all you need to do is create the account and write roughly ten lines of code, at least for the Bluesky integration portion. Whatever logic you have for the bot's content is another matter.\nNow, I will say that I did run into a problem early on. Bluesky's docs clearly say &quot;Typescript&quot; on top of their code sample, but when I looked at the code, nothing struck me as code that wouldn't work in regular Node. However, when I tried running it, I got:\nSyntaxError: Named export 'BskyAgent' not found. The requested module \n'@atproto/api' is a CommonJS module, which may not support all \nmodule.exports as named exports.\n\nThe error message continues to showing how to rewrite it, but I want to give a shout-out to Giao Phan of Pipedream for helping me as well. Here's the fix:\n\nCool. So how am I going to use this? I already have a bot I really love on Mastodon called Random Comic Book. This bot uses Pipedream and the Marvel Comics API to post random comic book covers from the history of Marvel. I love seeing these comic covers.\nGiven that I already had all of the logic done (you can read about that logic in my post from way back in 2017), all I needed to do was add a step to my Pipedream workflow to post to Bluesky after it posted to Mastodon.\nAs it isn't terribly long, I'll share the entire thing, and then point out the important bits.\n\nThere are two main changes here from the simple script I shared earlier. First, to use images with a Bluesky post, you need to upload the bits. My Pipedream workflow saved the Marvel image to /tmp/cover.jpg for use by Mastodon and Bluesky can use it as well:\n\nThis was simply enough and is documented well at Bluesky.\nThe next part was a bit weirder. In one of my first tests, I saw that the link to the Marvel record for the comic wasn't being turned into a real link:\n\n\n\nAgain, the Bluesky docs discuss this, I just didn't expect it to be an issue. That's where the RichText portion comes in.\n\nYou then pass the rt object in with your post:\n\nAs I said, that felt weird, but didn't take long to figure out. And now that I know, it won't be an issue for next time.\nHere's an example with everything working:\n\n\n\nAnd if you want to follow the bot (assuming you have a Bluesky account), you can find it here: https://bsky.app/profile/randomcomicbook.bsky.social\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "<Code><Br> - First Episode",
		"date":"Tue Feb 06 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1707242400,
		"url":"https://www.raymondcamden.com/2024/02/06/codebr-first-episode",
		"content":"Just a quick note to share that today I did my first episode of the &lt;Code&gt;&lt;Br&gt; show. I had a great audience with some great questions and comments. My next show will be in two weeks and I hope to see you there!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Generative AI as Your Content Assistant",
		"date":"Fri Feb 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1706896800,
		"url":"https://www.raymondcamden.com/2024/02/02/using-generative-ai-as-your-content-assistant",
		"content":"Last week I had the honor of presenting one at TheJam.dev. This was my first presentation on generative AI and I got to share what I thought was an interesting use case - helping with the writing process.\nNow to be clear, I don't mean using GenAI to write blog posts, that would be a horrible idea. (IMO!) Instead, I looked at how it could help with some of the process. Let me back up a bit and give some background.\nI've been a fan of John Birmingham for many years now. He's an author who writes in the military/sci-fi/etc genre and has some pretty fascinating ideas. I initially discovered him via his &quot;Axis of Time&quot; trilogy which dealt with the idea of a modern international naval fleet being sent back in time to 1942. Now, that by itself is cool, however, I loved that he didn't just focus on the military aspect, but spent a lot of time talking about the culture clash between the &quot;uptimers&quot; (folks from the future) and the contemporaries. I suppose you could say it was a bit like Tom Clancy but not just focused on the action. I'd pretty much recommend any of his books and if you've read him already, let me know in a comment below.\nAs a follower of his work, I subscribed to his Patreon and it's been really interesting. He shares drafts of chapters from upcoming works, but more importantly, he also talks about his process quite a bit. As a writer myself, I find this really fascinating.\nRecently, he was talking about his own use of GenAI, and discussed how he's using it from more of a 'framework' perspective. Ie, how to bring in a character's motive at the right time, and how to set up plot points. This is still 'creative' work but more ... I don't know. Management of the work?\nAs I said though, I thought it was really interesting and it got me thinking. How could I use GenAI on my blog as a way to help with the writing process? Here's what I came up with.\nAs a quick aside, everything I discuss below makes use of Google's Gemini API and Eleventy, but would certainly be useful elsewhere.\nSuggesting Titles\nThe first demo I built involved helping me come up with titles for blog posts. Now, I typically don't struggle with this, but I was curious if GenAI could perhaps suggest alternatives for better titles.\nI began by testing out a prompt:\n\nGiven the following title for a blog post, share three suggestions that may improve the title and drive traffic to the post:  \"SOME TITLE\". Present your answer in JSON form. The top level key of the JSON result should be \"suggestions\" and each suggestion should use the key \"title\" for the suggested title and \"reasoning\" for the reasoning.\n\nYou'll notice that I specifically ask for three suggestions and say I want to help drive more traffic. Now, I'll be honest. That feels a bit gross and spammy. I don't necessarily want clickbait titles. That being said, I wanted to see some other ideas for my titles.\nThat prompt seemed to work well with a few tests in AI Studio, so I jumped into code. I took the code Google exported, and then wrote a bit of code to:\n\nLet me pass a filename in via the command line\nParse the front matter to get the title\nThen call the GenAI endpoint.\n\nHere's the entire script:\n\nAnd how does it work? This blog post is titled: &quot;Using Generative AI as Your Content Assistant&quot;\nHere's what Gemini suggested:\nSuggested Title: The Power of Generative AI: Your New Content Assistant\n\nReasoning: This title emphasizes the strength and potential of generative AI as a content creation tool, capturing the reader's attention and making them curious about the possibilities it offers.\n\n------------------------------------------------------------------------\nSuggested Title: Unlock Your Content Creation Potential with Generative AI\n\nReasoning: This title suggests that generative AI can unlock new levels of productivity and creativity in content creation, attracting readers who are looking for ways to enhance their content strategy.\n\n------------------------------------------------------------------------\nSuggested Title: Revolutionize Your Content Creation: Meet Your Generative AI Assistant\n\nReasoning: This title highlights the transformative potential of generative AI in content creation, appealing to readers who are eager to adopt cutting-edge technologies to streamline their workflow.\n\nYeah... I think that's pretty cool. However, none of these really feel like &quot;my&quot; voice for here. I absolutely see some merit in the suggestions and it gives me something to think about, but obviously, I left the original title as is.\nWriting the Description (Summary)\nFor my next demo, I looked at a part of my writing process I really don't enjoy, writing the one-sentence description value used in my front matter. This string ends up going into my &lt;meta name=&quot;description&quot;&gt; tag and isn't used anywhere else.\nI thought this would be an excellent use of GenAI's summarization feature. I began with a prompt like so:\n\nGiven the following blog post, write",
		"tags":[
	        
            "generative ai",
            
            "eleventy"
            
		],
		"categories":[
            
                "writing"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jan 28 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1706464800,
		"url":"https://www.raymondcamden.com/2024/01/28/links-for-you",
		"content":"Good morning, friends. I'm enjoying a lazy Sunday morning before I head out to my first (in-person) conference of the year, THAT Conference Texas. I'll be speaking on web components and can't wait to see the other great sessions as well. If you're a reader and will be there, please tell me hello!\nSimpler Node File Handling with FSX\nI've made use of Node's fs package for years, and while it's not terribly difficult, Nicholas C. Zakas has come up with an interesting design for a more modern filesystem API, FSX: &quot;Introducing fsx: A modern filesystem API for JavaScript&quot;. While the name is most likely going to change, you can check out the project here: https://github.com/humanwhocodes/fsx\nAs just one example of what he proposes, here's reading a JSON file:\n\nHe mentions it 'returns a JSON value' but I assume he means, a value parsed as JSON into regular data. (As a JSON string is just a string.)\nDeep Promise Education\nPromises are an incredibly important part of JavaScript, and something I cover in my &quot;A Beginner's Guide to Wrangling Asynchronicity in JavaScript&quot; presentation. To help developers get a better understanding of how Promises work, Henrique Inonhe has created an excellent learning tool that contains learning exercises a developer can do at their own pace to truly ground themselves in understanding the specification. Head over to the promises-training repository and carefully read the instructions on how to get started using the tutorials. From what I can see, this looks to be really intensive and could be really useful. If any of yall have already given this a shot, please leave a comment below as I'd love to hear about your experiences.\nEnding in Beauty\nFor the last link I'll share today, I won't give any explanation at all, just click for beauty at the drawing.garden. Turn your speakers up a bit as well.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Generative AI to Improve Image Filenames",
		"date":"Fri Jan 26 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1706292000,
		"url":"https://www.raymondcamden.com/2024/01/26/using-generative-ai-to-improve-image-filenames",
		"content":"Last night I had an interesting thought. Many times I work with images that have vague filenames. For example, screenshot_1_24_12_23.jpg. Given that there are many APIs out there that can look at an image and provide a summary, what if we could use that to provide a better file name based on the content of the image? Here's what I was able to find.\nAs always, I began by prototyping in Google AI Studio. I apologize for stating this in basically every post on the topic, but I really want to stress how useful that is for development.\nI used a very simple prompt:\nWrite a one sentence short summary of this image. The sentence \nshould be no more than five words.\n\nAnd then did a quick test:\n\n\n\nIf it's a bit hard to read in the screenshot, the result was:\nA Mardi Gras king cake.\n\nWhich is absolutely perfect. So, I took the code from this prompt and whipped up the following script:\n\nThe main part is the getImageSummary function which is a modified version of the code AI Studio output. As you can see, I do kinda assume .jpg only, which isn't great, and I don't handle errors at all, but for a quick test, it's fine.\nAfter the function, I basically list the files from a source directory, get the summary, and use the excellent @sindresorhus/slugify package to translate the sentence into a slug for use in renaming. Now, one thing to keep in mind is that if you run this script multiple times, you will get multiple results with slightly different summaries. You could wipe the output directory before running perhaps.\nSo how well did it work?\nSample Images\nI tested with these five images. Below each image you can see their original filename and how they were renamed.\n\n\n20240107_120237.jpg to an-nfl-game-between-the-saints-and-the-falcons.jpg\n\n\n\n20240108_152420.jpg to cat-interrupts-work-day.jpg\n\n\n\n20240113_193655.jpg to a-small-cake-with-yellow-frosting.jpg\n\n\n\n20240120_170218.jpg to cat-sits-in-a-box.jpg\n\n\n\n20240122_184642.jpg to a-mardi-gras-king-cake.jpg\n\nHonestly, these feel perfect. I will say the &quot;cat-interrupts-work-day&quot; one is... perhaps a bit more funny than practical. I think that would be a fair criticism, but in general, this worked incredibly well.\nIf you want the full source code with the images as well, you can grab it here: https://github.com/cfjedimaster/ai-testingzone/tree/main/rename_images\nLet me know what you think in the comments below.\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Google Gemini and ColdFusion",
		"date":"Tue Jan 23 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1706032800,
		"url":"https://www.raymondcamden.com/2024/01/23/working-with-google-gemini-and-coldfusion",
		"content":"Most of my recent work with generative AI has been with Google Gemini lately as I find it really simple to use. With most of the complexity being on the prompt side, I appreciate that the code doesn't get in the way. I thought it would be interesting to see how difficult it would be to get the API running in ColdFusion, and unsurprisingly, it was pretty simple. Here's how I got it working.\nGetting the Code\nAs with pretty much every single post I've done about GenAI, I started in AI Studio. For my demo, I wanted to re-use a prompt I built for an earlier blog post, &quot;Texting Email Summaries using Google PaLM AI and Twilio&quot;. Basically:\nSummarize the following text into two to three sentences: \n\nIn AI Studio, I entered that prompt like so:\n\n\n\nI then clicked &quot;Get code&quot;. This provides options for JavaScript, Python, Android, Swift, and cURL. I selected cURL as I figured this would give me the basic HTTP call I'd need to recreate in ColdFusion. Here's what it generated.\n#!/bin/bash\n\nAPI_KEY=&quot;YOUR_API_KEY&quot;\n\ncurl \\\n  -X POST https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${API_KEY} \\\n  -H 'Content-Type: application/json' \\\n  -d @&lt;(echo '{\n  &quot;contents&quot;: [\n    {\n      &quot;parts&quot;: [\n        {\n          &quot;text&quot;: &quot;Summarize the following text into two to three sentences:\\n\\nTEXT\\n&quot;\n        }\n      ]\n    }\n  ],\n  &quot;generationConfig&quot;: {\n    &quot;temperature&quot;: 0.9,\n    &quot;topK&quot;: 1,\n    &quot;topP&quot;: 1,\n    &quot;maxOutputTokens&quot;: 2048,\n    &quot;stopSequences&quot;: []\n  },\n  &quot;safetySettings&quot;: [\n    {\n      &quot;category&quot;: &quot;HARM_CATEGORY_HARASSMENT&quot;,\n      &quot;threshold&quot;: &quot;BLOCK_MEDIUM_AND_ABOVE&quot;\n    },\n    {\n      &quot;category&quot;: &quot;HARM_CATEGORY_HATE_SPEECH&quot;,\n      &quot;threshold&quot;: &quot;BLOCK_MEDIUM_AND_ABOVE&quot;\n    },\n    {\n      &quot;category&quot;: &quot;HARM_CATEGORY_SEXUALLY_EXPLICIT&quot;,\n      &quot;threshold&quot;: &quot;BLOCK_MEDIUM_AND_ABOVE&quot;\n    },\n    {\n      &quot;category&quot;: &quot;HARM_CATEGORY_DANGEROUS_CONTENT&quot;,\n      &quot;threshold&quot;: &quot;BLOCK_MEDIUM_AND_ABOVE&quot;\n    }\n  ]\n}')\n\nBasically, an endpoint that needs my key and a body where all I need to care about is the text property.\nThe ColdFusion Version\nIn ColdFusion, that's just one cfhttp call. Here's my first version with a hard-coded string to summarize. In this case, an email I got from Disney+ about how awesome they are that I should be giving them more money.\n\nHere's the result, which is probably a bit too small to read.\n\n\n\nThe important part is the result within candidates. Here is the text value:\nThe price of your Disney+ subscription will increase to $139.99 per year on \nNovember 12, 2023, but you'll continue to enjoy 12 months for the price of \n10. Explore plan options or visit your Account Settings to manage your \nsubscription, including updating payment or changing your plan.\n\nWoot. So given that this first example is static, let's make it a bit more dynamic. I'll convert the Gemini part into a function, and then pass some dynamic text to it (well, static, but you get the idea):\n\nHere's the result:\n\n\n\nI'll remind folks my CFML skills are probably pretty rusty and I'd probably include this in a CFC, not just a function, but you get the idea. Let me know what you think and leave your comments below!\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "My New Show - <Code><Br>",
		"date":"Mon Jan 22 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1705946400,
		"url":"https://www.raymondcamden.com/2024/01/22/my-new-show-codebr",
		"content":"For a while now I've been kicking around the idea of doing a live stream. I've done a good job this past year of working on my YouTube channel and I've enjoyed getting (slightly) better with that process. In general, though, my Youtube videos tend to &quot;how to do X&quot; type short walkthroughs. I like that, but I've wanted to give &quot;live building&quot; a try as I thought it would be fun to build stuff out in front of an audience. That's not something I can do in a presentation (typically) but could do in a live show. I considered Twitch, but in the end, reached out to Certified Fresh Events about hosting a show there instead. I've presented at CFE many times and think it's a great platform, and I'm pretty darn good friends with the owner which makes it a more friendly option for me.\nWith all that said, I'm happy to announce the launch of &lt;Code&gt;&lt;Br&gt; - or, Code Break, starting February 6th at 12 PM CST: https://cfe.dev/talkshow/code-break/\nThis show will (generally) follow a schedule of the 1st and 3rd Tuesday of each month. And as I said, the general idea is that each session I'll build something live in front of yall, but for this first session I'll be doing something a bit different and talking (and showing) my tech stack. I.e., what tools/programs/websites/etc I use as part of my day-to-day.\nPlease show up and use this opportunity to heckle me from the comfort of your office chair. Also, note I'll be posting these to YouTube as well so if you miss it, you can always catch the replay.\nFinally, why not use the comments section below to leave me some ideas of things you would like to see me cover? I'm definitely open to your suggestions!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using GenAI to Classify an Image as a Photo, Screenshot, or Meme",
		"date":"Thu Jan 18 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1705600800,
		"url":"https://www.raymondcamden.com/2024/01/18/using-genai-to-classify-an-image-as-a-photo-screenshot-or-meme",
		"content":"File this under the &quot;I wasn't sure if it would work and it did&quot; category. Recently, a friend on Facebook wondered if there was some way to take a collection of photos and figure out which were 'real' photos versus memes. I thought it could possibly be a good exercise for GenAI and decided to take a shot at it. As usual, I opened up Google's AI Studio and did a few initial tests:\n\n\n\nI then simply removed that image and pasted more info to test. From what I could see, it worked well enough. I then took the source code from AI Studio and began working.\nThe Code\nFirst, I grabbed some pictures from my collection, eleven of them, and tried to get a few photos, memes, and screenshots. To make it easier for me, after downloading them I renamed them so it would be quicker to see if it worked right. As I mentioned above, AI Studio gave me the code, but I modified it slightly so I could pass a directory of images:\n\nIt worked perfectly!\n\n\n\nIf you want a copy of the source, you can grab it here: https://github.com/cfjedimaster/ai-testingzone/tree/main/detect_meme_ss\nThe Photos\nOk, technically you can just head over to the GitHub repo to see these, but here are the source images. First, the 'regular' photos:\n\n\n\n\n\n\n\n\n\n\n\n\nNext, the screenshots:\n\n\n\n\n\n\n\n\n\nAnd finally, the memes. Enjoy.\n\n\n\n\n\n\n\n\n\n\n\n\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "You Should Attend theJam.dev 2024!",
		"date":"Wed Jan 17 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1705514400,
		"url":"https://www.raymondcamden.com/2024/01/17/you-should-attend-thejamdev",
		"content":"Next week, a very cool, and very free, online event is being held by the fine folks at Certified Fresh Events, theJam.dev 2024. This is a two-day online conference covering web development, AI, serverless, frameworks, and certainly more than just the Jamstack.\nSpeakers include Cassidy Williams, Alex Russel, Zach Leatherman (creator of Eleventy!), Rizèl Scarlett, and more. I'll be giving a quick lightning talk on generative AI and writing, and you can see the full schedule on the website.\nDid I mention it was free? Did I mention it was online? You've got no reason to miss this, so check it out!\n",
		"tags":[
	        
            "jamstack"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Drag/Drop in Alpine.js with PDF Embed",
		"date":"Tue Jan 16 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1705428000,
		"url":"https://www.raymondcamden.com/2024/01/16/using-dragdrop-in-alpinejs-with-pdf-embed",
		"content":"Drag and drop support in JavaScript is probably two to three hundred years old now (plus or minus a few years), but I use it rarely enough such that when I need it, I run over to MDN's article on it as a quick refresher. I thought it might be fun to combine the web's drag and drop support with Adobe's PDF Embed library. Here's what I built.\nSupport Drag and Drop\nLet's begin by just handling drag and drop, not worrying yet about PDF rendering. I began by adding two events to my core Alpine.js div:\n\nYou'll notice both a drop event and dragover. Why both? From MDN:\n\nBy default, the browser prevents anything from happening when dropping something onto most HTML elements. To change that behavior so that an element becomes a drop zone or is droppable, the element must have both ondragover and ondrop event handler attributes.\n\nWe only care about the drop, so dragover just does nothing in this case. Also, note both use .prevent to prevent the default handling of those events by the browser.\nIn JavaScript, the drop event will have access to the file that was dropped, and with this, we can check for a PDF:\n\nEssentially - look for a file (a user could drag and drop multiple) and check the type property to ensure it's a PDF.\nAnd that's it. So how do we get the PDF rendered?\nAdding PDF Embed\nI've talked about PDF Embed many times here, but if you've never seen it before, you can take a quick look at the Getting Started guide for a review. Basically, include a JavaScript library, figure out what div element will hold the PDF, and then use a few lines of JavaScript to initialize, point to the PDF, and render. Here's a sample of how this would look:\n\nThat's vanilla JavaScript, but how do we use this in Alpine?\nFirst off, notice how the default code listens for an event, adobe_dc_view_sdk.ready. This event is fired when the library is loaded and ready to go. However, you can also check for window.AdobeDC as well. The best solution is to use both - listen for the event and check the window variable.\nIn Alpine, I used a variable, pdfAPIReady, set to false, and in my init, checked for the window variable:\n\nThat handles the case of, &quot;The Embed library was ready before Alpine even got started&quot;. To handle it not being ready, we can tell Alpine to listen for the adobe_dc_view_sdk.ready event. This brings up two issues:\n\nIt's a document event\nIt's an event with a dot in the name\n\nLuckily, Alpine supports that with two directives: .dot.document. Here's how it looks:\n\nNotice I changed the dot in the event to a dash. When Alpine sees the .dot directive, it understands what I really want to listen for. It's absolutely a bit 'wordy', but it works.\nCool. So next I added some UI. This UI will only show up when I'm ready to render events so it makes use of x-show:\n\nNow I can return back to my JavaScript code that handles the drop:\n\nAfter I've checked to ensure I've got a PDF, I use a FileReader object to read the data of the dropped file. This is async and the onloadend handler can then take the final result and pass it to the next function:\n\nThis is a small modification of the default code from the docs, with the big change being using a promise instead of a URL for the source. And that's really all there is.\nYou can find the complete code below, but you may want to open it up on CodePen to get a bit more 'space' to actually see a PDF.\n\n  See the Pen \n  Drag drop to PDF View by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "alpinejs",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jan 13 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1705168800,
		"url":"https://www.raymondcamden.com/2024/01/13/links-for-you",
		"content":"Hello friends. This week has been absolute hell as I've been sick since Wednesday. I'm on the mend, but it's taking its sweet ole time moving out of my system. I feel like I can barely put two words together but I somehow managed to do some work, give and record a presentation, and... I think that's it actually. On a happier note, it's my wife's birthday and she's awesome, so that makes the day pretty dang good. Let's get on with the links!\nHuge list of deep debugging tricks\nThis came out a little while ago but I never got around to sharing it. The list of 67 debugging tricks is a fascinating collection of browser debugging tips. I thought I had a pretty good understanding of all the ways you could debug with devtools but wow, this post really blew my mind. Credit for this collection goes to Alan Norbaur.\nIntroduction to Text Embeddings\nAnyone looking at, or curious about GenAI, has heard about text embeddings. It's a fairly complex topic, and &quot;An intuitive introduction to text embeddings&quot; does a good job of trying to make it simpler. I'll be honest, I understood, kinda, about half of what was discussed, but it was definitely worth my time and at least helped improve my understanding.\nThe End of Jamstack\nOr is it? Zach Leatherman shares his thoughts in &quot;The Tension and Future of Jamstack&quot;. There's definitely been a shift in the community over the past few months and with Netlify seemingly moving on from the term... I kinda don't care. I view my tools, Eleventy, static sites in general, to be a powerful approach that's not going away anytime soon. It was never a golden bullet for all situations, but I continue to think &quot;static first&quot; before thinking about a full-on app server.\nRelated to this, the CloudCannon folks are doing a great set of interviews that included yours truly:\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using AI and PDF Services to Automate Document Summaries",
		"date":"Mon Jan 08 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1704736800,
		"url":"https://www.raymondcamden.com/2024/01/08/using-ai-and-pdf-services-to-automate-document-summaries",
		"content":"I first discovered Diffbot way back in 2021 when I built a demo of their APIs for the Adobe Developer blog (&quot;Natural Language Processing, Adobe PDF Extract, and Deep PDF Intelligence&quot;). At that time, I was impressed with how easy Diffbot's API was and also how quickly it responded. I had not looked at their API in a while, but a few days ago they announced new support for summarizing text. I thought this would be a great thing to combine with the Adobe PDF Extract API. Here's what I found.\nFirst off, if you want to try this yourself, you'll need:\n\nAdobe PDF Services credentials. These are free and you get 500 transactions per month for free. For folks who may not know, I work for Adobe and this is one of the products I cover.\nDiffbot credentials. They provide a free two-week trial but no free tier. That being said, I've had to reach out to them a few times when building stuff and they've provided really great support for me so I definitely think they're worth you checking out.\n\nAlright, let's look at how a summary flow might work.\nStep One - Extract the Text\nThe Extract API (sorry, the &quot;Adobe PDF Extract API&quot;, wait, this is my blog, I can shorten things!) is pretty powerful. It uses AI to intelligently parse a PDF to correctly find each and every element detail in the document. So text, fonts, colors, position, and so forth. It can also find images and tabular data as well which leads to some pretty powerful use cases. (For a good example of this, see my blog post where I scan multiple scientific journals to collect and aggregate astronomical data and create reports.)\nFor this demo, we literally just need the text. For that, I'll make use of the REST APIs. The &quot;flow&quot; for nearly all aspects of the PDF services available are:\n\nExchange credentials for an access token\nAsk to upload a file for input (in this case, a PDF to be extracted)\nUpload the document\nKick off the job\nPoll for completion\nDownload the bits\n\nNote that there are also SDKs you can use, but I've found our REST APIs so simple I just hit the endpoints directly. Here's the script I wrote to do the Extract process. It's basically everything I said above and pointing to a source PDF in my local filesystem.\n\nOk, hopefully, you're still reading. In general, I try to avoid posting giant blocks of code like that, but if you focus on the lines at the end, you'll see I'm just hitting utility functions that do what I described in the flow above. Authenticate, ask to upload a PDF, kick off a job, check it, and download the result.\nOne note I'll add. Extract returns a zip file containing a JSON result set, and optionally, tables and images. One nice thing about the REST API is that I can get directly to the JSON and just store it.\nThe JSON result can be quite huge. For my source PDF (an incredibly boring Adobe security document) of three pages, the resulting JSON is 4560 lines long. You can find my source PDF here and the raw output from Extract here. Instead of putting all 4.5k lines here, let me show a snippet - two unique elements found by the API:\n\nIn the sample above, you can see the first element is textual, and contains a Text property, while the second one is a figure. For my demo, I just need to use the Text property when it exists. Let's see that in action.\nStep Two - Create the Summary\nI mentioned earlier that the Diffbot API was fairly simple to use. Let me demonstrate that.\nFirst, I'll set up some variables and read in the JSON I got from the first step. To be clear, I could do everything in one process, but there's really no point in running Extract more than once. What's cool is - I could actually do multiple calls on the result. As an example, one other cool feature Diffbot has is to get entities from text, i.e., what a document is speaking about (people, places, etc). Anyway, here's the beginning:\n\nNext, I need to parse out the text from the Extract result:\n\nNext, I craft an HTTP request to Diffbot. Check their docs for more information.\n\nAnd that's it. As a final step, I simply output it:\n\nGiven the source PDF, the final result is:\nAdobe has a vendor security review program that evaluates vendors that collect, store, process, \ntransmit, or dispose of Adobe data outside of Adobe-controlled physical offices or data center \nlocations. The VSR program includes requirements for vendors to follow when handling sensitive \ndata and assigns a risk level score to vendors based on their compliance with Adobe standards. \nIf a vendor fails the VSR program, Adobe holds discussions with the business owner to understand \nthe details of the vendor's security practices and determine whether or not to continue working with them.\n\nMy three-page PDF is now one simple paragraph. You can imagine how useful this would be for organizations with millions of documents. Combine this with other services (like the entities feature I mentioned previously) and it makes working with large libraries that much easier.\nTry It!\nIf you want to chec",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Function Calling and GenAI",
		"date":"Wed Jan 03 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1704304800,
		"url":"https://www.raymondcamden.com/2024/01/03/function-calling-and-genai",
		"content":"I love when I work on one demo, hit an issue, discover something else and get joyfully distracted into learning something completely different. In this case, it was a suggestion to help with an issue I was having with output from a prompt, and while it wasn't a good solution for what I was doing, it was an eye-opening look at a really cool feature of Generative AI - Function Calling.\nNow, I'm new to GenAI, and new to this particular feature having been introduced to it a bit less than twenty-four hours ago. I know it's supported by Google AI APIs as well as OpenAI and a quick search around other offerings seems to imply it's a universal thing.\nI want to give a quick shout-out to Allen Firstenberg who helped me wrap my head around this a bit. Any misunderstanding is on me though, not him.\nWhat is Function Calling?\nTypically in a GenAI application, you issue a prompt and get a response in text, or an image if you're using something like Firefly.\nFunction calling is a bit different. Instead of returning text, the idea is to return the intent of your prompt mapped to a particular function.\nThat probably doesn't make sense, but I think a good way of thinking about it is how Alexa works. Now, I haven't done any Alexa development in a couple of years, but it was incredibly cool. When building an Alexa skill, you define the various &quot;intents&quot; that should be supported by it.\nConsider a coffee store. When you go in, you probably only want to do one of two things:\n\nAsk for a menu (i.e. what kinds of coffee do you have)\nMake an order\n\nWhen building your Alexa skill, you define those two intents, and define what arguments they may take. The first one, &quot;what's on the menu&quot; would take none, but the second one, &quot;I'd like to order an espresso&quot;, would have an argument for the product.\nYou would define a few sample prompts (utterances in Alexa development), and from there, Alexa was smart enough to map random human input. So for example, I could say:\n\nI need a damn coffee, please.\nI want an espresso if you don't mind.\nI'd like to buy a double frap mocha latte with unicorn sprinkles and magic.\n\nAlexa would map all of these to one intent - making an order. Even better, it would then determine the product:\n\ncoffee\nespresso\ndouble frap mocha latte with unicorn sprinkles and magic\n\nAnd finally, here's the cool bit. While Alexa did all the work of figuring this out, it would pass to your code something like this:\n\nThis worked really well (at least when I last used it) and kinda maps to what function calling does.\nOk, seriously, what is it?\nAlright, so given the above, for our GenAI application, we can consider this feature to be a way to map your input to a function, with intelligent parsing of the input into various arguments.\nI'm going to steal an example from the Google docs involving movies. In their code sample, they define three functions:\n\nfind_movies\nfind_theaters\nget_showtimes\n\nEach of these has arguments. get_showtimes as an example has arguments for the location, movie, theater, and date. find_theaters is simpler and just requires a location and movie.\nIf I use a prompt like Which theaters in Mountain View show Barbie movie? then the API attempts to map that to a function and figure out the arguments. Here is a portion of the result for that call demonstrating this:\n\nThe important thing to note here is that the result is not the end!. Rather, you are expected, much like in Alexa, to take this and implement that logic yourself. The API has handled the parsing and figured out the intent from the prompt, so the hard parts are done, now it's up to you to actually do the boring logic bit.\nShow me a demo!\nSo I played with this a bit, of course, and built a simple demo. First off, note that the Node.js SDK does not support using this feature yet. However, the REST API was so trivial I'm almost tempted to not ever return to the SDK.\nAt a high level, you pass in an object that will contain your prompt and then an array of function_declarations objects that define each function along with its arguments. This is all passed to the endpoint along with your key and... literally that's it. I also found that when I messed things up, Google's API did a really good job of describing what I did wrong.\nLet's look at a function that supports a function (sorry if that's confusing) related to ordering a product:\n\nIn the tools section, you can see one function, order_product, with a description and two parameters - product and quantity. I've also specified that only the product is required.\nI pass this entire object and just return the response. While I'm absolutely not sure about my use of functions here, at least in terms of using the REST endpoint, it really is that simple which is great for testing.\nSpeaking of - I did some quick tests with an array of inputs:\n\nAs you can see, I have a couple 'normal' inputs along with some off-the-wall ones. I was curious what would happen. Let's take a look.\nFirst one:\nFor pro",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "The Return of the Comment(s)",
		"date":"Tue Jan 02 2024 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1704218400,
		"url":"https://www.raymondcamden.com/2024/01/02/the-return-of-the-comments",
		"content":"In the twenty plus years this blog has been around, I've had various different comment systems. Initially, I simply stored them in a database (this blog used to be powered by ColdFusion), but eventually moved to Disqus. I had a pretty huge amount of comments and was generally OK with the service, but eventually, folks simply stopped commenting.\nI then made the decision to simply kill off the integration. I wrote some scripts to get my data, stored them as flat files, and you can still see the old comments on posts that had them.\nAbout a year or so I added in Webmentions, which works ok, but doesn't really feel the same.\nAfter some time thinking about it, I decided maybe its time to try again. The excellent and incredibly easy to set up Giscus uses GitHub discussions to power commenting. Now, that does mean that you need a GitHub account to comment, but with this being a very technical blog (a technical cat blog), I figured it was a safe assumption.\nI'm only enabling commenting for posts from this year and forward, and if you're curious, that code looks like so:\n{% assign year = page.date | date: &quot;%Y&quot; %}\n{% if year &gt;= 2024 %}\n\t{% include 'giscus' %}\n{% endif %}\n\nI love me some Liquid.\nAnyway, leave me a comment! Say hello, introduce yourself, and so forth.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "My 2023",
		"date":"Sat Dec 30 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1703959200,
		"url":"https://www.raymondcamden.com/2023/12/30/my-2023",
		"content":"Happy New Year (well almost) my fabulous readers. Typically at the end of the year, I like to take stock of what I accomplished through the year and share it in one last blog post. I honestly don't expect anyone to actually read these summaries, but they help me determine what I did well and what I didn't accomplish over the year while figuring out what I want to adjust for next year. So here's how I did in 2023.\nWriting\nMy goal for the blog is consistent, useful, helpful, and fun posts. My publishing 'plan' is one post a week for 52 posts a year. Counting this post, I published 118 posts which I'll take as a big ole win. My traffic, according to Netlify, hovered between three and four hundred thousand page views a month. I think Netlify's numbers are a bit high. My GoatCounter stats show 225 thousand page &quot;visits&quot; over the year. Either way, I think my traffic is pretty good so folks are still reading my content (thank you!).\nOutside of my blog, I also wrote multiple articles (four) for the Cloudinary blog. I list these on my About page if you wish to check them out. For work, I wrote on our Medium blog, and you can see all of my posts on my profile page. I published twenty articles there which I think is pretty good.\nAlong with Maya Shavin, I also released the third edition of &quot;Frontend Development Projects with Vue.js 3&quot;. Longtime readers will know I went from being really into Vue.js to... not quite as much. Working on the update to this book and really digging deeper into Vue 3 has made me appreciate it more, but I don't see myself doing more blogging on it in the future. I may always change my mind on that too.\nPublic Speaking\nSigh. So, right or wrong, I judge myself very harshly when it comes to events. There's a part of me that feels like I'm a complete failure if I get a rejection from a conference, and I got quite a few. I'm trying to think most positively about that and recognize that the competition for spots is fierce, but between us friends here, it's a sore spot for me. Last year I spoke at twelve events, of which four were in person. I really would have preferred hitting fifteen speaking engagements, maybe twenty, with a good ten or so in person.\nFor 2024, I've been confirmed for three events, two of which are in person. I've also been working with my friend Brian Rinaldi to craft better conference proposals. And if you're reading this and looking for speakers, reach out!\nVideos\nFor probably a decade I've talked about doing more YouTube videos, and I'd throw out one or two and then nothing for months. This past six months or so I've been doing much better and creating more videos, including an entire series of learning Alpine.js. All in all, I published a bit over thirty videos and while I'm biased, I think they're pretty good.\nFor the year, I got a bit over twelve thousand views, which isn't a lot, but it's something. I also gained a hundred and thirteen subscribers, which according to my stats is a 999% increase.\nLooking at my stats, you can really tell when I started doing more videos about halfway through the year:\n\n\n\nMy thinking for next year is to just try to keep the same pace, and hopefully get my subscriber count over one thousand.\nThe Rest of It...\nI'm ending 2023 in a pretty good state. I didn't lose one pound this year, but I'm generally healthy. My wife and kids (all eight of em) are happy and healthy. I've got a very good job and a really good manager as well. I'm incredibly lucky and privileged. I'm incredibly appreciative of everyone who comes here (especially if you read all the way to this) and I hope your 2024 is filled with joy, love, and laughter.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Dec 23 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1703354400,
		"url":"https://www.raymondcamden.com/2023/12/23/links-for-you",
		"content":"Welcome to the last links I'll share ever... in 2023. As I said when I started this series, my goal was to share cool and interesting stuff with my readers, and I think it's been a great success so far. Plus, when I get excited about something, I just love to share it. I hope the holidays treat you well. They can be incredibly wonderful, and incredibly stressful at the same time. I've been struggling with some crippling anxiety the last few days, and this is despite having most everything locked down and prepared for Christmas. (Mostly thanks to my super-organized wife.) I'm doing my best to relax and work on that, but I just want you to know (well mostly me, yes I'm talking to myself), its all going to work out just fine.\nWhew. Ok, enough of that, let's get to the links!\nBuild a Livestreaming App using Amazon IVS\nAmazon IVS (Interactive Video Service) is an incredibly powerful platform that lets you, basically, rebuild your own Twitch. As powerful and complex as it is, it really isn't that difficult to get started. To make that process even easier, my good friend and IVS developer advocate Todd Sharp released a free playlist on YouTube that will help you get started:\n\nThis is - I kid you not - thirty videos of content, for free, that you can start watching today. I have not yet started this series, but it's on my list to begin in January. As an added bonus, Todd is an incredibly gifted presenter and educator, so you'll be learning from the best.\nMySQL Shorts\nIt's possible I shared this before, but speaking of good friends sharing good videos, my other best friend Scott Stroz has been posting short tutorials on MySQL called MySQL Shorts. I've watched many of them, but didn't realize he had over fifty different videos. Also, these videos are a great way to be reminded how powerful SQL is and how powerful MySQL is. Check out the playlist below:\n\nLearn CSS Grid\nI've shared content from Josh Comeau before. Last time, I was blown away by the interactive visualizations he included with his content. Well, he's done it again, with a guide to CSS grid that truly makes it easy to understand. CSS Grid is powerful, but honestly it doesn't make a lot of sense to me. Josh's guide really helped with that. I'll be honest and say it's still not 'stuck' in my head, but I've got this guide bookmarked and I've already used it a few times.\nMerry Christmas\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Generative AI to Detect Cat Breeds",
		"date":"Mon Dec 18 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1702922400,
		"url":"https://www.raymondcamden.com/2023/12/18/using-generative-ai-to-detect-cat-breeds",
		"content":"Let's be honest, what other use is there for generative AI than working with cats? If you read my previous post on Google's Gemini AI launch, you may have seen my test prompts asking it to identify the kind of cat shown in a picture. I decided to turn this into a proper web application as a real example of the API in action. Here's what I came up with.\nThe Front End\nFor the front end, I decided to make use of a native web platform feature to access the user's camera via a simple HTML form field. By using capture=&quot;camera&quot; on an input tag, you directly get access to the device camera. There are more advanced ways of doing this, but for quick and simple, it works fine. Even better, on desktop it simply acts as a file selector.\nMy thinking was - provide a way to get an image (either via camera or file selection), display the image, and send it off to the back end. While incredibly simple and vanilla JS would have been fine, I went ahead and used Alpine.js for the interactivity. First, the HTML, which just needs to provide the UI for the image, a place to display the image, and another place to display the result.\n\nNow let's turn to the JavaScript. I'm going to begin by sharing the initial version as it's simpler than explain how it failed. All I needed to do, initially, was notice when a file was selected, or a picture taken, and render it out to the DOM. (I used a bit of CSS not shared here to keep the visible size in check. More on that in a bit.) I also needed to send a base64 version of the file to the server side code. Here's the initial version:\n\nThe gotPic method is fired whenever the input field fires an onchange event. I take the file/image used, read it as a data URL (base64), and then assign it to the image in the DOM and send it to the server. Nice and simple, right?\nWell, everything worked fine on the desktop, but when I switched to my camera, the Samsung S22 Ultra Magnus Extreme 200 Camera Lens Edition (not the real name), I ran into issues where Google's API complained that I was sending too much data. I then remembered, my camera takes really detailed pictures, and I needed to resize the image before sending it on.\nI was already resizing in CSS, but obviously, that's not the same as really resizing. I found this excellent article on ImageKit's site: How to resize images in Javascript? In this article, they describe using an HTML canvas element to do the resizing. I had not used Canvas in probably close to a decade, but I was able to repurpose their code into my front end well enough:\n\nYou'll notice the code uses a max dimension for both width and height, and correctly handles resizing while keeping the same aspect ratio. Again, I can't take credit for any of that, thanks to Manu Chaudhary for his blog post.\nThe net result of this change is that now I'm sending a much smaller image to the back end service, and it's finally time to take a look at that.\nThe Back End\nFor my back end, I decided to use Cloudflare Workers again. I was a bit hesitant as it's had some issues with NPM packages and my demos before, but it didn't have any issues this time. If you remember from my last post, Google's AI Studio lets you easily output sample code from your prompts, so all I had to do was incorporate that into the Cloudflare Worker.\nHere's the entirety of the code:\n\nThe majority of the code is boilerplate from the AI Studio export, except now I get my image data from the information POSTed to the worker. I want to especially call out the prompt:\nLook at this picture and if you see a cat, return the breed of the cat.\n\nInitially, I tried hard to get a result in JSON and have it return a blank string if the picture wasn't a cat. But then I noticed something cool - Gemini did a really good job of handling pictures that weren't of cats. Like... shockingly good. I actually really appreciated that the app wouldn't just say &quot;Not a Cat&quot;, but explain what it was instead. Obviously, there's room for both styles for an application like this, but I went and kept Google's verbose and helpful responses.\nNow for the fun part... the results.\nThe Results\nLet's start with a few pictures of cats.\nFirst up is Pig, my favorite cat who is not fat and doesn't look like Jabba the Hut at all:\n\n\n\nNext, a picture of Luna. In this case, the breed is incorrect - but at least close.\n\n\n\nNow let's throw Google a curveball:\n\n\n\nThis really surprised me. The description is 100% accurate, and honestly, if I had seen this picture and didn't know, I would have recognized it as a sculpture of a cat, not as a watering can. I mean, I guess it's kind of obvious, but I honestly don't think I would have noticed that myself.\nNow let's go totally crazy:\n\n\n\nYep, that's right Google. How about AI generated cat images?\n\n\n\nI think it handled that pretty well.\nNext...\n\n\n\nYep, that's Bigfoot alright. Just think, all those Bigfoot &quot;researchers&quot; could retire and simply connect their trail cams to AI!\n\n\n\nI've got to say - I'm impressed ",
		"tags":[
	        
            "generative ai",
            
            "serverless",
            
            "cloudflare"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Google Gemini and AI Studio Launch",
		"date":"Thu Dec 14 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1702576800,
		"url":"https://www.raymondcamden.com/2023/12/14/google-gemini-and-ai-studio-launch",
		"content":"While it feels like just yesterday I first blogged about Google's PaLM APIs and MakerSuite, it was actually over two months ago, and of course, GenAI offerings are iterating and improving at lightning speed. In the past week, Google has announced Gemini, their new generative AI model. Naturally, I was curious about the API aspect of this and took a quick look.\nMakerSuite rebranded as AI Studio\nFirst off, the web UI (which I reviewed back in my first post) has been renamed to the generic and boring, but probably more enterprise and appropriate, AI Studio. Along with that, when creating new prompts, it will default to use Gemini models. (You can still select PaLM if you want.)\nAnother change... and I have to be honest, I don't know if this worked before or not, but you can now easily add images to your prompt. I'm pretty sure this is new as it's called out in the UI:\n\n\n\nYou can paste in an image as well, but doing so will prompt you to change the model on the right from &quot;Gemini Pro&quot; to &quot;Gemini Pro Vision&quot;. After that, you can just paste which is handy. As a test, I typed:\nWhat kind of cat is in this picture?\n\nAnd pasted in this picture:\n\n\n\nAnd got this result:\nThe cat in the picture is a Ragdoll. Ragdolls are a large breed of cat that\nis known for its docile temperament and beautiful blue eyes. They are also \nknown for being very affectionate and loyal to their owners. Ragdolls are \ntypically pointed, meaning that they have darker fur on their faces, ears,\nand tails.\n\nIn my testing, I did some more iterations on the prompt but didn't need to re-upload or paste the image which made it really handy for trying things out.\nThe Code\nI was curious about the code and as before, that's one click away. Here's the default code you get if you select JavaScript:\n\nIf you compare this to the code previously generated (look at the sample in my earlier post), you can see it's &quot;similar&quot;, but definitely been tweaked quite a bit. The npm module has changed to @google/generative-ai. Also, you can see how parts is used for both the prompt and the attached picture. Without even looking at the SDK docs, this is pretty simple to grok.\nI swapped out my initial picture with this one:\n\n\n\nAnd modified the code to run locally:\n\nAnd the result worked well:\nThis is a Calico cat.\n\nNice, right? Again, and I said this in the first post, I am so incredibly happy with how quickly you can go from testing in the web tool to working code. I've got a plan for a few more posts in this area coming soon!\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Image Dialog Web Component",
		"date":"Wed Dec 13 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1702490400,
		"url":"https://www.raymondcamden.com/2023/12/13/an-image-dialog-web-component",
		"content":"A lot of the talk (well, on Mastodon at least) lately concerning web components has been on &quot;HTML Web Components&quot;. The idea is that web components can progressively enhance &quot;regular&quot; HTML in the DOM instead of completely blowing it away with the Shadow DOM. (You can find a deeper discussion of this in Jim Nielsen's blog post.) This is something that's been on my mind for a while now as well and I've kept my eyes open for opportunities to build web components that enhance, not replace, content. With that in mind, I built a really simple component that does something fun.\nWe've all seen sites that use JavaScript to provide a thumbnail and detail view of images. So for example, you may have a set of thumbnails and when one image is picked, a larger image is loaded on top of the visible page as a modal view. Sometimes the background is dimmed and blurred a bit.\nThe web platform actually has a (new-ish) tag for this, dialog, and I looked at how I could build a web component that would make use of this.\nI began with simple HTML - a small image wrapped in an anchor tag linking to the full image:\n\nRight away, if nothing else I build works, if JavaScript is disabled or something else goes wrong, then the user can still see both the thumbnail and full image. It just plain works.\nNext, I wrapped it in a web component:\n\nThe nice thing about the web platform is that even without writing JavaScript, this doesn't break anything. It's just an unrecognized HTML tag and browsers can handle it. Now let's look at the code to make this actually do something:\n\nI begin by looking for the image inside my part of the DOM. My requirement is one image wrapped in an anchor, so after getting the image, I ensure the parent node is an anchor as well.\nNext, I create a dialog tag and set its HTML. The HTML uses the HREF value from the anchor for the image and uses a button to allow for closing the dialog. Note the use of &lt;form method=&quot;dialog&quot;&gt; as that will capture the button click and close out the dialog. Unlike &quot;usual&quot; modals like this, clicking elsewhere in the DOM does not dismiss it, but the ESC key will work.\nThis line, parent.parentNode.insertBefore(dialog, parent.nextSibling);, may look a bit weird. I want to add the dialog outside of the anchor tag (which is parent), but immediately after, so this little 'DOM dance' accomplishes that.\nThe last step is to add a click handler to the image that opens the modal. You can test, and fork, the demo below.\n\n  See the Pen \n  PE Table for Sorting (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne note - I added a bit of CSS to make the dialog stand out a bit, and in my CodePen, just styled dialog:\n\nPart of me thinks my web component should always use a particular class on the dialog it adds such that it doesn't conflict with other dialogs on the page. In that case, you would do something like this:\n\nComments, as always, are welcome. :)\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Dec 10 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1702231200,
		"url":"https://www.raymondcamden.com/2023/12/10/links-for-you",
		"content":"Welcome to - most likely - my last Links For You post of 2023. Who knows though, I'm off for nearly ten days at the end of the month so I'll probably have the time to keep on blogging. (If I can tear myself away from Assassin's Creed Odyssey.) I absolutely love this particular series of posts on my blog and will be continuing it in the new year. Let's get into the links.\nA Great Collection of Visual Studio Code Tips\nOne common theme for video games are blog posts along the lines of, &quot;Ten things I wish I knew before starting So and So.&quot; In general, these posts are spoiler-free and focus on game mechanics folks may not be aware of. I typically Google for these before I start any new game as a way to ensure I don't miss them either. In that vein, Bryan Braun wrote &quot;Things I wish someone would have told me about configuring VSCode&quot;. As someone who has used VS Code for years, I definitely recommend giving this a read as I ran into things I didn't know myself. It's short, sweet, and useful.\nBackground Sync for Web Apps\nThe Background Synchronization API is one of those APIs that seem really useful, but you don't see a lot of people talking about it. While part of the PWA suite of tools, it's a fairly complex API. Danny Moerkerke gives us not one, but two in-depth posts on the topic: &quot;Background Sync Is A PWA Super Power&quot; and &quot;Background Sync Is A PWA Super Power - Part 2&quot;. You may want to check out his other posts as he has done some great PWA posts.\nAn Example of HTML Web Components\nOn Mastodon lately, there has been a lot of talk on &quot;HTML&quot; Web Components. These are components that enhance existing HTML which means they can progressively enhance content and present at least something to the end user even when JavaScript is disabled or slow to load. Back in November, Jim Nielsen wrote a good introduction to the concept, and you should read that first, but once you have, then read this good example: &quot;HTML Web Components: An Example&quot;.\nFor Fun...\nLast year my wife and I went to a &quot;Christmas popup&quot; at a local restaurant and it was wonderful. While there, the restaurant was playing some music that felt like a hip, techno update to classic Christmas songs. Now - I'm incredibly conservative when it comes to Christmas music, so what I just wrote would have sounded horrible to me. But it was really fun and kinda cool. I Shazamed it and they were playing this album. Enjoy.\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "The Twelve (Generative) Days of Christmas",
		"date":"Fri Dec 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1702058400,
		"url":"https://www.raymondcamden.com/2023/12/08/the-twelve-generative-days-of-christmas",
		"content":"I tend to have a lot of silly ideas. Not useful ideas. Not good ideas. Silly ideas. Randomly yesterday I was thinking about the Twelve Days of Christmas song. If you aren't familiar with it, it starts off with a gift for one day, then repeats and adds a second day, and so on and so on. The gifts are:\npartridge in a pear tree\ntwo turtle doves\nthree French hens\nfour calling birds\nfive gold rings\nsix geese a-laying\nseven swans a-swimming\neight maids a-milking\nnine ladies dancing\nten lords a-leaping\neleven pipers piping\ntwelve drummers drumming\n\nI thought - what if I took each of these phrases and dropped them into an AI image generator? I did, and the results were... kinda fun. Before I show them, some notes.\n\nInitially, I did not alter the text in any way, except to sometimes swap out the numerical word (&quot;nine&quot;) with a digit (9). Pretty much every service I used had trouble with showing X items of something. I decided to not fight it and just go with it.\nNearly every service blocked eight maids a-milking. I'm not really sure why. Eventually, I found that this prompt generally worked &quot;eight young women working with cows on a dairy farm&quot;.\nIn general, I picked the best image out of the options I got, but obviously, that was my personal opinion.\n\nOk, let's take a look!\nImagine with Meta AI\nMy first set of images comes Meta's Imagine, which I believe launched just a day or so ago. As far as I can tell, it's free to use, but watermarks the images.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGoogle Search\nThe next set comes from Google's generative search in their search results. Honestly, this one was a bit wonky. First, you have to enable AI in search results for your account. Second, you have to, in search, ask for a drawing, so for example, &quot;draw a picture of a cat looking smug&quot;, and lastly, it only works in Chrome, which frankly is stupid. That being said, when you used a generic prompt like the above, the results would 'riff' on them and do things like, &quot;an oil painting in cubit style of ...&quot; which was kinda cool. Unfortunately, I couldn't get the 'maids' thing working, even with my alteration, and had to go to &quot;people&quot;, not &quot;women&quot;, and it refused to do dancing ladies at all, so you'll only see eleven results result.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nAdobe Firefly\nQuick disclaimer, I work for Adobe, although not on the Firefly team. You can try it out at firefly.adobe.com if you've got a Creative Cloud membership, and the tech is also used directly within Photoshop as well.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nChatGPT DALL-E 2\nThis is not the current version of DALL-E available for ChatGPT users (see the next section), but was available &quot;for free&quot; at labs.openai.com. Free with limited credits, but I had enough to generate my set.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nChatGPT DALL-E 3\nFor the most recent version of DALL-E, I've got my coworker Peter Nam to thank. I'm too cheap to pay for ChatGPT myself, but he is not. :) You will notice that both this set, and the next set, picked up on the Christmas theme without explicitly stating it, which is really freaking cool.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nImage Creator from Bing\nFor the final set, I used Image Creator from Bing. This is free, but they will &quot;slow you down&quot; after so many uses. (I didn't hit that while testing.) As with DALL-E 3, Bing picked up on the Christmas theme, and honestly, the first result is just plain stunning.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nMerry Christmas!\nI hope you enjoyed this little experiment, I know I did. I'd love to see others give this idea a try as well, and if you, do reach out and let me know!\n",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Awesome Netlify Updates",
		"date":"Wed Dec 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1701885600,
		"url":"https://www.raymondcamden.com/2023/12/06/awesome-netlify-updates",
		"content":"For about two months now I've had on my queue to write about some of the incredibly cool updates Netlify has released but I just didn't have the time. I've been hosting this blog on Netlify for a few years now and have been incredibly happy with the platform, but the updates the past two months have been both surprising and just really freaking cool. Here's a quick look at what impressed me. As a quick aside, this isn't necessarily everything announced recently and you can take a look yourself at their blog for more changes.\nCaching Improvements\nThis is an area I haven't really done much in - both on my site or elsewhere. I generally let Netlify worry about the hosting/caching/etc for my site. But one of the first improvements that caught my eye was the ability to add caching in Edge Functions. I'm going to borrow the code from their post as an example:\n\nLooks simple enough, but what I really like is that redeploying your site automatically invalidates the cache. Also supported is SWR, which if I read right, lets you return a cached response while the code figures out new data for future requests.\nNetlify followed up this announcement with news of a Purge API which details how you can use tagging and an API for further control of the cache. If I read it correctly (and again, I'll point out this is an area I haven't really worked with), it looks like you can use tags as a way to handle cache purging based on very specific needs, letting you delete one part of your cache while ignoring another.\nBoth of the blog posts listed above also have links to demo repositories so definitely check that out, and here are those two articles again for easy reading:\n\nSWR &amp; Fine Grained Cache Control on Netlify\nCache-tags &amp; Purge API on Netlify\n\nNetlify Functions 2.0\nNetlify Functions were first introduced back in 2018, and while there's many serverless providers out there, I really appreciated how I could bundle both my Jamstack site along with my serverless functions all in one repo. I really prefered that versus using an external provider.\nNow this feature has been majorly updated with the 2.0 release. The changes are numerous.\nFirst, the entire way you write a function has changed, including access to additional context information and how you return information. Here's what Hello World looks like now:\n\nI'm especially interested in the geo data available in the context object. So for example, here is what it returns when I hit code using it:\n\nAlong with that - you now also get:\n\nCustom endpoint: Previously doable via redirect rules, now your code can simply specify a route.\nRouting: Even cooler, you can specify a route with parameters, like /cats/:id, and those parameters are then available in your code.\nMethod matching: You can now specify that a function only works for particular HTTP methods.\nEasier streaming of data.\n\nIf you want, you can test out the geo context stuff here: https://netlify-scratch.netlify.app/whereami\nThe code for this is::\n\nAlso, check out this great article by my buddy Brian Rinaldi on migrating to the new syntax: Updating Your Netlify Functions to 2.0. It's important to note that you do not need to migrate, the &quot;old&quot; way will continue to work.\nHere is the related blog post on Functions 2.0:\n\nIntroducing Netlify Functions 2.0\nDocs\n\nBuilt-in Image Optimization\nI've been a huge fan of Cloudinary since first discovering their service a year ago (check out my posts on them for examples). If you aren't aware, they let you do an incredible amount of image transformations literally on the fly just by crafting a URL. I'm not surprised at all that Netlify announced a beta for their own image CDN. Currently, they support three transformations - resizing, cropping, and formatting.\nHere's an example:\n\nThe original image was 900x900, but routed via the Image CDN, it gets proportionally resized to a width of 200 and returns a much smaller image. Even cooler, you can use a redirect rule to simplify this (I borrowed this from their docs):\n/transform-small/* /.netlify/images?url=/:splat&amp;w=50&amp;h=50 200\n\nIf you want to see this line, check out: https://netlify-scratch.netlify.app/. As a quick aside, this works just fine locally when using netlify dev which is handy!\nHere's more information:\n\nIntroducing Netlify Image CDN Beta\nDocs\n\nBlob Support\nUnfortunately, this feature has nothing to do with this...\n\n\n\nInstead, this announcement refers to a simple unstructured data storage system available to your site. Data can be scoped to a site or deploy and exists within a particular 'store'. Both &quot;regular&quot; and binary data can be stored, making it useful for many different use cases. Here's an incredibly trivial hit counter:\n\nIn the code above, I have a store, hitcounter, and one value, hits, which is a simple number. I deployed this to the /hits path and you can see it here in action: https://netlify-scratch.netlify.app/hits\nOne quick thing to note if you try this locally. Don't forget you ",
		"tags":[
	        
            "netlify"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Music Previews to My Now Page",
		"date":"Wed Nov 29 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1701280800,
		"url":"https://www.raymondcamden.com/2023/11/29/adding-music-previews-to-my-now-page",
		"content":"About two months or so ago I added a Now page to my site. It shows my current reading list, my last watched movies, my Untappd beer check-ins, and my most recent Spotify tracks. You can see that part here:\n\n\n\nWhen I built it, I used a Pipedream workflow to wrap calls to Spotify's API. My Pipedream workflow gets my most recent tracks, slims down the data quite a bit, and returns just what I need. I use some client-side code to hit that endpoint and then render it out on the Now page. (I also use a bit of caching with LocalStorage such that the endpoint is only hit every ten minutes.)\nCurrently, when rendering each track, I link to its URL and Spotify users can listen to the track completely. I thought it would be cool to let people preview the tracks right from the web page. Here's how I did that.\nUpdating the &quot;Back End&quot;\nIn my case, my back end is just the Pipedream workflow. As I mentioned, it hits the Spotify API and then transforms the data into something smaller before returning it. All I had to do was update that one step:\n\nTo be clear, this isn't strictly necessary, I could simply return everything Spotify sends, but as it is sending a lot I don't need, this small step really improves the performance of my API. As an example, here's one result from Spotify (this is in an array of results):\n\nStill here? Good. That's huge, right? Here's the transformed value:\n\nMuch slimmer. I could strip even more as I immediately see things I'm not using, but it's good enough for now.\nCoding the Preview\nMy initial code simply took the result of the API and rendered out the individual track items. Here's one example:\n\n\n\nInitially, that code looked like so:\n\nI began by removing the link around the image and by adding in the preview URL. I used a data attribute for that:\n\nNext, I needed to add event handlers to each track:\n\nSo far so good. Now for the tricky part. Playing music in JavaScript is incredibly simple. Given a URL that leads to supported audio, you can do:\n\nMy first implementation simply grabbed the URL:\n\nand did that - which led to me being able to click every rendered track and hear all the music playing at once in a god-awful mashup of epic proportions. To correct this, I had to get a bit fancy:\n\nIf a person has clicked on track A, then track B, I should stop playing A\nIf a person has clicked on track A, and then A again, they probably want to stop it.\n\nHere's how I did it:\n\nI basically just check the current src. If it matches, I stop (this is done with pause and setting the currentTime). If the &quot;new&quot; URL is the same as the last one, then I just leave. Otherwise, I load up the new song.\nThis worked perfectly until I realized an issue. If you click to preview track A, let it play and it finishes, if you click the same track, it wouldn't start up. So I then added one more line of code:\n\nThis now lets me listen to the same preview again and again... if I want to. If you want to see the complete code, just view source over on Now or see the repo version here: https://github.com/cfjedimaster/raymondcamden2023/blob/main/src/now.liquid.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using IndexedDB with Alpine.js",
		"date":"Sun Nov 26 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1701021600,
		"url":"https://www.raymondcamden.com/2023/11/26/using-indexeddb-with-alpinejs",
		"content":"A lot of my &quot;x with Alpine&quot; blog posts end up being, well, nothing special. That's a good thing I suppose as it really helps highlight how simple Alpine.js is. (Note, I go back and forth between including the &quot;.js&quot; when referring to Alpine. I should be more consistent I suppose. On one hand, Alpine.js is the formal name, but Alpine just feels simpler.) That being said, the impetus for this post was to get something basic done before I built something a bit more complex. So if you wish to TLDR - it just works, visit my CodePen for the full source, and come back for the next post. If you're still curious, keep on reading.\nIndexedDB - Vanilla or Library?\nBack in the Fall of last year, I wrote a short series of posts (Part One, Part Two, Part Three) showing the same basic application built and making use of IndexedDB via straight vanilla JS and then two libraries to help simplify it a bit. IndexedDB (IDB from here on out) is &quot;a bit&quot; complex, and libraries can definitely help simplify your usage of it, but for today I decided to just use the API without any additional libraries. I've got Alpine in play, and it's really lightweight, so I decided to not add any additional libraries.\nI also decided to work with the same basic application for this post, a Contacts database. Each contact has a first and last name and an email address. You can see the UI below (every single version used the same layout):\n\n\n\nThe left side is a simple table of contacts with an Edit and Delete button. On the right is a form that lets you add new contacts, or edit existing ones.\nIn the first post of my series from last year, I had a pretty clear separation between the functions used for DOM stuff and the IDB code. I took a slightly different approach for my Alpine version.\nThe Alpine.js Version\nLet's start off with the HTML:\n\nYou can see Alpine in use in two areas. In the table, I've got a loop over a contacts array with buttons making use of the data to pass to the edit and delete functions. Next, I've got a form. It handles both new and existing records so I use a hidden form field to handle storing the primary key for edits. Note the use of x-model to bind the values here to data on the Alpine side.\nThe layout is rather simple, but things get a bit more complex on the Alpine side. I'm going to show this in bite-sized chunks, but I'll share everything at the end.\nFirst, the variables:\n\nThe last four fields are used for editing, while the first two relate to the stored information. db is a pointer to the IndexedDB database and contacts is an array of contacts copied from the database. In other words, there's the actual persisted data and a &quot;local&quot; copy in use by Alpine. (Yes, that bugs me too and I'll talk a bit more about it later.)\nNext up is the init() method:\n\nIf you ignore the logs, this does two things - ask for the IDB database object and then a list of existing contacts. Here's how the database is setup:\n\nIDB is asynchronous, but not Promise based, so I wrap my use of it with a Promise creator. IDB requires you to open up a database and do any structure updates in an onupgradeneeded event. In my demo, I simply create a store (think table) with a defined primary key property (id) and auto-incrementing keys. Also, an index is used on lastname. My demo doesn't actually do any searches so this is kinda pointless.\nNext, this function handles getting all contacts. It was called back in init:\n\nThere's nothing Alpine in this at all, just pure IDB code.\nNow, here's what edit does:\n\nIn this case, there's nothing IDB-related. Rather, it's setting the values that will be shown in the form. The key value is hidden. Now let's move on to delete:\n\nThis is still mostly boilerplate, except the oncomplete, where when the delete operation is done, I update my local contacts array by fetching the information again.\nThe final function handles saving both new and existing contacts:\n\nI make a contact object based on the values from the form field. I only want to include the id property when it's not an empty string so I've got a little of logic there. As with the delete method, when this transaction is done, I once again fetch the values with a call to getContacts.\nHere's the complete demo:\n\n  See the Pen \n  Alpine.js with IDB by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFinal Thoughts\n&quot;Final&quot; sounds so dramatic, sorry. As I said, there wasn't anything surprising or special about this particular demo, it was mainly built as I've got something else in mind for later this week. Earlier I talked about how I store a copy of the contacts separate and apart from the stored data. This means I need to ensure that when changes are made (either edits/additions or deletions) I also have to edit the list. Right now the logic is &quot;get everything&quot;, which for a short list is fine. This would not scale. IDB can store a huge amount of data, but the code as it stands now would not really handle that well. I ",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Mon Nov 20 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1700503200,
		"url":"https://www.raymondcamden.com/2023/11/20/links-for-you",
		"content":"Good morning, readers. These link posts are typically reserved for the weekend, but my weekend ended up busy as heck with, ok, more than a few hours of Diablo 4, but other stuff including the beginning of Christmas decorating. To say we've got a lot of decorations would be an understatement, so it's a multi-day process, but we've begun and hope to wrap before Thursday. As evidence, here's Frank, our year-round gargoyle because, why not:\n\n\n\nHere are some links that hopefully entertain you!\nPhotoshop on the Web\nThis link is a bit old, but here's a look by Addy Osmani about how Photoshop was brought to the web. My assumption is that most people now know about https://photoshop.adobe.com, but if you don't, Addy does an awesome job talking about the effort required to make this happen. There's quite a bit to digest here, but the TLDR is... the web platform has truly moved forward in such an incredible way. Unfortunately, Apple's Safari continues to lag behind and can't be used for this application.\nAlternatives to JSON\nFor me, my history of working with APIs basically went from SOAP (ugh) to JSON, so JSON is been &quot;just how data is done&quot; for me for... probably nearly twenty years. However, JSON is definitely not the only way to transmit data. There's actually not one, but two alternatives: Protocal Buffers (protobuf) and Avro. If you would like a nice introduction to protobuf, check out this article: How LinkedIn Adopted Protocol Buffers to Reduce Latency by 60%. You should not, obviously, run out and change all your code. Like many things related to performance tweaking, you should first evaluate if it makes sense, but knowing you have options is always a good thing!\nAdvent of Code 2023!\nHappy happy, joy joy! I absolutely freaking love Advent of Code and recommend it every year. For folks who don't know what it is - Advent of Code presents a coding challenge every day from December 1st to the 25th. The challenges all have a central story and every day the challenge is split into two parts - with the second part typically being a modification from the first half. I typically share the following advice:\n\nGive up. No, seriously. It's ok to give up. I &quot;do&quot; AoC until it becomes too much and don't even pretend like I'll finish it. If you do, great! But give yourself permission to just play with it until it stops being fun.\nThe last year or so I've used Python for my solutions, and it's a great way to practice new (to you) languages.\nIf you can't get something working, check their Reddit. There are always solutions posted and a great way to practice your language of choice is to take a solution in another language and rewrite it in the one you're using. It's not cheating, it's learning!\nAnd remember, this is meant to be fun!\n\nIf you want, you can see my previous solutions, and follow along this year, at my AoC repo: https://github.com/cfjedimaster/adventofcode. Looks like last year I did 8 days and called it quits. I'm going to try for 10 this year, but we'll see what happens.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Simple Routing to Cloudflare Workers",
		"date":"Fri Nov 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1700244000,
		"url":"https://www.raymondcamden.com/2023/11/17/adding-simple-routing-to-cloudflare-workers",
		"content":"I've been &quot;playing&quot; with serverless for years now, but honestly still feel new to it. When it comes to organization in a project that uses serverless functions, I've typically tried to build one function per operation. So for example, if I had a need to get a list of cats, I'd have one function. If I had a need to get information about a cat based on an identifier, I'd probably build a second one. That being said, I recently came across an example Cloudflare function that did something cool - it used a router, specifically the very lightweight itty-router. Let me share an example of how it works.\nWhat is a Router?\nSo I kinda assume most folks know what I mean when I say &quot;router&quot; in terms of code, but it's absolutely possible you may not. A router lets you specify a set of URLs, both static and dynamic, and control what code is executed. Examples of places where stuff like this is used would be Express for the server, and also Vue Router for client-side applications. Here's a pseudo-code example:\n\nHere I've used some abstract &quot;router service&quot; to define three URL routes as well as the methods used for each. I've got two simple GET requests mapped to logic and then a POST that allows for adding new data.\nMost routers take this a step further and allow for dynamic route mapping. So for example:\n\nIn this, the router service will differentiate between a request to /cats versus a request to /cats/5. It will also &quot;grab&quot; that end value from the URL and provide it as a variable, id.\nMost routers can do a lot more than that, but I wanted to give you a high level idea before going forward.\nitty-router and CloudFlare\nTo use itty-router in Cloudflare, first install it as a dependency in your worker folder:\nnpm i itty-router\n\nNext, include it in your code:\n\nThen you can start using it. Here's a complete example:\n\nIn the worker above, I've got a hard-coded set of data to keep it simple. I've defined two routes. One to the worker with nothing in the URL that returns all the data. One with a dynamic name that attempts to find a match in the data.\nThe 'real' core of the function (the default export) simply passes the logic to my router.\nNow to be clear, a router is not required to support this. Cloudflare Workers give you access to the URL used to request the code so you could 'manually' build this inside your fetch function there, but I know I much prefer this version.\nAnd it gets even better. The itty-router package includes other useful bits. Consider this version:\n\nI've imported a json function that I can use in fetch. Now my router functions only return data and the JSON part is handled for me.\nAt this point, I was already pretty impressed by itty-router, but then while looking into the docs, I saw their CORS support, and check this out:\n\nLiterally about two seconds of work to add CORS support. Now the code above shows the default CORS support - you can absolutely tweak it to your liking, but dang is that easy!\nIf you want to see this simple example in action, you can hit it up at https://routetest.raymondcamden.workers.dev/ and https://routetest.raymondcamden.workers.dev/Luna. The complete source code for the worker may be found here: https://github.com/cfjedimaster/cloudflareworkers-demos/tree/main/routetest\nMore Ramblings\nThe above example barely scratches the surface of what the itty-router can do, and I highly encourage you to check the docs to see the other features as well. Now, I still kinda feel like I'm going to keep my serverless functions rather simple in terms of what each one supports, but I'm going to be more open to adding a &quot;bit&quot; of flexibility, and itty-router will be my goto tool for this. As always, I'm curious to know what you think, so let me know!\nPhoto by Bogdan Karlenko on Unsplash\n",
		"tags":[
	        
            "cloudflare",
            
            "serverless"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Tip for Properly Handling Loading States in Web Apps",
		"date":"Wed Nov 15 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1700071200,
		"url":"https://www.raymondcamden.com/2023/11/15/a-tip-for-properly-handling-loading-states-in-web-apps",
		"content":"This isn't something I was going to blog about, but after seeing the same issue a few times recently (although to be fair, last in a mobile game), I thought I'd share it with my audience. I apologize if the title isn't the best as it was a hard issue to describe, so let me begin by demonstrating the problem, and then the (hopefully) obvious solution.\nLoading Data\nHere's a super simple example of a web page that loads some data from the API. In this case, it's the Star Wars API which, unfortunately, has been pretty slow recently. On the flip side, that helps illustrate that issue.\nThe HTML is just an h2 and an empty ul and the JavaScript is fairly simple:\n\nThe result of the API call will contain a count variable, and if it's zero, it means we have no data. If it's anything else, we can render a list of ships. You can see this in action below:\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIf you got to this part of my article and the data was already there, just hit the nice little Rerun button in the bottom right to see how there's no indication that anything is loading at all. The user may think the page is broken and navigate away.\nThe solution is to simply add a loading message. Most folks I assume know this already, but let's just fix that quickly. Since my JavaScript blows away the inside of my list when the data is loaded, I'll fix the issue with HTML:\n\nHere's that version, and again, hit Rerun to see it properly.\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nCool. My expectation is that most people inherently get this, but if not, hopefully I've taught you an good way to handle things like this. But - let's consider an alternative.\nLoading Data - Part Deux\nIn the previous example, it's important to remember that there isn't two states (loading and done loading), but rather three: Loading, Ships, and No Ships. Let's consider a variant of the previous demo that makes use of Alpine.js. In my HTML, I'm going to handle only two states - no ships or ships:\n\nMy JavaScript simply fills the ship data on load:\n\nAnd now if you run this version, you see the issue (and again, smash that Rerun button):\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nYou'll see the page load up and clearly say, &quot;There are no ships&quot;, while that is clearly false. We don't know if there aren't ships!\nNow, I feel like this is an obvious thing to avoid, but like I said, I've seen it a few times now so I figured it was time to talk about it.\nAs a final note, here's a corrected version of the previous example. In this one, I use a new variable, loading, and toggle the DOM between showing a loading message or one of the two results (ships or now ships):\n\n  See the Pen \n  Loading Post V4 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Can GenAI help you win in Vegas?",
		"date":"Thu Nov 09 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1699552800,
		"url":"https://www.raymondcamden.com/2023/11/09/can-genai-help-you-win-in-vegas",
		"content":"No. Thank you and goodbye.\nOk, first off, I apologize for the click-bait style title. Every now and then when I get an idea for a demo, it doesn't work. But sometimes, it doesn't work out in a fun and interesting way, and I figure it's a good idea to share it anyway. (Also, there's always the strong chance that it didn't work out because I did something wrong!) Today's demo is a perfect example of that I think.\nEarlier this year I built a Blackjack game using Alpine.js and the quite excellent Deck of Cards API. Yesterday I was thinking about the game and I wondered - what would happen if I used generative AI to ask for help when playing it? So for example, I went to my game and saw this:\n\n\n\nIn Google's PaLM Makersuite app, I wrote:\n\nI'm playing blackjack and the dealer has a six of diamonds showing. I've got a jack of clubs and a jack of hearts. Should I hit or should I stay?\n\nAnd got this response:\n\nYou have 20, which is a good hand. The dealer has 16, which is below the average. If you hit, you risk getting a card that will bust you. So it's better to stay and hope that the dealer busts.\n\nWhich, ok, is pretty obvious. You would never hit with two Jacks. But I loved the descriptive response that reinforces the principles of the game. I thought - what if I added a button to the game that lets you ask GenAI for help? Here's how I did it:\nThe Front End\nOn the front end, I kept the UI changes pretty minimal. I began by adding a simple button:\n\n\n\nClicking this needs to do two important things. First, it needs to 'translate' the game state into a prompt, then it needs to pass this to a service that will handle the gen ai call. I created a new function, askForHelp, that handles it:\n\nI store the player and PC cards in two variables that contain an array of cards representing their hands. Each card has a 'value' which is either a number or a name, like Jack. Each card also has a suit. In theory, that's probably not critical information, but I figured it couldn't hurt. Here's an example prompt generated by a game:\nI'm playing blackjack and the dealer currently has 8 of clubs. I've got a 7 of spades and a QUEEN of hearts. Should I hit or should I stay?\n\nThis is passed off to my backend service (more on that in a second) and the result is then displayed in a JavaScript alert which is 100% horrible UX, but as this was an experiment, I figured it was good enough.\nIf you're curious, this is what the response was:\n\nStay. You have a total of 17, which is a good hand. The dealer's 8 is not a high card, so you are unlikely to lose if you stay.\n\nThe Back End\nFor my back end, I whipped up a quick Pipedream workflow. My workflow consisted of literally just three steps.\nThe first is my trigger, an HTTP endpoint. You can see the URL in the front end code above.\nThe second step is the built-in Google PaLM API action. All I had to do was tell it what to use for a prompt: {{steps.trigger.event.query.text}}\nMy final step simply returned the result:\n\nTime to Win, right?\nSo... yeah. My first few tests worked great, and then... things went off the rails. Here's an example. The dealer had a seven of hearts. I had a queen of spades and an eight of diamonds. When I asked for help, I got:\n\nThe dealer's upcard is 7, which is a \"hard\" 17. That means that the dealer must stand regardless of what you do. You have a \"soft\" 19, which means that you have an ace that can count as either 1 or 11. You should stay.\n\nOk, sure, I should stay, but what in the actual frak. I have 19? The dealer has 17? And it gets better. Once when I had 18 showing, I got:\n\nI would recommend hitting. With a total of 18, you are only one point away from 21, which is the winning hand in blackjack. The dealer's seven of hearts gives them a total of 17, which is below the 21 threshold. Therefore, there is a good chance that the dealer will bust if they hit, which would give you the win. However, if you stay, you will not be able to improve your hand and will have to hope that the dealer busts.\n\nHitting on 18. I'm no professional gambler, but I'm pretty sure that's bad advice.\nThis got me thinking about my prompt a bit more. I did some tweaks, like lowercasing the suit, as I was worried it may have confused the AI. I iterated through some options in Makersuite, and consistently, it seemed to struggle with basic math at times. Even when I was explicit:\n\nI've got a jack of clubs (worth ten) and an eight of hearts (worth 8).\n\nIt still thought I had 19. I did have better luck with this:\n\nI've got a jack of clubs (worth ten) and an eight of hearts (worth 8). My hand total is 18.  \n\nAnd I also tried this:\n\nI've got a ace of clubs and an eight of hearts. My hand total is 9 or 19.\n\nAnd that seemed to consistently work correctly. So with that in mind, I went back to my prompt generation and updated my logic there. Here's the updated version:\n\nMy Blackjack game has a utility function, getCount, that returns an object containing two values lowCount, where Aces are treated as one, and hig",
		"tags":[
	        
            "generative ai",
            
            "pipedream"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating Human-Readable Summaries of Data with Google PaLM Generative AI",
		"date":"Mon Nov 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1699293600,
		"url":"https://www.raymondcamden.com/2023/11/06/creating-human-readable-summaries-of-data-with-google-palm-generative-ai",
		"content":"Like a lot of folks, I've been spending a lot of time thinking about generative AI, and AI, in general, and oddly (well for me), trying to focus on productive uses for it when working with APIs. A few weeks ago I shared my initial impressions of Google's PaLM 2 API, and today I came up with an interesting use case for it.\nI've seen text summarization as a fairly common use case for gen AI, and I agree, it can be incredibly helpful when working with lots of text. However, I got to thinking today, would it be possible to use this as a way to summarize numerical, or other data? So given some process that returns a set of information, can we use gen AI to summarize it? Here's what I came up with.\nPlaying in MakerSuite\nI began by using Google's MakerSuite with some hard-coded data for the weather in our area (Louisiana) for the upcoming week. MakerSuite was really useful in letting me iterate over a number of different prompts trying to get the result I was looking for. Here's what I started off with:\nGiven the following temperature reports (with the first report being the earliest), write a three sentence summary of the weather over the next seven days.\n\nLow of 64, high of 84\nLow of 64, high of 83\nLow of 66, high of 83\nLow of 56, high of 70\nLow of 56, high of 68\nLow of 54, high of 69\n\nTo be fair, a three-sentence summary is probably a bit much, but here's what I got:\nThe weather will be warm and sunny with highs in the 80s and lows in the 60s for the next 7 days.\n\nMonday: Sunny, with a high near 84 and a low near 64.\n\nTuesday: Sunny, with a high near 83 and a low near 64.\n\nWednesday: Sunny, with a high near 83 and a low near 66.\n\nThursday: Mostly sunny, with a high near 70 and a low near 56.\n\nFriday: Mostly sunny, with a high near 68 and a low near 56.\n\nSaturday: Mostly sunny, with a high near 69 and a low near 54.\n\nSunday: Mostly sunny, with a high near 70 and a low near 54.\n\nThat first sentence is perfect, but all the rest is a bit much. I then tried this prompt (with the weather data again):\nGiven the following temperature reports (with the first report being the earliest), write a short summary of the weather over the next seven days.\n\nThat response was perfect:\nThe weather will be mild over the next seven days, with highs in the mid-80s and lows in the mid-60s. The best day to be outside will be on Tuesday, when the high will be 84 degrees.\n\nLike, I love that comment on Tuesday. So, I clicked the handy &quot;Get code&quot; button and started working on a real demo.\nMaking it Dynamic\nI began by deciding on how I'd get the weather and went to a service I used recently, Pirate Weather. After getting a key, you can easily get a forecast once you have the latitude and longitude of your location. They also (nicely) let you exclude things you don't need, so I made sure to do that in my call:\n\nFYI, that latitude and longitude are for Lafayette, Louisiana, my hometown. I took this result and mapped it to a new array containing the high and low temps, as well as the general 'summary':\n\nNote the slice at the end. Pirate Weather returns the current day in the forecast and I wanted to focus on the next seven days. Here's what this looks like:\n\nOK, with that data ready, I then simply pasted in the code output from MakerSuite, but swapped in my data in the prompt:\n\nYou will notice I slightly tweaked the prompt and changed 'temperature reports' to 'weather reports' to flag the fact that I was including information about the type of weather for that day. Also, I didn't bother rewriting the data in a human-friendly form, I simply dumped out the JSON. And the result (minus the additional data, just the text) was...\nThe weather for the next seven days will be partly cloudy with \na high of 84 degrees Fahrenheit and a low of 65 degrees \nFahrenheit. There is a chance of rain on the fourth day with\na high of 69 degrees Fahrenheit and a low of 57 degrees Fahrenheit.\n\nDang, that's cool! My only real complaint is that &quot;fourth day&quot; is kind of vague. I decided to push my luck a bit. First, I added the day of the week to the data:\n\nAs a quick aside, I could have used the Intl spec to get the day of the week instead, but this seemed quicker. This had a bad effect on the 'summary' though:\nTuesday: Partly Cloudy, high 84F\nWednesday: Partly Cloudy, high 83F\nThursday: Cloudy, high 83F\nFriday: Rain, high 69F\nSaturday: Cloudy, high 71F\nSunday: Cloudy, high 68F\nMonday: Cloudy, high 66F\n\nStill accurate, and yes, it's a more human-readable form of the data, but I could have done that without AI. I then tweaked my prompt a bit more:\nGiven the following weather reports (with the first report being the \nearliest), write a short summary of the weather over the next seven \ndays. The weather report is a JSON object containing values for the\nhigh temperature ('high'), low temperature ('low'), weather \n('summary'), and day of the week ('dayOfWeek'). Do not list out each\nday, but rather create a summary of the entire set of data.\n\nFrankly, it f",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Nov 05 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1699207200,
		"url":"https://www.raymondcamden.com/2023/11/05/links-for-you",
		"content":"Happy Sunday, and as I'm currently watching the Saints lose (to be fair, I'm an hour behind, watching it recorded), I figured why not go ahead and share some links that will be more winning than my poor team. As of now, I'm completely done with presentations and travel for the rest of the year, so hopefully I can catch up on some research and 'fun' technical stuff. Enjoy these links!\nSimple CSS Update Proposed - Autosizing Textareas\nWhile I'm very far behind in keeping up with CSS, I'm also very happy with how much CSS has been improving. In this article by Amit Merchant, he discusses a new CSS property in considering for autosizing textareas as you type. For a while now I think most browsers have let you pull at the corner of a textarea to increase the size, but having a simple CSS property to automatically size as you type sounds perfect. Currently, this is Chrome only.\nExploring the Potential of the Web Speech API in Karaoke\nNext up is a cool presentation by Ana Rodrigues on using the Web Speech API to build a karaoke web app. It's a quick twenty-minute presentation that's fun as heck to watch.\n\nThe Segmenter Feature of Intl\nI've used the Intl spec a lot, but usually just for formatting numbers and dates. In this post, &quot;Using the Intl segmenter API&quot; from Polypane, they go into detail about how the segmenter part of Intl can be really useful. Essentially, it lets you figure out the number of words, or sentences, in a string, in a locale-friendly manner. No need for regexes or other tricks, this should, in theory, &quot;do it right&quot; and be correct for the language being used.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Generic RSS Parser Service with Cloudflare Workers",
		"date":"Tue Oct 31 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1698775200,
		"url":"https://www.raymondcamden.com/2023/10/31/building-a-generic-rss-parser-service-with-cloudflare-workers",
		"content":"About once every three months I'll write a quick JavaScript demo and attempt to fetch someone's RSS feed... and then remember that the vast majority of RSS feeds don't specify a CORS header to allow remote scripts to load them. I know this - and yet I still tend to forget. I thought it would be kind of fun to build a serverless API via Cloudflare Workers to handle loading, parsing, and returning a RSS feed with CORS allowed. I figured this would be pretty easy, but I ran into a snag right away.\nWorkers and NPM Modules\nCloudflare Workers is Node.js compatible... with some issues. Cloudflare has a documentation page on it addressing what you may run into, and for me, my main issue is with older npm modules.\nAs it turns out, my goto Node RSS parser, rss-parser, is somewhat old. While it was updated seven months ago, it hasn't really changed much because, honestly, RSS hasn't changed much.\nWhile attempting to use it in Cloudflare Workers, I got an error when it tried to instantiate XMLHttpRequest. This was the old way of doing network stuff in JavaScript, long since replaced by Fetch.\nUnfortunately, I don't think there's a way around this, which means... parsing RSS by hand. Ick. But - let me show you what I came up with.\nVersion One\nFor my first version, I began with figuring out how I was going to parse the XML without rss-parser. I found fast-xml-parser and it worked well in Cloudflare's environment. Here's the initial version of my &quot;generic&quot; RSS parser hard coded to just use my own RSS feed.\n\nIf you look at the main fetch function for the Worker, it loads my XML and passes it to the fast-feed-parser library. This returns a JavaScript-ready version of the XML... that's still a bit messy. I wrote two 'support' functions, reformatData and fixLink to attempt to bring some sanity to the result. To be clear, there was nothing 'broken' about how the XML was parsed, but XML is pretty complex and while the resulting JSON I ended up with was 'correct', I wanted to simplify it quite a bit.\nThe last thing my code does is return the result with the appropriate headers. Here's an example of the output where I've reduced the number of entries to two:\n\nNot bad, a bit verbose for sure, but it gets the job done.\nVersion Two\nIn the second version, all I did was simply make the actual feed a URL parameter. Here's the code that changed:\n\nI'm looking in the querystring for the feed value. So for example:\nhttp://127.0.0.1:8787/?feed=https://www.raymondcamden.com/feed_slim.xml\n\nVersion Three\nSo, for the third and final form of this little RSS wrapper, I added another small wrinkle, which of course, ended up blowing up into something bigger! I thought that if I were to deploy something like this to production, most likely I'd want a sanity check on the RSS URLs being parsed. I'm not building a (hypothetical) service for anyone and everyone, so having limits is ok. I decided on a simple &quot;ALLOW&quot; list and set it up like so:\n\nSimple enough, but it's here where I remember why I liked rss-parser so much. The second RSS URL, https://recursive.codes/blog/feed, uses a different format from the first and third. I could simply return it as parsed, but as I had done a bit of 'data reformatting' initially, I thought it made sense to continue doing so. I began with a simple IF block:\n\nAnd then did some major work in reformatData.\n\nBasically, based on the two types of RSS (which should be RSS and Atom, yes there is an RSS type named RSS), I filter to a small set of feed metadata (title and link), and then the content is returned in an entries feed, filtered down to a title, the date published, the content, and link. I completely ignore the categories, although I could probably add that back in and document it as something optional.\nHowever the result is &quot;similar&quot; RSS results for different RSS feeds. There's one final bit I should do but didn't, and that's normalizing the pubDate value. If I have a slow week, I'll maybe post that as an update. Here's sample output, again with me filtering the entries to just two:\n\nYou can see this in action here: https://rsstojson.raymondcamden.workers.dev/?feed=https://www.raymondcamden.com/feed_slim.xml\nThe full source code may be found here: https://github.com/cfjedimaster/cloudflareworkers-demos/tree/main/rsstojson\n",
		"tags":[
	        
            "cloudflare",
            
            "serverless"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Cloudflare's AI Workers to Add Translations to PDFs",
		"date":"Tue Oct 24 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1698170400,
		"url":"https://www.raymondcamden.com/2023/10/24/using-cloudflare-ai-workers-to-add-translations-to-pdfs",
		"content":"Late last month, Cloudflare announced new AI features in their (already quite stellar)\nWorkers platform. I've been a big fan of their serverless feature (see my earlier posts) so I was quite excited to give this a try myself. Before I begin, I'll repeat what the Cloudflare folks said in their announcement: &quot;Usage is not currently recommended for production apps&quot;. So with that in mind, remember that what I'm sharing today may change in the future.\nThe Demo\nBefore I get into the code, let me share what I've built. Now, at the time I wrote this, Cloudflare's AI stuff was still in beta and there is no cost yet for using the features. This is, obviously, going to change. Their announcement blog does share proposed pricing for the feature, but again, I'd expect this to change. Because of all of this, I will not be sharing a live demo and I'll be removing my code from production. If anyone from Cloudflare is reading this and wants to ensure I'll be safe, hit me up. But in the meantime, I'll share some screenshots here, and you will have access to all of my code if you want to make use of it.\nFor my demo, I decided to add translation to the Adobe PDF Embed API. For folks who don't know, this is a free client-side PDF viewer you can embed on any web page. It gives you much greater control over the PDF viewing experience as well as giving you hooks into various events. In my demo's case, I can hook into the &quot;selection&quot; event so I know when a reader has selected text.\nMy demo will do this: Present a PDF to the user, and let them know they can select text to have it automatically translated to French. French was a (mostly) arbitrary choice, I could have used a dropdown of options, sniffed the user's language, and so forth, but I wanted to keep it mostly simple.\n\n\n\nWhen you select text, I grab it, pass it to the Cloudflare Worker, and return the result.\n\n\n\nIn case it's a bit too hard to read in the screenshot, I selected &quot;It is never too early or too late to start planning your legacy.&quot; The AI service translated it to &quot;Il n’est jamais trop tôt ou trop tard pour commencer à planifier votre héritage.&quot; My high school/college French is a bit rusty, but I asked a native speaker coworker and they said it was pretty well done.\nNow let's check out the code.\nThe Front-End\nSo honestly, the front-end isn't the interesting part, but I figured I'd show that first to get it out of the way. For readers who haven't seen the Adobe PDF Viewer tool, you basically:\n\nIdentify a div in your document to host the PDF\nAdd a script tag pointing to our library\nAdd some code that mostly just specifies the div, PDF to load, and any customization options.\n\nHere's the JavaScript code:\n\nThe first ten or so lines are the same code you get from our docs. Things only get interesting when I add my hooks in. You can see in the registerCallback section I'm listening for a &quot;PREVIEW_SELECTION_END&quot; event. On that, I get the actual selected content, ensure it's not empty, and then call my translation function. Let's look at that:\n\nThis boils down to simply calling my Cloudflare backend with the text and displaying the result. You're welcome to try hitting that URL, it won't work when I publish this post.\nThe Back-End\nAlright, here's where things get pretty freaking cool, and by cool I mean, well incredibly simple. If you took the time to read their introductory blog post, you can see it's rather easy. You add one binding to your wrangler.toml file, and then you can access the service in code. Here's the entire function for my translation service:\n\nBy my count, that's roughly 20 lines of code, and basically - get the input, call the service, return the data. I'm missing error-checking here, but in terms of simplicity, dang that makes me happy!\nThis is just my first demo with their new offerings, but considering how happy I've been with Cloudflare already, I'm stoked that this is being offered now, even in beta form.\nIf you want to check out the code, you can find the front-end here, https://codepen.io/cfjedimaster/pen/WNpbJbX. Remember that it will not work as I'm disabling the serverless function.\nThe back-end code may be found here: https://github.com/cfjedimaster/cloudflareworkers-demos/tree/main/translate\nLet me know what you think!\n",
		"tags":[
	        
            "cloudflare",
            
            "serverless",
            
            "pdf services",
            
            "adobe",
            
            "generative ai"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Oct 22 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1697997600,
		"url":"https://www.raymondcamden.com/2023/10/22/links-for-you",
		"content":"Hello friends and welcome to another post of links I hope you find interesting. In a few days, I'll be heading out to API World for my last trip of the year and my last in-person event. (I'll be giving the same talk for API World again later this month in their virtual event.) I just got back from All Things Open which was an incredible conference that I'm happy I was able to participate in, and I'd absolutely recommend it for next year. Let's get to the links!\nScraperAPI\nThe first thing I want to share is a cool service called ScraperAPI. As you can guess, it's a web scraping API that goes through the trouble of obfuscating your request to prevent sites from blocking your calls. I'm of two minds when it comes to services like this. I can get why people may want to block code from reading the HTML on their site, but at the same time, if you put something on a public web address, you have to expect people to want to, you know, actually read the content, automated or not. Obviously, you should automate web scraping with care, but I think it's a fair and completely reasonable thing to do.\nScraperAPI has an incredibly simple API with the basic version being as easy as this:\nhttps://api.scraperapi.com/?api_key=APIKEY&amp;url=http://httpbin.org/ip\n\nNow by itself, this is pretty handy. As I said, they handle masking the fact that it's a bot automatically. They also make it easy to change the country your request appears to be from as well as pretending to be a mobile device. You can see a lot more on their page about customizing requests.\nWhat I thought was really cool though was their endpoints for structured data collection, basically special wrappers for Amazon products, Amazon search, and Google search.\nSo for example, this endpoint (with a real key), will search the American Amazon store for Star Wars:\nhttps://api.scraperapi.com/structured/amazon/search?api_key=KEY&amp;query=Star+Wars&amp;country=amazon.com\n\nThe result is a JSON array of products with pagination support. As an example, here are two results:\n\nAll in all, a pretty impressive little service. You can check their pricing information on what it costs, and they do have a decent free tier. If you like what you see and want to sign up, please use this URL: https://www.scraperapi.com/?via=raymond49 I get a small commission on each sign up which will let me go buy those Star Wars items the API returned. ;)\nBullet Chatting\nA few weeks ago I was chatting with my buddy Todd Sharp when he introduced me to something I had never heard of before, Danmaku, or &quot;bullet curtain&quot; subtitling. This is a form of subtitles popular in Japan and China where messages are displayed in an animated way, appearing briefly before disappearing. I believe the norm for this is that the comments 'fly in' and out of the screen. Yeah, I know that sounds kind of vague. I tried to find a good example of this but struggled. Here's one that is nice and short though:\n\nHonestly, I think this would drive me crazy, but I'm old. Anyway, the point of all of this is that this particular style of chat is popular enough that the W3C has actually created a proposed spec for it: Bullet Chatting Proposal Within that spec you can look at use cases as well as a proposed API.\nStunning Well Done Visualization for a Technical Post\nI'm intentionally not starting off this section with the nature of the blog post because I absolutely, positively, want you to visit this page, no matter what your current knowledge of the topic is. That's because this post by Josh Comeau is an incredibly well-done post: Understanding the JavaScript Modulo Operator. I honestly almost didn't click on this when it came across me as I already knew what the modulo operator did, but then I realized it probably wouldn't hurt to check it out. I'm so happy I did. Josh's visualizations of the concepts in this post as some of the best that I've ever seen. Normally when I'm demonstrating something I'll just embed a CodePen, but Josh actually built embedded testing tools with simple illustrations and elegantly done animations. Seriously, I don't care how well you know the topic, check out the post and see for yourself.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Google PaLM to Gather Sentiment Analysis on a Forum",
		"date":"Mon Oct 16 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1697479200,
		"url":"https://www.raymondcamden.com/2023/10/16/using-google-palm-to-gather-sentiment-analysis-on-a-forum",
		"content":"I've really been enjoying working with Google's PaLM 2 AI API and this week I used it to build a pretty interesting demo I think. What if we could use the generative AI features of PaLM to determine the 'sentiment' or general health of a forum? I was able to do so and I think the results are pretty interesting. I'll remind my readers I'm still fairly new to this, so please reach out if you've got suggestions on how to do this better, or found any big mistakes in my implementation. Ok, let's get started!\nSentiment Analysis\nIn my first post on Google's PaLM API, I talked about how their &quot;MakerSuite&quot; was a really cool web-based UI to test out and play with the APIs. One of the things I found this week was their prompt gallery which gathers many different types of examples. In that list of samples, I found sentiment analysis:\n\n\n\nIf you open this sample in MakerSuite, you see that they are using tabular data as a prompt:\n\n\n\nAs you can see at the bottom there, they simply include a sentence with a blank field for the response. If you click Run, you can see how it parses the sample:\n\n\n\nLet's look at the code for this prompt:\n\nIt's pretty similar to what I've shown in the previous blog posts, but note that the 'table' is just a simple text formatting system - two lines, each prefixed with Sentence and Sentiment. The final part just leaves the value for Sentiment out so PaLM can 'answer' it. Simple enough, so let's see how to build our workflow.\nThe Workflow\nOnce again, I'm using Pipedream for my workflow. In my imagined scenario, I want to check the sentiment of my forum once a day, so I used the scheduled trigger with the right value for that timing.\n\n\n\nFor my next step, I need to get the data from my forum. For my test, I'm using the RSS feed from Acrobat Services API forum. Now, right away, you should see a potential problem. Technical support forums, by their very nature, will typically lean towards the negative. No one's coming to a support forum to sing the praises of your product, but rather, they are typically asking for help. I expected the results of my tests on this forum to lean negative and I was right, but I still think it's a useful metric, especially if things start to get really negative. Obviously, another type of forum may have completely different results.\nPipedream has an RSS parser action, actually one that works with multiple feeds, so I added that and specified the RSS URL for my forum:\n\n\n\nNext, I need to take the result of the RSS parse and turn it into strings I can use with PaLM. The RSS step returns an array of RSS items each containing various properties, like the title and date of the item from the feed. The description field contains what we want, but is a bit messy. Here's an example:\n\nI added a new step and used this code to translate the above into simpler strings:\n\nBasically:\n\nRemove all HTML\nReplace non-breaking spaces with spaces.\nSplit into sentences, but assuming a period.\nGet the first five sentences.\nJoin them into one string and trim.\nFinally, I filter out the list of RSS items to the first ten.\n\nI'm not sure about the decision on 5 sentences. PaLM seemed to be ok parsing it as input, but as I said, I'm unsure that it's actually valid.\nOkay, now for the big step - making my sentiment calls. In my last post, when I hit Google's service I was able to use a built-in Pipedream step to do it. I was almost disappointed by this - it was too easy.\nThis time, however, I need to make 10 calls and Pipedream doesn't support looping like that. So I switched to code:\n\nThis is one of the more complex Pipedream steps I've written. The main part of the step (the run function), takes the array from the previous code and for each, fires off a call to a function I wrote to wrap calls to PaLM (getSentiment). I fire these all at once and make use of Promise.allSettled to wait for them to finish. Then, I loop over the results and create a new array.\nI did consider changing this part of the prompt: Tell me whether the following sentence's As I think I said earlier, I was a bit concerned about how PaLM would handle a few sentences versus one. I did once try something like so: Tell me where the following few sentences and it didn't seem to hurt or help, so I just kept the prompt as is. I definitely think I could be wrong here.\nWith my results in hand, my next step was to craft an email. I decided to generate an 'average' for the results and create a table of individual posts with links and sentiment. Here's how that looks:\n\nI assign a numerical value to the results based on their order, with lower sentiment values having lower values. One issue here is that I'm not 100% sure I've covered all the possible responses from PaLM. In the earlier screenshot, you saw an 'in between' response which I never got. I did get empty responses which I considered 'Neutral'. But again, this is something I may need to address.\nAfter generating the first part of the text for my email, I then loop over the res",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Texting Email Summaries using Google PaLM AI and Twilio",
		"date":"Fri Oct 13 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1697220000,
		"url":"https://www.raymondcamden.com/2023/10/13/texting-email-summaries-using-google-palm-ai-and-twilio",
		"content":"Yesterday I shared my initial impressions of working with Google's PaLM 2 AI API. If you didn't read that article, the tldr is that it's incredibly easy to work with and I was able to get some Node.js code running in minutes. Exactly the kind of experience you want new developers to have with your product. Based on how easy it was to do that, I thought about building a real prototype of how the service could be used.\nWhat It Does\nMy simple prototype is based on the idea of handling an influx of emails. Imagine a support address or other important email address used for a company. If there is a lot of email coming in, or if the emails that do come in are critically important, it could be useful to let certain people know as soon as possible. In that notification, it would be even more helpful if a summary of the email was included. This is where generative AI can help.\nThe Workflow\nFor my workflow, I decided to use Pipedream. One of the triggers it has is email based so I went with that. This gives a unique email address that will kick off a process whenever a new email is received. Now, for my demo, that was fine, but I'd imagine in a &quot;real world&quot; scenario, you would use a real email account and Pipedream has triggers for that. Here's that step:\n\n\n\nNext, I needed to write code that would hit Google's PaLM API. As I shared yesterday, their API is fairly simple. But I didn't even have to do that as Pipedream already supports actions to work with their API. I literally just configured my access (which is just an API key) and then created my trigger. Remember that the workflow is fired off by getting a new email, so my prompt asks PaLM to summarize it:\n\n\n\nNote that my prompt is a combination of static text and content from the trigger:\nSummarize the following email in two to three sentences: {{steps.trigger.event.body.text}}\n\nNext, I added a Node.js code step whose purpose is to take the result of the PaLM API call and craft a message that will be texted. For this, I include the email subject, the sender, and the result of the API called:\n\nFinally, I added a Twilio step. I hadn't used my Twilio account in a while so I needed to log back in and update my password and such, but since Pipedream had a step for that, all I needed to do was tell the number Twilio gave me as a sender and then include my phone number. The final bit was the message body which used the result from the previous step.\n\n\n\nThe Results\nI tested it with an email I got from Disney recently. Here's the complete text:\nWe wanted to let you know that the price of your subscription will change to \n$139.99 per year on November 12, 2023. Your payment method on file will be \ncharged unless you cancel before then. You'll continue to enjoy 12 months \nfor the price of 10.*\n\nExplore plan options to find the one that best fits your needs. For more \ninformation on managing your subscription, including how to update your \npayment or change your plan, go to Account Settings or visit this FAQ for\ninstructions on how to cancel your subscription.\n\nThank you for being a loyal fan and continuing to be the best part of \nour story. We're working hard to elevate your streaming experience, and\nare excited to continue bringing you the movies, series, and exclusive \nOriginals you love.\n\nWe're always here to help. For any questions visit our Help Center.\n\nThe Disney+ Team\n\nI forwarded this to the email address Pipedream assigned to my workflow and pretty quickly I got a response:\nSent from your Twilio trial account - Summary of email &quot;Price Increase&quot; \nfrom Raymond Camden &lt;raymondcamden@gmail.com&gt;:\n\nThe price of your subscription will increase to $139.99 per year on November\n12, 2023. You can cancel your subscription before then or update your plan.\n\nNotice that the 'from' in this case is me as I forwarded it, but in a real use of this workflow, it would be the original sender. I don't know about you, but that seems like a great summary.\nAs another test, I forwarded the 'robo' mail American Airlines sent me last week when it got scared of a little cloud and wanted to cancel my flights. Here's the original text:\nSevere weather, which may impact the Dallas-Fort Worth (DFW) area, could \naffect your upcoming travel with American Airlines. At this time, there \nis no change in your flight plans. However, to better accommodate \ncustomers, American is offering additional flexibility that may allow \nyou to adjust your travel plans without a fee.\n\n\nVisit aa.com/travelalerts for details. You can change your flights on \naa.com by retrieving your reservation. If you booked your flight through\na travel agency or website other than aa.com, a representative from \nthat company will be able to assist you with changes.\n\nAnd here's the texted summary:\nSent from your Twilio trial account - Summary of email &quot;Fwd: \nSevere weather conditions may affect your upcoming travel plans&quot; \nfrom Raymond Camden &lt;raymondcamden@gmail.com&gt;:\n\nAmerican Airlines sent an e",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Look at Google's PaLM API",
		"date":"Thu Oct 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1697133600,
		"url":"https://www.raymondcamden.com/2023/10/12/a-look-at-googles-palm-api",
		"content":"Like, I assume, every single developer on the planet, I've been somewhat innudated with GenAI over the past year or so. I find it as fascinating as most folks (ask me about the number of times I've had cat-related conversions with ChatGPT) but while it's fun to play with, I've also been thinking a lot about the practical aspects of it, especially via API and integrations. I am still incredibly new to all this and, I'll admit, still way fuzzy on a lot of details, but I thought I'd share some initial impressions I've had working with Google's LLM, PaLM 2.\nFirst and foremost, note that the PaLM API, and MakerSuite, which is a web-based front-end, requires a Google login, and at least the API itself requires asking for access. For me, this took less then twenty-four hours, and I believe this time is even quicker now as Google is shrinking the waitlist. As I said, I had a pretty quick approval and I expect that's what most folks will have, but YMMV.\n\n\n\nWhen you load up MakerSuite, you are presented a set of different prompts to start playing with the API:\n\n\n\nWhen I first got here, I went right into the &quot;Text prompt&quot; option as I figured that would be the simplest and most recognizable of the options. Right away, I really appreciated the samples to help get started:\n\n\n\nI started with this prompt:\nWrite a story about a magic cat who can only do magic when speaking in reverse. \nAlso, their magic normally backfires in a humorous and harmless manner. \nThe story should be appropriate for a young child.\n\nAnd got:\nOnce upon a time, there was a magic cat named Mr. Mistoffelees. He was a \nvery special cat, because he could only do magic when he spoke in reverse.\n\nOne day, Mr. Mistoffelees was walking down the street when he saw a \nlittle girl crying. &quot;What's wrong?&quot; he asked.\n\n&quot;I lost my doll,&quot; the little girl said. &quot;I've looked everywhere for it, \nbut I can't find it.&quot;\n\nMr. Mistoffelees thought for a moment. &quot;I can help you find your doll,&quot;\n he said. &quot;But I can only do it if you speak in reverse.&quot;\n\nThe little girl was confused, but she agreed. &quot;Okay,&quot; she said. &quot;I'll try.&quot;\n\n&quot;Dolla,&quot; the little girl said. &quot;Lose, I did.&quot;\n\nMr. Mistoffelees smiled. &quot;Your doll is under the bed,&quot; he said.\n\nThe little girl ran to her house and looked under the bed. Sure enough, \nthere was her doll. She picked it up and hugged it tightly. \n&quot;Thank you,&quot; she said to Mr. Mistoffelees. &quot;You're a very kind cat.&quot;\n\n&quot;You're welcome,&quot; Mr. Mistoffelees said. &quot;I'm always happy to help.&quot;\n\nMr. Mistoffelees continued to use his magic to help people. He made \nlost children find their way home, he helped people find their lost \npets, and he even helped people find their lost keys. But sometimes, \nhis magic would backfire in a humorous way.\n\nOne day, Mr. Mistoffelees was trying to help a woman find her \nlost car. He said the magic words in reverse, but instead of making \nthe car appear, it made the woman's hair stand on end. The woman \nlaughed so hard that she forgot all about her car.\n\nAnother time, Mr. Mistoffelees was trying to help a man get a job. He \nsaid the magic words in reverse, but instead of getting the man a job,\nit made the man's tie fall off. The man laughed so hard that he forgot\nall about his interview.\n\nMr. Mistoffelees' magic was always a lot of fun, and it always made \npeople happy. He was a very special cat, and he used his magic to \nmake the world a better place.\n\nThis is pretty much on par with what I'd expect from ChatGPT and other services, although I was a bit surprised by the spelling of the cat. For folks curious, it looks to be a misspelling of Mephistopheles, although if it was going for a pun on &quot;fleas&quot;, then that's pretty cool.\nWhat I gravitated towards right away was the &quot;Get code&quot; button top. Clicking on this brings up four unique options:\n\n\n\nWhile it oddly included the response in the code, I absolutely loved how simple this was! Here's the JavaScript, after I manually removed the response:\n\nFirst off, look how easy the authentication is - I literally just need to paste in my key. I've used a lot of Google APIs in the past and nearly every time the authentication is a pain in the rear. (That's probably completely my fault!) Also note the great use of commenting here for the sample code. I'll be honest and say even with the comments, there's stuff here I don't quite get, but outside of that, I had this running locally near immediately.\nHere's the result of that:\n\nI find the safety ratings aspect the most fascinating, as it looks to be a great way to add 'guardrails' to your automated responses.\nAfter I had played with this, I took a look at the &quot;Data prompt&quot; feature:\n\n\n\nFrom what I could tell, this feature lets you take a sample list of data, and based on that input, you can then add additional rows of data and have PaLM specify the corresponding second",
		"tags":[
	        
            "generative ai"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automating Mastodon Postings with ColdFusion",
		"date":"Thu Oct 05 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1696528800,
		"url":"https://www.raymondcamden.com/2023/10/05/automating-mastodon-postings-with-coldfusion",
		"content":"I've had a lot of fun building Mastodon bots (see my list of super-important business critical bots as an example), typically using the Pipedream platform, and more recently, Cloudflare Workers. The Mastodon API is kinda stupid easy and with &quot;The Other Network&quot; going to hell in a handbasket, I don't see myself building bots anywhere else. Just yesterday I came home from the Adobe ColdFusion Summit and I thought it would be fun to see how easy it would be to build a Mastodon bot in ColdFusion. Here's what I was able to do in roughly ten minutes.\nFirst, don't forget that to add automation to a Mastodon account, you need to go into your preferences, select the &quot;Development&quot; section, and create a new application. You can give very precise permissions for your automation, but I typically just take the defaults. When you're done creating it, you're given your credentials:\n\n\n\nThe only bit you need from this is the access token.\nToot, toot, toot\nTo post your first toot (Mastodon's name for Tweets), you can send your text to the /api/v1/statuses endpoint of your server. If you haven't used Mastodon yet, one of the biggest differences between that and Twitter is the federated nature of the service. Each user (and bot) has their own server and each server has their own API endpoint. I've got a bot up on the botsin.space server so to create a toot, it's as easy as:\n\nThe result is a large struct of info about the toot:\n\n\n\nHere's an example toot:\n\nPretty Toots\nA toot can have media attached to it, more than one actually, but I think typically most folks will want to have an image attached to a toot. To do this, you first upload the media. When you do, you're given a result object that includes an ID value. Then when you toot, you can add the ID to the data. Here's an example of that:\n\nIf you're curious, here's the result:\n\n(Note - if you see some broken images, at least for me I've noticed a bit of slowdown on the botsin.space server today. It's run by an individual on his own time so it's going to be perfect, just FYI.)\nWrap It In a Pretty Package\nI took all of the above and wrapped it in a simple ColdFusion component. To be clear, the Mastodon API is quite intensive. I'm literally just wrapping the tiny bit I've needed to build my simple bots. If folks think this is useful and want to build upon it, let me know and I'll make an actual GitHub repo out of it. For now, here it is:\n\nAnd here's sample usage:\n\nThat's it, enjoy!\nPhoto by Mylon Ollila on Unsplash\n",
		"tags":[
	        
            "mastodon"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Oct 01 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1696183200,
		"url":"https://www.raymondcamden.com/2023/10/01/links-for-you",
		"content":"Hello from lovely Las Vegas, where, believe it or not, it is actually cooler (temperature-wise) than where I flew in from. This week I'll be speaking at the Adobe ColdFusion Summit and I can't wait to see some old familiar faces. I've got a speaker dinner in a few minutes so this post probably won't get wrapped up till late tonight (or tomorrow), but how about some fresh links to start off your October?\nWarp, a beautiful (Mac only) Terminal\nHaving been on Windows now for a few years, it really kinda grates on my nerves how many &quot;developer&quot; products tend to ignore the fact that most developers are on Windows. That being said, I've got a Mac for work, and my buddy Todd had shared this with me, Warp. Warp is a terminal, again, Mac only, that is pretty darn cool. For example, when you run a command, it will group the command and output into a graphically defined 'unit', making it a bit easier to visually group commands and results. It also has built-in code sharing, so you can take a command and its result and easily share it with others.\nThe best thing to do is watch this really well put together video by Jess (not sure of her last name), a developer advocate at the company:\n\nThe State of HTML Survey\nIf you've been in the web development space for long, you've probably seen, and taken, the State of JavaScript and State of CSS surveys. For the first time ever, you can now take the State of HTML Survey. This is an important survey meant to help browser and standards organizations better understand how web developers use HTML and what their pain points are. I highly encourage everyone reading this to make the time to fill it out.\nWifi &quot;Playing&quot; on an Airplane\nHaving just spent a few hours on a plane, this article was a really fun read. James Vaughan recently took a Southwest flight and had trouble with the inflight wifi. While the external connection to the Internet wasn't working, he was able to poke around a bit and find some interesting (and totally safe) information about the flight. He wrote up his experience and shared the data he was able to track. Check it out here: Wifi without internet on a Southwest flight\nJust For Fun\nI've had multiple people ask about the &quot;jellyfish&quot; behind my head when I'm giving a presentation. You can kinda see it in the\nscreen cap from a presentation below:\n\n\n\nThe picture really doesn't do it justice. There are two &quot;jellyfish&quot; (plastic things, not live animals) that swirl in the water while the color changes. If you want your own, and I love mine, you can get it through the link here (and yes, I get a small kickback from Amazon): Jellyfish\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Taking a Look at Pipedream's GitHub Integration",
		"date":"Fri Sep 29 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1696010400,
		"url":"https://www.raymondcamden.com/2023/09/29/taking-a-look-at-pipedreams-github-integration",
		"content":"It's been a little while since I've blogged about Pipedream. I'm still a very happy user of the service, I just hadn't had anything to write about recently. That changed earlier this month when they announced GitHub support on thier blog. I decided to test it out and here's what I found. I also recorded a video of it in action so feel free to skip my prose and just skip to the embed at the bottom.\nWhat is it, and what it is not...\nFirst and foremost, the important thing to know is that this feature will sync a copy of your workflow to GitHub. That's probably obvious. I'll show you what that looks like in a moment, but it means you can all the benefits of GitHub (branches, history, etc) and apply it to your workflow.\nHowever - while you can obviously clone your repo locally to get the files, you can't yet run the workflow locally. Now, I know that's been a request for a very long time and my guess (just my guess) is that this support is part of that process and in the future, we'll have that ability as well.\nAlso note that this feature is for people on the paid tier. Personally, that feels fair to me givin what Pipedream gives out on their free tier. I kinda figure if you're at the level of needing this then you are most likely at the level to be paying for it.\nGitHub Support is Project Based\nBeing that I've been &quot;away&quot; a bit from Pipedream, another feature I hadn't worked with was Projects. Projects serve as a way to group workflows together, and it's something else the service has needed so I'm really happy to see it. You can even include folders within a project for further organization. Pipedream also supports Workspaceswhich groups Projects underneath them.\nI'm going to stealborrow this graphic from their docs which shows this in action:\n\n\n\nAll of the above is just to point out that the GitHub feature is project based.\nAn Example\nTo demonstrate this (and remember, I'll have a video at the bottom showing the same), let's start off on the Projects screen:\n\n\n\nClicking on &quot;New project&quot; lets you create, you guessed it, a new project, but also allows you to enable GitHub Sync.\n\n\n\nI had already connected Pipedream to GitHub so I was able to quickly select my account. Note two things in particular here. The repository name will default to the project name, but you're allowed to tweak that. Next, by default the repo will be private, but I've unchecked it above. (I'm going to delete the repo after I publish this post as I've already got a completed repo to share.)\nOnce the project is made, you're dropped into the Project UI:\n\n\n\nOne thing to note is that you can't start creating workflows. I was a bit confused by that but eventually figured out I needed to click the nice obvious blue &quot;Edit&quot; button on the right. Doing so prompts you to create a branch. All development on your workflows will be done via branches.\nNow - let me pause here. I'll be honest and say that while I use GitHub every day, like, every single day, I keep things pretty simple in my repos. I do not make use of branches. Yes, I know that's wrong. That being said, I was a bit worried about all this and the cool thing is that Pipedream's UI makes the entire process braindead simple, even for me.\nAnyway, you can take their default branch new, or do as I did, rename it:\n\n\n\nAt this point you get a &quot;New&quot; button in the project UI for creating stuff:\n\n\n\nOk, so if you make a new workflow here, that process pretty much remains the same. You add steps, you test, etc. The biggest difference is that you get this in the navigation:\n\n\n\nClicking that will give you an awesome list of changes (which for my test with one part to a workflow is pretty small) and one button to do the merge.\n\n\n\nThe Bits\nOk, so for my real test, I built a workflow with 4 steps:\n\nAn HTTP trigger\nUse Spotify's API to get the top tracks from Depeche Mode\nManipulate the results to return a smaller set of information for each track\nAnd finally, return the result in JSON.\n\nIf you want, you can hit this workflow here: https://eowg7ft5xg257tw.m.pipedream.net\nThis workflow is available on GitHub here: https://github.com/cfjedimaster/GHIntegrationTest2\nIf you visit that repo, you'll see it has a readme and one folder for my one workflow. Going into it shows the contents:\n\n\n\nThe Yaml file describes my workflow at a high level. In it you can see the steps and metadata:\nschema: workflow/2022.04\nname: GetDepecheModeTracks\nsettings:\n  error_notification: true\ntriggers:\n- id: hi_ZbHvJ98\nsteps:\n- namespace: get_artist_top_tracks\n  runtime: nodejs18.x\n  uses: spotify-get-artist-top-tracks@0.1.1\n  props:\n    spotify:\n      authProvisionId: apn_3JhQwL\n    artistId: 762310PdDnwsDxAQxzQkfX\n    market: US\n- namespace: filterResults\n  runtime: nodejs18.x\n  uses: &quot;./filterResults/entry.js&quot;\n- namespace: returnJSON\n  runtime: nodejs18.x\n  uses: &quot;./returnJSON/entry.js&quot;\n\nFor my two code steps, they both have their own folders with one file, entry.js, which j",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Guess the (Marvel) Decade",
		"date":"Tue Sep 26 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1695751200,
		"url":"https://www.raymondcamden.com/2023/09/26/guess-the-marvel-decade",
		"content":"Many years ago, I first wrote up my experience working with the Marvel API. I find myself returning to it again and again, and this weekend I built a fun little game I think you may enjoy. It's called &quot;Guess the Decade&quot;.\nMarvel's art style has changed drastically over its long history. Back in 2018, I shared a demo that demonstrates just how much variety you can get just by looking at covers. So for example, Spider-Man in 1962:\n\n\n\nVersus 1988:\n\n\n\nAnd then 2018:\n\n\n\nGiven that there's such a variety of styles, I thought it would be fun to build a demo. If you want, you can just right to the game, but here's how I built it.\nThe Backend\nFor the backend, I built a serverless function with Cloudflare Workers. I've really been enjoying playing with them lately (see my earlier posts) and I figured this would be the quickest way to get the API running.\nLet me share the code and then I'll explain it.\n\nStarting at the bottom, the fetch method there will be fired when a request comes in for the function. It uses two credentials I've set up as secrets in Cloudflare, and then calls the main getCover function. In the response, I return the result and use CORS headers as my demo lives on another domain, at CodePen.\ngetCover is code I've shown before, it's what drives my randomcomicbook Mastodon bot, but the idea is to basically select a random year, random month, and then ask for 100 comics from that period. I filter out for comics that don't have a cover, and then return a random selection. I do a bit of manipulation on the result to return a very small part of the comic.\nYou can see this endpoint yourself here: https://randomcover.raymondcamden.workers.dev\nI want to note two things. In my testing, I'm seeing the function run real slow sometimes. I'm certain that's not Cloudflare, but the Marvel API. I need to confirm this with some testing though. And in maybe one out of like twenty or so calls, I'm getting an error with the results, which is also something I'd blame the Marvel API for, but I don't handle it well in my code. All in all, I'll just say - expect imperfection.\nAnd speaking of that, here's an example result from the call above:\n\nNotice how the title includes a date that doesn't match the date the Marvel API returned? In this case, it's the same decade, but in many examples, it will be the next one. I'll talk about how I handle that in a bit. Here's an example of that issue:\n\nNow let's turn our attention to the front end.\nThe Web Game\nFor the front end, I made use of my favorite framework, Alpine.js. My game has three states. The initial state simply tells the player what's going on.\n\n\n\nAfter you click the button, I get an image and render out buttons for guessing. I also show your current stats.\n\n\n\nWhen you click, I let you know how you did:\n\n\n\nLet's start with the HTML first:\n\nFor the most part, this is pretty simple. I think the only 'fun' part is how I handle decades. I simply loop eight times from 1950. Obviously, I could make this work for any date in the future, but I was being a bit lazy. Now let's turn to the JavaScript, this time, I'll break it down a bit. First, I specify my variables with default states:\n\nI'm not a fan of how I defined cover, but I got warnings in Alpine when it parsed the HTML. Now, I knew it wasn't a &quot;real&quot; issue because that HTML wouldn't be displayed till I had a cover, but I wanted the warning to go away. Next time, I'll use two variables, like coverTitle and coverYear.\nThe start method referred to from the button in the initial state just switches us to the main state and gets the first image.\n\nNow let's look at getImage, as there's an interesting bit in here:\n\nSo as I mentioned earlier, the Marvel API will sometimes return a comic and say it's from 1989, let's say, but with a 'date' field of 1991. Initially, I just assumed I'd have to live with it, but I then recognized that the comic titles all followed a pattern:\nUncanny Inhumans (2015) #8\nJourney Into Mystery (1952) #31\nStrange Tales (1951) #110\nThor (1966) #430\n\nGiven that, I figured I could write a regex to get the year, and amazingly, I got it right on the first try!\nThe final bit is the actual &quot;guess&quot; logic:\n\nThis basically comes down to checking the first three digits of your guess and the comic's year. I suppose you can say I've got an issue in the year 10000, and you're welcome to come find me then.\nThe last bit I want to share is some CSS. You'll notice that cover images I shared on top all have a red banner at the bottom that has the date on it. If that showed up in the game, it wouldn't be much of a game. I use CSS to crop it:\n\nThis does not work properly all the time, but works well enough most of the time. Enjoy! (Note, you could run it in the CodePen below, but it's going to be pretty small. Either hide the source or click &quot;Edit on CodePen&quot; to see it bigger.)\n\n  See the Pen \n  Guess the Decade! by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "alpinejs",
            
            "cloudflare"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Out the Alpine.js Intersect Plugin",
		"date":"Wed Sep 20 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1695232800,
		"url":"https://www.raymondcamden.com/2023/09/20/testing-out-the-alpinejs-intersect-plugin",
		"content":"A few weeks ago, I finally got around to looking at the official plugins Alpine.js supports and built a little demo that integrated the Intl spec with the Mask plugin. (You can read the post here: Integrating Intl with Alpine.js Mask). Today I thought I'd take a look at another plugin, Intersect.\nWhat Is It?\nThe Intersect plugin is a wrapper for the Intersection Observer API. This is a pretty cool web platform API that lets you monitor when DOM elements come into the visible part of a web browser. I first dug into this a few months ago in an article I wrote for Cloudinary, &quot;Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver&quot;.\nIn that article, I show how to use the API to determine when an image has become visible and swap out a lower-quality version of an image with a higher-quality one. I thought this was a pretty cool idea as folks without JavaScript still get the images, albeit more lightweight ones, and those with JavaScript get nicer images, but only when necessary.\nThe Intersect plugin looks to make using the API even simpler. By adding x-intersect to anything in the DOM (within an Alpine.js application), you can assign logic to fire when the item becomes visible. Here's an example:\n\nHere's a live example where I've placed x-intersect on an image tag. When you scroll down to it, you'll get an alert. If you scroll up and back, you'll get the alert again. This is done like so:\n\nWith the Alpine.js code being just:\n\nHere's the demo itself (and as an aside, Edge started acting up with the scrolling in the result pane for me, but I couldn't reproduce it anywhere else, so if you do have an issue with the demo, maybe quickly try Chrome/Safari/etc).\n\n  See the Pen \n  Alpine Intersection Demo (A) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nRight away, you probably ask, is there a way to handle intersects, but only care about the initial event of it being visible? Turns out that's pretty easy. There are 5 different modifiers you can use to tweak the behavior, with one being .once.\nTo make that modification to the previous demo, it's just a quick addition:\n\nIf you run this demo, you should only get the alert once.\n\n  See the Pen \n  Alpine Intersection Demo (A) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nA Real Demo\nGiven how easy it is to use, I thought I'd modify the demo I built for my Cloudinary blog post and give it an Alpine.js twist.\nI'll start with a demo that doesn't make use of this feature. In the initial version, Alpine will define a set of images and render them to the DOM, but first, it will make use of Cloudinary's APIs to serve it via its CDN, giving us some free automatic performance boosts. Here's the HTML:\n\nAnd the JavaScript behind it. I trimmed the list of images to make the code listing a bit shorter.\n\nI want to apologize for the cloudinize name for a method. That's literally one of the worst names for a method ever. But I like it. You can view this initial version below:\n\n  See the Pen \n  Alpine Intersection Demo (1) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAlright, so now I want to modify this in two ways. First, I want to use lower-quality images by default. This will reduce the initial load for all those images. This is done via a very small change to cloudinize:\n\nBasically, by adding q_30 to the URL, I've asked Cloudinary to reduce the quality down to 30%.\nNext, I modify my HTML to handle the intersection:\n\nIn the code above, I'm calling swapImage and passing the current loop index. My data is an array, so I don't care about the actual value in the loop, just the index. Also, notice .once - it only makes sense to 'upgrade' the image once.\nHere's the JavaScript:\n\nSince Cloudinary lets you swap out different operations by just changing the URL, this online line handles swapping out the 30% quality version with the 90% one. Here's a complete demo. You may want to open it in a new tab and check your devtools to see the new images load as you scroll.\n\n  See the Pen \n  Alpine Intersection Demo (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLet me know what you think, and definitely check both the docs for more about the plugin and the MDN documentation for details on the API as a whole.\nPhoto by Deva Darshan on Unsplash\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Sep 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1694973600,
		"url":"https://www.raymondcamden.com/2023/09/17/links-for-you",
		"content":"Another weekend and another set of links for yall to enjoy. Yesterday my wife and I drove our eldest to NOLA and the airport there as he begins a ten-month excursion teaching in Germany. I'm so incredibly excited for him and I know he is going to do incredible. Meanwhile, we're still waiting for the 90+ temperature days to come to an end. At this point, I don't expect that before October at the earliest.\nAs a reminder, I am still open to folks sponsoring these blog posts (or the blog in general). If you, your company, or your service, is looking to help support this site, just reach out!\nA Practical Guide to Service Workers\nBack a few years ago, I began digging deep into PWAs and with that, service workers. I don't really research them much anymore as it's more of a known quantity, but it does occur to me that there are probably still a lot of folks who need to learn how they work and how they operate. This guide by Kelvin Okuroemi does a really good job of introducing the concept and I really like the fact that he shows devtools usage as well. When working with service workers, knowing how devtools assist you is really important. Your browser devtools are always important, but even more so with service workers.\nI will quickly get on my soapbox and remind people that you do not need to be building an &quot;app&quot; to take advantage of PWA technologies like service workers. They can help any kind of website.\nBetter Sorting via Intl\nI love the Intl feature of the web platform. In fact, I used it a few days ago in my blog post on integrating it with Alpine's Mask plugin. Recently the Bytes newsletter featured an interesting article on it. Bytes is a free weekly JavaScript-focused email newsletter that I definitely recommend my readers sign up for. Each issue typically features a &quot;Spot the Bug&quot; article where readers are asked to look at a bit of code and, well, find the bug.\nIn issue 214, there was a particularly interesting bug shared. Unfortunatey I can't link directly to that part of the newsletter, but go ahead and read the entire thing to see both the issue discussed as well as the solution. (I suppose I've already spoiled it a bit by talking about Intl, but it's still absolutely worth your time!)\nLet's Talk about React\nLastly, here is a really deep thought piece concerning React, &quot;Things you forgot (or never knew) because of React&quot;. As you can imagine, this is a long look at what folks may be missing if their entire conception of the web is colored by the dependency on React. This can be best summed up by a quote from the epilogue:\n\nI’ve come to believe React's popularity is, in no small part, because folks don’t look beyond it.\n\nTo be clear, this isn't a hit piece on React, but more a &quot;wake up&quot; call for developers who have possibly focused so much on React that they haven't gotten an appreciation for the alternatives or for the idea that what React does may not actually be what they need.\nAs I said, this is a pretty deep post, but I definitely recommend checking it out. Personally, I never really got into React. Adobe is a pretty big user of it, but I've never needed to use it myself in my own job. React has always felt like a &quot;good framework that's not for me&quot; and honestly, if I ever need to pick up, I'm not too concerned about being able to do so quickly.\nOne Last Thing...\nWhen I started this series of posts, my idea was to share three links each time. Occasionally I'd share a fourth one that wasn't tech or industry-related in any way and I'm going to try to make that a consistent feature here, mainly as I love music and love sharing music with others.\nMy wife and I recently rediscovered Tetris Effects and I had forgotten how incredible both the visuals and soundtrack were. While this video doesn't show in gameplay, the track is one of my favorites from the game.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a QR Coder Web Component",
		"date":"Wed Sep 13 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1694628000,
		"url":"https://www.raymondcamden.com/2023/09/13/building-a-qr-coder-web-component",
		"content":"It's been a hot minute or so since I played with web components, mainly because I've been re-evaluating when I think it best makes sense to use them. One idea I've been chewing over lately is that progressive enhancement could really be the sweet spot for components, something I really got into earlier this year when I shared a sortable table component that, if it failed to load, wouldn't break anything.\nA few days ago, my buddy Scott shared an interesting tip for Chrome/Edge users, specifically that you could right-click on a web page and generate a QR code for the URL:\n\n\n\nAfter selecting this, the code is shown on top of the browser along with the URL:\n\n\n\nOne nice thing is that the URL there is editable, which means you can remove all the social tracking crap that you get when you follow links from Facebook, etc. Also, you can just type for fun in there and watch the QR code change. Go ahead and do that, I did.\nI thought it might be a nice idea to build this into a web component. The idea is, that you drop the component on a page and it automates the process of creating a QR code for the current URL. While a bit rough, here's what I came up with.\nChoosing the Library\nI knew I wasn't going to generate the QR code myself, so I googled around a bit to find a decent JavaScript library for working with QR codes. Many of the results I found were a bit old. Technically there's nothing wrong with that. I've seen the same for RSS parsing. The spec hasn't changed much so sometimes there's no need to do updates. That being said, I settled on QR Code JS by Daniel Jackson. I liked how simple the interface looked and it seemed to have some recent activity on the repo.\nAs an example of the API, here's a complete demo from their read.me:\n\nNice and simple. The library supports quite a few options, but I liked how simple that looked.\nThe Web Component\nOkay, now for the fun part, the component. In my mind, the ideal use would be to place the component in your HTML, roughly where you would like to offer the option to users. While not the best, in this example, I've just put it at the bottom of a page.\n\nI decided my component would have 3 arguments. Height and width, both optional with defaults, and the third &quot;argument&quot; is the text between the tags. As my output is going to be a button, the text here would be used for the button.\n\n\n\nNow let's take a look through the JavaScript. I begin by importing the library:\n\nNext, I define the component itself, starting with the class and constructor:\n\nThere are my defaults for height and width, and text will be used if you don't pass anything to the tag. You'll see how that works in a moment.\nThe real work begins in connectedCallback:\n\nI begin by updating height and width if passed in. The component itself is only one button, and note the use of the slot tag. This will use the default text defined above, unless you put something in the middle. One minor bit of warning there - if you do something like this:\n\nThe line break is considered input and will be used.\nNext, we need to do the interactivity. My idea was, on clicking the button, to generate the QR code and render it. I really struggled with where to put it. I initially considered centering it over the button (you'll see a bit of commented-out code related to that), but I ended up just centering it on screen.\n\nI also use a click event on the image so it can be dismissed. You can play with this yourself in the CodePen below:\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIt's a bit overwhelming in the small iframe above, but, it's just a demo so I'm happy enough with it.\nAs luck would have it, about three hours ago, I got the latest issue of Frontend Focus, and one of the linked articles was for a QR code web component: qr-code. This project is really cool and even supports animations. As I built mine just for practice with web components, I'd definitely take a look at this project for &quot;real&quot; usage.\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Fun With Front Matter: Part 4 - Featured Posts",
		"date":"Tue Sep 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1694541600,
		"url":"https://www.raymondcamden.com/2023/09/12/fun-with-front-matter-part-4-featured-posts",
		"content":"It's been a few days since my last post in this series. I'd like to blame something in specific but honestly, it's just life. Today's tip will - again - be short and sweet but hopefully helpful. The idea of a &quot;featured&quot; post is that there may be content that, no matter the age or view count in your stats, you want to highlight. It could be your first blog post. A post announcing a new job or life event. Or anything really. How can we use front matter to support this?\nMarking Featured Content\nOne approach to marking content as featured could be to simply add a featured value to the front matter, like so:\n---\nlayout: post\ntitle: Gamma Post 7\ntags: posts\ndate: 2023-04-08 12:00:00\nfeatured: true\n---\n\nThis is nice and clear and would probably be what I'd use for... for anything but Eleventy. One of Eleventy's features is the ability to quickly 'grab' data by tags. By that I mean, if we use the tag foo for a piece of content, we can then later get all content with the same tag using collections.foo. So while in general I want this series to be pretty engine agnostic, for now we'll use the tags attribute like so:\n---\nlayout: post\ntitle: Beta Post\ntags: ['posts', 'featured']\ndate: 2020-10-05 12:00:00\n---\n\nNote the switch in how the value is written as it's switched from one value to an array. You can also write that like so:\n---\nlayout: post\ntitle: Beta Post\ntags:\n    - posts\n    - featured\ndate: 2020-10-05 12:00:00\n---\n\nPersonally I prefer the shorter approach I think, but just try to be consistent.\nRendering Featured Posts\nAs I said above, by using the tags approach, we get the benefit of Eleventy making it super easy to work with. I would imagine featured posts would want to be highlighted, so I've added them to the home page, like so:\n\n\n\nThis comes down to simply using collections.featured:\n\nWe could also add a bit of 'flair' on the post page itself, and by flair I mean just a bit of text in a simple demo:\n\n\n\nI could also imagine the page layout itself including the list of featured posts, that way the entire list is always visible on every page.\nThat's it for this quick tip! You can find the source for this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/funwithfrontmatter4\np.s. Is any one actually reading these?\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Integrating Intl with Alpine.js Mask",
		"date":"Wed Sep 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1694023200,
		"url":"https://www.raymondcamden.com/2023/09/06/integrating-intl-with-alpinejs-mask",
		"content":"I've been using Alpine.js for quite a while now (although I still make silly mistakes, see the p.s. at the end) but haven't yet looked at the &quot;official&quot; plugins. Listed in the docs, those plugins include:\n\nIntersect - a simple hook to recognize when an element is visible (I plan on blogging about this later)\nPersist - a simple hook to add persistence to Alpine data (another plugin I plan on blogging about)\nFocus - a way to manipulate focus\nCollapse - a simple UI plugin for collapsible content\nMorph - another UI plugin that attempts to transform one set of HTML into another (I honestly don't quite get this one - yet)\nAnd finally, Mask.\n\nMasking Fields with Alpine\nThe Mask plugin adds a &quot;mask&quot; to an input field. This is a pretty common UX pattern where an input field will expect data in a particular form, and as you type, it will automatically force it into that form. I use the word &quot;force&quot; because, at least to me, sometimes these types of fields can be incredibly annoying. As an example, a field looking for a day (M/D/Y) that auto-inserts the slash but doesn't stop you from entering a slash will typically result in me having two slashes. Why? Because I type fast and don't even see the slash inserted. Then I have to back up and delete it and typically I make the same mistake again. I end up &quot;fighting&quot; with the field and it's more annoying than helpful.\nThat being said, as I played with the examples on the Alpine docs, I didn't have any trouble and it seemed to work really well.\nTo add the plugin, you can simply add another script tag, but ensure you put it before the core Alpine one. Here's an example from their docs:\n\nAnd then you use the x-mask directive to define the format of the field. So for example, to define a date mask, you could use:\n\nWhen defining the mask, the letter 'a' allows for any alphabetical letter, 9 maps to digits, and * to any character. So above, I'm basically saying: Number number slash (auto typed), number number slash (auto type), followed by four numbers.\nHere's a CodePen using the example from the docs. Note the use of placeholder to let the user know we mean the American style of dates.\n\n  See the Pen \n  Alpine Mask testing by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne thing I'll note about this example - you'll want to keep the input type set to text (or blank, which defaults to text) otherwise the UI of the browser's date field conflicts with Alpine's plugin. It also means if you need a real date object out of the field you'll need to parse it. For the heck of it, here's an example where I bind a value, realDate, to the input field.\n\n  See the Pen \n  Alpine Mask testing (1) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThis works for the most part but needs to handle invalid dates better as you can enter a day value of 99.\nDynamic Masks\nThe plugin also allows for dynamic masks. This is done by adding :dyanmic to the markup. I'll start off with their example for credit cards:\n\nNote the magic keyword, $input, refers to what's in the field currently.\nInstead of defining it inline, you can also just pass the name of a function, and Alpine will run that function with the input as an argument. Here's an example that attempts to do basic mapping on a US phone number:\n\n  See the Pen \n  Alpine Mask testing (1a)) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe dynamic part kicks in by looking at the beginning of the number. If the user includes 1 for the international number, I format it differently, and since the format inserts a + in front, the logic has to handle the initial state of just starting with a 1 and then switching to +1. This is probably not perfect, but it gives you an idea of how flexible the plugin can be.\nMoney, money, money\nFinally, the plugin has a special feature just for money. The simplest form is:\n\nThis will do two things. It will automatically add commas for thousands separators and use a decimal for, well decimal inputs. I don't think I need to show a demo of this, but the docs have a few you can try quickly. What interests me is the additional arguments that $money support. After $input, you can pass up to three optional arguments. In order they are:\n\nThe decimal separator. In their docs, they show specifying a comma for values like so: 999,99.\nThe thousands separator. The same places that tend to use commas for decimals tend to use periods for thousands. So for example:  9.999,99.\nAnd finally, you can specify a different number for precision. Honestly, I'm not sure when you would use that in money.\n\nIf you wanted to set the mask for France, which uses a space for thousands and a comma for decimals, you could use this: $money($input, ',', ' ').\nAccording to this random doc on Oracle's site, America and Great Britain are actually two of the few places to use a period for decimals.\n\n\n\nOk, so given that we can be flexible in how we set up the money mask, can we use the browser's built-in Intl support to make",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Mon Sep 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1693850400,
		"url":"https://www.raymondcamden.com/2023/09/04/links-for-you",
		"content":"Welcome to another collection of links, and for today, a very &quot;component&quot; flavored set of links. I've been really interested in web components the past few months (you can peruse my articles on the topic) and lately there's been a lot of writing on the topic. That's been tied to chatter online as well, but one of the reasons I started this series of posts was to help people find things they may have missed on social media. Personally, I'm finding my own thoughts on the subject changing as well. I still like them, but I want to keep in mind the user experience on a site that uses them. As a web developer, I shouldn't sacrifice the end-user experience for my own developer experience. I think I'm most interested in cases where web components can progressively enhance functionality. Anyway, here's our links.\nShining Light on the Shadow DOM\nHere's a great presentation by Cassondra Roberts from CSS Day. She digs incredibly deep into styling and components. You can watch the complete presentation below:\n\nAnd the slide deck itself can be viewed here: https://allons-y.llc/presentation/cssday/#0\nWhy Not Web Components?\nNext up is an article by Dave Rupert, &quot;If Web Components are so great, why am I not using them?&quot;. This is a short article but does a great job of explaining why it's taken so long for web components to get any kind of traction. Speaking for myself, while I had been hearing about them for a very long time, I hadn't really felt comfortable spending time learning about them until recently. I pretty much agree with every point Dave makes here.\nSEO and Web Components\nFinally, a quick article by Burton Smith, &quot;SEO and Web Components - 2023 Edition&quot;, where he shows how search engines, well Google and Bing alone, handle indexing sites with web components. To be honest, the results match up with what I'd expect (Google has done a good job with JavaScript-generated content for a long time), but it was nice to see that the reality matched the expectations. I would have liked to see another search engine covered, like DuckDuckGo perhaps, but traffic from there is probably near zero for most folks.\nOk, now I feel bad saying that. I just checked my stats and while it is small compared to Google (14,477 referrals), DuckDuckGo (498) brought in more traffic than Bing (373).\nAnd finally...\nThanks to the JavaScript Weekly newsletter for sharing this gem - Chip Player JS. This site servers over twenty thousand different midi tunes, all playable via the site itself. It also has a badass visualizer.\n\n\n\nAnd yes, you can find Axel F on there.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Join Us at The Undefined Show!",
		"date":"Fri Sep 01 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1693591200,
		"url":"https://www.raymondcamden.com/2023/09/01/join-us-at-the-undefined-show",
		"content":"On September 11th, Todd Sharp, Scott Stroz, and myself will be launching a new livestream called The Undefined Show. Every Monday at 8 PM EST, we'll be hosting a session on different topics, including development, languages, developer relations, and most likely quite a bit of non-technical things as well. Heck, at bare minimum, join us to heckle!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Fun With Front Matter: Part 3 - Handling Edits",
		"date":"Thu Aug 31 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1693504800,
		"url":"https://www.raymondcamden.com/2023/08/31/fun-with-front-matter-part-3-handling-edits",
		"content":"I hope by now that folks are getting that the point of this series isn't so much technical but inspirational. I think a lot of people approaching front matter tend to keep it rather simple - title, date, tags or categories, and when I envisioned this series I really wanted to explore some more interesting things you could do. Today's entry is an example of that. Given that a (good) blog post always contains a date, how would you handle noting a post that's been edited? Here's a simple example.\nAdding to Front Matter\nLet's start by simply adding a new edited property to our front matter. Here's an example in a blog post:\n---\nlayout: post\ntitle: Gamma Post\ntags: posts\ndate: 2020-10-10 12:00:00\nedited: 2021-10-12 12:00:00\n---\n\nThis is the Gamma post.\n\nThis post was written, originally, on October 10, 2020, but was edited on October 12, 2021. That was so simple I almost feel dumb sharing it, but we've got to start somewhere, right?\nDisplaying the Edit\nOriginally, my post layout had code like so:\n\nThe dtFormat filter looks like so:\n\nI've said it before and I'll say it again - I love Intl.\nAdding support for an edited field then just takes adding a bit of code:\n\nWhen displayed, you get something like so:\n\n\n\nCool, but let's maybe take it up a notch. Sometimes it would be nice to know what was edited. How about optionally supporting a description of the edit. Here's an example:\n---\nlayout: post\ntitle: Delta Post\ntags: posts\ndate: 2020-10-06 12:00:00\nedited: 2021-10-12 12:00:00\neditReason: Fixed bad link.\n---\n\nNow let's add this to the layout:\n\nBasically, if an edit, show it, and if an edit reason, show that. Those IFs may get a bit hard to read there, but I think it's acceptable. Here's an example of this rendering.\n\n\n\nAs with the other posts in this series, you can find the source at my Eleventy repo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/funwithfrontmatter3\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Fun With Front Matter: Part 2 - Follow-ups",
		"date":"Tue Aug 29 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1693332000,
		"url":"https://www.raymondcamden.com/2023/08/29/fun-with-front-matter-part-2-followups",
		"content":"Today I'm following up (heh, get it) on the series I started yesterday on interesting use cases for your Jamstack site's front matter. In yesterday's post, I described how to use front matter to define a list of &quot;related posts&quot; to a blog post. Today's post is a natural follow-up to that one and deals with... well follow-ups!\nWhen I was describing the behavior of related posts in my demo yesterday, I mentioned that the &quot;relationship&quot; would be one way. So post A may be related to B and C, and we would render that, but it would not automatically create a relationship from B to A. As I said in the post, I can definitely see people disagreeing with that, but I had in mind a more natural way for an older post to be related to newer content, a follow-up.\nDefining the Follow-Up\nSo unlike the previous post where I had to define an array in front matter, this time I'm going to use a simple key/value pair. Here's an example:\nfollowup: /posts/beta/\n\nAs discussed in the last post, I need some way to link to another post and the URL seems like a natural fit. That was easy!\nDisplaying the Follow-Up\nNow that I've got a way to define a follow-up, let's look at how to display it. As with the previous post, my example code uses a post layout where I define how blog posts are rendered. Previous, the content above where the blog post would go was rather simple:\n\nIn my opinion, if I've written a follow-up to a post it should be noted on top, so I'm going to add it there:\n\nThe change here is to simply check for the existence of the followup data, and if so, display it. To get the post we'll call a new filter named getByURL. Remember that we need to pass the actual data (the URL) as well as the collection of data to check.\nLet's look at that code now.\n\nI got fancy and made use of reduce to transform the array of posts to one object based on the URL. Nice and simple. Here's an example of it being rendered:\n\n\n\nAnd that's it! Yes, this is a pretty trivial use of front matter, but as I said, I think it pairs well with the previous post. You can find the source code for this tip here: https://github.com/cfjedimaster/eleventy-demos/tree/master/funwithfrontmatter2\nExtra Credit\nOk, if you want, you can stop reading now. In my original post, I mentioned how I wanted this series of posts to be Jamstack engine agnostic, but that I'd be using Eleventy as it's my favorite. I want to share a quick tip that's specific to Eleventy and this post.\nIf you remember from yesterday's post, I had an array of URLs pointing to related content. The filter to transform those URLs into an array of pages looked like so:\n\nIn today's post, I'm doing similar logic, but for just one URL. One nice thing that Eleventy supports is the ability for one filter to call another, and that means I can rewrite the above logic (assuming I've got a site using both) to be a bit simpler:\n\nNow I simply map the passed-in array to an array of pages using the getByURL filter. This example can be found in the same repository linked above.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Fun With Front Matter: Part 1 - Related Posts",
		"date":"Mon Aug 28 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1693245600,
		"url":"https://www.raymondcamden.com/2023/08/28/fun-with-frontmatter-part-1-related-posts",
		"content":"I'm kicking off a little series of tips today that's been sitting in my &quot;Blog Ideas&quot; queue for some time. The idea, &quot;Fun with Front Matter&quot;, was based on the idea of taking a look at some of the fun/interesting/hopefully useful things you could include in your Jamstack site's front matter. More than just title, date, and so forth, but useful features you could add to your site driven by data in your content's front matter. Some of these things are features I've talked about before, but I thought a little series would be fun to explore this week. All of my examples will use Eleventy but could be applied to any other project. With that out of the way, let's get started!\nTagging Related Posts\nIn this first blog post, I'm going to discuss how to handle &quot;related posts&quot;. Typically this means, I'm writing about subject X, and there are a few older articles I think my readers may want to review as well. Normally on my blog, I just link to them in either the introduction or conclusion of a post. But let's consider a simple way to automate this process in our front matter and Eleventy layout templates.\nThe first decision we need to make is if this will be 'bidirectional' - i.e., if post A says it's related to post B, should post B be related to A? I'm going to say no. In my opinion, I typically want folks to go back and look at those older posts, but folks landing on those older posts may not need to read the newer ones. I do have a blog post in this series coming up that will address that, but for now, I'm going with the idea that these links will be one-way.\nOK, easy enough. Next, we need to figure out how to add related posts to our front matter. I use YAML in my Eleventy front matter, and while YAML is easy, it can get a bit complex when you leave simple key/value pairs.\n\n\nA look at how simple YAML is...\n\nThe syntax for arrays, or at least one syntax, looks like so:\n---\nlayout: post\ntitle: Gamma Post\ntags: posts\ndate: 2020-10-10 12:00:00\nrelated:\n    - One thing\n    - Leads\n    - To another\n---\n\nIn the front matter above, related designates an array with each array item beneath it, indented. Note that tabs are not allowed in YAML! I entered four spaces for each item.\nSo that's how to define an array. The next question is... what exactly do we put there??? When I'm working on my blog, for example, I've got a folder named posts that includes over six thousand Markdown files. These files are separated by folders based on year and then by month. We need a unique identifier here that can be used to directly associate with one other piece of content.\nIf we look at page content, you will see this:\n\n\n\nLooking at the available options, one possible solution would be to use fileSlug, but that may change in the future, especially if your site grows to a point where you begin re-organizing your content.\nWhat shouldn't ever change though is the url value. So let's use that. Given a small sample site (I'll share a link to the source in a bit), I specified the list like so:\n---\nlayout: post\ntitle: Gamma Post\ntags: posts\ndate: 2020-10-10 12:00:00\nrelated:\n    - /posts/delta/\n    - /posts/alpha/\n---\n\nNow that we've tagged the posts, it's time to use them.\nDisplaying Related Posts\nIn my simple blog, each post makes use of a post template that looks like so:\n\nAs a mostly arbitrary decision, I decided to display related posts after the main content. Obviously, there are other options, like on top, or the side. First, though, check to see if any related posts exist.\n{% if related %}\n&lt;h3&gt;Related Posts&lt;/h3&gt;\n{% endif %}\n\nInside, I'm going to use an ordered list. I'll get my related posts by passing my array of content to a filter I'll write in a second named getRelated. Eleventy filters don't have access to collections, so I pass that as the second argument. If you haven't seen this syntax before it may be a bit confusing, but related will be the first argument and collections.posts the second.\n{% if related %}\n&lt;h3&gt;Related Posts&lt;/h3&gt;\n\t&lt;ul&gt;\n\t{% assign posts = related | getRelated: collections.posts %}\n\t{% for post in posts %}\n\t\t&lt;li&gt;&lt;a href=&quot;{{ post.url }}&quot;&gt;{{ post.data.title }}&lt;/a&gt;&lt;/li&gt;\n\t{% endfor %}\n\t&lt;/ul&gt;\n{% endif %}\n\nNote that I'm just linking to the post and showing the title. It would also make sense to include the date. Now let's look at the filter.\n\nGiven that our first argument is an array of related posts, we want to filter the larger array, posts, to those that are in the array. I suppose I could have used .filter as well. The net result would be just the related articles. Here's a screenshot of that post:\n\n\n\nYou can find the source for this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/funwithfrontmatter I hope this helps, and tomorrow I'll have another tip to share. Enjoy!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with the Storage API",
		"date":"Fri Aug 25 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1692986400,
		"url":"https://www.raymondcamden.com/2023/08/25/working-with-the-storage-api",
		"content":"Earlier this year at WWDC, Apple announced a whole set of new features coming to Safari in version 17. While that is not out yet, it's still a pretty large set of updates. I've not been shy about my view that Safari has been holding the web back for a while, but I'm happy for any improvements that show up. While looking at the long list of updates, I saw Storage mentioned:\n\n\nWebKit has made some big updates to the storage quota policy. Previously, an origin had a starting storage limit of 1 GB. When exceeding the limit, the subsequent storage operation would fail in Home Screen web apps, or the user would see a prompt asking to increase the quota for the origin in Safari. Starting in macOS Sonoma, iOS 17 and iPadOS 17, the quota is calculated based on total disk space without the user's input. The origin generally gets a much higher limit, and the user isn't prompted in Safari. To get the estimated value of the current origin quota and usage, you can use the newly supported `navigator.storage.estimate()` method.\n\n\nAs each origin gets a higher storage limit by default, WebKit will evict data by origin when the total usage of all origins is bigger than a certain value, the \"overall quota\", calculated based on total disk space. An origin is exempt from eviction when its storage mode is persistent. To check the storage mode of your origin, you can use navigator.storage.persisted(); to request the mode be changed to persistent, you can use navigator.storage.persist(). Critical bug fixes have been made to ensure the storage mode value is remembered across sessions, and eviction will count on it. The Storage API is now fully supported.\n\n\nEmphasis mine. I've written quite a bit about storage and browsers, but the Storage API itself is one I haven't played with. Here's what I've found.\nWhat does it do?\nThe Storage API breaks down to a few core methods:\n\n\nCheck for the type of persistence: One thing that is possibly a bit confusing is how the browser handles &quot;persistent&quot; data. If you're like me, you hear persistent and assume it means, well, persistent. That's not quite right. A browser will persist data but also evict it if storage begins to get limited. Think of it like a casual relationship. The data can stick around, but there are no guarantees if things get sticky.\n\n\nAsk to be persisted: Again, this could be confusing, but this is really the ability to ask for &quot;more* persistent persistent data. This means that the browser won't willy nilly delete the data, but rather ask the user if they're cool giving the website more storage. Asking to be persisted is basically asking your girl/boyfriend to get married.\n\n\nAsk for an estimate: This lets you check how much data has been stored along with a breakdown showing how it's broken down by type, for example, Service Worker based cache versus IndexedDB.\n\n\nLast but not least, get the file system: This gives a hook to the &quot;origin private file system&quot; which lets you read and write files and directories in a directory sandboxed for the website.\n\n\nFor today, I'm going to focus on the first three items.\nHow is my data persisted?\nTo see if data is persisted (and again, we mean really persisted past an eviction due to low storage), we can use navigator.storage.persisted. This returns a Promise that resolves to yes or no. So for example:\n\nThe default should be false, which makes sense I think if we try to be as 'nice' as possible to the user's disk drive.\nHow do I request the REALLY persistent goodness?\nTo ask for the really persistent version of persistence (not confusing at all), you can use navigator.storage.persist. Here's an example:\n\nIn this case, the true/false result refers to how the permission was handled. Now, here comes something interesting. I tested on a localhost server, and CodePen, in both Chrome and Edge. In both cases, when the request was fired, it was immediately granted and I wasn't asked. I tried in Firefox, and got a prompt:\n\n\n\nIt's entirely possible I granted storage on Chrome/Edge in the past. I tested in console on other websites and every attempt I ran always returned Permission for greater persistence was not granted., even if I added the call as an event handler based on user input. As a last-ditch effort, I rolled out a Glitch project: https://lacy-awesome-park.glitch.me/\nOn this site, Firefox worked perfectly, I was given a prompt. In both Edge and Chrome, I was immediately told no without a user prompt.\nOn a whim I quickly Googled and found this gem from 2017:\n&quot;Persistent storage does not prompt the user in Chrome. Instead, the browser uses latent metrics (is the page bookmarked, or does it have notifications permission granted for instance) to determine if the user is using the site sufficiently to allow storage to be persistent.&quot;\nAnd then found this article:\n&quot;Chrome, and most other Chromium-based browsers automatically handle the permission request, and do not show any prompts to the user. Instead, if a sit",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Debugging Cloudflare Workers with Logs",
		"date":"Tue Aug 22 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1692727200,
		"url":"https://www.raymondcamden.com/2023/08/22/debugging-cloudflare-workers-with-logs",
		"content":"As with some of my previous Cloudflare posts, I've got a video version of this content so if you would rather watch that than read, just jump to the bottom. For the rest of you, here's a look at how to do some basic debugging with your Cloudflare Workers.\nThe Worker\nBefore I get into how to debug, let's consider a simple Worker that has an API for returning random numbers. (Don't use Cloudflare Workers, or any serverless platform, for something so simple!)\n\nThe logic boils down to - check the query string for a min and max value, do some basic validation, and then return a random number in that range. If you want you can actually hit this API here: https://randomnumber.raymondcamden.workers.dev/\nDebugging Locally\nYou'll notice a few console.log messages in there. When running the Worker locally via npm run start, the console messages will show up right in your terminal. I ran the API a few times with different values in the query string and you can see the debug output:\n\n\n\nRather simple and actually what I expected to see when testing, so I was happy this &quot;just worked&quot;.\nDebugging in Production\nDon't. Ok, but what if you want to? Or simply check the output from logs? You've got two options for that. First, in the Cloudflare Workers dashboard, select your Worker, and then select the Logs tab:\n\n\n\nYou then click the nice blue button, Begin log stream:\n\n\n\nNow, hit the URL for your Worker, or if it's triggered some other way, wait for that to happen. Once one or more events have happened, you'll see them show up:\n\n\n\nFinally, click on one to see the output. It's a big JSON result that I've copied below. I did remove some of the data to shorten it up a bit.\n\nYou'll notice the log messages, on top, with a handy timestamp as well. By the way, note how the cf part includes detailed geographic information about the requestor.\nSo that's debugging in the dashboard, how about in your local terminal? Turns out that's rather easy with the wrangler CLI, just use:  npx wrangler tail X where X is the name of your Worker. So for me, that's npx wrangler tail randomnumber.\nOnce it's running, just hit your API again and you'll immediately see results.\n\n\n\nWhile I like the level of detail in the dashboard, I see myself using the console version much more as it's focused on just the actual logs.\nThat's it! Let me know what you think, and enjoy the video version below.\n\nPhoto by Timo C. Dinger on Unsplash\n",
		"tags":[
	        
            "cloudflare"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Aug 20 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1692554400,
		"url":"https://www.raymondcamden.com/2023/08/20/links-for-you",
		"content":"Looks like I was a bit late in doing my links post this month. Like most of the country, my kids went back to school over the past week or so and I think the prep time ahead of that just made the month fly by. Last week was incredibly rough for me. I'm not going to get into why, and I'm very much aware of how privileged I am to be writing this in the comfort of my home with a job to go to tomorrow, but yeah, I'm kinda glad it's a new week. (Although I'll be even happier when our high temps dip below 100.)\nLost at SQL\nI'm a big fan of online games as a way of learning technology. While I don't write nearly as much SQL as I used to (in fact, the query I wrote a week or so ago was probably my first query in years), but this looks like a very cool way to get up to speed with SQL, or Structured Query Language. (To be honest, I never hear people say that.)\nLost at SQL is a free online tool that walks you through writing queries to advance in the game. It's got a beautiful design (and a cool as hell intro video, don't skip it!) and a great story.\n\n\n\nComponent Party\nHere's a fascinating tool. Component Party lets you preview multiple front-end frameworks and see how they do various things, like declaring state or handling lifecycle events. For example, the screenshot below shows how state is declared in React, Vue, and Alpine.\n\n\n\nI'll remind folks that while the Alpine example is valid and nice and simple, usually one would declare state in JavaScript so as to not overload the HTML with too much code. But I love that Alpine allows for it. As a reminder, if you want to learn Alpine, I've got a full online (and free!) video course you can watch.\nBlast from the Past - &quot;New&quot; JavaScript Features\nHow about this - a blog post from 1996 covering new JavaScript features in Netscape Navigator 3. Fun stuff like the Array constructor, the Image object, and the src attribute for the script tag. Yes, before that everything was inline. Fun times, am I right?! My first experience with JavaScript was the initial release. I have fond (not) memories of locking up my machine with bad code mistakes and needing to reboot my Gateway PC to get back. Yep, good times.\nAnd finally...\nA few days ago my good buddy Brian Rinaldi shared a song from a band I've never heard of, Yard Act. The song is really darn good, but the video is excellent. It's a bit long, but trust me, watch it.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "ColdFusion Component for Adobe Acrobat Services",
		"date":"Thu Aug 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1692295200,
		"url":"https://www.raymondcamden.com/2023/08/17/coldfusion-component-for-adobe-acrobat-services",
		"content":"Last month, I wrote up a post demonstrating how to use Adobe Acrobat Services with ColdFusion. This week I took some of the code I had written for that post and turned it into a proper GitHub project. You can find the latest code here: https://github.com/cfjedimaster/coldfusion-cfc-acrobat-services\nTo use this, you'll need credentials, which you can get and use for free for up to 500 transactions. (The docs go into detail about how that works.)\nCurrently I only have a subset of our APIs supported, but I plan to hit most of the rest in the next day or so. To give you an example of how it works, here's a sample that uses our Extract API.\nFirst, you instantiate the component with your credentials. You would probably do this in your Application.cfc file instead of in one particular file.\n\nNext, you can upload the PDF:\n\nAnd then you simply kick off the Extract job:\n\nThis returns a 'job' object that contains a URL you can check for status. I used a simple while loop for that:\n\nIn the end, the job variable will contain two things - a link to the JSON output and a link to a zip file that contains any extract tables, images, and the JSON as well. Downloading the JSON is as simple as:\n\nOr, if you don't need to keep it (which, I would, because why process it more than once, but you do you), you can HTTP the JSON and work with it. Here's an example of that:\n\nThis returns an array of headers that would be a useful summary for a PDF.\nAnyway, I hope this is helpful to folks. I will remind everyone that my ColdFusion skills are perhaps a bit rusty so PRs are absolutely welcome at https://github.com/cfjedimaster/coldfusion-cfc-acrobat-services.\n",
		"tags":[
	        
            "adobe"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Building a Mastodon Bot with Cloudflare Workers",
		"date":"Mon Aug 14 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1692036000,
		"url":"https://www.raymondcamden.com/2023/08/14/building-a-mastodon-bot-with-cloudflare-workers",
		"content":"I can't go a day (or two) without building a bot of some sort, and last week was no exception. I've been a fan of the Marvel API for nearly a decade now and one of my favorite examples of it is my random comic book cover bot. I thought I'd use the Marvel API as a way to build another bot, but this time on the Cloudflare Workers platform. Here's how I did it.\nThe Architecture\nSo obviously I'm using Cloudflare Workers, but I decided to make this project a two-step process. In my last post, I shared how you can &quot;connect&quot; one Worker to another via service bindings. Obviously, this is practical for reuse, and while it may be a bit overkill for this, I wanted to put what I learned in the last post in practice. My project's two workers are:\n\n\nA scheduled Worker that makes use of their Cron triggers support. This worker will be run on a schedule, call the second Worker for its data, and then post to Mastodon.\n\n\nA Worker that wraps calls to the Marvel API, specifically one to get a random character.\n\n\nThe latter of the two is the simpler one, so let's start with that.\nUsing the Marvel API for Character Information\nSo, much like my 'random comic book' logic, I get a random Marvel character by:\n\nFigure out the total number of characters. I did this using their interactive tester and making note of the total number of results. As of August 2023, that number is 1562. Here's an example of that output:\n\n\n\n\n\nThen simply call the character endpoint with a limit of one and an offset of a random number in that range.\n\nFor the most part, it's all relatively simple, except Marvel requires you to sign your API requests. This was a minor sticking point for me as my previous Node.js code didn't work on Cloudflare and I had to switch to Web Crypto, which was nicely documented here: https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#web-crypto.\nHere's the entirety of the Worker:\n\nBy the way, notice the console.log? Later this week I'll show how that works in production. In case you're curious, here's what that JSON response looks like. It randomly selected one of my favorite characters, Galactus. In order to save on space, I removed many items from the arrays of data.\n\nNotice the thumbnail property is both a path and extension. Here's our lovely world devourer.\n\n\n\nNow on to the next Worker.\nThe Scheduled Tooter\nI love that subhead. So the next Worker is responsible for running on a schedule and actually doing the Mastodon posting. The Cloudflare docs cover how these are set up and how you can test. For the most part, this just plain worker. The biggest difference in the code is that you have a scheduled handler, not fetch. Here's a barebones Worker for scheduled execution.\n\nAnd your schedule is defined in the wrangler.toml file. In this case, every two hours:\n[triggers]\ncrons = [&quot;0 */2 * * *&quot;] \n\nThe first issue I ran into was how to connect this worker to the first one. In the last blog post, you'll see it's rather simple:\n\nBut, this expects an incoming fetch request, a HTTP-driven Worker. There isn't a request in a Cron-triggered worker. I asked on the Cloudflare forums and got help from the most Internet nickname ever, Cyb3r-Jak3. The fix is to simply make an empty (mostly) Request object like so:\n\nNext, I prepare my data for my toot:\n\nThis is where I should point out that while my random comic book cover bot is great, the character information is - unfortunately - a bit slim, especially for obscure characters. To be clear, I don't mean a lack of information, but a lot of 404s and no images. Honestly, I almost punted on this as a source of data, but figured I'd let it go and see how it looks after a while &quot;in the wild.&quot;\nNow, at this point, I've got the text for my toot, as well as the image url. I had intended to make use of the npm module I'd used in the past, mastodon-api, however when I included this in my Worker, I got compatibility errors with the Workers environment. It didn't seem like an easy workaround and I almost gave up when I thought, why not actually look at their API documentation and try using it without a wrapper?\nCreating a &quot;toot&quot; was incredibly simple:\n\nLiterally 10 lines or so of code. I was pleasantly surprised. I then looked into the image aspect. Like Twitter, if you want to associate an image with a toot, you first upload the image, get the ID, and then associate it with the new message.\nTo do this, I needed to first get the bits from the URL on Marvel's side and then send that to Mastodon. In the past, I would have simply saved the image to /tmp, but I don't think Cloudflare supports that. Instead, I did everything in memory. This took me the longest time, so hopefully this code can help others.\n\nWoot. Ok, with that in play, here's the updated toot code (I love saying toot):\n\nAnd voila - magic:\nThis is Alex Power, part of Power Pack, a favorite of mine when I was a teenager, and unfortunately, the detail links to a 404. :(\nAll in all, this is kind of ",
		"tags":[
	        
            "cloudflare",
            
            "mastodon"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Connecting Cloudflare Workers with Service Bindings",
		"date":"Fri Aug 11 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691776800,
		"url":"https://www.raymondcamden.com/2023/08/11/connecting-cloudflare-workers-with-service-bindings",
		"content":"I'll warn you ahead of time and say this post isn't too much more than what you can find in the documentation, but I wanted to see it work for myself so I had to setup a test locally. Cloudflare Service bindings are a way for one Worker to connect to another. That seems simple enough, but while it defines a &quot;connection&quot;, that connection is completely internal to the Cloudflare environment. I.e., incredibly fast with much lower latency. Let's consider a simple example.\nThe Receiver\nI began by creating a worker, named backworker, with just a simple message:\n\nThe Front\nI struggled with what to call that header, &quot;front end&quot; felt like a loaded term as it implies HTML, etc. Anyway, I made a second worker named frontworker. In order to &quot;connect&quot; it to the back, you need to edit your wrangler.toml:\nservices = [\n  { binding = &quot;backlogic&quot;, service = &quot;backworker&quot; }\n]\n\nTwo things to note here. The service value points to the name of the worker where the binding is how you will address it. I suppose normally you would make these the same. I chose a different name just so I could ensure it worked properly.\nIn order for this worker to communicate with the other, you use the env object and binding name in your code. Here's how it looks:\n\nYou use fetch to communicate, which is a network call, but remember this is going to be internal only. It does need a request object which can only be read once, hence the use of request.clone(). As I didn't bother changing my other service to return JSON, I just get the text response and include it in the response here.\nTesting\nWhen working locally, you will need to have both workers running. While I wasn't sure it was required, I ensured I started backworker first, and then frontworker. The CLI noted the binding:\n\n\n\nOpening it up and running gives you what you expect:\nHello from front, back said: Hello from Backworker\n\nThat's mostly it, but there's one more cool aspect. If my intent is for backworker to never be used by itself, I can actually disable its route in the dashboard:\n\n\n\nNow the worker is no longer available publicly, but the front one works just fine: https://frontworker.raymondcamden.workers.dev/\nIf you would like to test this yourself, you can clone the two workers from my new demo repository here: https://github.com/cfjedimaster/cloudflareworkers-demos\nPhoto by Patrick Wittke on Unsplash\n",
		"tags":[
	        
            "cloudflare"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Adding Form Fields Character Counters With Alpine.js",
		"date":"Wed Aug 09 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691604000,
		"url":"https://www.raymondcamden.com/2023/08/09/adding-form-fields-characters-counters-with-alpinejs",
		"content":"Nearly three years ago I shared a Vue.js tip on adding &quot;counters&quot; to your form fields. The idea is, if you have a max length on a text area, as an example, it would be nice to let the user know as they type exactly how many characters they've already entered. You can read that old post here: Vue Quick Shot - Form Field Character Counters. As I've been going through my older Vue posts and updating them to Alpine.js, I thought this would be an excellent candidate and a great example of where I'd use Alpine in development.\nRequire a Minimum Number of Characters\nThis post will pretty much mirror the old one, so let's begin with a simple example where a form field has a minimum number of required characters.\n\nI'm using HTML's built-in validation with the minlength attribute and have bound the form field to an Alpine variable min1. Now let's add a counter:\n\nI've used min1.length as a quick way to display the current length. Now, I could have used a computed getter, but honestly, that feels like overkill. For completeness' sake, here's an example of how that would look. First the HTML:\n\nAnd then the JavaScript:\n\nSo this is where I get on my soapbox a bit and say that generally I would rather not have &quot;logic&quot; like .length in my HTML. I would usually go with the getter, however with the logic being so simple, I feel it's ok. I appreciate I've got options here and you do you when it comes to your code.\nNow let's get a bit fancy. Let's conditionally apply a style when the field doesn't have enough characters. Again, first the HTML:\n\nYou can see the conditional class declaration in the span tag, where if toShort is true, the class bad1 is applied. Here's that class:\n\nAnd the JavaScript:\n\nNow as you type, the span will be colored red until the minimum number of characters is added.\nBut hey, the only thing better than more JavaScript is no (or less at least) JavaScript!\n\n\n\nCSS lets you style invalid form fields, and even better, you can combine this with selectors to style other things. So here's our third example:\n\nI'm still using JavaScript obviously for the x-model, but I've done two things. First, I marked the field as actually required and then removed the binding for my class. I then just added this CSS:\n\nNow we get the exact same result with less JavaScript.\nRequire a Maximum Number of Characters\nNow let's flip it around and work with fields that require a maximum number of characters. Adding the character counter is no big deal, but this time we're going to use CSS a bit differently. If the user tries to enter too many characters, they will be blocked, period. (Unless they use devtools, but your backend validation will handle that, right?) In this case, we're going to use CSS to add a warning as they approach the max.\nHere's our HTML:\n\nNow I'm using a getter named tooLong. Here's that code:\n\nAs you can see, I picked a value (7 and higher) that is &quot;close&quot; to the max of the field. Now as the user types, when they &quot;approach&quot; the max, they will get a visual warning. I used this CSS, and honestly, it's not great, but that's a design thing that could be corrected:\n\nYou could further enhance this perhaps by using the 'warning' color as you approach, and the 'bad' color at 10, but as we used 'bad' for, well bad stuff, and the max isn't bad, just the max, I'm not sure if that would make sense. Anyway, enjoy the CodePen below!\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "JavaScript Syntax Sugar for Shorter Stuff",
		"date":"Tue Aug 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691517600,
		"url":"https://www.raymondcamden.com/2023/08/08/javascript-syntax-sugar-for-shorter-stuff",
		"content":"Please forgive the somewhat alliterative title of this post. I promise I wasn't going for clickbait! Recently I was looking at some code from a friend of mine and saw something I had not seen in JavaScript before. It obviously worked (and I confirmed myself of course) but I wanted to know why. Luckily I've got some smart followers online who helped me out. Here's what I discovered.\nJavaScript Object Literals\nBoth of the features I'll demonstrate apply to object literals. Basically:\n\nIf you've been doing anything in JavaScript for any amount of time, you've probably worked with an object literal from time to time. Both of the features below don't change how these work, but rather help you write them quicker. Both are optional of course and you should use whatever makes sense for you. Finally, both of what I'm going to share are ES6 features and should be universally safe to use.\nProperty Value Shorthands\nThe first feature, and the one I knew about, let's you skip a common pattern of creating a property of an object with a name of X based on a variable X. So for example:\n\nObviously in this case the three first lines aren't needed, but imagine they were passed in as arguments or created elsewhere. The shorthand methods lets you skip the X:X syntax:\n\nYou can mix and match too if you want:\n\nIn that example, I've written isOld the &quot;full&quot; way and made a new property based on another variable. So this is what I already knew. Now for what I didn't.\nMethod Value Shorthand\nWhen I was reviewing my friend's code, I saw something like this:\n\nSpecifically, the cat object literal has a property, meow, that is a function. You can of course, mix it up and include anything else:\n\nAnd just to be clear, this is shorthand for:\n\nHonestly this feels even more useful than the first item I covered.\nThanks!\nI don't know about you, but I love it when I discover stuff like this. I don't know how I missed the method value shorthand. Heck, I probably saw it and it just didn't click. I want to send thanks to both Šime Vidas and Dr. Axel Rauschmayer for their help in pointing me to the relevant specs for this feature. You can see our conversation here: https://mastodon.social/@raymondcamden/110820954856487108. Any mistakes in my post above are 100% my fault, not theirs. (And give them both a follow. They've both helped me multiple times in the past!)\nIf you want to see this &quot;running&quot;, you can check out the simple CodePen below.\n\n  See the Pen \n  Shorthand stuff by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Jason on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Caching to a Cloudflare Worker",
		"date":"Sun Aug 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691344800,
		"url":"https://www.raymondcamden.com/2023/08/06/adding-caching-to-a-cloudflare-worker",
		"content":"Last week I blogged about my first experience building a Cloudflare Worker serverless function. In that post, I built a simple serverless function that wrapped calls to the Pirate Weather API, a free and simple-to-use API for getting weather information. For today's post, I thought I'd show how easy it is to add a bit of caching to the worker to help improve its performance. As with my last post, I've also got a video walkthrough of everything you watch instead. (Or read and watch, go crazy!)\nThe Application\nIn the last post, I shared the complete code of the Worker, but let me share it again:\n\nAs a reminder, it does the following:\n\nFirst, it grabs the API key from a secret. In production, this is set up using the wrangler CLI, and locally it's done by adding a .dev.vars file following a key=value format for defining secrets.\nNext, it hits the Pirate Weather API. The Worker is hardcoded to only get weather for Lafayette, LA. Spoiler, it's hot. I don't care when you're reading this, it's hot.\nFinally, it shapes the result to only return the daily forecast and any alerts for the location.\n\nThis all works reasonably well. On my local machine when I fire up the development server and hit it a few times, I see timings between 1 and 2 seconds. Here's an example of how that looks:\n\n\n\nCloudflare KV\nCloudflare Workers come with multiple different things it can integrate with on their system, including Cloudflare KV, a key/value system with highly performant persistence. As a key/value system it resembles the Web Storage API quite a bit. You can store data by giving it a key and fetch the same thing back with the key. Also like Web Storage, complex values need to be JSON encoded and decoded.\nYou can, and should, check the Workers and KV docs for how they work together, but let me demonstrate how simple it is to get started.\nFirst, you create a KV namespace. This can be done via the wrangler CLI and looks like so:\n\nFor my test, I'm using weather4 for the name (I've iterated on my demo a few times and wanted to keep the files separate), so I'll make a namespace with the same name:\n\nWhen run at the CLI (note, if you don't have wrangler installed globally, you can prefix the command with npx to use the command from your Worker project). This will output:\n\nThe configuration file is wrangler.toml, and any scaffolded Worker project will include one. To add it, you can open the file and paste in this:\nkv_namespaces = [\n{ binding = &quot;weather4&quot;, id = &quot;b1342a22bcfd4af68f075223739025b3&quot; }\n]\n\nIf you fire up your Worker project again, however, you'll get an error:\n\n\n\nLuckily this is very clear and tells you exactly what to do. When you use KV in a Cloudflare Worker, they want you to create a development copy of the namespace. This can be done by literally rerunning the last command and adding --preview:\n\nThis time it outputs:\n{ binding = &quot;weather4&quot;, preview_id = &quot;29f5c2406dc24417bfd7bf6a79c2c5a7&quot; }\n\nAnd you then copy just preview_id to your wrangler.toml:\nkv_namespaces = [\n    { binding = &quot;weather4&quot;, id = &quot;b1342a22bcfd4af68f075223739025b3&quot;, preview_id = &quot;29f5c2406dc24417bfd7bf6a79c2c5a7&quot; }\n]\n\nAt this point, you can run the development server and not have any errors. (Hopefully.)\nUsing the Cache\nUsing the cache involves two steps - reading from the cache to see if it exists and writing to the cache when the data is fetched. Before we add that, I first moved the actual API call into its own function:\n\nBack in the main part of the function, I can check for the cache like so:\n\nThe API is async, hence the await keyword, and you access it via the env argument and via the namespace. Finally, you pass the key value. If the value isn't in the cache, you'll get a null result. I can modify my code like so:\n\nRemember that we need to JSON encode complex values, so if it was in the cache, it was a JSON string. If we need to get the data, we just do that inside the if condition:\n\nNotice I've added a value, created, just to see when the data was cached.\nFinally, the value needs to be actually cached, and here is where I got most happy with the API. To store the value, you use put, and as you can probably guess, you'll pass a key and value. But you can also pass an expiration value! You can expire at a certain time, or after a certain number of seconds. Best of all, this is all automated. If you tell KV to expire at a time and that time has passed, when you get the value, it will return nothing again.\nThe smallest time you can cache for is 60 seconds. Obviously, for a weather forecast you would want to cache for hours, not seconds, but for testing purpose I'll set it to 60 seconds:\n\nAlso, note I'm running JSON.stringify before setting the value. And that's it. After doing this, and hitting the API a few times, check out the difference in speed:\n\n\n\nSo with a few lines of code, and two commands run the CLI, the worker is now returning results in near instantaneous speed. I love it!",
		"tags":[
	        
            "cloudflare"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "I Know What You Did Last Summer (With Glitch and Cloudflare)",
		"date":"Fri Aug 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691172000,
		"url":"https://www.raymondcamden.com/2023/08/04/i-know-what-you-did-last-summer-with-glitch-and-cloudflare",
		"content":"Every now and then I get a dumb little idea, and too often, I turn those dumb ideas into little web toys. About five years ago, I discovered\nMarkov chains, which in my limited understanding is a deterministic way to guess what would come after some input. A bit like autocomplete for example. If I type, &quot;I like&quot;, I'm more likely to type &quot;cats&quot; after that than &quot;yard work&quot;. It's fairly complex (see the Wikipedia link above for more details) and perhaps a tiny bit like GenAI. For me, I just think it's neat.\nFive years ago I took the excellent titlegen npm package, a list of Cure songs, and built a generator for... well Cure songs: Generating Random Cure Song Titles with Markov Chain. I was thinking about this post and wondered if I could do something with horror movies. Why horror movies? Maybe because it's 200 degrees here and I'm really pining for October and Halloween. Either way, I built it! And here's how I did it.\nGetting the Data\nIn order for my demo to work, I needed data for the Markov chain. For my data I used the really simple TMDB API. This gives you access to loads of movie and TV data and while I've seen folks use it before, this was the first time I tried it and honestly, I was really impressed with it. To get my data, I hit their discover endpoint with these arguments:\n\nThe horror genre (27), per the docs.\nNo video (I believe that means direct-to-video) or adult movies.\nOriginal language as English\n\nThe API can only return 20 results per call but supports paging. I decided to use Cloudflare Workers again because I'm enjoying the platform and knew I could get something up super quick. I want to point out that I'm also using Glitch and Glitch absolutely supports server-side code, I just felt more comfortable doing my server-side code in Cloudflare and my front-end in Glitch.\nOk, so here's the entire worker:\n\nOn top, I've got a function that wraps calls to the TMDB API. My worker hits the KV cache (something I'll be blogging about more on Monday, for now, just think of it as a simple key/value caching system) if possible, and if not, grabs 400 movies from the API, filtering out to the just the titles. I cache for 6 hours so the worker can return quicker. Finally, I return the data along with CORS headers so I can use it from another server. You can hit this endpoint here: https://horrormovies.raymondcamden.workers.dev/\nPresenting the Data\nFor the front end, I used Glitch. I set up my HTML, JavaScript, and CSS there. You can view all the code at the project but I'll focus on the JavaScript.\nOn page load, I fetch my titles from the Cloudflare Worker, and then use titlegen to initialize the ability to generate titles. As I said, their utility package is super simple. Here's the entire JavaScript file:\n\nAs you can see, it's one line to initialize titlegen, one to input the data, and then just running next() to get a new title.\nAnd that's literally it. Play with the full version here:\n\n\n  <iframe\n    src=\"https://glitch.com/embed/#!/embed/random-horror-movie?path=script.js&previewSize=100\"\n    title=\"random-horror-movie on Glitch\"\n    allow=\"geolocation; microphone; camera; midi; encrypted-media; xr-spatial-tracking; fullscreen\"\n    allowFullScreen\n    style=\"height: 100%; width: 100%; border: 0;\">\n  \n\nObviously, it doesn't always work, but sometimes the &quot;silly&quot; results are funny as hell:\n\nFinal Destination 5: The Addams Family 2\nYou Should Have Eyes\nH.P. Lovecraft's Dracula\n\nI hope you've enjoyed this 100% useless bit of code today! Note that the font I used, while excellent, apparently doesn't support numbers.\nPhoto by Ben Griffiths on Unsplash.\n",
		"tags":[
	        
            "serverless"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Learn about the Photoshop API Next Week",
		"date":"Thu Aug 03 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1691085600,
		"url":"https://www.raymondcamden.com/2023/08/03/learn-about-the-photoshop-api-next-week",
		"content":"Next week, Tuesday August 8 at 11AM CST, I'll be giving a free, online presentation at CFE.dev on &quot;Automating Image Workflows with Photoshop APIs&quot;. Here's more information from the event description:\n\nInarguably, Photoshop dominates the professional image editing software world. But did you know that many of the capabilities of Photoshop can be added to your own applications via Adobe's Photoshop APIs? In this session, Raymond Camden will show you how to add image automations including modifying and enhancing images with powerful filters using the Photoshop APIs.\n\nIf you can't make it, just watch the recording later, and reach out with any questions.\nPhoto by Glenn Carstens-Peters on Unsplash\n",
		"tags":[
	        
            "adobe",
            
            "photoshop"
            
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Building a Basic (Pirate) API Wrapper with Cloudflare Workers",
		"date":"Tue Aug 01 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1690912800,
		"url":"https://www.raymondcamden.com/2023/08/01/building-a-basic-pirate-api-wrapper-with-cloudflare-workers",
		"content":"I've been playing with Cloudflare Workers the past few weeks, and while I've had a few technical issues here and there, I've been really impressed with the developer experience overall and just how powerful the platform is. I thought I'd share a quick demo of a simple &quot;API wrapper&quot; built with Workers. If you want, you can skip to the end of this post where I've shared a Youtube video of what I'm covering here. Let's get started!\nWhat We're Building\nFor this demo, I'm going to build a simple wrapper around the excellent Pirate Weather API. This API returns, you guessed it, weather data. The serverless API I'll build will hide the key from the client-side code and will only get information for one city, Lafayette Louisiana. Someone could take the endpoint for our serverless API, but it wouldn't be terribly useful for them with it locked down to one location.\nPre-Reqs and Docs\nI'm not going to cover the same ground as their excellent docs, but I'd recommend taking a quick look at the getting started guide. You will also, of course, need an account with Cloudflare.\nYou will also need a key for Pirate Weather. I'm sharing mine here in code but will probably delete the key (eventually).\nScaffolding the Worker\nTo begin, I'll scaffold a new Worker with:\n\nThis will ask me a few questions. For the name, I'm using weather2. For the scaffold, just the hello world one, and finally I elected to use JavaScript, not TypeScript.\n\n\n\nIn the end, it will prompt you to immediately deploy to production, but I said no to that. I'll do that later.\nFinally, you'll see this:\n\n\n\nAs the output says, once you change into the project directory, you can fire up the dev server with npm run start:\n\n\n\nI haven't tried all the options here, but b works fine and opens a new tab. The default hello world scaffolded application returns 'Hello World!' in the browser. Now let's get started coding!\nMake it JSON\nYour worker code lives in src/worker.js. Here's that code:\n\nThe first thing I want to do is return JSON. I'll begin by adding a new object to return and specifying a header in my response:\n\nSimple enough so far, right?\nGet the Weather\nFor the next iteration, I want to get the weather. I know the longitude and latitude of Lafayette, Louisiana, so I'll add those and my Pirate Weather key directly to the code:\n\nYou can check the Pirate Weather docs for more information, but getting a basic forecast involves hitting their endpoint with the key and location. Here it is being loaded via fetch:\n\nThe result is... huge. Nearly 2000 lines of data huge. Let's fix that. For our purposes, we only want the daily information and alerts:\n\nThis returns a much more sensible 400 or so lines of code. I could even reduce the size even more, for example, removing wind data if I don't care about it. As an example, here's one daily record:\n\nAnd yes, the &quot;feels like&quot; temperature is 107. Sigh. Shockingly, here's one of the alerts. You get one guess as to the type of alert:\n\nWoot! We're done. Almost...\nRemoving the Key\nSo while my Pirate Weather key isn't visible publicly, I would like to remove it from the code itself. The Cloudflare docs cover environment variables and make it super easy to use.\nFirst, add a .dev.vars file to your project. This ends up being like a dotenv file:\nPIRATE_KEY=lrokzEoN2n7ifLAVgrChU4V6XPEyqAZp5ikO6UWF\n\nThis is a set of name/value pairs that will be applied to the environment variable of your worker. I removed the earlier const declaration and added a new line inside the main function of the worker:\n\n**Important - the Cloudflare Development environment doesn't typically require restarting, but for this change, you will want to use the x key to exit, and just run npm run start again for it to pick up the new file and environment variable.\nNext, to get this variable available in production, you can use the wrangler CLI (part of the installation process) like so: wrangler secret put PIRATE_KEY:\n\n\n\nDeploying\nNow for the shortest section of the post. To push the worker live, just run npm run deploy:\n\n\n\nIf you want, you can hit this yourself here: https://weather2.raymondcamden.workers.dev/\nThe Video Version!\nI hope you liked this. As I said, I'm very happy with the development process in Workers. I will say I've had a bit of bad luck with their support forums. For example, I've got a post up there that's about two weeks old with no response. But hopefully, that's not the norm. As I mentioned above, today I recorded a video of all the above:\n\nPhoto by Zoltan Tasi on Unsplash\n",
		"tags":[
	        
            "cloudflare"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Update for My Subscribers",
		"date":"Mon Jul 31 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1690826400,
		"url":"https://www.raymondcamden.com/2023/07/31/update-for-my-subscribers",
		"content":"Hey folks, forgive the quick note. Today I migrated my newsletter from Mailchimp to Buttondown. As I mentioned here before, I had hit the free tier limit on Mailchimp and while I truly liked their service, this site doesn't really bring in any money so I wanted a cheaper solution. Buttondown looked good and affordable, and even better, it had a heck of a lot of support for migrating from Mailchimp. I took the plunge today and did the migration. That involved:\n\nExporting the list from Mailchimp (and turning off the RSS campaign)\nImporting into Buttondown\nPointing Buttondown at my RSS (more on that in a sec)\nUpdating my Netlify serverless function to hit Buttondown's API for subscriptions\n\nAnd that's basically it. In theory - everyone subscribed should get a notification about this post. In theory. The email will look a bit different and I'll probably tweak it over time. The biggest change is that I've moved from sending the entire blog post in an email to just the excerpt. Normally if I were you, I'd look at that as a slimy way to get page views. But I've noticed, and I've had people reach out, that my posts don't necessarily render well in email, so I figure this is a good change.\nTo be clear, this site still points to a &quot;full&quot; RSS feed here, https://www.raymondcamden.com/feed.xml, so folks using feed readers still get everything. This new feed is just for the subscription service and if you're curious, you can see it here: https://www.raymondcamden.com/feed_slim.xml\nIf you've never subscribed, you can do so here and you'll get one email per new post. I may start doing more emails now that I've got a bit more bandwidth. I'll also use this opportunity to say I'd love to get a sponsor for this site. Buttondown is cheap (and I get a bonus if you signup via that link) but I'd still appreciate the support!\np.s. As a final request, if you do get the email, could you send me a quick note just so I know yall got it? Please and thank you!\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jul 29 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1690653600,
		"url":"https://www.raymondcamden.com/2023/07/29/links-for-you",
		"content":"A quick links post today as I'm on vacation with my family in Saint Louis. I grew up here but haven't been back in some time. STL is a great city with some really cool things to do. My favorite stop so far has been the Arch. I visited the Arch many times growing up but I forgot how majestic it was.\n\n\n\nBefore I get to the links, some news. As I mentioned a few times, I've been looking for a sponsor to pay for a new mail service. MailChimp is great, but I've hit the limits of their free tier. I've decided to switch to Buttondown which will cost me a few bucks a month but seems to be more affordable than MailChimp and with 100% of the features I need baked in. I was ok with writing some code, but writing no code is even better. I'll also be using a new RSS feed for the emails that only returns an excerpt. That will mean smaller emails sent out which I think will work better in email clients.\nDynamic Slots in Web Components\nThis is a pretty fascinating article by Cory LaViska that explores how dynamic slots could be supported within web components. He makes it clear that this might not be a good idea, but it's a really interesting technique.\nThe 2023 State of Jamstack Report\nEven though Jamstack is dead (see next link), you might still be interested in the report by Kontent.ai. It's one of those &quot;sign up and we'll send you a PDF&quot; things which I'm not a fan of, but you can get your copy of the report here.\nJamstack is Dead!\nForgive the clickbait header. ;) My good friend and Jamstack book coauthor Brian Rinaldi wrote up an interesting article on recent updates in the Jamstack ecosystem, &quot;Is Jamstack Officially Finished?&quot;. I've been involved in this space way before the term even came to be, and it's been fascinating to see it evolve in both technological features as well as how it's marketed. In general, I agree with Brian's conclusion that we will see less &quot;broad&quot; Jamstack resources/events/etc as developers and communities tend to focus more closely on specific tools.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Integrating Acrobat Services with ColdFusion",
		"date":"Fri Jul 21 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1689962400,
		"url":"https://www.raymondcamden.com/2023/07/21/integrating-acrobat-services-with-coldfusion",
		"content":"Last week I shared a look at how to integrate the Adobe Photoshop API with ColdFusion, and that got me itching to see how difficult it would be to do the same with our Acrobat Services. While ColdFusion has native PDF features built-in, I think there are aspects of the platform that may be of use to CF developers.\nThe Acrobat Services Platform\nLet's start by briefly describing what Acrobat Services are. At a high level, they're all about document management via APIs. Broadly the services are categorized like so:\n\nPDF Services - this is the &quot;catch-all&quot; bucket of services that do simple things like converting to and from PDFs, splitting, merging, and so forth. This group is probably the least useful to Adobe ColdFusion developers as the native capabilities are pretty similar.\nPDF Accessibility Auto-Tag API - this service helps make PDFs more accessible by finding content to tag. It identifies reading order, tags tables, text, lists and so forth, and even gives you a report when done. This is not meant to be a &quot;one-stop&quot; accessibility solution, but rather help do a large percentage of the grunt work for you.\nPDF Extract - this uses Adobe Sensei AI to analyze and extract a PDF. It intelligently handles complex document structures and can also return tabular data in CSV or Excel formats. It even extracts images.\nSign API - for document signing and tracking purposes. This is really powerful but I've not dug terribly deep into it.\nPDF Embed API - a handy JavaScript library for rendering PDFs on your web page. You have much more control over the flow and integration versus &quot;built-in&quot; PDF viewers in browsers.\nDocument Generation API - probably my favorite feature, and what I'm using today in my demo, so let me go into detail below.\n\nNote that all of these services are available now for free with 500 document transactions per month. SDKs are available for Java, .NET, Node, and Python. There's also a powerful REST API I'll be making use of in my demo. In the past, I've recommended the excellent Java SDK wrapper built by Tony Junkes. It handled some conflicts between our Java SDK and ColdFusion. Now however I'd recommend just hitting up the REST API.\nDocument Generation\nPut simply, Document Generation let's you create a template in Microsoft Word. You then send that template to our API along with your data, and you get a custom PDF (or Word doc) out of it. Here's an incredibly simple example. Imagine this Word doc:\n\n\n\nSee the various code-like-looking things in there? Each of these will be parsed by the API when you send your data. You can do simple variable replacements, conditional logic, and even looping. In the example above it's just a list, but you can create dynamic tables. Dynamic images are supported as well.\nWe've got an online playground where you can test it out, and even a Word Add-In to make it easier for non-developers to test.\nGiven the Word template above, imagine this data:\n\nYou get this output (and I'm using PDF Embed here as an example):\n\n\n\nlet localhost = '9861538238544ff39d37c6841344b78d';\nlet prod = '33f07f2305444579a56b088b8ac1929e';\nlet key = document.location.host.indexOf('raymondcamden.com')>0?prod:localhost;\ndocument.addEventListener(\"adobe_dc_view_sdk.ready\", function(){\nvar adobeDCView = new AdobeDC.View({clientId: key, divId: \"adobe-dc-view\"});\nadobeDCView.previewFile({\ncontent:{ location:\n{ url: \"https://static.raymondcamden.com/images/2023/07/document.pdf\"}},\nmetaData:{fileName: \"document.pdf\"}\n},\n{\nembedMode: \"FULL_WINDOW\"\n});\n});\n\nAs I said, this is rather simple and you can absolutely build more complex templates, but it gives you an idea.\nUsing the REST API\nIn order for us to use this in ColdFusion and make use of the REST API, all of the services follow the same basic pattern:\n\nUse your credentials (in my case, the newer OAuth credentials) and ask for an access token. This is the exact same code I used in my last post\nAsk the API to make an asset. This requires you to tell it the type of the file.\nUpload the asset. In our case, the Word doc.\nCreate a job. Each API has different inputs and requirements.\nCheck the job to see if it's done.\nWhen done, save the file.\n\nDocument Generation is a bit different in that it recently added additional support for SharePoint and S3. Soon all the services will support this. For now, though I'm going to use a local file for my testing.\nOur Demo\nFor this demo, I'm going to automate the process of creating offer letters for prospective job candidates. The data will be stored in a simple MySQL table. Here's my Word template:\n\n\n\nI've got tokens for first and last names, salary, and a conditional piece of logic based on where they live. Also, note the use of $formatNumber. The template language used in Document Generation is JSONata and while not everything is supported, you can use many of the formatting functions.\nNow let's consider the code. As a reminder, my CFML is quite rusty, so probably don't consider this 'best pra",
		"tags":[
	        
            "adobe"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Alpine.js and Form Fields",
		"date":"Tue Jul 18 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1689703200,
		"url":"https://www.raymondcamden.com/2023/07/18/alpinejs-and-form-fields",
		"content":"A few years back, I wrote up a detailed blog post on how Vue.js handled form field bindings: &quot;Vue and Form Fields&quot;. The idea behind the post was that even though we knew things &quot;just worked&quot;, it might be helpful to see different form fields in action and show the bindings between them and Vue's data. As I've been doing more and more with Alpine.js, I thought it would be useful to update that post with Alpine examples. Best of all, while building the demo for this post I discovered I was incorrect about how Alpine handles one particular type of form field, so I learned something in the process. (And hopefully, you will too!) Let's get started.\nThe Simple Input\nLet's start off with the simplest types, that is:\n\ninput type=&quot;text&quot;\ninput type=&quot;email&quot;\ninput type=&quot;number&quot;\ninput type=&quot;password&quot;\ninput type=&quot;search&quot;\ninput type=&quot;tel&quot;\ninput type=&quot;url&quot;\ninput type=&quot;hidden&quot;\n\nThese all have virtually the same UI, with the exception of password and search, and basically the same UX. For these, you can use x-model to bind to your data and everything works as expected.\nThere are two things I'll point out though. First, when using type=&quot;number&quot;, remember that the value of a form field is going to be a string. If you were to do any math with a variable bound via x-model and forget this fact, you'll be reminded pretty quickly. Like Vue, Alpine has directives you can apply to quickly fix this:\n\nIn this case, adding .number is all that's required.\nAnother interesting side case is this:\n\nGiven this value in our JavaScript:\n\nWhat do you think will be rendered?\n\n\n\nYep, the entire string is shown. To be clear, this is expected, JavaScript can bypass the maxlength attribute. If the user edits the field though, they will only be able to delete characters until the maxlength value.\nThe Weirder Inputs\nNow let's turn our attention to the slightly weirder input types.\nLet's begin with:\n\ninput type=&quot;button&quot;\ninput type=&quot;submit&quot;\ninput type=&quot;image&quot;\n\nFor button and submit, using x-model is going to set the relevant values for each, but only the button type will show the value. So given:\n\nWhere button1 is the literal string &quot;button1&quot;, you get:\n\n\n\nThat's probably not too surprising, but there you go. For image, it works as long as you have a valid image URL for your data:\n\nAnd then in JavaScript:\n\nHow about type=&quot;color&quot;? It also works, but be sure to specify a valid HTML color:\n\nAnd in JavaScript:\n\nPer the MDN spec I learned that you can mix upper and lower case with hex, but the value will be lower-cased. I validated that by using #cC8800 and Alpine reported it the same until I picked a new color at which point the hex was in lowercase. I don't necessarily see this biting anyone, but keep in mind that if you are doing any kind of special validation, you will want to check in lowercase only.\nFor our final &quot;weird&quot; input, consider input type=&quot;range&quot;. Visibly this looks the most unique, but it still takes a simple value. However, keep in mind that like input type=&quot;number&quot;, the output will be a string, and to use it properly as a number in Alpine, you will want to add the directive like so:\n\nThe Date Inputs\nNow let's turn our attention to the various date-based inputs:\n\ninput type=&quot;date&quot;\ninput type=&quot;datetime-local&quot;\ninput type=&quot;month&quot;\ninput type=&quot;time&quot;\ninput type=&quot;week&quot;\n\nLuckily, there's nothing special at all to report here. You do, however, want to be aware of how to set these values if you have defaults in Alpine. So for example, month values look like so:\n2023-04\n\nAnd week values look like so:\n2023-W02\n\nMy suggestion is that if you need to default these and you're struggling, don't default them, just output the value, and play around to get an idea of how to format your values right. A good resource for this is, of course, on MDN: Date and time formats used in HTML\nWhew, almost done!\nRadio Fields\nRadio fields in HTML are defined by having the same name, but different values. In Alpine, you would define your options as an array:\n\nAnd define the selected item, if any, as a string:\n\nAnd render it in HTML as such:\n\nSome things to note here. I used the loop index to generate unique IDs for each radio field. Alpine has a 'magic' $id function that could have been used as well. I need a unique ID so I can use a label that associates correctly.\nCheckbox Fields\nCheckboxes work very similarly to radio fields with the exception that two or more values can be selected. While the value of a radio field will be one value, the value of a checkbox field will be an array, no matter how many items are selected. This may be easy to miss if you are rendering out the value to HTML, as the string version of the array won't be evident. To make it really clear, I added this to my demo Alpine application:\n\nIn order to set a default, you w",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jul 16 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1689530400,
		"url":"https://www.raymondcamden.com/2023/07/16/links-for-you",
		"content":"Hello readers. Today I'm writing this post in lovely Natchez, Mississippi as my mother-in-law gives my wife and I a quick break out of town. Natchez is one of our favorite &quot;getaway&quot; destinations as it's a short drive, a nice drive (mostly backroads), and a great mall town. Without getting too annoying, I'll remind folks I'm still on the lookout for a sponsor for the site. Any sponsorship will go right to Mailchimp to ensure the mailing list doesn't hit the free tier quota. Reach out to me (raymondcamden@gmail.com) if you're interested!\nA Look at Upcoming JavaScript Features\nFirst up is a great article detailing future JavaScript improvements, &quot;What’s Next for JavaScript: New Features to Look Forward to&quot; by Mary Branscombe. There are multiple cool things here, but I think I'm most excited about the Intl MessageFormat spec. Intl is one of my favorite new(ish) parts of the web platform and anything that adds to it will be pretty useful I think.\nMDN Launches their Playground\nThere are a lot of ways for developers to test code online, my usual go-to is CodePen](https://codepen.io), but MDN recently launched their own playground, as detailed here: &quot;Introducing the MDN Playground: Bring your code to life!&quot;. What makes this different than sites like CodePen is that it's integrated right into their material, making it much easier for you to try things out as your learning. I love that level of integration and I think this could make MDN an even more important resource for web developers.\nTips for Potential Conference Speakers\nLast but certainly not least is this incredibly useful article written by a great friend of mine, Brian Rinaldi. In &quot;The Art of the CFP: Getting Your Session Accepted&quot;, Brian goes into detail on how you can craft your conference proposal into something that is more likely to be accepted at conferences. Speaking from personal experience, I've been presenting at conferences for over twenty years, and I still absolutely struggle with this. I get more rejections than acceptances so I took what he wrote to heart. (Also I'm lucky enough to know Brian well so I can hit him up for personal advice.) Whether you considering your first conference proposal or your hundredth, I'd read this article.\nSomething Completely Different...\nI'd like to end with something not tech related at all, or at least not web tech. My buddy Todd Sharp recently shared this video by Avenged Sevenfold. While this is not my style of music, the video was absolutely incredible. You don't see much stop-motion animation anymore and this is a great example of the art form.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using the Adobe Photoshop API with ColdFusion",
		"date":"Fri Jul 14 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1689357600,
		"url":"https://www.raymondcamden.com/2023/07/14/using-the-adobe-photoshop-api-with-coldfusion",
		"content":"So yeah, I used to blog quite a bit on ColdFusion (if you want, you can peruse the three thousand posts here), but it's been a while since I've really written any CFML. That being said, I've been working with Adobe's Photoshop API recently at work and I thought it would be fun to build a quick ColdFusion wrapper for it. To be fair, I did the bare minimum, but one of my favorite things to do with ColdFusion was build service wrappers and I forgot how much fun that was. Keep in mind, I'm way rusty when it comes to CFML so this is probably not &quot;Best Practice&quot;, but feel free to take the code and run with it.\nAlright, so let's start off by quickly explaining what the Adobe Photoshop API is. The API wraps several features of Photoshop (and Lightroom) and lets you build automations around them. So for example, you may want to remove the background of images added to an Azure blob storage and save the results. You may want to take a PSD and generate JPG renditions and modify the text for localized audiences. There are quite a few features you can use, and even better, it supports working with Photoshop Actions (and JSON actions) for really complex workflows. I'll be giving a free online presentation early next month if you want a full introduction to it, and if you want, you can read a Node-based introduction blog post I wrote here: Automating Image Workflows with the Photoshop API. There is a free trial that you can sign up for today.\nThe Photoshop API does not work with local files, only cloud-based storage. This means your assets must be in:\n\nS3\nAzure\nDropbox\n\nTechnically, any public URL for reading is fine. And technically, for output, you can provide any URL, but the API is going to send its bits to it and you would need something there to accept the result and store it. In general, most folks will just make use of the options above.\nI use S3 quite a bit, and I also knew ColdFusion has recently improved its cloud service support so I figured it was a good chance to try it out. I was really surprised actually. So given that I've got my S3 credentials already, I set this up in my Application.cfc:\n\nBy the way, I'm using the excellent CommandBox dotenv package to store my secrets in a .env file. That's my usual process in Node and I was happy to see how easy it was with ColdBox.\nAlright, so given this, to generate a read URL for an item in my bucket the code looked like so:\n\nIn this case, I'm getting cayenne.jpg under an input folder and specifying a one-hour duration. By the way, the ColdFusion docs all say durations are in days, but that's not the case, you can specify durations in different units as I've shown here.\nTo test that it worked I did this:\n\nAnd it worked just fine. (Once I used the right region. Sigh.)\nFor my output URL, I just switched to the PUT method, again, I'm really happy with CF's support for this:\n\nAlright, so to work with the Photoshop API, it takes a few steps:\n\nTake my credentials and ask for an access token.\nGenerate a job pertaining to a particular feature. Every part of the service will have different inputs and outputs depending on what you are doing, but the process is the same - generate your arguments, and kick off the job on the Adobe side. The result of this is a unique &quot;job url&quot;.\nCheck the job url for status.\n\nThat last part can be a bit tricky. It takes a few seconds for the API to complete its work, and generally, I'd do a &quot;while&quot; loop with a slight delay. Ie, keep checking the job to look for success and failure. I know ColdFusion got improved async support but for today I kept it simple and just checked my job once.\nFor my component, I kept it to a grand total of 3 methods:\n\nGet access token\nDo a Lightroom AutoTone call\nGet the job status\n\nLet me share the entire CFC and then I'll break it down:\n\nSo first off, I built a simple caching system for getAccessToken such that it only needs to run once. In cases where you're doing multiple calls, this will speed things up.\nThe createAutoToneJob method is based on the API reference for the service. I didn't support every part of the API, specifically leaving out overwrite and quality, but you can see where you pass in the input and output URLs, and that gets passed to the service.\nFinally, getJob does just that - check the current status of the job. Here's an example of what you get while it's still doing stuff:\n\n\n\nThat text may be a bit hard to read, but basically, there is a status key reporting 'pending'.\nSo as I said, the 'best' way to use this would be to poll, check for success or error, wait, and poll again. I went a bit lazy and just used CF's sleep() method to wait:\n\nAnd that's it! Here's the entirety of my test CFM. The only real change here is that I asked for a 'GET' url for the output as well so I could render it:\n\nHere's the input picture:\n\n\n\nAnd here's the result:\n\n\n\nThat's it. Remember you can sign up for free and check the docs for a lot more information.\n",
		"tags":[
	        
            "photoshop"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Using PDFs with Algolia and Adobe PDF Extract API",
		"date":"Wed Jul 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1689184800,
		"url":"https://www.raymondcamden.com/2023/07/12/using-pdfs-with-algolia-and-adobe-pdf-extract-api",
		"content":"Over two years ago, I wrote an example of how to add PDF search to your Jamstack site, &quot;Using PDFS with the Jamstack - Adding Search with Text Extraction&quot;. In that post, I used the Adobe PDF Extract API to get the text from a set of PDF files. This was done in an Eleventy data file. This text was then used to drive a client-side search built with the open-source Lunr project. I like Lunr a lot, but I feel like Algolia has much better search support, especially for larger datasets. I took a look at what it would entail to build a similar demo with Algolia integration instead. Here's what I found (and I'll share a link to the complete source at the end).\nSetup\nI began with an empty Eleventy project that included a folder named pdfs. In that folder, I dropped 3 PDFs. I knew I'd want those PDFs viewable via the production site, so I ensured they were copied to the output directory. Here's my initial .eleventy.js configuration:\n\nProcessing PDFs\nI needed my site to have knowledge of the available PDFs as well as the text inside them. To build that, I used a data file named pdfs.js that was responsible for:\n\nEnumerating the PDFs in the source directory\nChecking for a cached text file in a cache directory\nIf not there, use the PDF Extract API to get the contents of the PDF and parse out the text, then cache it.\nReturn to Eleventy a list of PDFs and text contents.\n\nI'll share the complete file in a sec, but here's how each of those parts breakdown. First, get my PDFs via globby:\n\nFor each file, we loop and figure out the name of the corresponding text file:\n\nWe check the cache, and if it doesn't exist, call out to get it, otherwise, we read in the cached text:\n\nMy getPDFText function wraps the call to the Extract API. While the Extract API returns a lot of data from a PDF, I only need the text. Here's the function:\n\nYou'll notice that the API returns a zip, and to ensure I don't overwrite anything, I save it to a random file name. I then use a Node zip library to parse it and read out the structured data result. From that, I grab the text elements. For more information about what Extract can do, check out our docs. As an FYI, Extract, and the rest of the Acrobat Document Services now has a free tier of up to 500 calls per month!\nPopulating Algolia\nFor the next part, I logged into my Algolia dashboard and created a new index. Now, Algolia provides a lot of knobs you can tweak for optimal search performance. I did nothing there and got fine results. I just want to point out that you can and probably should do some thinking about your data, how folks want to search, and so forth. I love that Algolia lets me keep it simple when I want to and get complex when I need to.\nTo create my integration, I needed 3 things from Algolia:\n\nMy Application ID\nMy Admin key\nThe name of my index\n\nAlgolia needed one thing from me, my actual data. For that, I built a LiquidJS file responsible for outputting the text and PDF file names in JSON. I named this file algolia.liquid:\n\nNote the use of slice. Algolia indexes have a max size per object of 10k characters. I went with 9500 to give &quot;room&quot; for the filename as well. Again, I want to point out that I'm doing things really simple in this demo. My data could include more than the name of the file and the text contents. As an example, I could include a date for the PDF. While Algolia has a max size for their objects, there are no real restrictions on what I store in the index.\nWith that, I could start populating my index. Algolia has an excellent Node package (algoliasearch), so I installed that. I then needed to decide when I'd do the integration. I thought the eleventy.after event would be perfect. Here's how I used it in my .eleventy.js file:\n\nPretty simple, right? I initialize my Algolia index with my credentials, read my data, and run one call, saveObjects. There is a bit of an issue with the fact that I don't have a unique object ID specified in my data, so you can see where I manually add that with a forEach. I'll also point out that the result of the call would provide information about how the index went, but in my case, I'm assuming the best. Nothing wrong with that, right?\nSearching with Algolia\nOk, so at this point, I needed a simple search interface. Last year I wrote a blog post on integrating Algolia with Alpine.js, &quot;An example of Algolia Search with Alpine.js&quot;. I decided to make use of that:\n\nThis template loads up Algolia's JavaScript library as well as Alpine.js. Note that the apiKey I'm using here is a &quot;search only&quot; Algolia key, not the same as the one used to populate the index. I won't go into too much detail here, as it's covered in that earlier blog post, but basically - take the input - hit the index - let Alpine render the result. Here's an example of how it looks, and remember, the ugly is my fault, not Algolia:\n\n\n\nOne thing I'll point out about this demo is that the link goes to a page, pdf.html, where I pass in the name of t",
		"tags":[
	        
            "algolia",
            
            "eleventy"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Want to learn Alpine.js?",
		"date":"Fri Jul 07 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1688752800,
		"url":"https://www.raymondcamden.com/2023/07/07/want-to-learn-alpinejs",
		"content":"For the past two weeks or so, I've been releasing videos on YouTube all about learning Alpine.js. Each video is relatively short (although longer than a Tiktok video) and can be quickly consumed, with the entire playlist coming in under an hour. Each video has links to CodePens that let you immediately play with the concepts I cover.\nI really like Alpine (as you can see by many other posts on the topic) as it's an incredibly simple, and practical, JavaScript framework. There's no build process, no hundreds of megabytes of download, and it's something you can pick up rather quickly, as I hope my video series demonstrates.\nCheck it out, leave me a comment, and let me know if you think Alpine is right for you!\n\nPhoto by Valdemaras Januška on Unsplash\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Creating a Blackjack Game with Alpine.js and the Deck of Cards API",
		"date":"Tue Jul 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1688493600,
		"url":"https://www.raymondcamden.com/2023/07/04/creating-a-blackjack-game-with-alpinejs-and-the-deck-of-cards-api",
		"content":"Some time ago I ran across a pretty fascinating service, the Deck of Cards API. This API handles everything imaginable related to working with decks of cards. It handles creating a shuffled set of cards (containing one or more decks), dealing out a card (or cards), and even reshuffling. Even better, it includes card images you can use if you don't want to find your own:\n\n\n\n\nIt's an incredibly feature-filled an API and best of all, it's completely free. No need for even a key. I've known about this API for a while and have contemplated building a card game with it, but realized that games can quickly go from simple to fairly complex. In fact, my friends strongly urged me not to spend time on this, and honestly, they were probably right, but I've got a long history of building code demos that don't make sense. ;)\nFor my demo, I went with the following rules:\n\nObviously, basic Blackjack rules, try to get close to 21 as possible without going over.\nNo betting, just one hand at a time.\nNo doubling down or splitting.\nDealer has a &quot;soft 17&quot; rule. (I'm mostly sure I've done that right.)\nThe game uses six decks (I read somewhere that it was a standard).\n\nGame Setup\nInitially, the player and computer both have an array representing their hands.\n\nThe deal method handles setting up the hands for both players:\n\nTwo things to point out. First, I deal to the player, then the PC (or dealer, name-wise I kinda go back and forth), and then back again. I also modify the card result object to have showback set such that I can render the back of the card for the dealer.\nHere's how that's done in HTML:\n\nBACK_CARD is simply a constant:\n\nGame Logic\nSo at this point, I could hit the app, and get a Blackjack hand:\n\n\n\nAt the bottom, I used a div to display the current status:\n\n\n\nMy logic was like so:\n\nBegin with the player, and let them hit or stand\nIf they hit, add a new card and see if they busted.\nIf they stand, let the dealer player.\n\nLet's focus on the player first. To hit, we simply add a card:\n\nBust checking was a bit complex. I built a function to get the 'count' for the hand, but in Blackjack, Aces can be 1 or 11. I figured out (and hope I'm right), that you can never have two 'high' aces, so my function returns a lowCount and highCount value where for the high version, if an Ace exists, it's counted as 11, but only one. Here's that logic:\n\nIf the player busts, we end the game and let the user start over. If they stand, it's time for the dealer to take over. That logic was simple - hit while below 17 and either bust or stand. In order to make it a bit more exciting, I used a variable and async function, delay, to slow the dealer's actions so you can see them play out in (kinda) real-time. Here's the dealer's logic:\n\nFYI, pcText is used in the white status area as a way of setting game messages.\nAnd basically - that's it. If you want to play it yourself, check out the CodePen below, and feel free to fork it and add improvements:\n\n  See the Pen \n  Blackjack (Don't do this, Ray) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n\nPhoto by Jack Hamilton on Unsplash\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jul 01 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1688234400,
		"url":"https://www.raymondcamden.com/2023/07/01/links-for-you",
		"content":"So yeah, I know my last post was just a link, but I promise the post after this will be actual new content, not just me sharing stuff. Then again, it's my blog, so who knows what I'll do. ;) Happy July, and for those of you suffering with me in the South (heat and multiple other reasons), stay hydrated.\nRSS Styling\nFirst up is a cool article by Darek Kay, Style your RSS feed. While on one-hand I'm so happy I don't need to work with XML often, on the other hand, I've already been impressed by the XML ecosystem, especially XSLT and XPath. Turns out, you can add a style sheet written in XSL and make your RSS feed prettier. This is really cool, and I've done it to my feed as well.\nThe 11ty Bundle\nNext up is the 11ty Bundle by Bob Monsour, a huge collection of Eleventy resources from around the web. He even has a firehose RSS feed you can subscribe to in your feed reader of choice.\nJSON to Chart\nLast is a resource my buddy Todd Sharp shared with me. JSON to Chart is a super useful little tool that lets you paste in some JSON data and generate a chart. I'm not talking about a charting library, this is literally, &quot;I've got some data, and I'd love to make a quick chart of it to share with others&quot; type thing.\nThis is a trivial example, but take some JSON like so:\n\nPaste it into the site and specify name for x-axis and age for y-axis, and you get this:\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Algolia DevCon 2023 Videos",
		"date":"Fri Jun 30 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1688148000,
		"url":"https://www.raymondcamden.com/2023/06/30/algolia-devcon-2023-videos",
		"content":"Normally I save links for the &quot;Links for You&quot; post I do a few times a month, but as I've been a bit quiet here recently (most of my side work has been on my Alpine.js video series), I thought I'd share this quick note. This week, Algolia held an online conference (DevCon) with two days of short presentations about their products, demos, and so forth. I've only had a chance to watch the first two so far, but over the next week, I'm going to try to watch them all. There's some great stuff in the playlist, including, of course, a lot of AI-related material as well as examples of real-world Algolia.\nAs a reminder, I use Algolia on my search here, and it's been incredible for me (and hopefully my readers).\nAnyway, check out the playlist below:\n",
		"tags":[
	        
            "algolia"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating Bootstrap WebC Components in Eleventy",
		"date":"Mon Jun 19 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1687197600,
		"url":"https://www.raymondcamden.com/2023/06/19/creating-bootstrap-webc-components-in-eleventy",
		"content":"For some time now as I've explored web components, it's occurred to me that web components could be a great way to make working with Bootstrap simpler. Not that Bootstrap is necessarily difficult, but I've always thought it would be cool to take something like so:\n\nAnd write it as so:\n\nThis would absolutely be possible with web components (except for the fact that web components need to have two words, not one), but it brings up an important problem. The web component would make it easier for me to use Bootstrap but would do absolutely nothing for the people actually using my website. I'd be improving developer experience (DX) while sacrificing user experience (UX). The &quot;sacrifice&quot; isn't terribly bad, but as I would be using JavaScript to basically just spit out HTML, it's not necessarily the &quot;right&quot; thing to do.\nOf course, this is where WebC can save the day. I get the DX locally of a simpler interface to Bootstrap and the UX of shipping plain HTML as everything is done in the build process. With that in mind, I thought I'd take a quick look at building a few Bootstrap components. Here's what I came up with.\nInitial Setup\nBefore I got started, I looked at the basic Bootstrap starter template and built that as an Eleventy layout.\n\nNote that I needed to add webc:keep so WebC didn't try to roll it up into its asset bundling. It doesn't support remote URLs anyway, but I still needed it.\nStarting Simple - Badges and Alerts\nI decided to start simply and build a WebC component for both the Alert and Badge components. These are really simple in that they just wrap content and have few options.\nThese were simple, but ran into an issue with the WebC language - you can't access attributes &quot;in code&quot; when using WebC. While you can use JavaScript in a setup block, that code can't introspect arguments passed to the component. Because of this, I went to JavaScript for the entire component. Here's bs-alert.webc:\n\nNote the comment on top that while I wanted to use type as an attribute, it conflicted with the webc:type attribute.\nAnd here is bs-badge.webc:\n\nBoth are basically the same. Using them looks like so:\n\nBy the way, WebC components can absolutely be one word. I could have used both &lt;alert&gt; and &lt;badge&gt;, but I kinda felt like I wanted a differentiator between my &quot;regular&quot; HTML and my Bootstrap WebC components. That was a personal choice.\nMore Advanced - Cards\nFor my next component, I built a wrapper for the Bootstrap Card component, one of my favorites in the Bootstrap library. This time I'll start with the usage examples:\n\nAnd now let's look at the component:\n\nPretty simple, right? Notice how the header, title, and subtitle all make use of webc:if to decide if they need to render. This makes them optionally show each part of the card UI based on input.\nMaster Class - Accordions\nSo, the Bootstrap Accordion ended up being the most complex to build. For this, I ended up with two components - one for the wrapper, and one for each part of the accordion. Here's how it looks in use:\n\nThe bs-accordion.webc file is simplest:\n\nThe item component was harder. I ended up &quot;giving up&quot; on WebC as a template language itself and just switched to Liquid:\n\nAlso, notice that I needed to assign a unique ID to connect the button to the div inside it. WebC for some time now has had an undocumented feature, uid, where each component can use this to get a unique ID for itself. I had to use a bit of logic to handle the expanded attribute which is optional for my wrapper.\nTry It Yourself\nThe source code for this demo may be found on my Eleventy GitHub repo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/webc_bootstrap. However, I ported it over to Glitch to make it even easier to play with:\n\n  <iframe\n    src=\"https://glitch.com/embed/#!/embed/webc-bootstrap?path=.eleventy.js&previewSize=100\"\n    title=\"webc-bootstrap on Glitch\"\n    allow=\"geolocation; microphone; camera; midi; encrypted-media; xr-spatial-tracking; fullscreen\"\n    allowFullScreen\n    style=\"height: 100%; width: 100%; border: 0;\">\n  \n\n\nPhoto by Minh Tran on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jun 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1687024800,
		"url":"https://www.raymondcamden.com/2023/06/17/links-for-you",
		"content":"Happy Almost Father's Day. I'm the proud father of eight kids and being a father makes me incredibly happy. Also scared, stressed, worried, and anxious, but all worth it. This week was pretty rough for me. Both my wife and I got sick early in the week (not Covid), and while I got better after twenty-four hours or so, I'm still not 100% right. Add the fact that we have a massive heat wave this week I've been sticking to the couch mostly, working on my Diablo 4 barb.\n\n\n\nAdocasts for Learning Adonis\nA few months ago I was introduced to Adonis. Adonis is a framework for building Node.js app and it comes with a heck of a lot of features. If Express is jQuery, Adonis is Vue+React+Everything Else. It's a very opinionated framework and that may turn some folks off, but from what I've seen so far it's really impressive. I haven't built a &quot;server app&quot; in nearly a decade. I've focused on Jamstack and serverless and just haven't needed one. While I don't see my &quot;default&quot; mindset changing, I can say Adonis has me excited and I'm hoping to build a few demos with it.\nThat being said, I did find the docs a bit hard to grok, specifically the lack of a proper &quot;Getting Started&quot; guide. The Adonis docs are extensive, but there's no real guide that walks you through building a simple application. That's where Adocasts come in. Adocasts is a set of video tutorials that helps bridge that gap in the Adonis docs. Even better, each video tutorial has a full transcription with code. I've not watched any of the videos and have just relied on the written form and the site has been invaluable for me.\nMaking a Network Request When Leaving a Page\nHere's a great blog post by Alex MacArthur on how you can make a network request when a user leaves the page. He covers various methods and what works and what doesn't, and ends up on the sendBeacon API, a web feature I've yet to play with. Read more here: Reliably Send an HTTP Request as a User Leaves a Page\nWorking with Node's Test Runner\nWhen I was first learning Python, one of the things I thought was really cool was the fact that testing was natively baked into the platform. Well, Node now has this too (as of version 20). Phil Nash on the Sonar blog wrote a great and simple-to-understand look at this new feature: Hands on with the Node.js test runner\nAs I said above, I don't really build Node.js applications much, but I write a heck of a lot of simple scripts and serverless stuff. The more the platform improves, the better my life gets!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Quick example using AWS Node.js SDK V3 for Signed URLs",
		"date":"Fri Jun 09 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1686333600,
		"url":"https://www.raymondcamden.com/2023/06/09/quick-example-using-aws-nodejs-sdk-v3-for-signed-urls",
		"content":"This probably falls into the &quot;it was easy for everyone else in the world but me&quot; bucket, but I really struggled to find good search results for this and figured I'd better write it down so when I google for it again in a few months, I'll find my own blog. Specifically - today I was trying to use the AWS Node.js SDK to generate signed URLs. One to create read-only access to a bucket item and another to allow uploading.\nEverything I'm sharing is covered in the docs, but I struggled to find the relevant parts.\nSo first off, V3 of the SDK is modularized, so instead of installing a giant SDK, you get just what you need. A lot of the demos show S3 so that's handy. You can install it like so:\n\nHowever, the bits that work with signing URLs are another package:\n\nThis part above is what took me the most time to figure out. Ok, now for your imports. You'll need a generic one for S3 and another for the signing bits:\n\nThe S3 client uses a set of functions related to operations and you need to import each one you'll require. As I said, my needs were &quot;Public Read&quot; and &quot;Upload&quot;, which corresponded to:\n\nOk, I configured my S3Client with my region like so:\n\nAnd as all my work was in one bucket, I went ahead and hard-coded it like so:\n\nYou'll notice I'm not passing any credential information. The SDK can pick up from either a configuration file in your user profile, or environment variables. I'm using dotenv, so I set them in .env:\nAWS_ACCESS_KEY_ID=visit_my_amazon_wishlist_or_else\nAWS_SECRET_ACCESS_KEY=my_key_is_so_secret_becky\n\nOk, so all of the above was about an hour for me. I honestly blame myself, not the AWS docs. The last part was my utility functions:\n\nI've hard-coded my expiration there, but you could make that dynamic. Once everything up it's trivial as heck, it just took me a while to get here. Here is an example of how I used it in my script:\n\nThis was built for another demo, but I've ripped out that bits and you can see the complete, if useless code below (as it doesn't actually do anything with the URLs):\n\nPhoto by Dimitri Karastelev on Unsplash\n",
		"tags":[
	        
            "aws"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Goodreads Data in Eleventy - Update",
		"date":"Thu Jun 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1686247200,
		"url":"https://www.raymondcamden.com/2023/06/08/using-goodreads-data-in-eleventy-update",
		"content":"Yesterday I shared a blog post where I detailed how to take your data export from Goodreads and make use of it in an Eleventy site. While describing the process, I mentioned that I wasn't terribly confident in the approach. Things got even worse when I tried to make use of the Google Books API as well. (That's not the fault of the API, more just an issue with how Goodreads reported book titles.) Well, today, Brian Koser reached out and pointed out a much easier way to accomplish the same thing. To be honest, I love it when I say something and folks point out a way to make it better - it's like free content for my blog! Anyway, here's what Brian shared.\nTurns out, your bookshelves on Goodreads have an RSS feed. I never noticed it before, and thankfully he shared a screenshot I've included below:\n\n\n\nEach of your shelves at Goodreads has it's own RSS feed which means you can either fetch all your books, a particular shelf, or just your currently reading list. There are, however, some caveats.\n\nKeep in mind that client-side code can't load in RSS directly unless CORS has been set up, and I've never seen a RSS feed make use of CORS to let you fetch it remotely.\nYou could, however, build a serverless proxy to load it. Of course, you wouldn't just mirror the XML because, XML, eww. You would want to parse it first. That being said, I'm going to stick with the build time, Eleventy data approach for my update.\nThe RSS feed for &quot;all&quot; books unfortunately caps the list at 100. You could build logic to get a shelf for each year, if you organized your books that way. I have a few &quot;year&quot; shelves, but nothing since 2015. But if you did organize like that, it wouldn't be too hard to write code to get N sets of RSS feeds and just concat the results. It would be manual, but only once a year.\nAnd finally, the RSS feed Goodreads uses has unique fields in it. Here's an example:\n\n\nMy XML/RSS is a bit rusty, but I believe you are supposed to specify additional items and I don't see them doing that, but luckily it's easily enough to handle.\nParsing the RSS\nAlright, as I said above, I'm still going to use Eleventy data which means my information will be as up to date as the last build. I wish I could say I was still reading 1-2 books a week like I did as a kid, but that ship sailed a long time ago. To begin, I got my RSS feed for &quot;all&quot; (keeping in mind it's only 100 items) and parsed it like so:\n\nNormally rss-parser is much simpler to use, but in order to get the non-standard fields in, I have to pass in a list of custom fields in the item property. There's more here than what I specified but I only grabbed what I thought was important.\nTo get the items, it's one line:\n\nNext I need to reformat this into something nicer. As before, I'm going to filter out books I set as wanting to read, but I do it a bit differently:\n\nThere isn't a shelf for many of my books so I have to just filter out the ones I definitely marked as wanting to read. After filtering, I map to a new shape. This is pretty close to the previous version except now I have images!\n\nThe most critical part here is read_at, as I set to a real date when I can, and an empty string when I can't. I don't think that's a great solution as I follow up with this sort:\n\nAnd when the date is blank, I should do... something. I just don't honestly know what. I believe the dates are read for the sets of books I marked as having read already. While it could be done better, here's the new data file:\n\nDisplaying the Books\nAs I said, I mostly matched the shape of books from the previous post. Here's all the books (again, &quot;all&quot; being just 100):\n\nAnd even better, the version with images:\n\nAnd now all the images work!\n\n\n\nYou can find the source code for this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/goodreadstest2\nThank you again to  Brian for the share!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Using Goodreads Data in Eleventy",
		"date":"Wed Jun 07 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1686160800,
		"url":"https://www.raymondcamden.com/2023/06/07/using-goodreads-data-in-eleventy",
		"content":"I've been a Goodreads user for a few years now, and much like how I use other 'tracking' services, I'm not there for other folks' reading lists or recommendations, but instead, as a way to track what I've read. I especially like looking back over the past year and being reminded of the books I really enjoyed. Recently, myself and others were talking on Mastodon about how to work with this kind of data, other services, and so forth. Goodreads does not have an API unfortunately (it used to, but it shut it down) but they do let you export your data. I decided to take a look at this and see if (and how) it could be used in Eleventy. Here's what I found.\nEdit on June 8, 2023: Be sure to see my update here.\nGetting your Goodreads data\nSo, according to this web page, you can request a copy of your data at any time. I followed the directions there and was presented with a cheerful warning that it could take up to thirty days for my request to be processed.\n\n\n\nSurprisingly, I requested my data on Sunday afternoon and it was ready by Monday. By no means should you assume that's a standard response rate, but it's probably closer to the normal response time than thirty days.\nYour data export is a zip. You extract that zip and you get... more zips. Lots, and lots of zips. I unzipped them, removed the zips, and here's the list of files I got.\n\n\n\nIn case you don't feel like counting, that's thirty-nine different files. Honestly, I wasn't sure which file was the one I needed, but I found the relevant information in review.json. I don't typically write reviews for books on Goodreads. I'll do a quick start rating, but as I said, I use Goodreads more as a personal log and assume no one else but me gives a darn about what I've read.\nThe file is an array of records, oddly starting off with one that's more metadata than data:\n\nAfter this is a long (well for me, as I said I've been using it for a while) list of books. My particular data set begins with a lot of books that I set as have been previously read. I believe I did this when I first started. I didn't try to log every single book I've read, that would be impossible, but I probably spent a few minutes adding the ones that came to mind. I only point this out because many of these records don't have data about when I read them.\nHere's an example:\n\nLater on, when I started recording books as I read them, the data is a bit more complete. Here's an example where I felt really compelled to leave a review:\n\nIt's important to note that the information about the actual book is next to nothing. You get a title in the book property and that's it. Alright, so what can we do with it?\nConverting Goodreads data for Eleventy\nFor my first demo, I simply copied review.json to my project root and then added a new file, goodreads.js, to the _data directory. This file reads in the JSON and helps simplify it a bit for Eleventy:\n\nThe first thing I do is filter to items that are marked read or currently-reading. I had a few records in my data set for books I wanted to read and this clears that out. It also removes that first 'meta' item from the array.\nNext, I rewrite the data to be a bit simpler. The original data uses (not provided) a lot for null values, so you can see where I check for that. I also go ahead and parse the dates. Finally, I rename book to title.\nWith this done, I can use it in a template, like so:\n\nIt's not pretty, but here's the end of the list:\n\n\n\nMost likely you'll want to show your most recent books. That can be done like so:\n\nThe data is already sorted from oldest to newest so a simple reverse is all you need. You could add the read_status value and just display what you're currently reading too.\nPut some lipstick on that pig...\nAs I said, the actual book data is limited to just the title. I thought it would be cool if I could get more information. Shockingly, there doesn't seem to be an Amazon API for this. I did find a &quot;Product Advertising API&quot;, but it didn't feel right to me. Shockingly (yes I like using that word), Google actually has an API for this and it's free: Google Books API.\nThe Google Books API lets you search for books and returned detailed information for them. This includes cover images and I thought that would be great to add to the display. I created an .eleventy.js file and built a short code:\n\nTo use the API, I request a &quot;title&quot; match to help ensure it matches right, I also set the printType to book to differentiate from magazines and other publications. My code assumes the first result is right (more on that in a second) and returns an image pointing to the cover thumbnail.\nSo how well did it work? Pretty bad! From what I can tell, the issue is that many of my books are part of a series. So for example, the book may be called &quot;Ruin and Rising&quot;, but Goodreads marks it as &quot;Ruin and Rising (The Shadow and Bone Trilogy, #3)&quot;. This made most of my tests return nothing.\nWhile this feels totally unsafe, I added this rege",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Jun 03 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1685815200,
		"url":"https://www.raymondcamden.com/2023/06/01/links-for-you",
		"content":"Happy June folks. Not sure what that actually means, but Happy June nonetheless. Before I get into the links, a quick reminder that I'm looking for sponsorship for the site, primarily to let me upgrade my Mailchimp account. If you, or your company, wish to help out, just send me an email. I'd be happy to mention your sponsorship on the site. I'll also remind folks of my Patreon. Alright, I promised I wouldn't be (too) annoying about asking for donations, so let's move on!\nBadass Natural Language Parsing with Chrono\nPardon the French, but the Chrono library really is badass. It takes natural language expressions (for a subset of languages, though) for dates and converts them into real dates. So for example:\n\n&quot;An appointment on Sep 12-13&quot; - &quot;2023-09-12T17:00:00.000Z&quot;\n&quot;tomorrow&quot; - &quot;2023-06-04T15:36:14.087Z&quot;\n&quot;six days ago&quot; - &quot;2023-05-28T15:36:14.087Z&quot;\n\nIt's got quite a few options detailed in their docs](https://github.com/wanasit/chrono) so check it out and see if it will be useful in your projects. I got a quick CodePen working below if you want to play with it.\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLaying Out a Print Book With CSS\nThis great post by Ian G McDowell goes into incredible detail about how CSS can be used to create a beautiful book layout on the web. I continue to be amazed at how far CSS has come in the last decade and if I ever see a &quot;Peter Griffen trying to work with blinds is the same as CSS&quot; meme gif again I'm going to have to throw down. (Actually no, I'm about as scary as an avocado.)\nWhile I'm praising CSS, I'll also point out that you can sign up for a great newsletter on the topic, cssweekly.\nHow to Add Hotlink Protection to Your Web Fonts With Netlify Edge Functions and Deno\nI've yet to make use of Netlify Edge functions and if you're in the same boat, this is a great blog post showing where they make sense. Written by Sidney Alcantara, this post explains how edge functions can be used to check for, and potentially block, direct access to web font resources. For sites using commercial fonts (legally of course) that require protection, this is a great and relatively simple solution you could have up and running quickly.\nAnd Lastly...\nHere's a quick and fun video. &quot;The Prisoner&quot; was one of the most fascinating shows to ever air on TV. Enjoy!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Web Components in Alpine.js",
		"date":"Fri Jun 02 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1685728800,
		"url":"https://www.raymondcamden.com/2023/06/02/using-web-components-in-alpine",
		"content":"To be honest, the TLDR for this entire post is, &quot;It just works&quot;, so I'd more than understand if you stop reading, but like most things in my life, I like to see it working to reassure myself of the fact. So with that out of the way, let's consider a simple example.\nFirst Attempt\nI began by defining a super simple Alpine application that just has a list of cats:\n\nIn the HTML, I iterate over each cat and display it with a web component I'll define in a moment:\n\nAs the component hasn't been defined yet, all I'll see are 5 &quot;cat:&quot; messages:\n\n\n\nAlright, let's define our web component:\n\nAll this component is doing is picking up the name and age attributes and rendering it out in a div. Let's see what this renders:\n\n\n\nSo what happened? Alpine successfully added the components to the DOM, but the attributes were updated after the connectedCallback event was fired. This was - I think - expected - and luckily is simple enough to fix with observedAttributes and attributeChangedCallback:\n\nAnd voila, you can see the result below:\n\n  See the Pen \n  Alpine + WC by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSmall Update\nCool, so that worked, but I wanted to be sure that updating data in Alpine worked, so I added a quick button:\n\nThis was tied to this handler:\n\nI'm just giving a name and age based on the number of cats already in the data set. Again, no surprises here, but it works as expected:\n\n\n\nYou can find this version below. I encourage you to hint that &quot;Add Cat&quot; button multiple times because more cats is always a good thing.\n\n  See the Pen \n  Alpine + WC (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nPhoto by Christopher Alvarenga on Unsplash\n",
		"tags":[
	        
            "alpinejs",
            
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Form Participation Support to Web Components",
		"date":"Wed May 24 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1684951200,
		"url":"https://www.raymondcamden.com/2023/05/24/adding-form-participation-support-to-web-components",
		"content":"Many years ago when the web platform began to really improve, when everything was &quot;HTML5 this&quot; and &quot;HTML5 that&quot;, I remember being particularly excited by the updates to forms. I started my web career doing a lot of form processing and have always thought it was one of the more important aspects of the platform. Anything that improved that was a good thing. In my explorations of web components, I was ecstatic to discover that web components can be participants in forms. So what do we mean by that?\nWhat's a Form Field?\nForm fields have a number of different features, including:\n\nIncluding a name and value as part of the overall form. This is the bare minimum thing a form field provides.\nParticipate in form validation with custom logic.\nReset, which means different things to different fields.\nAutocomplete\nRestore (like if you go back after submitting a form, or re-open a closed browser)\nHandle being disabled\nHandle being focused\n\nThat's quite a bit, and I'm probably forgetting something, but given the power of forms, the complexity is probably not too surprising.\nForm Participation in Web Components\nThe good news is that, as long as you write the code for it, a custom web component can 100% participate in a form. The platform gives you the ability to set up the association, handle things like reset logic and autocomplete/restore, specify custom logic, and the rest of the expected behaviors as well.\nIn my research on this topic, I ran across multiple articles on it (and I'll share those at the very end), and in general it didn't seem too hard to do, but I ran into an issue that stopped me completely before I figure it out. Let's start off by looking at the bare minimum requirements for a web component that will work in a form.\nThe First Example\nOk, let's begin with a simple component that doesn't actually do anything:\n\nThis component, creatively-named form-component, simply outputs a bit of text and a form field within it's shadow DOM. To start participating in forms, we begin by adding a static formAssociated value to the class:\n\nNext, in our constructor, we use attachInternals like so:\n\nThe attachInternals method (documented here on MDN) adds support for forms. In some examples, I saw private members used for the pointer, or 'fake' private members using names like internals_. That's not required, but you may see it in the wild.\nThen we need logic to set the value in terms of what the form is going to send. That's a bit wordy, but essentially there is a special way for your code to set the value that represents itself in the form. This is done like so:\n\nAnd in theory, that's it. In practice, I saw something else that was required as well. And maybe it was obvious, but it wasn't for me. In order for your custom component to be a part of the form, it must use the name attribute. If you are building a form that will post to a server and not using JavaScript instead, then this is how it's always been. But it just didn't click to me that I needed it and kept seeing my web component value missing in the post. Anyway, just add a name:\n\nLet's consider an example, and let me preface it by saying this is a bit messy. I spent a good amount of time messing around with stuff when it wasn't working right.\n\nAlright, in my constructor, I'm setting up a shadow DOM as well as attaching internals as described before.\nIn connectedCallback, I output a bit of HTML. I want you to notice the name there. This is not what is sent on posting the form, instead it's the name used when the component is in the DOM. I kept that there as a reminder to myself.\nThe next line sets a form value of an empty string. Why? Here was another interesting tidbit. If I submitted my form without typing anything, the form field did not show up in the post. This was not how other form fields acted, so for example, a text field. My guess is that with no value, it was null, so it didn't get sent along. Starting off by setting it to a string means I'd at least get the form field name there and an empty value.\nFinally, I find the input field and listen for change events. When that fires, I update the form value.\nThat last bit of code, the getter for value, is a bit of trash in this example but demonstrates something interesting. If in my code I query selected my form-component tag and output the value, I got nothing, even if I had typed something. That's because there wasn't a value property of the web component. There is an associated form value, that's set with setFormValue, but it is not the same as a value property itself.\nI may not have done the best job explaining that and I apologize, but it kinda makes sense. You can test this below. All I'm doing in the form action is posting to an echo service.\n\n  See the Pen \n  FP1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe Better Example\nFor my first &quot;real&quot; example (and this will still be somewhat incomplete), I thought it would be cool to build a &quot;gradient&quot; form fie",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun May 21 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1684692000,
		"url":"https://www.raymondcamden.com/2023/05/21/links-for-you",
		"content":"Welcome to another &quot;Links For You&quot; post. My queue of links to share seems to be growing quicker than my schedule of posting these (roughly every two weeks), so I may post one next week as well. As always, I hope these posts are useful and informative for yall. Before getting into the links, I'm going to do a bit of a &quot;PBS Fundraiser&quot; request. A while ago, I set up a service for folks to subscribe to the blog and get emails on every new post. This was done via Mailchimp and I documented the process of how you could do it too: Adding an Email Subscription to Your Jamstack Site\nUnfortunately, their &quot;RSS to mail&quot; campaign feature has a bug where for each unique item added to your RSS, you get an email two days in a row. Mailchimp is aware of the issue but it hasn't been corrected yet. Doubly unfortunate is that with my subscriber list now over a hundred people and with me blogging more, I'm hitting the free tier limit. To their credit, when I reach out to Mailchimp and ask for additional credit, they've always given it to me, but even once they fix it, I'm on a trajectory to hit the limits again.\nThe first commercial tier is only 13 dollars a month, and would certainly cover me for some time even with continued growth. Right now my blog costs nothing to me and I'm trying to keep it that way. If a few folks signed up for my Patreon, that would be all I need to cover the costs and I'd definitely appreciate it. If a company wishes to cover the cost, I'd also be willing to call them out and thank them on these &quot;Links For You&quot; posts. I'll try not to get too annoying about asking for pledges, honest. ;)\n\n\n\nThe End of Computer Magazines in America\nWhen I was younger and beginning my lifelong passion for coding, I devoured computer magazines. As a kid, my favorite was Family Computing. (You can find an archive of it online.) Every issue had programs for multiple computer systems that I would type, by hand (uphill, in the snow, etc.) and it really helped me develop a love for coding. Later in life, I would regularly read one or two PC magazines a month. It's been years since I've done it and this article does a great job of discussing how they came to an end: &quot;The End of Computer Magazines in America&quot;. Thank you to Axel Rauschmayer for the share!\nList.js\nList.js is a fascinating little library that adds search, sorting, and filters to HTML lists and tables. It's got a simple API and looks like a great way to make sets of data easier for folks to work with. It's a simple JavaScript library that doesn't require any other framework and is only 5kb gzipped. Even better, it's got a nice CDN URL too making it easy to play with. You can see an example of it in action below.\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLearning about MutationObserver\nFor the last link this week, here's a fascinating article on a Web API I didn't know existed: &quot;Use a MutationObserver to Handle DOM Nodes that Don’t Exist Yet&quot;. The MutationObserver API lets you observe your DOM (either the body or a smaller portion) and recognize when changes occur. It's got a pretty simple API and great support. This excellent blog post was written by Alex MacArthur\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Eleventy by Example, by Bryan Robinson",
		"date":"Thu May 18 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1684432800,
		"url":"https://www.raymondcamden.com/2023/05/18/eleventy-by-example-by-bryan-robinson",
		"content":"I'd like to think I know Eleventy pretty well. I've written about it here a few times, this site is built on Eleventy, I've presented on it, but I certainly don't think I know everything about it. That being said, when I got my copy of &quot;Eleventy by Example&quot; by Bryan Robinson, my expectation was that I'd be reading it to get his take on teaching Eleventy but not really learning terribly much. I was wrong.\nOne of the strengths of Eleventy is how flexible it is. There's no &quot;One Way&quot; of Eleventy, but rather you're given the flexibility to approach projects in ways that make sense for your (or your team's) skills and strengths. This is why I fell in love with it in the first place. If Eleventy didn't support something out of the box, I had no concern I'd be able to add my customization with no trouble.\nGoing into his book, however, I was surprised, again and again, at his approach to solving common tasks in Eleventy. In nearly every chapter, I saw a new (to me) way of working with Eleventy. Not only did I learn more, but I actually found myself liking Eleventy even more.\nThe book does a great job of using multiple different types of projects as a way of teaching various parts of Eleventy. In some ways, it combines one of my favorite types of technical books (cookbooks that assume you know the basics already) with a general introduction.\nI would absolutely recommend the book, even if you've already started with Eleventy, as both a way to flesh out your understanding as well as a way to get inspiration for future work.\nHere's the table of contents, and if you buy via the link below I get a few cents, so thank you in advance. ;)\n\nSetting Up Your Website\nAdding Data to Your 11ty Website\nDeploying to a Static Site Host\nBuilding a Blog with Collections\nCreating Custom Shortcodes to Add Mixed Media to Markdown\nBuilding a Photography Site with the 11ty Image Plugin\nBuilding a Podcast Website with 11ty Plugins and Custom Outputs\nCreating a Static-Search with 11ty Serverless and Algolia\nIntegrating 11ty with a Headless CMS\nCreating Custom 11ty Plugins\n\n\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack",
            
                "books"
            
		]

	},

	{
		"title": "Handling Web Component Removal with disconnectedCallback",
		"date":"Wed May 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1684346400,
		"url":"https://www.raymondcamden.com/2023/05/17/handling-web-component-removal-with-disconnectedcallback",
		"content":"MDN does a fairly good job of covering the lifecycle events for web components but one in particular got my attention today, disconnectedcallback. As kind of the inverse of connectedCallback, it will be fired when an instance of your custom element is removed from the DOM. While I didn't doubt this worked as advertised, I wanted to build a quick demo myself so I could see it in action. Let's start off with a component that demonstrates why this event is needed.\nInitial Web Component\nThe first draft of our component is foo-cat. This component does two things, outputs a simple bit of HTML and then uses an interval to execute code every two seconds. In this trivial example, it's just going to log a message to the DOM, but in a real-world component, you could imagine a component hitting a remote API to get updated information.\nHere's the component:\n\nNow I'll use it in my HTML:\n\nI've added a button as well and used an inline event handler (yes, this is not best practice, but I'm trying to keep things simple, please don't report me to the JavaScript Elders, I'd rather they keep fighting about React, SPAs, and, well everything else). The killtheCat function just removes the element using a document selector (technically this will only remove the first one in the DOM, but again, going for simplicity):\n\nAlright, nice and simple, right? Here it is running in a CodePen:\n\n  See the Pen \n  Test disconnectedCallback by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIf you click the button, you'll notice the component goes away, but the messages keep getting printed to the page. If you want, you can open the CodePen link, go to devtools, and confirm the custom element is gone.\nFixed Component\nI was going to label this section, &quot;properly killing the cat&quot;, but thought that was a bit much. So as you can expect, having an event handler to recognize when the component leaves the DOM is exactly what we need to fix this issue. I made two changes. First, I kept a handle to my interval like so:\n\nAnd then simply added the event handler:\n\nSo again, while my component here is pretty trivial, you can see how important this would be in a real-world web component. You can test this out yourself below.\n\n  See the Pen \n  Test disconnectedCallback by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "My First Web Components Presentation - May 30",
		"date":"Mon May 15 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1684173600,
		"url":"https://www.raymondcamden.com/2023/05/15/my-first-web-components-presentation-may-30",
		"content":"I am super excited (and a bit scared) to announce I'll be giving my first presentation on web components later this month. On May 30th, at 8 PM CST, I'll be presenting virtually to the .Net User Group of British Columbia. This presentation will be online and open to anyone and will be recorded as well.\nI've really been enjoying digging into web components and I'm excited to have a chance to digest, summarize, and share what I've learned with all of yall.\nTo sign up, just RSVP here: https://www.meetup.com/net-user-group-of-bc/events/293540789/\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Updating and Supporting URL Parameters in Alpine.js",
		"date":"Fri May 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1683914400,
		"url":"https://www.raymondcamden.com/2023/05/12/updating-and-supporting-url-parameters-in-alpinejs",
		"content":"I think most of my readers know, when I get an idea in my head, I tend to go pretty deep into it. A few days ago, I blogged about updating and supporting URL parameters with JavaScript. That post itself was an update to an earlier post demonstrating how to do it with Vue.js. For this last post on the topic, probably, I'm going to demonstrate how it could be done with Alpine.js.\nBefore I start though a quick note. The demonstration application I've built is incredibly simple and works just fine without any additional framework. I really like Alpine, but I would not use it in such a simple example. That being said, I added it to my application for demonstration purposes and I hope that clarification makes sense. If not, just reach out!\nThe Initial Application\nI'm going to cheat a bit and steal some of the text/images from both the old Vue post and the one from a few days ago. Here's our application in its default state:\n\n\n\nThere's a list of items that consist of people, cats, and a dog. Each item has a name and type. On top, there are filters for the name and type. If you enter any text, the items that match the name (ignoring case) will be shown. If you select one or more of the types, only those matching will be shown.\n\n\n\nHere's how I built this with Alpine. First, the HTML:\n\nIf you aren't familiar with Alpine, just pay attention to the x- bits as they give you a clue as to what's going on. One of the things I like about Alpine is that I think a person with no knowledge at all about the framework could look at that and get a basic idea of what's going on.\nMy form fields make use of x-model for two-way binding, and the results are handled via x-for inside my unordered list. Now for the JavaScript:\n\nSkip past the hard-coded set of data and note the actual Alpine implementation. It's mostly done by listening for changes to the name and type filter. Both will fire off the same method, updateFilter. That method then updates the list of items we render based on your filters. You can see this in action below:\n\n  See the Pen \n  Work with URL Params, Alpine (Pre) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nNow let's get to the fancy version!\nThe Updated Application\nAs stated in the last blog post, we need to do two things:\n\nWhen a person filters in any way, update the URL to reflect the filter.\nWhen the application loads, check the URL to see if filters were supplied.\n\nLet's begin with the first requirement, updating the URL. Luckily we mostly use the exact same code. First, in updateFilter, we add a call to this.updateURL();. Here's that new function:\n\nThis ends up being a bit simpler than the JavaScript version but basically follows the same pattern. Check each filter and update the query string based on the values there.\nNow let's look at how we can handle existing URL parameters - we will do this in our init method:\n\nWe begin by creating a new URLSearchParams object and checking for each possible filter. Setting the nameFilter is simple, but typeFilter is an array. We can get that easily enough (if it exists) by using the split method.\nAnd that's it! As before, you can't really demo this on CodePen, but I did make one here: https://codepen.io/cfjedimaster/pen/zYmaEmv. Instead, test it out here: https://cfjedimaster.github.io/webdemos/history/alpine_history.html.\nOr, test an example with stuff already filtered: https://cfjedimaster.github.io/webdemos/history/alpine_history?filter=y&amp;typeFilter=person\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Add Streaming to Your Jamstack Site in Minutes",
		"date":"Mon May 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1683568800,
		"url":"https://www.raymondcamden.com/2023/05/08/add-streaming-to-your-jamstack-site-in-minutes",
		"content":"Last week I had the distinct pleasure of being on my buddy Todd Sharp's live stream, Streaming on Streaming. You can watch the recording of that session here:\n\nTodd is the principal developer advocate for Amazon Interactive Video Service (IVS), or, more simply, a way of taking the incredible power of the Twitch platform and putting it in your hands. IVS gives you:\n\nThe ability to create channels for your content\nMultiple ways to broadcast your content, from web-based to more powerful tools like OBS.\nMultiple ways to display your content, including a pretty simple web SDK\nSuper detailed reporting\nAnd deep integrations into the stream that allows for things like transcriptions, Lambda-based chat moderation, and more.\n\nThis is a commercial service but like pretty much everything in AWS, there's a free tier that lets you test things out and see if it makes sense for you.\nIn the session I had with Todd, he wanted to put their own documentation and console through a &quot;first-time developer's experience.&quot; So first off, thinking about and really caring about the initial developer experience with your product is probably one of the most important things you can do. I know in my own career in developer advocacy, it's near impossible for me to try something new without constantly examining and commenting on the developer onboarding. Todd deserves mad respect for doing this live on his stream. I'm kinda well known for breaking things, doing things wrong, and generally just being your worst nightmare when it comes to DX, so that was quite brave on his part. (And to be clear, we didn't preplan any of this stream. I wasn't given anything in advance, and the only real knowledge I had was reading his blog posts and general chat.)\nAs you can imagine, something like &quot;roll your own IVS&quot; is going to be really complex, but in our one-hour session, we got my channel setup, I broadcasted from OBS and a web tool and was able to build a simple web page to display the stream. I thought I'd share some of the highlights from that here as the idea of supporting streaming in the Jamstack sounds incredibly compelling. This will be somewhat high-level, but the docs are pretty thorough and will give you more detail about the particulars. You will also want to start off with the Getting Started guide.\nWith that in mind, here's a basic outline of how simple this is to get started.\nStep One - Get AWS\nI'm just going to skip this. I mean I guess I'm not as I'm calling it out, and you should never assume anything, but as a large percentage of folks already use AWS, my assumption is that you already have an account. I did and used my root one, but if you follow the getting started link I shared above, they have you set up a user with more restricted access, which is The Right Thing to Do, and We Always Do The Right Thing in tech. Ahem.\nStep Two - Make Your Channel\nThe next thing you'll do is define a channel in the IVS portion of the AWS console:\n\n\n\nAs a reminder, the search box on tox of IVS does a really good job of finding stuff. AWS can be a bit overwhelming, but their search has made things a lot easier lately.\nI kind of assume most folks know about streaming and get the basic idea of a channel, but if I were to start streaming, I'd probably only have one channel for myself. If I wanted to add streaming to my company's website, I could imagine having a channel for things like training, external events, and so forth. At the bare minimum, one channel is needed. When creating a new channel, the bare minimum is the name:\n\n\n\nNote that one of the options is automatically storing the streams to S3. I didn't enable that on the stream with Todd, but it's really nice that it's that simple to enable. Obviously, there's going to be a cost in storing large video files, but I appreciate how simple it is to enable.\nStep Three - Figure out your Broadcaster\nThe getting started docs concerning setting up your streaming cover using their SDKs, OBS Studio, or FFmpeg. I've used OBS Studio in the past, so during the stream with Todd, I used that. I'll warn folks though that if you've never used it before, it can be quite overwhelming. It definitely was for me (heck, I still barely know what I'm doing). Instead, let me share another option Todd shared with me later in the stream, stream.ivs.rocks. This is a web-based streaming solution that you can use to test IVS.\nOpen the page in your browser (note that it says Edge is not supported, but I had no issues with it), and click the gear icon to get to your settings. You'll want to specify your &quot;Ingest endpoint&quot; and &quot;Stream key&quot;, both of which you can find in the AWS console in your channel details. Don't make the same mistake I did - the &quot;Ingest server&quot; is NOT the same as the &quot;Ingest endpoint&quot;, you'll want to open and expand &quot;Other ingest options&quot; to see that.\n\n\n\nOnce you've done that, you can click the &quot;Start streaming&quot; button below, and li",
		"tags":[
	        
            "aws",
            
            "video"
            
		],
		"categories":[
            
                "jamstack",
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat May 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1683396000,
		"url":"https://www.raymondcamden.com/2023/05/06/links-for-you",
		"content":"Greetings from Tuscaloosa where I'm about two hours or so away from seeing my eldest walk across the stage and get his diploma at the University of Alabama. Today's kind of a big deal. I noticed, however, I've been a bit remiss in sharing these link posts the last few weeks so I'm taking a few minutes to do so now. Enjoy!\nNew Array Method - Group\nHere's something you don't hear often - a cool new web API only available in Safari. But something tells me that won't last as it's pretty darn useful. David Walsh has an excellent article on the JavaScript Array Group method and how it can be used to, well, group, arrays of objects. While it's not something you'll use all the time, I feel like pretty much everything I do in JavaScript involves arrays, so the more powerful they get, the better it is for developers. He post covers both group and groupToMap methods and you can find more at MDN:\n\ngroup\ngroupToMap\n\nTranscribing Voice to Text with Summarization and More\nThis is an incredibly detailed tutorial by Thomas Frank covering a workflow that transcribes voice notes, summarizes them with ChatGPT, and posts to Notion. His tutorial has both intensive written instructions as well as a great video. Even better, he uses one of my favorite services, Pipedream, as well. Check it out here: How to Take Perfect Notes with Your Voice Using ChatGPT and Notion\n2023 State of Web Components\nLast but not least is a detailed look at the current state of web components by EisenbergEffect, which I assume is not his real name, but that's all I can find. As the title implies, this gives an overview of the multiple aspects of the APIs that make up web components and their current status in the web platform. Here's the link, but note that it may need to login to Medium to access, and I got a warning I was near the end of free articles. Good luck: 2023 State of Web Components\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "My Experience at Antiques Roadshow",
		"date":"Thu May 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1683223200,
		"url":"https://www.raymondcamden.com/2023/05/04/my-experience-at-antiques-roadshow",
		"content":"So, as this is my blog, nothing is really &quot;off-topic&quot;, but this is probably the most OT thing I've ever shared here. My wife is a big fan of Antiques Roadshow. I enjoy watching it too, although it isn't something I mind her watching without me. A month or so ago she discovered they were doing a taping in Baton Rouge (about an hour or so away from us) and she decided to get tickets. Here's what I experienced.\nFirst off, tickets to Antiques Roadshow are typically only available in a lottery. We found out that eight thousand people signed up for the lottery and only two thousand pairs of tickets were given out. My wife, being the typically smart person she is, went for a VIP option they offered which gave us access to a cocktail party the night before and guaranteed tickets to the event. I'm not really a cocktail party kinda guy but it was a nice event and we got some great information about the show, how the process the next day would work, and had a chance for Q and A with the producer. (Or director. I forget.) Anyway, it gave me a chance to dress up which I don't do often. ;)\n\n\n\nOn the day of the event, we showed up around 10 AM. It opened earlier that day, but typically folks come in, get their stuff appraised, and don't really hang out. (At least that's what we did.) You are responsible for handling your own stuff, so you'll see a lot of carts and folks trucking out large paintings, furniture, etc. Our items (more on them in a sec) were pretty small so we used a cart to carry them plus some foldable chairs.\nYou start off in triage where you describe/show your items and are given a piece of paper that tells you what category your item fits in. These categories range from stuff like jewelry, watches, paintings, toys, sports, etc.\n\n\n\nOnce you have your items categorized, you then enter the event properly. The taping was at the incredibly lovely LSU Rural Life Museum and held along a tree-covered path. That along with the unusually nice weather we had made for a great day.\n\n\n\nAt this point, you find the area for the category of the item you wish to appraise first and get in line. The lines were mostly not too bad. For three of our items, it was less than a ten-minute wait. For one item it was a good hour and a half. The actual process is fairly quick. The night before at the cocktail party it was explained as such.\n\nThe appraiser will tell you about your item.\nThey will give you an approximate value.\nIf the appraiser thinks your item is cool, they'll flag an Antiques Roadshow production person.\nIf that person thinks your item is cool, then you get brought over for an in-depth review on camera.\n\nHere's a shot from our first appraisal:\n\n\n\nSo, did we have something worth hundreds of thousands of dollars? Not even close. ;) We appraised four things that I'll do a horrible job describing:\n\nThe first was a painting from my wife's grandmother. The appraiser was able to identify the artist and we've reached out to them via email to confirm. The estimated value was up to 500 dollars.\nThe second item was a small wooden box. It was roughly a hundred years old, maybe worth three to four hundred dollars. We weren't able to find out who manufactured it though.\nThe next item was some decorative eggs. The appraiser had a good laugh and said they were basically worthless unless sold together as a collection.\nAnd lastly, an opal ring from the 70s. Or so we thought. It turned out to be a &quot;fopal&quot;, or fake opal. Technically a thin slice of opal with some glass and a colored backing to make it look like a full opal stone. It was worth maybe a hundred dollars, but we both had a great laugh at that.\n\nOne of the best things though was just seeing all the stuff other folks brought. Everyone was open to talking about their items and we saw some really cool stuff there. None of our stuff &quot;made the cut&quot; for an on-air in-depth review, but we enjoyed it tremendously. We did do the parting shots video (that's not the real name I believe) so there's a chance we'll make the show when it airs (which won't be till next year).\nIt was absolutely a cool experience and I definitely recommend it to others if you have the chance!\nPhoto by sue hughes on Unsplash\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Updating and Supporting URL Parameters in JavaScript",
		"date":"Thu Apr 27 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1682618400,
		"url":"https://www.raymondcamden.com/2023/04/27/updating-and-supporting-url-parameters-in-javascript",
		"content":"Not quite a long time ago, but roughly two years ago I wrote a blog post on updating and supporting, URL parameters with Vue.js. The idea was this: Given an application that lets you perform various tweaks, it would be nice if the URL was updated to reflect the current state of the application. This would let you bookmark, or share, the URL with others and they would get the same view as you. In that post, I built a very basic &quot;data filtering&quot; application and then updated it to support updates to the URL. I thought I'd revisit that post and demonstrate building it in vanilla JavaScript. As always, I'd love to hear your thoughts on this, especially if you've done something similar.\nThe Initial Application\nI'm going to cheat a bit and steal some of the text/images from the older post. Here's our application in its default state:\n\n\n\nThere's a list of items that consist of people, cats, and a dog. Each item has a name and type. On top, there are filters for the name and type. If you enter any text, the items that match the name (ignoring case) will be shown. If you select one or more of the types, only those matching will be shown.\n\n\n\nLet's take a look at the code. First, the HTML:\n\nThis isn't too different from the earlier Vue version, but I've removed v-model and other Vue declarations. Now, the JavaScript. First, I've got my data hard-coded on top. Here's how it looks:\n\nNormally this would be loaded in via a network call or some such. Next, I define different variables and the &quot;start up&quot; code:\n\nThe only really interesting part is here that I listen for any change or input event on my fields on top, all of them going to the same particular function to handle those changes.\nrenderItems just handles generated my HTML list:\n\nBut updateFilter is a bit more complex. I need to potentially filter by text input as well as multiple different &quot;type&quot; filters:\n\nI think the only really odd thing above is Array.from, because querySelectorAll returns a NodeList, not a real array.\nAll in all, I've got a bit more code than the Vue.js version, but I'm also not loading Vue, so a net win for this simple application. You can test this yourself below.\n\n  See the Pen \n  Vue Blog Post about URL Params 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe Updated Application\nOk, for our new version, we need to do two things:\n\nWhen a person filters in any way, update the URL to reflect the filter.\nWhen the application loads, check the URL to see if filters were supplied.\n\nLet's start with the latter. In my updateFilter method, in the end, I added a call to a new function, updateURL:\n\nThis uses the URLSearchParams API to generate a new query string. I begin by checking the input field for a value and if it exists, set the filter param to it.\nFor the selected types, I check them all and simply append the value if they are checked. This creates an array I can then set to typeFilter by relying on an automatic toString conversion.\nFinally, I use the replaceState method of the History API to update the URL. The third argument doesn't need to be a full URL as I'm just changing the values in the query string.\nThat part's rather easy, but to support recognizing the parameters on load, I've modified my init function:\n\nI've added a few more variables to make it easier to check my individual type filters. I get my current query string and then begin checking for my two main values, filter and typeFilter. Working with filter is easy, but for the typeFilter, I need to check each possible value and check the appropriate box. Also, notice I've added a call to update the filter since it's possible we have filtering going on.\nAnd that's it. Now, I'd like to show you on CodePen, but unfortunately it won't work correctly there. You can grab the code there if you want (https://codepen.io/cfjedimaster/pen/dygWQwj?editors=1011), but don't bother trying to use it there. Instead, I put it in one of my repos and you can browse it here: https://cfjedimaster.github.io/webdemos/history/\nOr, test an example with stuff already filtered: https://cfjedimaster.github.io/webdemos/history/?filter=y&amp;typeFilter=person\nLet me know what you think!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Creating a Web Component for Reveal.js (Follow-up)",
		"date":"Mon Apr 24 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1682359200,
		"url":"https://www.raymondcamden.com/2023/04/24/creating-a-web-component-for-revealjs-followup",
		"content":"This weekend I blogged about a web component experiment wrapping the excellent Reveal.js presentation library. In that post, I created a component to wrap &lt;section&gt; tags that represented individual slides. I mentioned that I wanted to follow up on this and create a &quot;child&quot; component to represent slides. Here's what I did - including my first version which failed for a pretty obvious reason.\nVersion One - That I Did Wrong on Purpose to Test My Readers - Honest.\nAlright, so in my initial post, I created the &lt;reveal-preso&gt; tag. Here's an example of how such a component would work:\n\nMy plan was to simply replace &lt;section&gt; with a new component, &lt;reveal-slide&gt;. My initial, flawed implementation, was super simple:\n\nI then edited my HTML to add one on top:\n\nI intentionally just added one because I figured the other slides would continue to work as before. However, the result was broken:\n\n\n\nI've been using Reveal for a long time, and typically when this happens it means I made a typo somewhere in my HTML. So I did what any good web developer should do - run to StackOverflowopen up my devtools. When I did, I saw this:\n\n\n\nIn case that's a bit hard to read, it's basically showing this:\n\nReveal expects top-level &lt;section&gt; tags immediately under the &lt;div&gt; wrapper with class=&quot;slides&quot;. Since it was &quot;under&quot; the original web component, it wasn't found and properly handled by Reveal.\nIn case you want to see this broken version, you can find it below.\n\n  See the Pen \n  WC Reveal (2 Fork) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two - The Perfect One I Meant To Do All Along\nFixing it ended up being rather easy, although I'm not entirely sure how &quot;proper&quot; this is for web components, but one of the fun things about building demos on the web is seeing what you can use in the wrong way. I simply replaced my web component instance with a new section tag:\n\nThis works, but of course, my web component is basically self-destructing. If I wanted to do things like handle attribute changes and the such, as far as I know it wouldn't work. But in terms of the presentation, it worked just fine.\nI then decided to take it one step further. Reveal slides can be modified with data attributes, so for example:\n\n&quot;Proper&quot; HTML allows for any custom attribute as long as you prefix it with data, and then you can get and manipulate those attributes as you see fit.\nFor my web component, I thought it would be cool to allow you to use all of the attributes Reveal supports, but without the data- prefix:\n\nTo support this, I checked the attributes property of my web component instance. For each I find, I simply prefix it with data-.\n\nNow, this would be bad for standard attributes like id, width, etc. But Reveal doesn't really use those for slides.\nAll in all, this really &quot;reads&quot; nicely to me:\n\nFeel free to play with, and fork, this version:\n\n  See the Pen \n  WC Reveal (2 Fork Rev B) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nPhoto by Alexander Andrews on Unsplash\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Web Component for Reveal.js",
		"date":"Sat Apr 22 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1682186400,
		"url":"https://www.raymondcamden.com/2023/04/22/creating-a-web-component-for-revealjs",
		"content":"I've been a fan of Reveal.js for many years. Reveal.js is a web-based presentation framework that makes it (mostly) easy to create slides with just basic HTML. I don't mind Powerpoint at all, and it's incredibly powerful, but when I'm presenting on web topics (which is, usually, 99% of the time), I don't like the experience of &quot;alt-tabbing&quot; from a running Powerpoint to code or browser tabs. Reveal.js helps me avoid that.\nUsing Reveal (I'm getting tired of typing the dot jay ess) is relatively simple. You add a script tag. You add two link tags for CSS (one for the core CSS and one for a theme) and then start writing HTML. The section tag is used for each slide. Here's an example of how easy it is, from their docs:\n\nIn the snippet above, you can see the two section tags representing each slide. There's quite a bit to it and if you want to see more, check out their demo, but for today, I was curious if I could simplify the creation of Reveal presentations with web components. Here's what I came up with.\nVersion One\nFirst, I'll share the HTML I wanted to use. I actually wrote this first and then began building the web component so I could see it update itself.\n\nMy HTML makes use of the web component, reveal-preso, and supports a theme attribute. Inside the component are a set of section tags for the slides. (As an FYI, I could have also built a reveal-slide child tag, and I will do that in my next post!) I don't have any script or link tags at all. Now let's look at the component definition.\n\nAs you can see, this is relatively simple. My connectedCallback rewrites the inner HTML such that it's wrapped with the divs that Reveal expects. I then dynamically load all three resources - my JavaScript and both CSS resources. I've got an event listener on the script such that when it's been loaded, I can initialize the presentation. From what I could see (via Googling and Stack Overflow), there's no onload for CSS scripts. I saw a few suggested workarounds, but I decided to keep it simple and just worry about the JavaScript resource. I accept that may be problematic. One more issue - my code checks for the theme argument one time only. If you were to change it via JavaScript later, it would not pick up on that. (If I may be so bold as to suggest a best practice here, I'd probably suggest you always have code to recognize changes to attributes or clearly document what's not going to work.) Here's how it looks:\n\n  See the Pen \n  WC Reveal by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPersonally, I think this is pretty cool! But notice that it expects the entire web view. That's the default for Reveal and probably how most people would use it, but I really wanted to see if it was possible to embed Reveal along with other web content. Here's how I did that.\nVersion Two\nFirst, I checked the docs. I had only ever used Reveal as a full web view presentation and I didn't know if the framework itself supported. Turns out, it absolutely has an embedded mode. There's also support for multiple embedded presentations. This requires two things - a slight change to how you initialize the Reveal object and supplying specific dimensions of the container holding your presentation.\nI began by modifying my HTML a bit:\n\nI've got some basic crap on top and bottom, and in the reveal-preso tag, I've added a height. My component is going to support both height and width, but it will default both. Now let's look at the updated component:\n\nI've updated the code to look for height and width now and default both. When I rewrite the inner HTML content, I include those values. Lastly, I changed how I initialize the presentation. You can see this in action here:\n\n  See the Pen \n  WC Reveal (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThere are still things I'd tweak here. I'd definitely add support for changing the height and width. Reveal itself also supports recognizing changes to its size. One note - I did do a quick test with 2 presentations on the page, and it worked, but it only shows one theme which I believe there is no workaround as Reveal expects only one theme CSS. It may be possible, but I'm not sure.\nNext Version?\nAs I said earlier, I want to iterate on this one more time. I want to create a child tag, reveal-slide (or perhaps something shorter) and look into importing the tag from another JavaScript resource. I also want to support dynamically changing the height and width. I'll work on this tomorrow or later this month. As always, let me know what you think!\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing the Web Share API",
		"date":"Thu Apr 20 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1682013600,
		"url":"https://www.raymondcamden.com/2023/04/20/testing-the-web-share-api",
		"content":"A week or so ago I discovered the Web APIs list on MDN. It's basically an index listing of the various web APIs documented on the site. While I knew most of them, more than a few were either unclear to me or entirely unknown. This is what inspired my post on the Cookie Store API. For today, I want to share what I found about an API I was aware of, but had not yet had a chance to play with - the Web Share API. While generally well supported, my testing with it left me feeling like it's not quite ready yet for usage. Here's what I found.\nWhere is it supported?\nThe browser compatibility chart for Web Share is pretty green, which is good, with the main exception being Firefox. According to MDN, it can only be enabled by changing a user preference, which means no one but devs will ever use it on that browser. There's a bug on bugzilla created three years ago with an update 8 months ago but I can't really tell what's going on there.\nHow does it work?\nAs an API, it's actually really simple. You have two methods, share and canShare. The purpose of canShare is to basically act as a validation for something you would pass to share, but in my testing, it returned true every time I found a way to make share fail, so I'm not sure it's actually useful now. (I'll show some examples later.)\nAs for share, it works by letting you pass an object of four optional properties, with at least one needing to be sent. These properties include:\n\nurl\ntext\ntitle\nfiles\n\nThe method must be invoked via what MDN calls transient activation, which in simpler terms just means user interaction, like a button click.\nLet's look at a sample of this. I've got a button. Nothing special, just a button:\n\nAnd then a bit of JavaScript:\n\nThe result of share is a promise hence the use of async/await in there. This promise either returns undefined or an exception. In theory, you could put something after the await to let the user know they shared, but, on Windows, the promise returns immediately, not after a successful share, and I honestly don't see the point. The user just literally did the share, which is an interactive experience, so they know already.\nYou'll also notice I'm just sharing text. That seems a bit unhelpful, but I try to start my experiments as simply as possible. So what happens when you click? I began my testing with Microsoft Edge, on desktop. After clicking, I get this UI in the top, center of my viewport:\n\n\n\nSo... this is a bit weird. That first option is a person I worked with at IBM years ago. I'm fine not blurring the email as she isn't even there anymore as far as I know. I have zero idea why this person's email would be the first one suggested. Even odder, sometimes that person's email would be the second option. Clicking either of my names opened up an Outlook UI that was actually rather well incorporated with the browser... except nothing was there:\n\n\n\nI'll add that there is no way to figure out exactly what email address that is. I tried hovering and waiting, right clicking, but no go. I mean I know it's me, but I've got a couple of different email addresses. The email did show up, but without the text I had set to share so it was pointless.\nI then tried Twitter and Mastodon. Clicking Twitter opened up my desktop Twitter app... with nothing in the text field. Mastodon worked perfectly, although it opened a second window just for composing:\n\n\n\nSo hey, how about the Mac? Get ready.\nI began with Microsoft Edge on my Mac. Note that the options are a lot smaller, but I don't use my Mac a lot so I've got the bare minimum installed there. I do have Mastodon installed but it's not an option. On the flip side, I really like how it's presented. Unfortunately, there's a pretty gnarly bug here. If you dismiss the share for any reason, which is very easy to do, like switching to another app after you click and before you share, then sharing is broken. Forever.\nYou get this lovely error:\nDOMException: Failed to execute 'share' on 'Navigator': An earlier share has not yet completed.\n\nOk, not forever, but until the page is reloaded. There is a reported Chromium bug on the issue. Safari does not have this issue. The UI looks pretty similar with the only difference being that Safari showed the UI near the button versus Edge having it more centered.\nOn mobile, I tested Edge on my Android phone. The options here made a lot more sense:\n\n\n\nThe two people I blurred at the end were the last two people I texted. The two Slack groups were my most recent channels. I tested Facebook Messenger, chose my wife, and it sent the text to her exactly as if I had typed it. (I warned her first.)\nAdding More Options\nWhat happens if we add more options? I added a second button to my web page with a new ID, and then this JavaScript:\n\nNow I'm specifying text, title, and URL. Normally I assume you would want to use the title of the web page, but I wanted to be really clear as to what values provided what output. Right away, I noticed something cool. The share options",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Need Help with ColdFusion?",
		"date":"Fri Apr 14 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1681495200,
		"url":"https://www.raymondcamden.com/2023/04/14/need-help-with-coldfusion",
		"content":"As folks know, I love it when yall send me questions via email. I like to help others, I like to dig into issues and figure things out, and I'm always on the lookout to learn new things. However...\nAs I just got an email about ColdFusion Event Gateways, I want to let folks know that my ColdFusion skills are somewhat rusty these days. You are absolutely ok to reach out to me about it, but I haven't used ColdFusion on the regular in probably over a decade now. (This week was an exception when I spent the better part of a day helping out one of our Acrobat Services customers who use ColdFusion.)\nSo given that I thought I'd suggest folks consider the CFML Slack at cfml.slack.com. When I've needed to do a few CF things recently I't gone there. It's incredibly active with lots of really smart folks. You'll find channels related to Adobe ColdFusion, Lucee, Box, and more.\nBut of course, Slack still doesn't offer a way to allow people to sign up unless developers set something up on their own. I'm truly surprised that they haven't made a change in that area yet, but for now, if you want to sign up you just hit the form at cfml-slack.net. Big thanks to Charlie Arehart for blogging about the signup URL.\nPhoto by Brett Jordan on Unsplash\n",
		"tags":[
	        
            "misc"
            
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Using the Cookie Store API",
		"date":"Wed Apr 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1681322400,
		"url":"https://www.raymondcamden.com/2023/04/12/using-the-cookie-store-api",
		"content":"Today while browsing a list of web APIs over at MDN, I ran across one that surprised me - the Cookie Store API. This is clearly marked as experimental (currently only supported in Chrome/Edge) but looks to be fascinating. Cookies are the oldest (as far as I know) way for web applications to store data on the client. They're also typically the least recommended way of doing so, for many, many reasons. However, sometimes you need to work with cookies, and this looks like a really nice new way of dealing with them. Here's a quick look.\nThe Old Way\nFor the past few hundred years or so, working with cookies in the browser involved string parsing. There really wasn't even an &quot;API&quot;. You would read document.cookie, which returned a delimited string of cookie values:\n'_ga=GA1.1.277504870.1615419234; ezux_ifep_238929=true; ezouspvh=1900; __qca=I0-1813545485-1623786777967; ezouspva=0; ezouspvv=0; ezux_et_238929=10099; ezux_tos_238929=5877205; _ALGOLIA=anonymous-c45533a4-0752-4b4d-90cb-b641b642cf4f; _ga_T5V3C8M5RY=GS1.1.1669062149.711.1.1669064843.0.0.0; hitCounter=0'\n\nTo get a particular cookie, you would parse by semicolon and then split it into name-value pairs. However, cookies can have more than just a name and value. For example, most cookies have an expiration date. But this is not readable by JavaScript.\nEven odder, while the value of document.cookie is a list of all cookies, you can use:\n\nTo write one cookie, leaving the others alone. When writing cookies, you can set additional values, like expiration, by using semicolons. Here is an example from MDN:\n\nBecause of how wonky this is, there are libraries out there to simplify it. MDN used to have a nice little library for it but unfortunately, it seems to have been removed from their site. Thankfully there are probably ten billion or so other JavaScript libraries/utilities/simple functions out there that you could use instead. Or...\nThe Shiny New Way\nSo as I mentioned at the start, it looks like we have (or will have, hopefully), a new modern API for working with cookies. The Cookie Store API provides an asynchronous API to read, write, and delete cookies. It also lets you listen for changes to cookies as well. As mentioned above, currently this is only compatible with Chrome and Edge (well Opera and Samsung Internet as well). No idea if Safari will add this and your guess is as good as mine.\nTo check for support, you can look for the cookieStorage object:\n\nOnce you've confirmed you can use it, here are some quick examples of the methods.\nGetting a Cookie\nTo get a cookie, you use get. Fancy, right? It does support optionally checking for a URL scope, typically used on larger sites to restrict a cookie to be available on only a portion of it. Here's the simplest example:\n\nAs mentioned before, the API is asynchronous so you can either await it, or use then. I prefer await.\nIf the cookie doesn't exist, you'll get a null response, otherwise an object like so:\n\nNotice that the value is a string. You will need to parse it in your code if you wish to treat it as a number.\nSetting a Cookie\nAs you can imagine, setting a cookie is also pretty easy. If you want to set a cookie and a value, you just do:\n\nThis will also be asynchronous so either await or be sure to use then. Note that even though I pass a number, it will become a string when stored. If you want to set options, you use the form:\n\nIn this example, I simply specified a 24-hour expiration date as an option, but there are other options as well (check the set docs for a list).\nDeleting a Cookie\nAgain, another simple API, but remember it's async:\n\nThere's also an option like get where you can pass an object containing additional options. A good example here would be path, and again, think of the large website needing to ensure a cookie named foo in a path doesn't interfere with one in another path.\nCookie Monster (AKA, all the cookies)\nThe final method I'll show is the getAll method which returns an array of cookies. Running cookieStore.getAll().then(console.log) on my blog returns:\n\n\n\nListening for Cookie Events\nOne cool aspect of the API is the ability to fire off code when cookies change. So for example:\n\nThis fires a CookieChangeEvent. This event contains a few interesting properties:\n\ntype will tell you the type of change, being either deleted or changed.\nchanged is an array of cookies that have been changed.\ndeleted is an array of cookies that have been deleted.\n\nThe docs for the event don't specify why changed and deleted are an array, but if I had to guess, due to the async nature of the API it's possible to change/delete N cookies before the event can fire in response, hence needing the results in an array.\nAn Example\nWhile - in general - I would not recommend using cookies for new projects, I did whip an incredibly simple demo that keeps track of the number of times you've visited a site. It does this using a hitCounter cookie. First, the HTML:\n\nAnd then the JavaScript:\n\nBasically, read the cook",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Speech Synthesis and Recognition with Alpine.js",
		"date":"Mon Apr 10 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1681149600,
		"url":"https://www.raymondcamden.com/2023/04/10/using-speech-synthesis-and-recognition-with-alpinejs",
		"content":"Recently, I worked on two interesting (imho!) articles for our blog at work on integrating web APIs with the Adobe PDF Embed API. The first blog post demonstrated using the Web Speech API to let you select text in a PDF and have it read to you. I followed this up with an article on using the Speech Recognition API to let you use your voice to control a PDF document, for example, asking it to navigate forward and backward or performing searches.\nI thought it would be interesting to look into integrating these APIs in a simple Alpine.js application. I didn't expect any trouble of course, but as with a lot of things I do here on the blog, I just wanted to see it working to reassure myself it wouldn't actually be a problem.\nFor my Alpine.js application, I found a great demo that would be perfect for what I had planned. Back in 2019, I blogged an example of building Eliza in Vue.js. Eliza is an old school, really old school chatbot that isn't terribly intelligent but can trick people into thinking it's a lot more intelligent than it really is. (I do that too!)\nI began by simply converting the existing Vue.js application to Alpine.js. For the most part, this was relatively simple. My HTML basically switched from v- crap to x- crap.\n\nThe JavaScript is also pretty similar, except in the simpler more direct (again, imho) Alpine.js setup:\n\nThe Eliza library I use has a few methods. getInitial starts a conversation. transform will create a response to input. The only really fancy part here is auto-scrolling the textarea to the bottom. Feel free to play with this version here:\n\n  See the Pen \n  Alpine Eliza by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEliza, the OG ChatGPT.\n\n\n\nAdding Speech Synthesis\nSpeech Synthesis, if you take the defaults, is pretty trivial. And honestly, the default voice, at least on my system, sounds exactly like a computer therapist. I began by modifying inputResponse to call out to a new method to handle it:\n\nThe initial version of my speak method was just two lines:\n\nAgain, the API itself can be configured quite a bit, but as I said, the out-of-the-box voice worked perfectly for me.\nAdding Speech Recognition\nSo this part was going to be simple. Honest, it was. And then things took... a weird turn. All the best &quot;simple&quot; demos do. I began by adding some UI so that the end user could enable speech recognition, and I tied it to checking for support first. Here's the HTML:\n\nAs you can see, it shows up if a variable, speechRecogSupported is true. It uses a checkbox to let the user enable (or disable) the feature.\nIn the JavaScript, I created two variables for this:\n\nI then added code inside init to handle it. First, see if we have support:\n\nNext, I monitor the checkbox and when enabled, start listening:\n\nThis version doesn't check for the checkbox being unchecked, but I'll get to that eventually. Basically, this waits for a transcript value to come in, sets it to the user's message, and acts as if they had clicked the chat button.\nSo, this worked but had a hilarious side-effect, namely that I spoke, it recognized it and entered it as input, Eliza responded, and the speech recognition service recognized that as well, and all of a sudden, the app is basically talking to itself. It was crazy and fun, so of course I saved that version here: Alpine Eliza with Speech (with bug)\nThe fix for this ended up being a bit weird. I figured I'd need to turn off speech recognition while Eliza was talking. I initially tried this:\n\nThis however returned an error about the speech service being already in an enabled state. I couldn't figure out why that was happening and on a whim, I tried adding in a delay (with setTimeout), and that corrected it. It then occurred to me, that the speak method wasn't asynchronous, and that Eliza would still be speaking when I turned on recognition again.\nThere is no event I could use, only a boolean, speaking, that is true while SpeechSynthesis is actively speaking. I ended up with this version:\n\nBasically, while speaking is true, I want to loop, but in an interval of quarter seconds, and when it's finally not speaking, kill the interval.\nThis is probably brittle as hell, but it seemed to work well. Note that Edge, on M1 Macs, has an issue with speech recognition in general, so it may not work for you on that platform. And for yet another issue, I discovered that the SpeechRecognition API will censor curse words. If you say &quot;fuck&quot; (I don't think I've used that word here much, but we're all adults, right?), the transcript value will be ****. I tried a variety of curse words (I know a few), and most of what I expected to be censored was. I've reached out online to see if this is a setting that can be tweaked and will let yall know what I find. You can find my final demo below.\n\n  See the Pen \n  Alpine Eliza with Speech (without bug) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nPhoto by Volodymyr Hryshchenko on Unsplash\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Apr 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1680976800,
		"url":"https://www.raymondcamden.com/2023/04/08/links-for-you",
		"content":"Today's Links For You is a special one, not just because it's my birthday, but... oh wait, yeah, that's the only reason. Whether today is special or not, I hope everyone out there reading this has a great weekend. Ok, here are your links!\n6 CSS snippets every front-end developer should know in 2023\nOver the years, I've been incredibly happy watching the web platform mature and grow and while CSS is still my weakest area, I try to pay attention to its updates and latest developments as well. This article by Adam Argyle covers six different CSS techniques developers should be aware of. I knew... none of them.\nAll JavaScript and TypeScript Features of the last 3 years\nOne good &quot;tech roundup&quot; deserves another. This article by Linus Schlumberger enumerates every single JavaScript and TypeScript change from the past few years. It's a huge list, but a good way to quickly see just how fast the platform is advancing.\nAnd now for something completely different...\nI like scary movies, but in general, I'm not a fan of gore. No, my main thrill is &quot;creepy&quot; movies, ghost stories with jump scares, creepy environments, etc., that's my jam. Recently I discovered a TikTok creator who uses CGI to create creepy short videos and I absolutely love it. I've often been a fan of media created from Creepypasta, like the Channel Zero show.\nThis week, a story from Kotaku hit my inbox about the Backrooms. The concept is simple. If you were somehow able to &quot;no clip&quot; (a video game cheat that lets you pass through walls) out of our reality into ... someplace else ... what would it like? Turns out it's pretty horrifying.\nIf this sounds interesting to you, check out the Backrooms playlist by @Kane Pixels. It's probably about 1.5 hours total and it scared the hell out of me. I'm willing to bet half or so of my readers will hate it, but the rest will (hopefully) be enthralled like I was.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Working with CloudCannon and Eleventy - My Experience",
		"date":"Thu Apr 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1680804000,
		"url":"https://www.raymondcamden.com/2023/04/06/working-with-cloudcannon-and-eleventy-my-experience",
		"content":"I've been working with the Jamstack (in its various iterations and names) for many years now. In that time, one area I had not really looked into is the use of a content management system (CMS). I recently had a chance to look into how CloudCannon adds CMS capabilities to Eleventy and I thought I'd share my experience. I went in, admittedly, a bit concerned. One of Eleventy's greatest strengths is its flexibility. Unlike other Jamstack solutions that have a proscribed way of doing things, Eleventy is incredibly open to how it can be used to build a site. My assumption was that it would be difficult for a CMS to &quot;grok&quot; a particular Eleventy implementation and support it. Turns out my concerns were unwarranted. Let me share what I discovered.\nStart with the Guide\nThis is probably obvious but start with the Eleventy-specific guide created by CloudCannon. While it begins with Eleventy-specific content, it then goes into more general content about CloudCannon is an excellent way to start. In many cases, I've seen platform products produce short, isolated content related to a specific tool, but this guide takes the time to go a bit further into the more general things a new user would want to know. I appreciate that and as I work in developer relations it's a strategy I'm going to consider in the future.\nSupport for Eleventy 2.0 is Ready to Go\nWhen I first began testing, one issue I ran into quickly was support for 2.0. Eleventy 2.0 is a fairly recent release, so I wasn't too surprised that it caused an issue (also, my initial test was over a month ago). I reached out to support, and they identified it as an issue, gave me a workaround as well as a general ETA for a proper fix, and in my case, I just waited. I had to reach out to support a few times in my experiments with CloudCannon and I was impressed with how quickly I got a response.\nAbout that Flexibility\nAs I mentioned in the beginning, I was concerned about how a CMS would handle the flexibility of Eleventy. In one of my first tests, I worked with a copy of my blog, which is both a large site and a somewhat complex one. As I worried, things did go a bit haywire but it turns out CloudCannon has a built-in way of handling that - configuration files. I added a simple YAML file that quickly corrected the issues I was seeing. Obviously how and when you use this will depend on your site, but it's important to know that if you do see issues with how the CMS is working with your site, it's possible a quick configuration file will help things out.\nIf you're curious about the specifics, CloudCannon had difficulty understanding the path to my posts and regular pages. This was corrected with a few lines of YAML:\ncollections_config:\n  posts:\n    path: _posts\n  pages:\n    path: ''\n    filter: strict\n\ncollections_config_override: true\n\nHTML, It Matters\nThe next issue I ran into was an odd one. I'd go to edit a post and I'd be forced into the raw code editor, not the pretty visual one. This turned out to be entirely my fault. For my blog, I use some code to lazy load images. The HTML looks like so:\n\nI use JavaScript to find the image tags with data-src, wait for them to become visible, and then load them at that time. Unfortunately, this means people without JavaScript don't see the image at all. I need to rethink this solution at some point, but the important part is that the invalid HTML caused the CMS to not be able to use the nicer editor. Support mentioned that the error could have been handled better (with a warning for example), but it was entirely my fault, and indicative of a site that's been patch worked for quite a few years.\nPassthrough File Copy, It Also Matters\nI started over with a new site, a copy of the one that people can build with my Eleventy Blog guide. This was an incredibly simple application and I figured it would be a good test for CloudCannon, but once again I ran into an issue. While using the editor, I uploaded an image, and it rendered fine there, but when saved and viewed publicly, it did not show up.\nThis may shock you, but the issue was my fault again. One thing that bites new Eleventy users is that they quickly discover Eleventy will ignore files it doesn't process, including images, CSS, and JavaScript. Eleventy makes it easy to correct this via the Passthrough File Copy feature but it's a bit &quot;low&quot; in the docs and easily missed when learning. This week, Eleventy released additional documentation to help address this: Adding CSS, JavaScript, Fonts and that's a great addition.\nIn my own Eleventy blog guide, I was trying to keep things as simple as possible and never addressed this. Therefore my blog didn't have support for images, which in retrospect was kind of dumb. I've since updated my guide to add this, and once my code was added to my repository, CloudCannon was able to pick up on it immediately.\nI'll also note that while debugging this (and a big shout out to CloudCannon support for again helping me out), they po",
		"tags":[
	        
            "eleventy",
            
            "cloudcannon"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "WebC Updates in Eleventy - Looping",
		"date":"Tue Apr 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1680631200,
		"url":"https://www.raymondcamden.com/2023/04/04/webc-updates-in-eleventy-looping",
		"content":"Last week I took a look at updates to WebC support in Eleventy, specifically if/else support. In that last update, looping support was added as well so I thought I'd build a quick little demo showing it in action. As usual, I ended up finding another new feature/change to WebC so it was good practice at WebC in general. Here's what I came up with.\nFor my demo, I decided to build a simple WebC component for a lightbox image gallery. I've made use of Parvus before and it's a nice, and pretty simple, library for lightbox UI. Once you have the JavaScript and CSS loaded on your page, you can use them by doing two things.\nFirst, wrap your thumbnails with links to the full-size image, and ensure you use the lightbox class:\n\nThen you just need one line of code:\n\nObviously, you can get more complex with it, but in general, it's fairly simple to use. Here's how I built a WebC wrapper for it. I began by creating a _data file named images.json:\n\nMy data is an array of objects, each with a thumb and url key. I'm using a placeholder service to simplify working with unique URLs. With this data, I can then pass it to a WebC component. I created index.webc, and added the following:\n\nI haven't defined lightbox.webc yet, but for now I just add it to my page and pass in my data. There are three ways of passing attributes to WebC components, but it's important to note that if you are passing complex data, like an array of objects, you need to use the dynamic attributes and properties feature.\nNow let's look at the component:\n\nLet's focus on the top first. My HTML has one main wrapped div (I use webc:root to remove &lt;lightbox&gt; from the resulting HTML, I don't need it) and then the new feature is in the inner div. I use webc:for to loop over the array of data passed to it and output a link to each main image and output the thumbnail.\nThe rest of the component handles CSS and JavaScript, and remember that both of these will be bundled up. Let's look at how I use this in my layout Liquid file as this shows another recent change:\n\nWhen loading bundled assets, you now use a shortcode, getBundle, to inject the bundled up code. (There's also getBundleFileUrl.) I struggled with this as I forgot to use {% versus {{.\nThat's really all there is to it. I still feel like I'm struggling a bit with WebC. As I've said before, not because of anything wrong with it, but it's definitely been a bit of a struggle to grok it. I hope these examples are helping others as much as they are helping me. If you would like the full source code, you can find it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/webc_lightbox\nI also set up a Glitch if you want to see this in action yourself: https://glitch.com/edit/#!/webc-for-demo Let me know what you think!\np.s. You may notice I used an odd URL in my sample images, specifically the ?.png part at the end. This is not a requirement of the placeholder service I used, but something Parvus required. When it sees an image URL that doesn't end in a &quot;regular&quot; extension it doesn't consider it to be a real image. I'm filing a bug report on that now.\n",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "eleventy"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Awesome Algolia Updates (and some fixes here...)",
		"date":"Thu Mar 30 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1680199200,
		"url":"https://www.raymondcamden.com/2023/03/30/awesome-algolia-updates-and-some-fixes-here",
		"content":"I've been a huge fan, and user, of Algolia for a while now. I first wrote about it back in 2020 when I described how I added Algolia search to Eleventy. Later on, I described how one might migrate to Algolia from Lunr. All in all, I've been very happy with Algolia and my usage on this blog. Honestly, I feel like I'm the only one who makes use of my search page but I do so nearly daily so it's critical to me. (And recently, a friend reached out specifically about my search and I'll discuss that below.)\nThe only real issue I ran into when using Algolia here was the size of my content. Algolia's free tier maxed out at ten thousand records. That's very generous and my blog has a bit over six thousand posts so I was definitely covered, but more than once I attempted an operation that needed to temporarily duplicate my content and I'd hit that limit. That's alright as I just found ways to handle it with a bit more code.\nSo - luckily - that isn't a problem anymore! Yesterday, Algolia announced a pretty significant change to their free tier. Now the total number of records you can store is one freaking million. That's huge! Also, they previously had features that were not available under the free tier. That's been changed as well and everything is available. Their commercial plan is also significantly cheaper too.\nYou can find more details on their pricing page. I will point out one important thing though. To get these new limits, you will need to create a new Algolia application and index. That's very quick and if you use the CLI, you can copy settings from one index to another, but I was lazy and just did it by hand. (I also didn't configure my index very much so there wasn't much I needed to do.)\nAll in all, I think this is a great change, and as I thought their free tier was already pretty generous, it's definitely way beyond that now.\nAlright, so I mentioned I had some fixes here. It turns out that when I first made my index, I did not correctly index my post dates. They worked fine for display, but there weren't properly sorted. I kinda didn't notice it until a friend pointed it out. Algolia documents what's required for this and even has a great example of showing a date that may be used for display as well as one used for sorting.\nMy fix was pretty easy. I use a JSON file (two actually, one for incremental updates, and one for a complete rebuild I can run from the CLI) that generates the content I want to index. Here's how it looks:\n\nHere's an example from the built version:\n\nThat date value is what I was originally sorting with and it's not valid. As the docs specify, it needs to be a Unix time stamp. Luckily, that was trivial in Liquid, but I had to dig a bit:\n\nThe important part is the %s mask for dates. Here's how it looks in JSON;\n\nOnce I had that, and it was updated in my index, my search page began working correctly. I still have some work to do there. I want to add pagination as well as sorting by relevance. Having multiple sorts wasn't an option before as I'd need to use either a replica, and I'd go over the max records, or a virtual replica, which wasn't available in the free tier. That's all not a problem now!\n",
		"tags":[
	        
            "algolia"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "WebC Updates in Eleventy",
		"date":"Tue Mar 28 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1680026400,
		"url":"https://www.raymondcamden.com/2023/03/28/webc-updates-in-eleventy",
		"content":"It's been a little while since I've blogged about the Eleventy WebC feature, and that's good because just recently some nice little nuggets landed in the project. Specifically...\n\nI want to share a demo of loops later, but I thought I'd first look into else and elseif, specifically in regards to my first post on WebC back in October: First Experience Building with Eleventy's WebC Plugin\nIn that post, I built a WebC file to create a simple placeholder with SVG. The component was incredibly simple, but it needed a bit of logic which wasn't possible in WebC at the time. Luckily I could &quot;escape&quot; by embedding Liquid:\n\nThis worked, but I wanted to see if I could use the new directives instead. I ended up with this version first:\n\nYou'll notice that both width and height now are dynamic and will use 199 each for a default. Next, I made the text element only show up if the text attribute was passed. You'll notice though that viewbox wasn't updated.\nSo... I had a quandary. I knew I could turn viewbox into a JavaScript expression, but I wasn't sure how to &quot;embed&quot; a check for undefined variables. I first tried the nullish coalescing operator which sounded like it would work, but it doesn't support a variable that's not declared.\nI then decided to pivot. What if my logic changed to - you either specify height and width, or I default both? I rewrote the component like so:\n\nI'm not terribly happy with the fact that I have to repeat the code twice, it feels a bit risky in case I make other changes to the SVG, but then again, it's still only a few lines of code.\nIf you want to see this in action, you can find the code up on Glitch here: https://glitch.com/edit/#!/webc-placeholder-latest. The live version may be found here: https://glitch.com/edit/#!/webc-placeholder-latest\np.s. As a quick aside, if you like WebC, check out https://11ty.webc.fun/ for good examples.\n",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "eleventy"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "New Blog Same as the Old Blog",
		"date":"Mon Mar 27 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1679940000,
		"url":"https://www.raymondcamden.com/2023/03/27/new-blog-same-as-the-old-blog",
		"content":"Welcome to the new blog! Looks familiar, right? So... a month or so ago I decided it was time to start reconsidering a rewrite. Still with Eleventy of course, but I really wanted to reorganize my site and possibly clean up stuff I no longer used. My site repo (https://github.com/cfjedimaster/raymondcamden2020) has been around since 2020 so it wasn't too old, but when I built it, I was new to Eleventy and as time went on, I patched and modified so much I'm surprised it actually ran. Add to that some six thousand or so Markdown files, I felt it was time for a change.\nI decided to switch to a new repository - https://github.com/cfjedimaster/raymondcamden2023. I know the &quot;best&quot; thing to do would have been to use a branch on my existing repository, but I didn't want to fiddle with branches and I really wanted to have a &quot;blank slate&quot;. I won't be deleting the old repo, but from now on I'll be using the 2023 one.\nMy goals were:\n\nRebuild the basic architecture.\nRemove stuff I don't really need anymore.\nSwitch to a new theme.\n\nLet's see how well that worked.\nNew Architecture\nI had two main goals in mind here. First, to move most of my Eleventy site itself under a new src folder, and secondly to put into place some of the great ideas Lene Saile documented in her excellent blog post, Organizing the Eleventy config file. My Eleventy config file had gotten pretty gnarly. It was over 300 lines, not really organized, and just a mess.\nMy new config file is a third of the size, right under 100 lines. More importantly, my collections, filters, and shortcodes are all abstracted out. I feel like I could do some more trimming in the filter area, I mean check out this list:\n\nBut the important thing is that now it would be a heck of a lot easier to do so.\nI also went ahead and split up my _includes folder such that the layout files exist in _layouts. Not a big deal, but again, helps organize things. I even found a layout I could get rid of.\nRemove Stuff\nWhen I looked at the root of my site, I saw pages that I had not touched in ages. Demos for stuff I built and never returned to. And so forth. I just got rid of em. I figure if they were tied to a blog post, I know I shared a link to the repo, and as it still exists, folks can still get the code.\nI also started using a misc folder under my source for &quot;junk&quot; that's mostly for me. Like my stats page for example. I just added a permalink to them to route them to the old URL so I can get to them via muscle memory.\nNew Theme\nWell... I tried. I was considering moving to a theme that would be super minimal. I first tried writing my own. I wasn't happy with it. I then found a great, simplistic theme, but the code behind it was hella complex with loads of dependencies. I loved how it looked, I just wasn't happy with the code behind it. (To be clear, it wasn't bad code, it just had a lot going on.)\nI did make a few small changes. I added header anchor links via Rhian's excellent post here: Adding heading anchor links to an Eleventy site.\nI changed my avatar (look up in the left corner to see just how freaking old I am). And I probably did a few other small things I forgot as well.\nI also removed Vue from two places. In one, I simply switched to vanilla JavaScript. In another, I switched to Alpine. Right now I still have Vue on my stats page, but I'll be switching that to Alpine as well.\nEither way, in the future when and if I decide to tweak my theme, in theory it will be easier next time.\nAs always, let me know what you think, check out the repo, and let me know if you find any bugs.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adobe Firefly in Beta",
		"date":"Tue Mar 21 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1679421600,
		"url":"https://www.raymondcamden.com/2023/03/21/adobe-firefly-in-beta",
		"content":"I'm currently at Adobe Summit and this morning at the keynote we announced the beta of Adobe Firefly. Firefly is a generative AI service, which by itself isn't new, but Firefly has a strong commitment to being a more responsible AI service. You can read more about that and Adobe Sensei here if you would like. As an Adobe employee, I'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.\nFor my test, I looked up a list of Dungeons and Dragons classes and found this excellent list here. I then went to Firefly and used prompts like so:\n\ndungeons and dragons &lt;class&gt; as a cat\n\nIn general, this worked well, but sometimes I added a bit to get things a bit closer to what I had expected. The &quot;physical&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.\nHere's a Fighter:\n\n\n\nHere's a Paladin:\n\n\n\nThe Barbarian, which is very similar, and could have been improved if I asked for a common weapon like an axe:\n\n\n\nNow for something really fun - the Bard. The double-sided lute was crazy and I absolutely loved it:\n\n\n\nNext up is the Cleric:\n\n\n\nFor Druid, I specifically asked to add a &quot;leafy staff&quot;, and the result wasn't what I expected, but I loved it:\n\n\n\nHere's the Ranger - I added &quot;bow and arrow&quot; and the result was much more stylish than I expected:\n\n\n\nNext is the Monk:\n\n\n\nThe Rogue ended up being my favorite, it looks like they're carrying the severed head of someone they just assasinated:\n\n\n\nFinally, while there are multiple magic-using classes in D&amp;D, I went with simple and just used &quot;wizard&quot; as a prompt. The eyes on this one are crazy good:\n\n\n\nThis was fun, and if you want to try it yourself, head over to the site and request Beta access. I'm not sure how long it will take to get in, but if you are interested, sign up as soon as possible.\nPhoto by Amauri Mejía on Unsplash\n",
		"tags":[
	        
            "adobe"
            
		],
		"categories":[
            
                "design"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Mar 19 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1679248800,
		"url":"https://www.raymondcamden.com/2023/03/19/links-for-you",
		"content":"Hello friends, tomorrow I'm heading out to Vegas for Adobe Summit, so I expect the posting to be a bit light this week.\nAutomating your Mastodon profile with Pipedream.com\nHere's a great article that talks about using one of my favorite services, Pipedream to automate the updating of a Mastodon profile. I really like Mastodon and the flexibility of its API is pretty great. I've been focused on writing bots, but I love how Stefan uses it to update his profile instead. Check out his article and see for yourself.\nWebComponent * 2\nI 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.\nFirst up is Awesome Web Components, 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.\nNext up is a set of toots tagged, WebComponentsAdvent. I'm a big fan of the &quot;Advent of X&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've finished.) When clicking the link, be sure to scroll down to the bottom to read in the right order.\nAnd now for something completely unnecessary...\n\nI know what you're thinking, &quot;My office is missing something, Raymond, do you have any suggestions?&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 jellyfish lava lamp that'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's cheap, and a lot of these kinda things don't work as well as the product page says, but this one's been a true delight.\nHere is a picture of it in my office, but honestly it's not a great picture and it looks a heck of a lot cooler in motion:\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Another Week, Another Mastodon Bot - Random Album Cover",
		"date":"Fri Mar 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1679076000,
		"url":"https://www.raymondcamden.com/2023/03/17/another-week-another-mastodon-bot-random-album-cover",
		"content":"Last September, I blogged about how I used the Spotify API and Pipedream to discover new music: Discover New Music with the Spotify API and Pipedream. I used a Pipedream workflow to select a random track from Spotify and email me a track every morning. I'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, Random Album Cover. You can see an example toot here:\nI have no idea what you'll see when viewing this post as it will be generated during a build, but I'm looking at a striking album cover from an artist I've never heard of, NLE Choppa. So, how was it built?\nFor the most part, it follows the logic of my previous post, doing the following:\n\nSelect a random letter\nRandomly decide to make it the beginning of a search string (&quot;A something&quot;) or in the middle (&quot;something A something&quot;)\nSelect a random number between 0 and 1000\nHit the Spotify API. Their API doesn't have a &quot;real&quot; random search, but we use the random letter and offset to search.\nGiven our set of results, select a random record from that.\n\nAll of the above hasn't changed from the previous post, except I switched the search from track to album. Next, I download the image to a temporary directory. This is straight from the Pipedream samples:\n\nAnd then I post the toot. This code is pretty short as it makes use of the excellent mastodon-api package. My only real work is crafting the text to go along with the image.\n\nI just want to go on record as saying that this is like the third or fourth time I've used reduce without checking the docs and I'm definitely a JavaScript expert now. Definitely.\nI'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't running in a Node step, so maybe I'm not an expert. Maybe.\nIf you want to check out the complete workflow, you can do so here: https://pipedream.com/new?h=tch_m5ofq7\n",
		"tags":[
	        
            "pipedream",
            
            "mastodon"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Progressively Enhancing a Table with a Web Component",
		"date":"Tue Mar 14 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1678816800,
		"url":"https://www.raymondcamden.com/2023/03/14/progressively-enhancing-a-table-with-a-web-component",
		"content":"Edited May 29, 2024: I discovered a bug in my implementation, described here, and I edited the text below to include the correction. Sorry!\nBack 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: Building Table Sorting and Pagination in a Web Component. In that example, the component looked like so in your HTML:\n\nI 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's what I came up with.\nFirst, I set up a table of simple data:\n\nNote that I make use of both thead and tbody. I'm going to require this for my component to work, but outside of that, there's nothing special here, just a vanilla table. Now let's look at my component. First, I'll name it table-sort:\n\nIn my constructor, I'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're sorting ascending or descending:\n\nAlright, now for some real work. In my connectedCallback, I'm going to do a few things. First, I'll do a sanity check for a table, thead and tbody inside myself:\n\nNext, I look at the body of the table and get a copy of the data:\n\nFor 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:\n\nFinally, I copy over a reference to the body. This will be helpful later when I render the table on sort:\n\nAlright. At this point, the component is set up. Now let's look at the sorting event handler:\n\nThe 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'll show the issue in a sec.\nAlright, the final part of the code is rendering the table:\n\nThis 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.\nWhew! The final thing to do is to wrap my table:\n\nNow let's test it out in the CodePen below:\n\n  See the Pen \n  PE Table for Sorting by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nHopefully, 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:\n\n\n\nOops. 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'd maybe have you add a data- attribute to the table, but that didn't feel right. Instead, I came up with another solution - an attribute to the web component:\n\nIn this case, I'm specifying that the fourth column is numeric. Here's how I supported this in code. In connectedCallback, I look for the attribute:\n\nSince 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.\nThe final bit is to check for this when I create a copy of the data:\n\nAnd that's it. You can test that version below:\n\n  See the Pen \n  PE Table for Sorting (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Reminder about Web Components and Attributes",
		"date":"Thu Mar 09 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1678384800,
		"url":"https://www.raymondcamden.com/2023/03/09/reminder-about-web-components-and-attributes",
		"content":"After my post yesterday about web component lifecycle events, I had an interesting conversation with Thomas Broyer 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's post, I thought a bit of repetition would be ok. And heck, I'll take any chance to write more web component code as it gives me more practice.\nSo as a reminder, yesterday's post specifically dealt with what code is best used in a web component's constructor versus the connectedCallback event. Specifically, it dealt with the use case of checking attributes and handling web component elements created via JavaScript. To be clear, I don't mean the definition of the web component, but creating an instance of one, like so:\n\nWhile I didn't bother setting a title in that example, I could have done so like this:\n\nAnd it works as expected. But here's an interesting question. What if later on I change the title? Imagine this code:\n\nWhen run, what will it do? Check out the CodePen below to see:\n\n  See the Pen \n  WC Tests (5) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs you can see, it does not work. Remember you can open your browser's console here if you want to see the messages. It will clearly say that the title attribute matches the update, but that's what you'll see reflected in the DOM.\nThe 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.\nThe first part is simple:\n\nThe next part involves adding an event handler named attributeChangedCallback:\n\nIf you try this, you'll see that it's fired multiple times. I had a &quot;hard-coded&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.\nNow 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, updateDisplay, that handles updating the display. Here's the entire class:\n\nNotice that updateDisplay just uses querySelector to find its h2 node and update the text. Now our code that updates the title after a few seconds will work correctly:\n\n  See the Pen \n  WC Tests (5) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIf you don't see the switch, just click the &quot;Rerun&quot; button on the bottom right. Anyway, as I said, I've discussed this before, but it definitely tripped me up the first time I ran into it so hopefully this helps others!\nPhoto by Chris Lawton on Unsplash\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Interesting Caveat with Web Components and the Event Lifecycle",
		"date":"Wed Mar 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1678298400,
		"url":"https://www.raymondcamden.com/2023/03/08/interesting-caveat-with-web-components-and-the-event-lifecycle",
		"content":"I've been exploring web components the last few months and as part of that exploration, I've been reading &quot;Web Components in Action&quot; by fellow Adobian Ben Farrell. I'm still at the beginning of the book but so far it's been great. It is a few years old now, but for the most part, the only thing I've seen out of date is that at the time of publication, Microsoft Edge didn't have complete support for web components yet. That's been corrected (good thing, I switched to Edge a while back) so it's not really a concern.\nHowever yesterday I read something that didn't quite jive with my understanding. The fourth chapter, &quot;The component lifecycle&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 connectedCallback event. The constructor is called when the component is created, but connectedCallback is not fired until the component is added to the browser's DOM. That last bit is important. If you add an instance of a web component to a DOM element, let's say a div you created in JavaScript, but that div itself is not in the browser's DOM, the event won't fire.\nBefore going further, let's look at a quick example. Assume this JavaScript for a trivial component:\n\nIf we use &lt;my-component&gt; in the DOM, we will see both console messages for each instance of the tag. Here's a CodePen that demonstrates this. Note that you will need to &quot;Edit on CodePen&quot; to actually see console messages, or open your console right here on my site.\n\n  See the Pen \n  WC Tests by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAll of this made sense, and really touched on something I've been noodling over - what should I put in the constructor versus connectedCallback. He made one point that didn't seem right to me - that if you check the value of an attribute in the constructor, it will be null. I've been doing this in my previous examples, and heck, even MDN shows it in one of their examples:\n\nHere's an example where it clearly works just fine:\n\nAnd if called like so:\n\nI get:\nMy Component: ray\nMy Component: No title\n\nAs I said, this matched my expectations. Here's a complete CodePen for this:\n\n  See the Pen \n  WC Tests (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo, 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:\n\nIn this case, I've made a new my-component and added it to my DOM. I would have assumed this just worked, but instead, you get an error:\nUncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes\n\nIf you want to see this yourself, open up this CodePen, and open your browser's console, not the CodePen one. The error doesn't get floated up right to the 'virtual' console CodePen uses.\nNow it makes sense, and it's an easy enough correction to move that logic to connectedCallback:\n\nAnd in doing so, I can then create instances in JavaScript, and even set my title:\n\nIn the CodePen below, you can see I used both a &quot;regular&quot; instance of the component in HTML as well as the two defined here and all three act correctly:\n\n  See the Pen \n  WC Tests (3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nI hope this makes sense, and as always, reach out if it doesn't. Going forward, I'll be doing more of my attribute validation and setting in connectedCallback.\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding a Chart to an Alpine.js Application",
		"date":"Mon Mar 06 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1678125600,
		"url":"https://www.raymondcamden.com/2023/03/06/adding-a-chart-to-an-aplinejs-application",
		"content":"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 Alpine.js application. I finally got a chance to play around with this over the weekend and I thought I'd share the result. For this post, I've used Chart.js, which is a free, open-source charting library that's relatively easy enough to use. Certainly, others could be used as well and as always, if you've got an example, I'd love to see it. With that out of the way, let's take a look at the application.\nBefore the Chart\nI'll start by sharing what I built before I added a chart to the display. This application consists of a list of cities. For each city, we use the Pirate Weather API to get an hourly forecast and from that, I display the temperature over the next twelve hours. Here's how that looks:\n\n\n\nI probably should have included a timestamp but for now, this gets the point across. Let'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.\n\nWhen 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:\n\nHere 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 Promise.all 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.\nThe actual API call is done here:\n\nI 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.\nWith the forecast information ready, the table can now be displayed. Here's the UI:\n\nBasically 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.\nHere's a CodePen demonstrating the complete application.\n\n  See the Pen \n  Alpine + ChartJS (Initial) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAdding the Chart\nFor 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's the chart I came up with:\n\n\n\nNote that this was me doing the bare minimum in terms of &quot;design&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's what I had to do.\nFirst, I added the library, https://cdn.jsdelivr.net/npm/chart.js. And hey, thank you Chart.js for not forcing me to npm anything. I appreciate it.\nNext, I added a canvas to my HTML. Because I'm lazy, I used the same ID as their docs, but this can be changed of course.\n\nNext, I added a new method to my code, renderChart, to handle the process. Here's that code.\n\nLet's examine this. The very first line simply gets a reference to the canvas tag where Chart.js will do its work. The next few lines of code are all me &quot;prepping&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 map and reduce. I am a JavaScript master and I will absolutely pass the next arbitrary coding challenge I get for a job interview. Honest.\nThe net result of the above three blocks of code is three arrays. Each of these can then be passed to my chart declaration. You'll see names 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't perfect, I know some places were below negative twenty recently, but it works for now.\nYou can demo this version here:\n\n  See the Pen \n  Alpine + ChartJS (Chart) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSome Quick Notes\nOk, 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.\nFirst, I'm still relatively new to Alpine and still trying to figure out the &quot;best&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'm not sure I'm happy with how I organized my code. I think my feeling is that I should use the following rules:\n\nPut the init() method on top.\nPut any and all simple variable declarations next.\nPut methods after.\n\nSecond, 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't kill my access to the API as CodePen tends to rerun stuff quite a bit. (I need to disable that I thin",
		"tags":[
	        
            "alpinejs",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Mar 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1677952800,
		"url":"https://www.raymondcamden.com/2023/03/04/links-for-you",
		"content":"Good morning readers! I'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 Algernon Sydney Sullivan award) and we stayed up a few extra days. We're about to head back to Louisiana so I thought I'd share a few quick links with folks. Have a great Sunday.\nTaking Eleventy into the Spiderverse with eleventy-fetch\nHere's a great post by Jeff Sikes where he describes how he made use of the Marvel API in an Eleventy site. That'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'm happy they just didn't shut it down.\nEleventy Collection Schemas\nYet another awesome Eleventy tip from Stephanie Eckles, this post documents an Eleventy plugin for enforcing your frontmatter setup in Eleventy collections. This is really 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!\nFrontend Development Projects with Vue 3 - Second Edition\nAbout 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'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'm feeling somewhat better about it. I still don't think Vue is going to be my framework of choice going forward, I really prefer Alpine, but for any &quot;application&quot;, I'd definitely build it in Vue 3.\nYou can get the book from Packt here, https://www.packtpub.com/product/frontend-development-projects-with-vuejs-3-second-edition/9781803234991, or if you buy from Amazon here I'll get an Amazon Associates kickback. Either way, check it out and let me know what you think.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Supporting PDF Embeds in an Eleventy WebC Component",
		"date":"Wed Mar 01 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1677693600,
		"url":"https://www.raymondcamden.com/2023/03/01/supporting-pdf-embeds-in-an-eleventy-webc-component",
		"content":"Way back in the old days, in August of 2021, I wrote up an example of adding support for Adobe's PDF Embed API as an Eleventy plugin: &quot;An Adobe PDF Embed Plugin for Eleventy&quot;. 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 WebC template language. While it was a bit difficult at times (and a big thank you goes to Zach for patiently helping me), I think it's at a point now where it can be shared. I will warn folks that I'm still struggling a bit with the best way to work with WebC, and at least one feature I'm showing isn't documented yet (but I've confirmed it will 100% ship), but hopefully this example will be useful for folks.\nBefore we start, know that if you want to try this yourself, you will need a free credential. Credentials are host-based, which means a credential for raymondcamden.com will not work for localhost. You can definitely create a few, and if you really 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've never used your local hosts file for something like this, reach out and I'll help explain it. (Or heck, that may be my next post.)\nAlright, so I began actually by writing a template that called my component. I kinda figured I'd write the example code in what felt like the most logical manner, and then I'd actually build the WebC component to match it. Here's how I did it:\n\nI've got two attributes to define the size of my PDF, my client credential (it's host-based, so safe to share here, go ahead, steal it, I won'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'm keeping it simple here.\nNow let's look at the actual component:\n\nLet's discuss. I begin by adding a noscript 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't use the noscript tag directly but had to use a template instead with the webc:is directive. Zach explained why... but I honestly don't quite get it. Not yet anyway.\nNext, note the div:\n\nThis is the part not yet documented. Every instance 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.\nNow 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'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.\nThe 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.\nThe 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'm using a script tag as, technically, it's JavaScript, but it comes out as CSS, so I map it with webc:is as I did on top. Again, thanks go to Zach for this tip. Finally, I need to use webc:keep 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.\nWhew. Hopefully, this made some sense. If you want to test this yourself, I made a Glitch, because Glitch is freaking cool. You can see it here: https://glitch.com/edit/#!/impossible-early-system?\n",
		"tags":[
	        
            "eleventy",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Update to My Eleventy Blog Guide",
		"date":"Sat Feb 25 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1677348000,
		"url":"https://www.raymondcamden.com/2023/02/25/update-to-my-eleventy-blog-guide",
		"content":"Last January, I announced the release of a guide I had written for building a simple blog in Eleventy. 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.\nThat being said, I didn't do anything else. This is not to say that the 2.0 release wasn't lacking in new features, but as my guide is meant to be as simple as possible, I wasn't on the lookout to add any new features if it didn'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.\nAnyway, you can find the guide here: https://cfjedimaster.github.io/eleventy-blog-guide/guide.html.\nAnd you can find the repository here: https://github.com/cfjedimaster/eleventy-blog-guide.\nFeedback is always welcome!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "My First Bug",
		"date":"Fri Feb 24 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1677261600,
		"url":"https://www.raymondcamden.com/2023/02/24/my-first-bug",
		"content":"I've told this story a few times before, but I don't think I've actually ever shared it on my blog. My interest in and introduction to computers came at a very early age. My mother's employer sent her home with an Apple 2 (either the Plus or E model, I forget which), and while it was supposed to be for her, it also included a bunch of games, so I immediately became attached to it. At around the same time, I saw a movie that had a huge impact on me. No, not Star Wars, but instead, TRON. While I was pretty young, I definitely knew it was fiction, and working with computers wouldn't be quite that cool, but it really fired up my interest. I mean, just look at this...\n\nSo at some point, I stopped playing the games (err, well, stopped playing the games exclusively) and took a stab at trying to learn to program. Applesoft BASIC was a simple language, and best of all, you could literally turn on your machine and immediately begin writing programs. It's hard to describe just how exciting that was - having a development environment as a default meant I spent a heck of a lot of time writing programs. Shoot, I'd sometimes write the same simple program multiple times just to see the result again.\nMy manual was the Applesoft BASIC Programming Reference Manual:\n\n\n\nThis was a good manual, but I quickly ran into an issue, and by quickly I mean on page 2. Here's where I got stuck:\n\n\n\nBASIC programs consist of lines of code preceded by line numbers. By default, execution will go from the lowest number to the highest, but basic jumping around was supported as well. The typical program would use line numbers counted by ten. This lets you &quot;slip in&quot; lines of code you may have forgotten. Never complain about writing code in Notepad again - this was truly old-school coding. (And to be fair, it was a hell of a lot better than using punch cards. I'm old, but not that old.)\nAnyway, I followed that text very carefully, and when I ran it, I got an error. Here it is recreated in the Windows AppleWin emulator:\n\n\n\nI swear I looked at this for hours (most likely it was far less than that) and I just couldn't figure it out. I'd look at the manual, look at the screen, go back to the manual, and I just had no clue.\nThen... I went back to the manual, and read past the lines of code...\n\n\n\nSee that highlighted line? Yeah, young Ray didn't notice it. I had entered the first line of code... and then used the spacebar to wrap the cursor to the next line.\nDumb, right? Of course, with a few years of developer relations and technical writing experience, I look at that and immediately think I'd have moved that statement above the lines of code to make it more obvious.\nGetting it right gave me such a feeling of complete and utter joy. It's that feeling that has had me hooked on writing code.\nLuckily, I've not made that same dumb mistake since. I've made huge numbers of other, more unique dumb mistakes. I look forward to what I'll screw up next!\nPhoto by Neringa Hünnefeld on Unsplash\n",
		"tags":[
	        
            "apple"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Fri Feb 17 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1676656800,
		"url":"https://www.raymondcamden.com/2023/02/17/links-for-you",
		"content":"Normally I write these &quot;Links For You&quot; posts on the weekend, but my family and I will be on vacation for the next few days and the laptop is not invited. With that in mind, here's what I'd like to share with yall, enjoy!\nEleventy 2.0\n\n\n\nI'm super excited that Eleventy has hit 2.0! You can read about all the changes on the blog post, and there's quite a bit, but for me the biggest updates are:\n\nDramatic reduction (nearly a third) in dependencies.\nFaster builds (especially important on my large site)\nAsync friendly shortcodes and filters\n\nThere's a lot more, so again, check the post for details. I'm running 2.0 here, and in the new version of my site that I'm working on now.\nHiding DOM Elements\nI love working with the web and even more so, I love being surprised by what's possible. Here's a great example of that:\n\n            \n                \n                \n                    Šime Vidas\n                    @simevidas@mastodon.social\n                \n            \n            Basically, swap &lt;div hidden&gt; for &lt;div hidden=until-found&gt; to allow the user to search within the hidden text via find-in-page. https://webplatform.news/#1675864782000\n                2:17 PM • February 8, 2023&nbsp;(UTC)\n            \n        \nIn this toot, Šime Vidas is sharing a new value available to the hidden attribute... which I didn't even know existed! It's basically the HTML version of hiding an element via CSS. And of course, MDN has docs for it: HTMLElement.hidden\nConfiguring an Alias for Mastodon\nIn this post by the awesome David Neal, he describes how to create an alias for your Mastodon account with Eleventy and Netlify. I used this myself to create the alias @raymondcamden@raymondcamden.com for my &quot;real&quot; account @raymondcamden@mastodon.social. It took all of five minutes and honestly it's a really cool trick!\nAs an aside, David does commissioned artwork. Here's one he did for me:\n\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Mastodon Bot Listing Page in Eleventy",
		"date":"Wed Feb 15 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1676484000,
		"url":"https://www.raymondcamden.com/2023/02/15/building-a-mastodon-bot-listing-page-in-eleventy",
		"content":"Chalk this up for yet another thing most folks probably won't need, but it was fun to build so I figured I'd share. I've had a lot of fun building bots for Mastodon. If you're curious about the process, you can read about my experience here: &quot;Building a Mastodon Bot on Pipedream&quot;. It occurred to me that it might be cool to build a page on my blog that shows off the bots I've built. It also occurred to me that it's 100% possible I'd build a bot and forget about it.\nSo with that in mind, I built a page that does just that. For each of my bots, it displays their last post. You can see this in action here: https://www.raymondcamden.com/bots. Here's how I built it.\nI began by looking at the Mastodon embed code written by Bryce Wray, &quot;Static embeds in Eleventy&quot;. In his post, he defines a simple shortcode named stoot. Once you make this available in your Eleventy site, you can then do this to embed a toot:\n\nSimple enough, right? And in theory that could have been enough. All I'd need to do is go to my bots, find one toot, and get the ID. But I thought it would be cool to embed the last toot they did or at least the last toot at the time of building. To enable that, I created another shortcode, lasttoot. It works like so in the template:\n\nTechnically the capture isn't required per se, but as the shortcode returns the ID of the last toot, it would be useless without it. As you can see, I pass in both the server and the username of the bot. Now let's look at the code.\n\nPretty simple, right? All Mastodon users have an RSS feed of their activity. I used an RSS Parser to bring it in, get the most recent toot, and get the GUID value. That value looks like this (line breaks added for readability):\n\nAs all I need is the ID, I do the split/pop calls to grab it. By the way, I wrote this code and was satisfied with it, but then thought, do I really need an RSS parser? The RSS feed type won't change, maybe I could just use vanilla JavaScript. I did some searching, found solutions using DOMParser, and gave it a shot. It was then I discovered that it was a client-side JavaScript solution, not server-side. Sigh. I also then remembered I was using rss-parser elsewhere on my site so I wasn't really hurting anything by using it again.\nTo render my bots, I just repeated a bunch of calls to both shortcodes:\n\nAnd that's it. I did run into one interesting issue. For RandomComicBook, the links to the particular comic book were being auto &quot;previewed&quot; in the embed, and since I also show the attached media, the cover, it ended up being displayed twice. I commented out that of the stoot embed for now as it solves it for me. Also, as one more quick aside, the CSS you'll see on my bot page is a bit of a mess. I took Bryce's CSS, messed with it a bit, and got it to &quot;good enough&quot; for my site. It's even embedded directly on the page which is bad practice, but as I'm planning on moving to a new theme soon and doing a big rewrite, I figured it was ok to leave it a bit messy for now.\nAnyway, let me know if you've got any questions on this, and pour one out for all the fun Twitter bots that have been destroyed by the Muskman!\n",
		"tags":[
	        
            "eleventy",
            
            "mastodon"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Lessons Learned in Twenty Years of Blogging",
		"date":"Sun Feb 12 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1676224800,
		"url":"https://www.raymondcamden.com/2023/02/12/lessons-learned-in-twenty-years-of-blogging",
		"content":"Way back in 2003, I wrote my first blog post, it was short and sweet and I can share the entirety of it here:\n\nWelcome to my blog. I've been working in software development for many years now. Mostly in ColdFusion, although recently I've been working in Java as well. I plan to use this space to share ColdFusion tips - as well as share what I'm learning with Java (and hopefully save others from the stupid mistakes I make). As always, comments are welcome, and I hope this blog becomes an informative resource for all.\n\nFunny that I thought I had &quot;many years&quot; of development experience back then. While I've been coding since I was 9 or 10 or so, I didn't start writing code professionally till 1994 or so.\nMy blog was completely custom-built, in ColdFusion, and while I can't seem to find an original picture of the first design, I did find this from 2005:\n\n\n\nIt may be hard to read, but apparently, on February 3, 2005, I posted not once, not twice, but three times. To be fair, this was pre-Twitter, and as I've said before, I used to post a lot of notes, links, and so forth, as a way of sharing cool stuff with others.\nThat custom blogware turned into an open-source project called BlogCFC. I'm proud to say that it became one of the most popular open-source blogging projects for ColdFusion users with hundreds, if not thousands, of instances out in the wild. I had a lot of people contribute to it, provide feedback, and just generally help make it a great platform. I was torn when I finally walked away from it, but still very proud of what I achieved with it.\nI eventually decided to move off of the ColdFusion platform and try something new, well new to me, WordPress. I was immediately impressed by how polished the administration was. I was also immediately turned off by how quickly WordPress (or PHP, or my database) would crash and burn, usually while I was asleep. My blog has always had &quot;decent&quot; traffic, but nothing so high as to really require a lot of special tuning, or at least I thought as much.\nIt was around this time (2016) that I made my first move to the Jamstack (Welcome to RaymondCamden.com 2016). Back then we just called them static websites. My first iteration used Hugo, which handled the size of my site rather well but was way too restrictive in terms of how it worked. I then moved to Jekyll which gave me the development freedom I needed but unfortunately also showed me the pain of managing Ruby. Finally, in February (not sure I do big things in February so often) of 2020, I moved to Eleventy and have been incredibly happy with my platform since then. I've changed designs once or twice in that time (and am contemplating another change soon), but don't see myself ever leaving Eleventy. (Of course, give me another twenty years and who knows.)\nA lot has happened in these past 20 years. Here's a random list, in no particular order:\n\nI wrote 6,338 blog posts. Some of them were good. I also wrote 2.3 million words. Some of them were even spelled correctly.\nWhen I started the blog, my wife and I had adopted 3 kids from South Korea. In the twenty years since we adopted 4 more from China.\nI wrote some books. Honestly, a few of my books were written before the blog started, but overall I've contributed to, co-authored, or solo-authored, 18 books. As someone who has been a lifelong reader, I always dreamed of being a famous author. In my young mind, that was Stephen King, and while I didn't end up being published for fiction, the fact that I can say I'm a professional writer makes me incredibly proud.\nMy LinkedIn history only goes to 2004, but in that time, I've had 10 jobs. I've been in developer relations since 2011 or so and it is my dream job.\nI lost my wife, which feels a bit weird to say in a bullet point, but it happened. I'm happily remarried and with my stepson, our family is now 10 strong. I could write an entire blog on we manage such a large family, but that's for another day.\nRelated to the above, I learned how important, and helpful, therapy can be. I learned that asking young developers to code in their free time to buff up their resumes for future jobs is absolutely impossible for some people. As a single father, I had absolutely no mental energy at night to do anything but blank out in front of the TV.\nI grew as a father, a husband, and as a person.\n\nAs I look around myself now, I've got a lot to be grateful for. I have a wife who has helped me realize it is ok to enjoy life, to be myself (as crazy as I'd like), and to just plain live. I had kids who continuously surprise me, bring joy to my life, and teach me. I have friends who don't judge me, who are there for me, and who inspire me. I've got a good job, a home for my family, and food. All in all, I am so incredibly lucky, and while I suffer incredible anxiety and fear at times, I remind myself that I am blessed.\nSo that's a lot of pre-amble, let me get around to what I actually wanted to discuss, lessons learned from two decade",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Progressively Enhancing a Form with Web Components",
		"date":"Fri Feb 10 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1676052000,
		"url":"https://www.raymondcamden.com/2023/02/10/progressively-enhancing-a-form-with-web-components",
		"content":"I've been loving playing with web components lately and today I'm excited to share another one. Especially excited as this one is a great example (I think!) of using a web component to enhance HTML, but that fails gracefully for a user with JavaScript disabled. Before I begin, a quick thank you to Simon MacDonald for helping me get over the hump at the end of this one. For folks curious, I'll share where I got stuck and what he and I discussed after I get through the main part of this post.\nAlright, so what did I build? I was curious if it would be possible to use a web component to turn a &quot;long&quot; form into a multistep process. Much like a typical e-commerce checkout flow, I'd want to show a part of a form one at a time, and when done, submit everything. The idea is to make the process a bit less intimidating to the user. So for example, consider this form:\n\nThis isn't terribly long, here's how this looks with a bit of CSS:\n\n\n\nRight away, we can improve this a bit by adding a bit of natural grouping with the fieldset and legend tags:\n\nAnd here's how this looks:\n\n\n\nNicer! Looking at this, what if we could display one fieldset at a time, and dynamically add navigation? If you read my Slideshow web component post, you saw an example of this. Given a list of images for input, I add a Previous and Next button to let you navigate the images. I built something similar for this - MultistepForm:\n\nTaking this from the top, I begin by counting how many fieldset tags I have wrapped in my tag. I then set my current page to 0. The layout defined in the tag is defined by the content passed in and loaded via &lt;slot&gt;&lt;/slot&gt;, with the navigation added to the bottom. Notice the two spans in there, they will be dynamic based on the current step and the total number of steps, as defined by the total blocks of fieldsets.\nIn my connectedCallback, I add event listeners, and then grab the fieldset blocks. For each, I hide them with display=&quot;none&quot; and call updateDisplay. The previous and next handlers are the same as I used in my Slideshow component. The real change is in updateDisplay, which loops through the fieldsets and shows/hides based on the right value. As this is non-destructive, the user can type stuff into the form fields, navigate the form, and finally submit it.\nTo make use of this component, I simply wrapped my HTML in &lt;multistep-form&gt;` tags, and was done!\nYou can try this out yourself below:\n\n  See the Pen \n  Multistep Form WC (V2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nAll in all, I really dig this component. I love that it 'breaks' into a regular form so in theory, this is completely safe for any user. In theory. :)\nOk, so that's the main post, feel free to stop reading. Now for the issue that stumped me and Simon helped me figure out. In my initial build of this component, I did not use the slot tag. I figured my component could edit the &quot;regular&quot; DOM items, not the shadow dom, and weirdly, I had no issues hiding the fieldsets, but couldn't bring them back. I assumed (guessed) that by not using slot, the original content was lost in some nether world of the DOM. I'm not sure. But using slot and manipulating the content directly in the shadow dom of the component had things working right away.\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Another Update to my Slideshow Web Component - JavaScript Support",
		"date":"Wed Feb 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1675879200,
		"url":"https://www.raymondcamden.com/2023/02/08/another-update-to-my-slideshow-web-component-javascript-support",
		"content":"Last month I shared a simple web component I built to embed slideshows onto web pages. If you didn't get a chance to read that, you can see it in action in this CodePen below:\n\n  See the Pen \n  Slideshow Web Component by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nAfter I wrote this, Šime Vidas shared an excellent update to my component with some great modifications. I talked about this version in a blog post, and it's the version I'll be using for my post today. What am I covering today?\nWhen I demonstrated how to use my web component, it was done via a script include (well, it's on CodePen, but you get the idea), and then a bit of HTML. Here's an example. (And again, thank you to Šime for his improvements.)\n\nThis works well, but, what if you wanted to modify the contents of the slideshow with other images? Let's see what happens when I try that.\nFirst, I'll add a button to the bottom:\n\nThe idea here is that a user would click this to load other content. Now let's write a bit of JavaScript:\n\nIn this code, I've set up a click event handler and when it's used, it sets the images attribute of my web component to new URLs. Remember, even though Šime changed my component to allow for inline images, the JavaScript code of the component still actually updates the attribute, like so:\n\nSo, in theory, our code should work, right? Check it out below and see for yourself:\n\n  See the Pen \n  Slideshow Web Component by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nYep, not working. You can open up your console too and clearly see the console message being displayed and no errors, but the change doesn't have the effect we would have assumed. So what went wrong? As usual with my code, multiple things.\nFirst off, web components will not, by default, monitor changes to attributes. Your web component class needs to specify what attributes it will care about when it comes to things changing. This can be done with one line of code:\n\nThe getter observerAttributes returns an array of attributes that should be monitored for changes. For this demo, I'm specifying just the images attribute, but in theory, you could imagine responding to changes in width and height as well.\nNext, we have to write code to notice those changes. This is done via the attributeChangedCallback function:\n\nFor now I'm just logging the attributes. As you can tell, this gets called for any change, but for us, it will always be images.\nFinally, there's a problem with how I changed the images:\n\nIt may be immediately obvious, but this changes a property of the DOM element, not an attribute. There's a great StackOverflow post you can read on the topic, but my code should have done this instead:\n\nWhew. So with just that done, if the button is clicked, the following will show up in the console:\n\nIt's a bit hard to read as it's two long lists, but you can definitely see the previous and new values. Now let's make the changes.\nHere's my updated attributeChangedCallback:\n\nAs I said, I can assume the change will always be to images, but if I wanted to I could check the name value. I then copied some code from the constructor - split the string into an array, update the total number of images and reset us back to the first. The totalPictures line there is not from the constructor, but is a change I made to the HTML being generated:\n\nHaving the span there lets me update it easily. Here's a demo:\n\n  See the Pen \n  Slideshow Web Component (v3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nOk, better, right? There's still a bit that can be done. I definitely do not like the fact that I copied a few lines of code when updating my display. That code in both the constructor as well as the attribute changed callback could be abstracted out into a render function so there's no code duplication. As the intent for this post was to demonstrate an example of updating a component via JavaScript, I'm fine leaving that out so the focus is on just that, but feel free to fork the CodePen and share with me your update!\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Feb 05 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1675620000,
		"url":"https://www.raymondcamden.com/2023/02/05/links-for-you",
		"content":"Hey folks, I'm writing to you from another world. A world where I somehow got eight-plus hours of sleep two days in a row. Now, previously I would have told you this was a make-believe world, but now I know the truth, it exists. (And I'll just do my best to forget the nightmare I had a bit before I work up.) With that out of the way, here are a few links to start your week.\nTheJam.dev Videos\nI was privileged to speak at TheJam.dev last month, a free online conference covering the Jamstack. The sessions are slowly making their way to YouTube, and while mine isn't on there yet (I was the last session), I  know it's coming soon. You can watch a good chunk of the presentations now and I've embedded the playlist below. I just bugged Brian and he let me know everything should be there by mid-week.\n\nThe State of Developer Conferences\nSpeaking of Brian, I wanted to share an article he wrote about the current state of conferences, nicely titled, &quot;The State of Developer Conferences&quot;. As someone who both speaks at and runs conferences, he has a great perspective on how things have changed since COVID.\nAs for myself... I just want to share what I've learned. I love to travel, and miss the fact that I don't do as much, but at the end of the day, I'll use whatever means necessary to reach folks and help. Between you and me, I've been posting more videos to YouTube lately, and while they're just short little snippets, I'm hoping it helps reach folks who prefer video content to writing.\nEleventy Tip - Anchor Links and Headers\nLast up is a great post by Rhian van Esch, &quot;Adding heading anchor links to an Eleventy site&quot;. In this post, he describes a few options by which headers defined in Markdown can automatically become anchor links. I plan on adding this to my own blog in the next few weeks. Plus, the article is a good reminder that Markdown can be modified to support new features, and unsurprisingly, Eleventy makes it easy to do so.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using JavaScript in a WebC Component",
		"date":"Fri Feb 03 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1675447200,
		"url":"https://www.raymondcamden.com/2023/02/03/using-javascript-in-a-webc-component",
		"content":"A week or so ago (time is so weird these days), I gave a presentation on Eleventy's WebC plugin. While working on the slides, I built a bunch of demos of various things and knew I'd share a few on the blog. Here's one in particular I wanted to write about. This isn't anything not covered by the docs, but like most things, I needed to try it myself first to see it in action. Specifically for this post, I want to talk about JavaScript in WebC components.\nLet's begin by creating a simple tag, cat, that will render a random cat:\n\nThe component uses EJS to generate a random width and height and then outputs it using the PlaceKitten image holder service. Not terribly useful, but it's cats, and cats always serve a purpose. If we add this to a page:\n\nWe will end up with two random cats. (Random at build time of course, once deployed it's going to be static HTML.) Here's a completely gratuitous screenshot just so I can have some cat pictures in the post:\n\n\n\nOk, so far so good, but let's add a bit of interactivity to the component. I want to make it so that when you mouseover the picture, the browser plays a sound file of a cat meowing. I would never do this in real life because it would be annoying af as the kids say, but whatever. Here's the new version:\n\nThe code basically just looks for cat images by class and adds the event listener. If we add this, and check our site, we'll get... nothing. If you view source, you'll see why:\n\nWhat happened to our JavaScript? Well, it turns out, WebC automatically detects this, and &quot;gathers&quot; up the JavaScript into one bundle you can use in your code. To use the bundle, you can add a bit of code to your layout:\n\nThis is Liquid code, but similar code exists for layouts written in WebC, and other languages. Once added, we get the correct behavior, and you can see it in source now:\n\nWhat's nice is, despite cat being run twice, only one copy of the code is used, which makes sense, but is still nice to see. And since the code is checking for multiple items matching the class, only one copy of the code makes sense.\nAs an aside, you may not want WebC to bundle your code. If so, simply add webc:keep to your script tag, like so:\n\nRead more about this at the docs: Asset Bundling\nWant to play with this? You can see the code here, https://glitch.com/edit/#!/webc-javascript-example, or run the released version here, https://webc-javascript-example.glitch.me. Enjoy!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "My town sure seems to have a lot of...",
		"date":"Thu Feb 02 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1675360800,
		"url":"https://www.raymondcamden.com/2023/02/02/my-town-sure-seems-to-have-a-lot-of",
		"content":"Ok, so I realize this will make me sound old (spoiler, I am old), but I swear I feel like my town (Lafayette, LA) has about ten thousand or so storage businesses. And banks. Oh, and hotels too. For a while now I thought it would be interesting to see if I could build a tool that would actually do that - count the number of a type of business. This week I took a stab at it and while the results aren't perfect, it was fun, and that's all that matters, right?\nFor my demo, I decided to use Google Map's Places API, or more accurately, that part of the JavaScript library. (Google's Maps APIs don't support CORS so if I wanted to do a direct call I'd need to setup a serverless proxy. Overkill for a dumb little demo.)\nThe Places API supports a few different ways of searching, and for my first attempt I tried the Text Search version. This supports free form queries including things like, &quot;banks lafayette, la&quot;, and I thought it would be a good way to start. I began with some simple HTML, asking for a type of business and a location:\n\nI've got two form fields, a button, and then a div for the results. You'll notice I also have a div for a map. I'm not using a map, but the Google Maps JavaScript library requires a div for a map. Even if you don't show it. Seems a bit weird, but what's one more div between friends, right?\nNow let's consider the code. First off, the Google Maps JavaScript library is usually loaded via a script tag where the url includes your key, the libraries you need, and the name of a callback function. I was building on CodePen and that didn't quite work well. Instead, I simply appended a script tag to the end of my DOM using this:\n\nI could have made that a nice function but I just dropped it at the end of my code. Now let's look at the function called by the library:\n\nAs I mentioned above, you must have a map div even if you don't plan on showing it. I created a Map object based on sample code from their docs and I've got zero clue where that latitude and longitude is. In the end, it doesn't matter as it won't be used. The rest of the code creates my service object and assigns some DOM elements to variables for use later.\nWhen the search button is clicked, doSearch is run:\n\nI grab the values, do a bit of simple validation (the user has to enter something), and then call the text search API via the service object I created. Note how query is crafted from the user input.\nNow for the fun part, handling the results. Google's library supports pagination, and actually makes it quite easy to use. I defined total as an empty array in doSearch and it's global to the page. Here's how I use it:\n\nAs I said, the service handles pagination well. When I call nextPage(), it automatically knows to run handleResults again. So all I need to do is keep adding to the total array (but only after filtering out closed businesses) and when done, render out to HTML.\nAs you can see in the text, there's a max of 60, which is unfortunate, because even in our mid-sized town, there's a crap ton of results for some of my searches. Still though it was kind of fun. For our town, searching for church returns the max, searching for bar returns 56. But I think it would have been 60 too if I didn't have the filter for closed businesses. Test it out yourself here:\n\n  See the Pen \n  Testing Places API (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo that was round one. Let's make it simpler. The Google Places API also supports a Nearby Search operation. This lets you pass in a location and a business type, where the types are a nice long list of, well just about everything. In my second version, I switched my HTML to a drop down:\n\nWhich is populated via JavaScript:\n\nI just want to go on record as saying I've now used reduce twice this week and I'm definitely now a leet coder. Or lute coder? Whatever.\nI then removed the address and simply got your location via geolocation:\n\nI love using async/await to &quot;patch&quot; over old APIs like geolocation and make them a bit nicer to use. The final change was to switch to the Nearby API:\n\nThe radius value is in meters and 1609 is roughly equal to a mile. Here's a screen shot of in action:\n\n\n\nI'm sharing a picture and not embedding CodePen because geolocation is blocked when embedding the CodePen. I checked, and as far as I know there's no workaround, so for now, you'll need to click a link, sorry about that: https://codepen.io/cfjedimaster/pen/MWBZJWZ\nLet me know what you think. Keep in mind, the results are based on the data Google has, and it's not always going to be accurate. I know I saw things in my test that were incorrect.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Cloudinary Debugging Tip",
		"date":"Tue Jan 31 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1675188000,
		"url":"https://www.raymondcamden.com/2023/01/31/cloudinary-debugging-tip",
		"content":"I've been blogging about Cloudinary here for the past few months, and I wanted to share a quick tip. A few weeks ago, I was privileged to be interviewed on the Cloudinary podcast, Dev Jams:\n\nWhile showing some code, I came across an image being loaded by Cloudinary that was returning a broken image. Obviously I'd done something wrong, but what? I began by opening the image in a tab, but that just gave me a 400 error:\n\n\n\nTurns out - there's a simple way to get to the issue. Open up your browser devtools and switch to the Network tab. Click on the request and go into the response headers. Scroll down until you see x-cld-error, and you'll have your error message there:\n\n\n\nYou'll notice it's also present in server-timing, but x-cld-error is the clearer message. Hope this helps!\n",
		"tags":[
	        
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick WebC Tip",
		"date":"Fri Jan 27 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1674842400,
		"url":"https://www.raymondcamden.com/2023/01/27/quick-webc-tip",
		"content":"Whenever I think I shouldn't post something because I'm covering something completely obvious, despite the fact that I missed it, I always find at least one other person who was also a bit slow in either remembering a basic tip or figuring out the simple stuff. So hey, that one person, good morning, and I hope this helps.\nAlright, so if you are using Eleventy's new WebC feature, or starting to learn it, one of the first things you'll do is create a .webc file in your editor. However, you may notice you don't get the nice formatting you are used to in your editor. Here's a screenshot from Visual Studio Code:\n\n\n\nI was playing around with WebC for a few weeks before it really clicked with me that I wasn't getting proper formatting, or code hinting. Luckily it's easy enough to fix. In Visual Studio Code, open up your File Association settings. And then add *.webc as an html file:\n\n\n\nAfter that - voila:\n\n\n\nYou also get your expected code hinting and such which is useful of course. While this particular example is for Visual Studio Code, any decent editor would support a similar configuration as well.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Update to My Slideshow Web Component - by Šime Vidas",
		"date":"Tue Jan 24 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1674583200,
		"url":"https://www.raymondcamden.com/2023/01/24/update-to-my-slideshow-web-component-by-sime-vidas",
		"content":"A few days ago I blogged about how I built a simple web component to create a &quot;Slideshow&quot; component. Basically, given a set of URLs, show one image at a time and provide controls to let the user navigate. I had planned to update that component (and still do!), but I wanted to share the work done by Šime Vidas. Šime is someone I have a huge amount of respect for when it comes to the web platform, so having him work on my component is quite cool, and I especially like that one of his changes was one I really thought I wouldn't like... until I saw it in action. It drives home the point that we as developers have quite a few different ways to approach working with web components and that's a great thing to have.\nOk, so let's look at his changes, first on the HTML side. In my version, the URLs for the images were provided as an attribute, like so:\n\nThis felt nice to me. I liked that I could have line breaks and white space in there, making it easy to add or swap out images when needed. Šime's change was to use &quot;proper&quot; image tags instead, like so:\n\nAs I said, I thought I wouldn't like this, but the more I see it, the more it seems like a very good change. First off, you have complete support for folks without JavaScript enabled. Yes, they won't get the &quot;one image at a time with controls&quot;, but they will get the images. That's an excellent fallback. It's a bit more typing, but I think it's worth it. And heck, it's still pretty easy to move/add/edit/etc in this form. Notice the alt attribute - I'm going to come back to that later.\nOn the code side, he supports this like so:\n\nHe ends up writing the tag data into the attribute, so in theory, his version would support both HTML styles.\nLastly, and this goes back to the JavaScript-disabled fallback, he added this CSS:\n\nThe :not(:defined) aspect basically tells the browser to apply this when slide-show isn't a defined web component. This is your reminder that if you're still sharing the &quot;Family Guy window blinds is CSS&quot; meme, stop. CSS freaking rocks.\nNow, this is where I need to kinda think out loud. As I work with web components, I think about the developer experience. In my first version, the &quot;install&quot; experience is just adding the script tag (or including my JS in a build process). That's it. When I see the change he made, my first thought is - well now a developer has to add both JS and CSS. That kinda worries me, but at the same time, if you look at UI libraries, many do require both a JS and CSS resource, and that's ok.\nLike I said - I'm still thinking a lot about how web components can best be used, so I apologize to my readers if I seem to be a bit wishy-washy in terms of how I build things. :)\nI'll share his CodePen below, and then I have even more thoughts. (As an FYI, the placeholder.com server seems to be having issues today. If you get a broken image, it's them, not me. ;)\n\n  See the Pen \n  Slideshow Web Component by Šime Vidas (@simevidas)\n  on CodePen.\n\n\n\nOk, some quick notes. First off, in this new form, the preload function doesn't really do much, as the &quot;real&quot; img tags are going to load the images anyway. That could be removed from the code.\nSecondly, the alt tags, even if supplied, wouldn't help, because we only take the URLs from the DOM. But the code could be modified to read it along with src. The same could be done for title as well. My plan is to update my component by taking Šime's code and modifying it to do that.\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jan 22 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1674410400,
		"url":"https://www.raymondcamden.com/2023/01/22/links-for-you",
		"content":"Happy Sunday, programs. Here's some links for you to enjoy this week. I'll be speaking this week at the free event, The Jam.dev, and I hope to see you there (virtually) as well!\nEleventy 2.0 Beta\nEleventy 2.0 has been in the works for a while now, and the final release will be here soon. The 2.0 beta release was shipped this week with lots of new features and improvements. While not a new feature, the fact that the node_modules size went down near 80% is freaking awesome. If you are still on 1.x, I'd take a look at this article, New Features and Upgrade Considerations for Eleventy v2.0.0, which will help you prepare for the upgrade. I've been running a 2.0 canary for a while so I hit the major issues upgrading back then, but all in all it should be a pretty painless process. I've upgraded my blog here to the 2.0 beta and everything worked fine.\nEleventy and Cloudinary Example\nRegular readers will know I'm a big fan of Cloudinary, and this week I had an interesting conversation with Daniel Braun about how to integrate the two. Specifically, he was trying to find an easy way to get dimensions of an image stored in Cloudinary. He discovered that Cloudinary has a transformation that returns metadata about an image: fl_getinfo. I find this completely fascinating as it's the only (that I know of so far) transformation that returns textual data, not a new image. It's super useful and for Daniel, it let him do stuff with Cloudinary and not use the SDK or REST-based API. He wrote a great blog about it in detail: Eleventy, Images and Cloudinary.\nThe fact that Cloudinary has this flexibility just makes me like it even more.\nAwesome Web Components\nWant to learn more about web components? I do! Serhii Kulykov has set up a repository of links for everything web component related: Awesome Web Components. There are a huge amount of resources here so whether your an expert or just a beginner, you'll find lots of stuff to check out. As an example, I was happy to discover not one but two books are already in development. As someone who likes to use printed books to learn tech, I'll definitely be picking up one (or both) of them!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "A Simple Slideshow Web Component",
		"date":"Fri Jan 20 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1674237600,
		"url":"https://www.raymondcamden.com/2023/01/20/a-simple-slideshow-web-component",
		"content":"As I continue to play around with and learn more about web components, I thought I'd build a simple component to make it easier to add a slideshow. By that, I mean something that renders one picture but provides controls to go to more images. I've probably built this many times in the past, both in JavaScript and server-side code, and I thought it would be a nice candidate for a component. As with most of my demos so far, there's a lot more that could be done with it, but I thought I'd share what I have so far. Once again I want to give a shout-out to Simon MacDonald for helping me get this code working. (At the end of the post, I'll share the mistake I made, as I think it's something others will run into, as well as a modified version Simon built.)\nOk, so I began by &quot;designing&quot; how I wanted to use the component in a regular HTML page. I wanted to allow for a list of images passed in via an attribute:\n\nNote that I added some space around the URLs. I did that to make the code more readable and easier to modify. (I had to modify my source URLs a few times.) The tag also supports a width attribute and generally should always be used, but defaults to 500.\n\nNow let's look at the JavaScript. It's not terribly long so I'll share the whole bit, and then talk about what each part is doing:\n\nAlright, so from the top, I start with some basic validation. If you don't pass any images, the tag doesn't have anything to do so it might as well abort. I mentioned that the tag supports a width attribute and while it defaults, I would probably use it consistently in production. This part,\n\nIs the bit that lets me add line breaks and stuff around the URLs. I really like this little bit as it makes it much easier for the developer making use of the tag. User experience FTW!\nSpeaking of experience, I added a preload function that automatically loads all the images. In theory, this will make the slideshow zippier as the user navigates through the images. I don't wait for it to finish, which I think is a good trade-off between trying to load things early and letting the user navigate as soon as they want.\nNext up, I have the basic layout of the component. It's just an image with a paragraph beneath it. That paragraph contains my buttons as well as some text letting the user know what picture they're on as well as how many total images are available. I also create a style element with just a bit of layout control. This could be prettier. I don't do pretty.\nThat's most of the constructor explained. In the connectedBacllback event handler, I add my event listeners to the buttons, being careful to bind the this scope correctly and I totally didn't mess that up the first time around, honest. (I made a completely different mistake.) The event handlers do basic &quot;end of range&quot; checks and just update a value for the current image, then chaining off to updateImage to update the DOM.\nYou can see the entire thing in action below:\n\n  See the Pen \n  Slideshow Web Component by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe source for this demo is up on my GitHub repo here: https://github.com/cfjedimaster/webcomponents/tree/main/slideshow\nSo, let me leave you with a few notes.\n\nIn my original version, I was making use of getAttribute and setAttribute with an attribute name that was camel-cased. You can't do that. I had currentImage which isn't a valid web component attribute. I remember that now - but didn't when I was building.\nI mentioned Simon helped me out, and he also built his own version. (His version was built before I added the text.) You can find it here: https://gist.github.com/macdonst/75b0980e6b0bd033dd2d36c085dbba75\nThere's something missing from this component that I need to add, and will do so in my next post. It should be possible, via JavaScript, to modify the images. In theory, you could modify them now, but it wouldn't change anything. Web components definitely let you support that and I'm going to build a second version to demonstrate that! (And give me an excuse to blog again!)\n\n",
		"tags":[
	        
            "web components"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Covers, covers, covers",
		"date":"Wed Jan 18 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1674064800,
		"url":"https://www.raymondcamden.com/2023/01/18/covers-covers-covers",
		"content":"And now for something totally non-tech related, I've been working on a playlist of covers for a while now. I'm a heavy Spotify user and absolutely love how it provides suggestions for a playlist and makes it easy to add ones you agree with. I knew Spotify had an embed for songs but wasn't sure if they had the same for playlists, and of course, they do. Customization is limited to a regular and compact form and a dark or orange theme. I went with the orange theme because why not add a bit of color to the page? I also increased the height a bit. For folks who don't have Spotify accounts, you can still get a pretty decent preview of the tracks. (As a quick FYI, you'll notice it isn't orange at all. Turns out the Spotify desktop application rendered the preview in orange, but clearly shows gray and black as options. I'm going to report the issue, but for now, I went with the darker theme.)\nI'll call out a few particular really good ones below. First off, the &quot;just like heaven&quot; cover by the Lumineers is beautiful.  I think I prefer it to the original. Next, I'll call out the &quot;Love Will Tear Us Apart (Again)&quot; by In the Nursery. I've heard many covers of &quot;Love Will Tear Us Apart&quot; but this one is the most unique. It's an absolutely fascinating take on the song. Finally, the cover of &quot;Your Love&quot; by Scott Bradlee's Postmodern Jukebox is fun as hell. Anyway, enjoy!\n",
		"tags":[
	        
		],
		"categories":[
            
                "music"
            
		]

	},

	{
		"title": "Followup to My Intl Short Number Post",
		"date":"Tue Jan 10 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1673373600,
		"url":"https://www.raymondcamden.com/2023/01/10/followup-to-my-intl-short-number-post",
		"content":"A few days ago I shared a blog post about using the Intl object in JavaScript to create short, more readable numbers. So for example, instead of 9123456, it would display 9.1M. This was done using the notation option in Intl.NumberFormat. Yesterday I randomly ran into an interesting modification on this using yet another option, compactDisplay.\nThe compactDisplay option is only used when notation is set to compact. It supports two options, short which is default and what I demonstrated in the previous post, and long.\nSo given a number, i, you would use it like so:\n\nAnd the result is, well, longer. ;) What's nice though is that it's still a shortened, simpler version. You can see the result here:\n\n  See the Pen \n  Intl Number Formatting (long) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nAwesome. And if you want to dig deeper into Intl, I recommend the very cool Intl Explorer site. It's an interactive explorer for the Intl spec and covers everything. (If I had paid more attention to it, I would have seen compact sooner!)\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jan 08 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1673200800,
		"url":"https://www.raymondcamden.com/2023/01/08/links-for-you",
		"content":"Welcome to the first links post of 2023! As always, the idea here is to use theses posts as a quick way to share cool links, updates, and so forth. I've got some good ones this week!\nDynamically Showing and Hiding Slot Content in a Web Component\nLast week, I wrote a post concerning showing and hiding slot content in a web component. For my example, I used it in a component that loaded content remotely and wanted to provide an easy to show the various states (loading, ready, and failure). A good friend, Simon MacDonald had some ideas to improve the code.\nOne simple change that I did, was moving the HTML result to a template. I've not made use of the template tag because it's separate from the web component JavaScript file, but he simply made use of it via JavaScript, like so:\n\nThis is on top of the file, nicely separated, and I dig this. I'll probably start doing this in future components.\nThe biggest change though was in how he handled changing the states. Remember I said I've got three states based on the remote API call. He used a new variable, state, that he specified as an observed attribute:\n\nThis is then tied to code that monitors changes to attributes:\n\nupdateState is a nice generic function that hides all the slots and then 'brings back' the correct one based on the state of the component. As I said, it's generic, so when (ahem, yes when) I get around to properly adding error support, I just need to set the right state. If for some reason my component had four states, it would be easier to handle as well.\n\nWhat really fascinated me about this is that the state really isn't an attribute. Sure you could pass it in, but the component handles that for you. But you can treat it as if it were one, and get the built in mechanics like attributeChangedCallback to help you use it.\nAnyway, I really dig his update, and you can find the complete code below:\n\n  See the Pen \n  Slot Test Hide Show Weather Demo (v2) by Simon MacDonald (@macdonst)\n  on CodePen.\n\n\nShort Number Formatting in Java\nA few days ago I posted about short number formatting in JavaScript, and then I followed that up with a Python example. My very good friend (and continual inspiration) Todd Sharp shared an example of it in Java:\n\nHe wanted to make sure I warned my readers he wrote this quickly so it may not be the most organized, but I was really impressed with how simple it was, and even more so, this particular line:\n\nThis lets you specify numbering formats for values, allowing you to specify something custom. This comes in handy for numbers over a trillion. Most folks don't know what comes after trillion (Quadrillion) and switching to letters is a simple way of handling it. In fact, my entire reason for looking into this was to build something exactly like that. I hope to have that post done later this week.\nOrganizing an Eleventy Config File\nFor my last link, here is an incredible article on optimizing how you setup an Eleventy config file: https://www.lenesaile.com/en/blog/organizing-the-eleventy-config-file/. This post was written by Lene Saile and contains a wealth of information and opinions. I didn't agree with everything, but even the options I didn't agree with seemed like solid tips. Definitely check it out. My own config setup here for my blog is somewhat messy, and one day I'll get around to cleaning things up. (Who wants to take a bet on whether or not I do?)\nThat's it for today, and have a great rest of your week!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Short Number Formatting in Python",
		"date":"Thu Jan 05 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1672941600,
		"url":"https://www.raymondcamden.com/2023/01/05/short-number-formatting-in-python",
		"content":"Yesterday I wrote a blog post about creating short number formats in JavaScript. Definitely check out that post first, but the idea was to take something like 9496301 and display it as 9.5M. In that post, I used the built-in Intl object and it worked really well. It got me thinking, could you do the same in Python?\nFirst off, I checked and was happy to see that like JavaScript, Python supports numeric separators. This makes it much easier to read large numbers in code. It also meant I could take my test array and copy and paste it into a Python program:\n\nI literally just now noticed that Python is also ok with the trailing comma. Sweet. Ok, so first I checked into just regular number formats, and of course, Python supports that, both with a built-in format function and f strings. In my case I wasn't worried about decimal places and the like, but could easily add commas. Here's a simple example:\n\nThe first time I print the value, I don't format it, but pad it 30 characters to make my output easier to read. The formatting is done in the second variable, by just supplying :,. Here's the output:\n999                           999\n1000                          1,000\n2999                          2,999\n12499                         12,499\n12500                         12,500\n430912                        430,912\n9123456                       9,123,456\n1111111111                    1,111,111,111\n81343902530                   81,343,902,530\n1111111111111                 1,111,111,111,111\n62123456789011                62,123,456,789,011\n1111111111111111              1,111,111,111,111,111\n\nCommas work for some countries, but not all. I checked and there's a locale-specific version as well: :n. Here's an example where I set the locale to German.\n999                           999\n1000                          1.000\n2999                          2.999\n12499                         12.499\n12500                         12.500\n430912                        430.912\n9123456                       9.123.456\n1111111111                    1.111.111.111\n81343902530                   81.343.902.530\n1111111111111                 1.111.111.111.111\n62123456789011                62.123.456.789.011\n1111111111111111              1.111.111.111.111.111\n\nOne odd thing with the n operator is that when I didn't specify a locale, it used nothing. I'm not sure why. Running locale.getlocale() definitely returned en_US, but maybe the expectation is that you should always set a locale when using it. I tried this and it worked:\n\nI'm chalking that up to something I did wrong, or misunderstood.\nOk, so that's basic formatting, how would you do the nice 'short' format? Use the numerize library. You can find it here, https://github.com/davidsa03/numerize, and after installing it via pip, here's an example of it in use:\n\nAnd the output:\n999                           999\n1000                          1K\n2999                          3K\n12499                         12.5K\n12500                         12.5K\n430912                        430.91K\n9123456                       9.12M\n1111111111                    1.11B\n81343902530                   81.34B\n1111111111111                 1.11T\n62123456789011                62.12T\n1111111111111111              1111111111111111\n\nNoticed that it worked perfectly... except for the final huge number, but as I mentioned in the last post, JavaScript's Intl also didn't handle it exactly right, although I do think it handled it better, returning 1111T instead. Either way, numerize is pretty nifty and was quick to use.\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Intl for Short Number Formatting",
		"date":"Wed Jan 04 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1672855200,
		"url":"https://www.raymondcamden.com/2023/01/04/using-intl-for-short-number-formatting",
		"content":"One of my favorite things about working on projects to blog about it is when I get random offshoot ideas for other posts while working on the code. That's exactly what happened yesterday. I was playing around with another idea I had and randomly discovered something cool I thought I'd share. I've long been a fan of the Intl object in JavaScript. It's an incredibly useful and powerful way to handle date and number formatting without relying on external libraries. While I've known about, and have used, Intl for years, I was absolutely pleased to find a feature I didn't know about.\nA few years ago (holy crap, make that eleven years ago), I wrote up an example of using Jasmine. In that blog post, I used Jasmine to test a function that wrote large numbers in an easier-to-read shorthand format. So for example, given 341,941,776, it would write it as 342M. Yes, it's not as precise, but it's much quicker to scan and (hopefully) easier to wrap your head around if looking at a large number of stats. (As an aside, if using this in HTML, you could always use a title attribute to show the 'real' value: &lt;span title=&quot;3232&quot;&gt;3K&lt;/span&gt;.)\nTurns out, Intl has this support baked in if you pass the notation option. This option supports four values: standard (the default), scientific (for scientific formatting), engineering (according to the docs, &quot;return the exponent of ten when divisible by three&quot;, which seems weird to me), and compact, the one we want to look at today. For a complete list of all the options supported, you can always check the MDN docs.\nLet's take a look at this in action. First, I'll define an array of numbers to use as tests:\n\nNotice on the larger numbers, I use underscores to represent commas. This is a new feature called 'Numeric separators' that improves readability. This is well supported, except for IE, but IE is dead so we're fine with that.\nFor each number, I can generate the formatted version of it like so:\n\nI've hard-coded it to en-US, but that could be dynamic of course. Given my inputs, the results are pretty much what you would expect I think. Check the CodePen below:\n\n  See the Pen \n  Intl Number Formatting by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFor the most part, I think this works as expected, but note the rounding on the items lower than 100K. I can see people not being happy with that. Also note what happens when you get to quadrillion - Intl simply returns thousands of trillions. That may or may not be a big deal to you. (And I'll note, this is what my original blog post was supposed to be about, I hope to get back to that soon!)\nLet's return back to the issue I mentioned about rounding. If you would prefer a bit more specificity in the smaller numbers, you can use another feature of Intl, maximumSignificantDigits. According to the docs, this lets you set a max size for significant digits, from 1 to 21, defaulting to 21. Here's an example:\n\nI forked my CodePen to show the difference with this in use:\n\n  See the Pen \n  Intl Number Formatting (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFor no real reason I can explain, this &quot;feels&quot; a bit better to me. Enjoy!\nPhoto by Mika Baumeister on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Dynamically Showing and Hiding Slot Content in a Web Component",
		"date":"Mon Jan 02 2023 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1672682400,
		"url":"https://www.raymondcamden.com/2023/01/02/dynamically-showing-and-hiding-slot-content-in-a-web-component",
		"content":"Happy New Year and Happy First Post of the Year! Not sure that's a thing but this is my blog so I'm making it a thing. The last few days I've been playing with web components again, this time based on a simple idea: Could I create a web component that relies on external data, and use slots to provide content for the various stages of loading? What I mean by that is something like this:\n\nThe idea here is the component would handle automatically showing and hiding each slot based on the state of the remote, async process. I was able to get an example of this working, but want to be clear there's parts to this I'm not 100% convinced I understand correctly. As always, I'm looking for your feedback, so drop me a line if you have any questions or clarifications.\nAttempt One\nFor my first attempt, I used a fake async process that simply used setTimeout. First, I wrote some simple HTML:\n\nAnd then I created my slot-one web component:\n\nFrom the top, I start off creating two DOM elements. One renders the slots and the other uses CSS to hide them. Notice I'm pointing to the slot element, not the div that will be rendered when the component is loaded.\nIn connectedCallback, I use querySelector on the shadowRoot to unhide the loading slot and get a pointer to the ready slot.\nI do my async process, which in this case is just setTimeout, and when it's complete, I hide the loader and show the ready state. This seemed to work just fine, and you can see it in action below:\n\n  See the Pen \n  Slot Test Hide Show by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThis seemed to work fine, so I then turned my attention to more of a real test.\nAttempt Two\nFor my second attempt, I wanted to do two things. First, switch to a 'real' async process. I got a free key for Weather API. Given a location value and a key, I could get the weather report here: https://api.weatherapi.com/v1/current.json?key=${key}&amp;q=${q}&amp;aqi=no. This returns a bunch of information, but for simplicity sake, I decided to just return the current temperature. Here's the function:\n\nYes, I didn't add error checking here, and I should, but as I'm on vacation, I'm being a bit lazy. (Ok, those of you who know me know I don't need an excuse to be lazy. ;)\nOk, with this in place, my goal this time was pretty simple - after getting the result, make it available to the slot. To handle this, I used a simple variable token. Here's how it looks:\n\nI use brackets to represent the variable and the component should handle the replacement. I thought this would be trivial, but here's where I ran into a brick wall. Here's the JavaScript I had used to work with the slot before:\n\nBut when I tried to write contents of the slot, nothing worked. I tried innerHTML, innerText, even textContents. Nothing worked. I then tried something else:\nlet readyDOM = this.querySelector('*[slot=ready]');\n\nThis is matching any HTML element with the slot attribute set to ready, i.e. the DOM item from the HTML inside the web component. Also note I'm not using shadowRoot. So... I can hide and show the slot elements, but the actual text/HTML is in the &quot;real&quot; item with the named slot. I think that makes sense. Here's the complete connected callback handler:\n\nThis works, and I said, I think it makes sense, but I'd love it if someone were to share some details as I'm a bit fuzzy here. You can see the complete demo below, and yes, it is missing the error handler, but that wouldn't be hard to add.\n\n  See the Pen \n  Slot Test Hide Show Weather Demo (v2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nFor folks curious, right now in my zip code it is 73.9 degrees. Because Louisiana.\n",
		"tags":[
	        
            "web components",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Wrapping Up 2022",
		"date":"Wed Dec 28 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1672250400,
		"url":"https://www.raymondcamden.com/2022/12/28/wrapping-up-2022",
		"content":"I have a tradition here where - usually - I write up a &quot;here's what I did this year&quot; post. Typically I write this just for myself as I figure it's a good way to take stock and really appreciate what I'm accomplished. Sometimes it's just a nice way to say goodbye and look forward to the next year. I feel like every year is incredibly busy as well as exciting, but kind of in the Chinese curse manner. That being said - my kids are healthy and happy, my wife is healthy and happy and I'm well employed. No matter what else happens, that's a win for the year over all.\nWhat I Accomplished\nThis was a huge year for my blog and I'm very happy to say that this will be my one hundredth post! While I'm not at the crazy high levels I was back in the early 2000s, I've been really inspired this year when it comes to writing. I discovered, and wrote about, some really fun stuff, like Aline.js and Cloudinary. I continued writing in Python and built a bunch of Eleventy demos as well. If you are truly bored, you can see all my stats for the blog on my ugly, but functional, stats page.\nI also continued to write for our team at Adobe on our developer blog. I released a book with Brian Rinaldi on the Jamstack. On top of that, I'm still writing for external publications. You can see them listed on my About page. I plan on integrating those articles a bit more into the blog via the method I described here: Support External Articles in an Eleventy Blog\nSpeaking wise, I gave 19 talks, with maybe a bit more on the virtual side than in-person. Overall I'm happy with that, but my current speaking queue is kind of slim so I need to start submitting more CFPs. Also consider this an open invitation to anyone reading - I want to speak to your group!\nWhat I Want in 2023\nSurvival. Ok, so maybe a bit more than that. Last year I mentioned three things I wanted to do in 2022 - learn Power Automate, Oracle Cloud Functions, and Adobe Sign. I definitely got more familiar with Power Automate, and while I definitely prefer Pipedream, Power Automate is an incredibly powerful platform. I also played with Adobe Sign, writing a few blog posts and just getting more familiar with it. Sadly, I signed up for an Oracle developer account and did the prereqs, I never got around to actually kicking the tires on their serverless platform.\nFor this year, I think I want to focus on:\n\nWriting even more Python. While dependency management and updating is a bit of a pain, I still absolutely love the language itself.\nFinally look at AWS Amplify - something I've been meaning to do for a while\nRedesign the blog. The current design is nice, but I think I want to go even simpler, perhaps killing off the lovely images I have on top. I've seen a variety of very good light weight blogs lately, super light weight, and I think I want to go that route. We'll see.\n\nThank You\nAs a final note, if you actually read this, thank you. I truly appreciate the readers who have given me feedback this year as well as those who have contributed. At times, it can feel like I'm in a room speaking to myself, so any and all feedback is always appreciated.\nI'll also note that you will not find me much on the bird side anymore. I still have an account, and will still auto-tweet new blog posts, but I'm going to be mainly on Mastodon. You can find me at https://mastodon.social/@raymondcamden. With this blog post, I'm even removing my Twitter link from the left hand nav. I may return in the future, but for now, I'm just happier off of it.\n\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Download Data as a File with Alpine.js",
		"date":"Mon Dec 19 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1671472800,
		"url":"https://www.raymondcamden.com/2022/12/19/download-data-as-a-file-with-alpinejs",
		"content":"As my readers know, I've been updating some of my earlier Vue.js examples to demonstrate how they would work with Alpine.js. Normally I post these &quot;conversions&quot; when I see one of the Vue posts pop up in my stats. Today I noticed this entry was &quot;trending&quot; - Vue Quick Shot - Downloading Data as a File. I thought it would be a great candidate for showing an Alpine version. Let's take a look.\nWhile I won't repeat everything from the previous post, I'll quickly cover how it worked. First, it makes use of the download attribute of the anchor tag. This will take a normal link operation and instead ask the browser to download the resource at the URL. To do this with client-side data, you can create an anchor tag with createElement, set it to point to your data, and then emulate a click event. I used this a few days ago in an Eleventy article: Adding Download Support in an Eleventy Site.\nIn the original article, I first demonstrated downloading a JSON file. But as most humans don't speak JSON, I then used Papa Parse to convert it to CSV. I'm going to follow the same flow for this update.\nFirst Version - Downloading JSON Data\nFor the first version, we're going to look at a tabular set of cats. I will not make you scroll past the gratuitous picture of a cat on a table. Heh, I lie:\n\nOk, so our data:\n\nThe application begins by simply rendering this into a table. (Not a sortable or paginated one, but check out my other Alpine.js posts for examples of that.) I started off with this HTML:\n\nAlpine is used to loop over the cats array. Here's the initial JavaScript:\n\nNow I'm going to add a download button that will trigger the download process:\n\nI then added my handler. This code is a bit simpler than the previous version which added to anchor to the DOM, invisibly, and then clicked.\n\nIf you read my previous article involving Eleventy and downloads, the biggest difference here is creating the data URL string that has the original data encoded into a string. You can test this version yourself here:\n\n  See the Pen \n  Alpine Data Save 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSecond Version - Downloading CSV\nFor the next version, we just need to convert the JSON data to CSV. Once again I'll use Papa Parse as it makes this trivial. Instead of\n\nWe can use:\n\nI then made two more changes. First, the filename:\n\nAnd then the mime-type:\n\nThat's literally the entire change. Now when clicking download you get a CSV that can be opened in Excel. My previous post had a screenshot of that but as this was before you could use Dark mode in Office, I figured I'd update it with a new one:\n\n\n\nHere's the entire example:\n\n  See the Pen \n  Alpine Data Save 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "alpinejs",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding Download Support in an Eleventy Site",
		"date":"Tue Dec 13 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670954400,
		"url":"https://www.raymondcamden.com/2022/12/13/adding-download-support-in-an-eleventy-site",
		"content":"I was thinking recently about how I would add &quot;Downloads&quot; support to an Eleventy site. By that I mean, a site where you have various resources (PDFs, zip, etc) and want to provide a way to let users download them in a consistent manner, as well as how basic tracking could be done as well. I came up with a few ideas I'd like to share, but as always, please let me know what you've done and what you would suggest.\nMethod 1 - Meta Refresh\nFor my first attempt, I imagined a site where I've got some files up in S3 (like I do for my images here) and I'd like to take a directory and set them up as resources. So for example, imagine a _data file named downloads.json:\n\nI've specified a root location for my files and then a simple list of resources. This could be more complex, so for example, I could imagine an array of objects where I have the file as well as a human-readable name. Heck, maybe even a date for when the file was last updated. But for now, a simple file is enough.\nI then created a downloads.liquid file that uses pagination:\n\nThe pagination aspect is vanilla Eleventy stuff, basically creating one unique file per download under a downloads folder, with the file name sluggified as part of the final generated HTML. I've got text telling people they can click if the download doesn't start automatically, but where is that part actually done?\nIn my front matter, note that I'm specifying a download value that uses the current file name of the download. I'm using this in my site layout:\n\nThis is the important line:\n\nA meta with the refresh command is incredibly old web tech, but it works well. In this case, I've specified that in two seconds, the browser should redirect to the resource specified by the front matter. So when a user goes to one of the files, like at http://localhost:8080/downloads/alpha.pdf/, they will then be directed to https://static.raymondcamden.com/images/2022/12/alpha.pdf. (Note that if you do not specify the right headers for your S3 files, they will not be downloaded, but simply viewed in the browser. You can add those headers when pushing files up. I did a quick test and while it isn't quite working for me, I think that's more the CloudFront caching I have in front of my S3. Long story short - that's an S3-specific concern you may want to ensure you address when setting up your downloadable resources.)\nFor the heck of it, I also added links to my downloads from the demo site's home page:\n\nBecause I've got a standard location for my downloads, I can use that in a simple list as shown above. What's nice about this approach is that if you have a random blog post talking about a resource, you know how to link to it to let users download it.\nFinally - the tracking I mentioned earlier is enabled simply by having a unique URL that auto-directs to the download. While Eleventy by itself doesn't have web analytics, if you had one defined for your site, then it should &quot;just work&quot; out of the box. I switched to GoatCounter a week or so ago and so far really love it. I got sick and tired of fighting Google Analytics for basic stats.\nMethod 2 - JavaScript\nThe best part of the first solution is that it's a non-JavaScript solution. As I said, meta tag support is ancient so it's pretty much guaranteed to work, but I also added a direct link in the HTML as well in case it doesn't work. To me, that feels like an incredibly safe and simple way to handle it. That being said, I was curious about a JavaScript solution as well. The one I used is one I've worked with in the past - creating a virtual A tag, setting the download attribute and location, and then triggering a virtual click. This works simply enough, but note that due to browser security rules, will not actually force a download for a file on a different domain. (But it &quot;fails&quot; perfectly, opening it in a new tab.) To get around that for my demo, I moved my downloads local to my Eleventy site.\nFirst, I created a fileassets folder and ensured it was copied to the build:\n\nTo get the files available as data, I then added this:\n\nI then created a new pagination template:\n\nAs described above, we make a new anchor tag, set the various properties, and then trigger a click. As I mentioned above, if for some reason I used the S3 files and the domains didn't match it wouldn't download, but it does open a new tab. For my test with local files though it worked just fine.\nAlso note that what I said about tracking downloads works here as well, although if you are assuming JavaScript support, most analytics libraries provide an API to track custom events and I'd use that instead.\nIf you want to see the source for my demo, you can grab it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/downloadtracktest\nLet me know what you think!\nPhoto by Markus Spiske on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "eleventy",
            
            "jamstack"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Mon Dec 12 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670868000,
		"url":"https://www.raymondcamden.com/2022/12/12/links-for-you",
		"content":"I had planned to release this over the weekend, but yesterday I got submerged into grinding levels on Octopath Traveler and the day just got away from me. I'm also a bit behind - I try to share these twice a month and it's already December 12th. As a child, I could remember adults warning me time would speed up as I got older. I didn't quite believe them. Now I know better. Sigh. Alright, let's get started.\nDear Console\nFirst up is a lovely little project by Christian Heilmann called &quot;Dear Console&quot;. This project collects a set of really useful tips for working with your browser console. As an example:\n&quot;Dear Console, give me a table of contents of the document indented by heading level&quot;\n\nAll of the tips are really useful, and as I said, it's a &quot;lovely&quot; site, a rare example of a decenter-centric site that just looks really well designed:\n\n\n\nHe's got 17 tips so far and is open to your contributions if you have something to share.\nGitHub Trends\nI'm a stat junkie, so when I heard about GitHub Trends, I had to check it out. By giving it permission to access your GitHub information, it provides awesome charts showing what you've accomplished over the year. It provides three main things. Either two simple &quot;cards&quot; showing your language and repository contributions, for example, here's mine for languages:\n\n\n\nThe second report is a high level &quot;report&quot; dashboard. You can share this with others. Here's mine: https://www.githubtrends.io/wrapped/cfjedimaster\nApparently I added eleven thousand lines of code this year and deleted two thousand.\nHues and Cues\nI've shared a few games on these posts, and it's time for another one. We'ved played Hues and Cues (affiliate link) many times now and it's really fun. The premise is simple. Given a color, describe it in one word with the goal of leading other players to figure out what color you're talking about. Sounds simple enough, but look at this board - it's evil!\n\n\n\nI struggle enough matching my clothing, this game is really difficult for me, but still a heck of a lot of fun. While my link above is to Amazon, we actually found this at our local Target, which surprisingly has a really good board game selection.\nThat's it for today, and have a great rest of your week!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Quick Test Post - Sorry!",
		"date":"Fri Dec 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670608800,
		"url":"https://www.raymondcamden.com/2022/12/09/quick-test-post-sorry",
		"content":"Hey folks, earlier this week I posted about a Pipedream workflow to automatically post new blog entries to Mastodon and Twitter. I discovered an issue with the workflow that ended up being a bug on the Pipedream side. (It happens!) They've corrected the issue and I need to test, so I've temporarily disabled Mastodon posting and am writing this post just to see if it posts correctly to Twitter. If so, I'll then restore the Mastodon step (Pipedream makes it easy to disable one part of a workflow) and see what happens when I post again.\nAs I feel guilty &quot;spamming&quot; my subscribers with noise, here's a quick tip. If you ever need to expose a local web site for external testing, check out the excellent tool, ngrok. From your command line you can expose a locally running web service and share the IP with others. It's a great way to let others test stuff on your machine.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Reading Image Sizes and Dimensions with Alpine.js",
		"date":"Thu Dec 08 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670522400,
		"url":"https://www.raymondcamden.com/2022/12/08/reading-image-sizes-and-dimensions-with-alpinejs",
		"content":"It's been a few weeks since I've done this, but while looking at my new stats (https://raymondcamden.goatcounter.com/), I saw one of my old Vue.js posts getting some activity: Reading Image Sizes and Dimensions with Vue.js. In that blog post, I showed how to take a user-selected file and check the file size and dimensions of an image. As I've been slowly going through my Vue.js posts and creating Aline.js versions, I thought this would be a perfect fit.\nI'm not going to repeat everything from the previous entry, but let me recap the highlights.\n\nObviously, your client-side code can't go into the user's machine and read ad hoc files. What it can do is get information about a user selected file. This can be done with an input tag using type=file.\nImmediately after selecting the file, your code has access to the file size.\nTo get the dimensions though, you need to do a bit of work. First, create a new Image object.\nYou need to set the source of the image to the contents of the file. This can be done by using a data URL.\nOnce the image is loaded (remember, images have an onload event), you can then check the dimensions.\n\nOk, so given the above, let's build a quick demo. First, the HTML. I'm just going to have the input field and a place to print out details about the image.\n\nA few things to note here. Like Vue, we sometimes need to reach out to the DOM, and like Vue, this is done via refs. You can see my setting x-ref=&quot;myFile&quot; to gain access to the input field directly. Also, note I'm using the change event. This will fire when the user selects a file. Now let's look at the code.\n\nMy Alpine app has two main variables, imageLoaded and image. The only real logic is in selectedFile. This will use $refs to grab the input field and the selected image. I then use a FileReader object to read in the bits, set it to the image, and when onload is fired, I can update my variables to the front-end displays. Given this source image for example:\n\n\n\nIf I select it, I'll see this:\n\n\n\nYou can test this yourself using the CodePen below:\n\n  See the Pen \n  Alpine Image by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nOk, so as I did in the previous post, let's consider a simple example that adds validation. Specifically - a max file size, a max width, and a max height. The HTML is mostly the same except now I show an error on a validation failure:\n\nIn the JavaScript, I added constants for my max values:\n\nAnd here's the Alpine app itself:\n\nFor the most part, this is the same, with the only change being that now I check the various properties and set a new variable, imageError, when something fails validation. You can test this below:\n\n  See the Pen \n  Alpine Image validation by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nI'll repeat myself, which my readers know I like to do, but the more I use Alpine, the more it just clicks with me.\n",
		"tags":[
	        
            "javascript",
            
            "alpinejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Automatically Posting to Mastodon and Twitter on New RSS Items",
		"date":"Tue Dec 06 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670349600,
		"url":"https://www.raymondcamden.com/2022/12/06/automatically-posting-to-mastodon-and-twitter-on-new-rss-items",
		"content":"I promise I won't be making every upcoming post about Mastodon, but as I realized I was pretty much limiting my Twitter use to posting about my new blogs, I figured why not automate that so I don't have to even open Twitter? And I'm automating the post to Twitter, why not do the same for Mastodon? As always, I look to Pipedream first when building integrations like this, and not surprisingly, the entire automation took roughly ten minutes. Here's how I built it.\nStep One - the Trigger\nFor the trigger, Pipedream has built in a &quot;New Item in Feed&quot; action. I selected that and entered my RSS feed (https://www.raymondcamden.com/feed.xml). I then tested it to confirm it got my feed items. So to be clear, the entire &quot;run this crap when a new item is posted&quot; logic was done in about sixty seconds.\n\n\n\nStep Two - Format the Post\nSo, for this step, I wanted to create the text that would be used for both Mastodon and Twitter. I spent some time thinking about how I wanted to format this. In the past, I've sometimes done this for Twitter: &quot;Blog Post: 'TItle of Post' link&quot;. I'll sometimes add a quick comment or shoutout to a product/service described in the post. I decided to make it even simpler for this workflow - the title in quotes, a line break, and a link.\nI created a Python code step for this:\n\nStep Three - Post to Mastodon\nAs I mentioned a few days ago, my first time working with Mastodon on Pipedream required me to use Node as the Python library wouldn't work well. The issue I ran into was fixed pretty quickly, so my assumption was that I was going to use Mastodon.py. However, along with Pipedream fixing that particular issue, they also added support for Mastodon as a defined account, which means you can set up your authentication (in this case, just the access token) system-wide and use it in multiple workflows.\nThey also added a basic Mastodon action for hitting the API. So for example, here's what you get when adding the action:\n\n\n\nOnce you've defined a Mastodon account, you can select it and then the code will be able to pick up on the authentication. Make note of the code - it's hitting the Mastodon API to verify credentials - a default call. But when I saw this, I was curious how difficult it would be to change this to posting a new toot.\nI checked the docs and found this for publishing a new status\nPOST https://mastodon.example/api/v1/statuses HTTP/1.1\n\nLooking at that, I modified the default Python code a bit:\n\nAs you can see, I'm posting the text of my new blog post, and that's it. No need for Mastodon.py at all. Although I would use it if I were doing anything more complex.\nStep Four - Post to Twitter\nGuess what? Posting simple text to Twitter has been supported in Pipedream for a decade or so. (Ok, maybe not quite that long.) I literally just added the action, selected my account, and used the same text I used for Mastodon.\n\n\n\nConclusion\nI mentioned earlier that all of the above took roughly ten minutes, and that's absolutely true. I think most of that was googling for the Mastodon docs and as I had not used them before, it took me a minute or two to find what I needed. If you want to try this out yourself, you can create a copy of my workflow here:\nhttps://pipedream.com/new?h=tch_3Z6f7V\nNote - if you like this, but don't want to post to Twitter (or Mastodon), you can simply delete, or disable, the relevant step. Enjoy!\nPhoto by Patrick Fore on Unsplash\n",
		"tags":[
	        
            "pipedream",
            
            "mastodon"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quirky Python Loop Thing",
		"date":"Mon Dec 05 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1670263200,
		"url":"https://www.raymondcamden.com/2022/12/05/quirky-python-loop-thing",
		"content":"Please don't take this as a &quot;Here is how you should do this in Python&quot; post but rather, &quot;I found this interesting behavior and thought I'd share it&quot; instead. I know I've said this a million times on my blog already, but I'm learning Python and try my best to take every opportunity I can to practice it. Currently I'm using Python for this year's Advent of Code, and while I'm already behind, I've enjoyed it so far.\nIn my work on day three's solutions, I ran into an interesting issue. As part of the process for solving the second half of the puzzle, I needed to look at an array of data in groups of three. It was safe to assume that my input would be evenly divided by that number. So for example, this array of 9 characters:\n\nNormally I loop over an array like so:\n\nGiven that prints every item, how can you print every third item? Turns out that's super simple. When working with arrays, you can provide a start, end, and count value in brackets. So for example:\n\nEven cooler, you can leave off start and end:\n\nSo for example:\n\nWhich returns:\na\nd\ng\n\nPerfect! Except that in order to work with my &quot;groups&quot;, I needed to know the current index I was on. Having the index would then let me simple &quot;plus one&quot; and &quot;plus two&quot; to get a group. Remember, it was safe to assume the array was divisble by three.\nSo... again, remember I'm still learning this. In order to get the current index, I turned to enumerate and tried this:\n\nAnd got... not what I expected:\n0 a\n1 d\n2 g\n\nAs you can see, instead of 0, 3, 6, I got 0, 1, 2. Which... I guess represents the current loop iteration and kinda makes sense, but as I said, it wasn't what I expected. How did I get around it?\nIn my Advent of Code solution, I just switched to a while loop:\n\nBut later I realized I could simply multiply by 3:\n\nI'm sure there's probably many different (and better) ways of doing this, but that's part of the reason I enjoy Python so much, the flexibility!\nPhoto by Artturi Jalli on Unsplash\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Mastodon Bot on Pipedream",
		"date":"Thu Dec 01 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1669917600,
		"url":"https://www.raymondcamden.com/2022/12/01/building-a-mastodon-bot-on-pipedream",
		"content":"Like a lot of people, I've been making more use of Mastodon lately (you can find me here) and less use of Twitter. I'm not leaving Twitter, I'm just reducing my use of it. I'm finding Mastodon a nicer place overall and when and if Twitter collapses, I'd be OK just tooting. That being said, I've built quite a few bots on Twitter, all for fun, and I was curious how difficult it would be to &quot;port&quot; them over to Mastodon. Turns out - it's rather simple, with Pipedream.\nThe first thing I did was google for bots and Mastodon. I know Mastodon has different rules than Twitter and each Mastodon instance itself has its own rules too. I found this great article from 2018 which still worked fine today: Easy guide to building Mastodon bots. As my blog is about building bots on Pipedream I won't cover the same material the author did, but you should definitely check it out. His recommendation for the BotsIn.Space server still works well today. When I signed up there I got approval in about an hour which was much better than I expected. For Pipedream, the important thing to do is set up your application and get your access token.\nHis recommended library, a Python module Mastodon.py, was also a darn good solution, and as I'm trying to use Python more and more, I planned to make use of it as well. Unfortunately, I ran into a bug with Pipedream where I couldn't use it. Fortunately, that bug is already fixed. (Pipedream rocks.) But I had finished my bot already so I'll switch to that the next time I build a super-important mission-critical enterprise serious bot.\nOk, so what did I build? I've got a Twitter bot named Super Joy Cat. This bot uses The Cat API to get a random image and then selects a random phrase to associate with the picture. The phrases are positive statements:\nYou are truly wonderful. Yes, you.\nYou make me think of a nap on a soft pillow in a warm sunbeam.\nYou make me purr.\nYou complete me.\n\nHere's how I re-created this in Pipedream for Mastodon.\nStep One - Define the Schedule\nI created my Pipedream workflow with the schedule trigger. I didn't want my bot to be annoying and I didn't want to worry about going over rate limits, so I specified an interval of four hours.\n\n\n\nStep Two - Random Cat Picture\nThe next step selects the random image. The Cat API makes this incredibly easy - you just hit an endpoint. The only option I specified was for the size of the image. Normally I'm doing all Python for my Pipedream workflows, but as I had this written already in my Twitter bot, I just copied and pasted it. (I did have to change $end to $.flow.exit.)\n\nStep Three - Download the Image\nAfter selecting the image in the previous step, we need to download the file to /tmp. Pipedream has a built-in action for this, but it currently has a bug that can impact workflow editing. Luckily the code to replicate it is rather simple and can be found in the documentation. Here's the code I used - note I'm hard coding the image name.\n\nAs a quick note, I just found out that the bug I ran into with the built-in action has already been fixed. Again, Pipedream rocks!\nSteps Four and Five - Defining and Selecting the Message\nMy next two steps are both Node.js actions with the first defining an array of messages and then selecting a random one. I could have done this in one step but liked having the messages by themselves. Here's the array:\n\nAnd here's the selection:\n\nStep Six - Post the Toot!\nFor the final step, it's time to actually make the toot on Mastodon. As I said, when I began making this bot, there was an issue with the Python library. When I searched for Mastodon Node modules, I came across mastodon-api, and while it's a bit old, it did seem to work well.\nMuch like Twitter, to create a toot with an image, you first upload the image and then post with your text and the result of the media upload. Here's the entire code:\n\nNow, you may notice a lack of error checking here, and yeah, I should definitely do something about that, but for now, I'm fine with it as is. And that's the entire bot!\nYou can find joyful happy cats on Mastodon here: https://botsin.space/@superjoycat\nWhile not the best pic, it's got Star Wars too, so here's a sample:\n\nYou can create a copy of my workflow here: https://pipedream.com/new?h=tch_O4Rf94\n",
		"tags":[
	        
            "pipedream",
            
            "mastodon"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Bare-Bones Eleventy Template for Glitch",
		"date":"Fri Nov 25 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1669399200,
		"url":"https://www.raymondcamden.com/2022/11/25/a-bare-bones-eleventy-template-for-glitch",
		"content":"A few weeks ago I blogged about a simple Alpine.js template for Glitch projects. I'm still new to Glitch and wanted to give it a whirl with an Eleventy demo I wanted to share. Glitch has an Eleventy template, but it's a bit verbose. It sets up a basic blog with sample posts and such, and that's great to learn, but if you already know Eleventy, you may prefer to start off a bit simpler.\nWith that in mind, I created this repository: https://github.com/cfjedimaster/glitch-eleventy It defines an .eleventy.js file that specifies an input and output directory. It sets up a very basic HTML layout and an empty index page that uses it. I also used Liquid for my demo whereas the Glitch-provided one uses Nunjucks.\nI was tempted to add a very basic style sheet (by basic I mean empty) and ensure Eleventy copied it to the output, but wasn't sure how often I'd use that in demos. As always, I'm open to suggestions (and PRs!) on this, but my goal is to keep this as slim as possible. If folks create new projects based on my repo and have to spend time removing stuff, then that's a failure imo. Anyway, let me know if this is helpful!\nPhoto by Michael Dziedzic on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "eleventy"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sat Nov 19 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1668880800,
		"url":"https://www.raymondcamden.com/2022/11/19/links-for-you",
		"content":"Normally I post these on Sunday, but I'm sitting in an office waiting room (nothing scary) on a Saturday and figured I'd go ahead and share today. As always, I'd love some feedback if you find these posts helpful, or want me to share something with my audience (almost 100 people, so not a terribly huge group but it's growing!). Let's get started.\nAdvent of Code\nAdvent of Code is a yearly coding challenge run from December 1st to the 24th. Every day they host two challenges, with the second one always being a variation of the first. It starts off fairly easy, but can get quite complex as time goes on. I haven't &quot;finished&quot; AoC in years, but that's ok, I find it a lot of fun. It's also a great way to try new languages. Last year I did all my solutions in Python and I plan on doing the same this year. The advice I typically give to folks considering this is - take it easy. If it starts becoming not fun, just plain stop. Another thing I've done in the past when stumped is to find a solution (there's a reddit just for puzzle solutions) in another language and &quot;translate&quot; it to the language you're using. You can access past events if you would like to get an idea of what's involved.\nJamstack Community Survey Results\nLike data? Like the Jamstack? Of course you do. The very details Jamstack Community Survey gives a huge heaping of details about the near seven thousand people who filled it out. It's not just Jamstack related, but also covers employment questions as well (remote versus in office, people who resigned, etc).\nBlast from the Past - BBC on Text Adventure Games and Infocom\nLast but certainly not least is this incredible BBC news report on text games and Infocom. When I grew up, I was a huge Infocom fan. This transitioned into being a fan of MUDs in college and I've got a great deal of respect for the genre. This video is interesting not just for the historical perspective, but also the &quot;interesting&quot; way of handling bug reports - binders of reports on paper. Makes you appreciate JIRA more, right?\n\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a YouTube Embed Web Component (both vanilla and WebC flavored)",
		"date":"Thu Nov 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1668708000,
		"url":"https://www.raymondcamden.com/2022/11/17/building-a-youtube-embed-web-component-both-vanilla-and-webc-flavored",
		"content":"It's been a week or two since I've played with web components, and this morning I was thinking about them (because that's just how cool I am) and comparing and contrasting them with Eleventy's WebC support. I think WebC is incredibly compelling, and honestly, if I knew I were deploying to Jamstack, I'd probably always pick that over &quot;vanilla&quot; web components. Using WebC lets me do quite a bit on the server, and in my build, and reduces the amount of JavaScript sent to the client. That's always a good thing (usually), but I can also see people using regular web components with Eleventy as well. I'm still very new to all of this and still figuring out what makes sense where, but I thought it would be kind of fun to build the same component in both and compare and contrast the result.\nFor my component, I decided to build a lazy-loading YouTube embed. Basically, it would default to an image thumbnail of the video (there's a standard URL format where if you know the ID of a video, you can get multiple different thumbnails) and only load the &quot;full&quot; YouTube embed experience after clicking.\nI found an excellent example of this by Arthur Corenzan way back in 2019, Lazy load embedded YouTube videos. In his post, he describes how he used the srcdoc attribute of iframe. Now, I don't claim to know every HTML tag and attribute out there, but I feel like I've got a pretty good handle on the platform, and I've got to say, that was a new one for me.\nsrcdoc lets you specify the content of an iframe instead of using a URL. So for example:\n\nYou can see this in play here:\n\n  See the Pen \n  srcdoc example by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nSo his technique makes use of this. His example looks like so (with extra stuff removed):\n\nCool, so let's take this basic idea and turn it into a web component first.\nThe Web Component Version\nMy web component code takes his template code and looks for arguments for height, width, videoid, and title. Here's the entire component:\n\nThere really isn't much to it. The only real logic is when I define innerHTML and use the attributes passed in. This is exposed as youtube-embed which means it can be used like so:\n\nAnd it just plain works! For the most part. If you read the comments on his blog post, you'll note folks mentioning that the video won't play on click, despite the autoplay parameter. I can confirm this behavior. As someone who was railed against autoplay videos for over a decade, the browser working against autoplay is something I can get behind. I think one could consider a modification of the component such that instead of a play button, it perhaps has text saying &quot;Click here to load&quot; or some such, so the expectation is set that it's one click to load, another to play.\nTo me though, the real benefit comes in when you look at the difference in what's being loaded. On a page where I use this component, the initial is right below 2.5kb. When you click to load the video, that goes to nearly a meg. That's a huge difference and we're saving all of that from the user's first load which is a great thing.\nYou can demo this version here:\n\n  See the Pen \n  youtube-embed by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nAnd the source may be found here too: https://github.com/cfjedimaster/webcomponents/tree/main/youtube-embed\nNow, let's look at the WebC version.\nThe WebC Component Version\nHonestly, I don't know if &quot;WebC Component&quot; makes sense as it's got the C in the name already. I'm probably overthinking it, but let me know what you think. Ok, let's talk Eleventy and WebC. First off, if you haven't read the WebC docs yet, please do. And if you need a little extra help, my introduction may help. I've got a small Eleventy site with the WebC plugin installed, configured to look at &quot;_includes/components/**/*.webc for components. I built a new Liquid page and used the same code to call my component:\n\nAnd actually, that's a lie. It isn't the same code, do you see what's missing? The script source. It's completely unnecessary now as Eleventy's going to handle all of that in the build process. For my actual component, here is youtube-embed.webc:\n\nThis is just a Liquid template with a bit of logic on top, and then just dynamic output. I'm not a fan of needing to wrap the entire thing in template tags so I can use Liquid, but I can get over it too.\nFor such a simple test, the size savings wasn't really big. I believe it was 1k less than the &quot;vanilla&quot; WebC version, but this one works without JavaScript, and any easy saving is a good saving!\nWhile I don't have this demo running anywhere, you can find it (and other things I was playing with) here: https://github.com/cfjedimaster/eleventy-demos/tree/master/webctest1 As always, don't forget this is a new feature, I'm learning, and it's bound to change before release.\n",
		"tags":[
	        
            "eleventy",
            
            "web components",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Support External Articles in an Eleventy Blog",
		"date":"Wed Nov 16 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1668621600,
		"url":"https://www.raymondcamden.com/2022/11/16/support-external-articles-in-an-eleventy-blog",
		"content":"A few weeks ago, I began helping a friend migrate his company blog from WordPress to a new solution. Being a Jamstack proponent, I suggested using Eleventy for their new platform. They were all technical folks and the idea of not having to manage and patch WordPress, PHP, and MySQL was very appealing. For the most part, I figured it would be a simple conversion. I'd get their theme (using the hardcore developer technique of &quot;view source&quot;) and simply set up a basic Eleventy blog. (Need help doing that? I've got an extensive guide that walks you through it!) Turns out, their existing blog had something interesting happening with it.\nYou can see their existing blog here, https://news.ascendingnode.tech/ (At the time I write this, the Eleventy version is done, just not yet deployed). In case you don't want to click, here's the front page of posts:\n\n\n\nIn the screenshot above, you see four blog posts. This is - again - a very typical blog interface. You see a title, a date, and an extract of the post. The title (and the word 'more' at the end) should link to the actual blog post. All four of the posts shown above look to be identical in terms of 'form' and function. However, if you click the first link, you actually end up on an external site.\nThis surprised me... but also excited me. I write on my blog as well as on external publications. Currently, I list those articles on my About page and honestly, I'm probably the only one who visits this. But I loved how the external article was presented as just another blog post. While their current blog doesn't have an RSS feed, you could imagine it being there just like any other post. In terms of discoverability, this feels like a perfect solution.\nSo how do we solve this in Eleventy? Here's what I came up with.\nFirst off, I wanted my solution to be in line with &quot;regular&quot; blog posts, by that I mean creating a Markdown file in the posts directory. So given a remote article &quot;A Story of Cats&quot;, I'd create a-story-about-cats.md.\nThen, I added a new key to my post front matter, remoteURL. I considered seeing permalink, but as far as I can tell, it can only be used for writing files to the filesystem, nothing more.\nLastly, I included a paragraph of text. This is the text you'll see on the home page, and in the RSS feed. Here's how I did this for their blog post on the external site:\n\nI was already using a data directory JSON file to specify a layout and tag for posts, but I converted this to JavaScript so I could add a bit of logic. In my case, remote URL blog posts should not get written to the file system:\n\nTo handle these posts in the front end, I simply had to check for the remoteURL value in front matter and ensure I linked to that instead of the regular URL. So on the index page, I have:\n\nThe rest of the page doesn't change. These &quot;remote&quot; posts still have a title, still have a date, still have content I can pass to an excerpt filter.\nFinally, in the feed.njk template, I do something similar:\n\nHonestly, this isn't a big deal code-wise, but as someone who blogs a lot and does a lot of external articles, I'm really happy with this solution. I'm going to implement it here too, but I'll probably be lazy and wait till my next external article is published. What's cool is - I've got a newsletter (sign up below!) that is RSS based - so when I use this technique to add a new external article, my subscribed readers will automatically get an update.\nPhoto by Dorrell Tibbs on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Some Options for Timing Pages in Eleventy",
		"date":"Mon Nov 14 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1668448800,
		"url":"https://www.raymondcamden.com/2022/11/14/some-options-for-timing-pages-in-eleventy",
		"content":"A few days ago I blogged about a page I added to my site to render all six thousand plus blog posts I've published. It's one of many &quot;one-off&quot; pages I've built here for various reasons, so as I was the intended target, I wasn't terribly concerned about the speed or UX of the page itself. I knew the code generating the page was kinda crap, but as it was a build-time only concern, I didn't think too much about it.\nThe more I thought about it though, the more I was curious about just how &quot;bad&quot; my page was. To be clear, it's definitely bad logic. If you didn't read the previous post, I'm doing this to generate the &quot;all&quot; page:\n\nGet all posts\nFigure out my year range (first post to last)\nFor every year, loop over every post and print a link if the year of the post matches the year of the index\n\nThat's roughly 20 (years) * 6000 (number of posts) iterations, or 120K. Luckily, however, this is the only inefficient code I've written in my life so I don't feel too bad. But I decided to do some digging to see if I could figure out some details on just how bad it is.\nBefore I start sharing examples, note that I'm testing this locally where I've got an .eleventyignore file that ignores a vast majority of my site. To see how bad things are, I went ahead and renamed that so I could see what would happen in production. Also, I'm using Eleventy 2.0.0-canary.16 except in one case that I'll specifically call out.\nFirst Attempt - Simple Timings\nThe first thing I tried was as simple as you could get, printing out the time before and after the 'bad' code. To do that, I used this code:\n\nThis prints out the current time to the millisecond. When I did a build, I got the following:\n08:36:58:208\n08:37:02:014\n\nAs you can see, roughly 4 seconds. As you can see, not bad. I thought about getting fancier and printing the difference in milliseconds. I thought I could assign the value to a variable and then use Liquid's minus filter, but while you can get &quot;time since epoch&quot; as a date format filter, it's in seconds, not milliseconds. You could multiply that out, but I was worried about the loss in precision when doing so.\nOk, so that seemed cool, and I really wanted to keep my code to the template in question, but for the heck of it, I created this shortcode:\n\nThis uses a global variable, _timer, to record the current time, and then print the diff on the second and later calls. I can then just add timer calls to my code. Here it is in the all.liquid template:\n\nThe poop at the end was just a quick way for me to confirm that a third call would properly show the difference after the second call. This returned the following lovely output:\nTIMER INITIALIZED\nTIMER DIFF:  3747\nTIMER DIFF:  0\n\nBecause I can't get tinkering, I remembered that Node itself had some timing code built in. I did a quick search, and found the console.time function. Together with console.timeEnd and console.timeLog, it lets you create timers. While it doesn't require a label, I built a short code that would allow for it. It doesn't ever &quot;end&quot; the timer, which I think is ok but I'm not certain:\n\nObviously, I wouldn't use timer2, just timer, but I was testing this along with my earlier shortcode. I added it to my template like so:\n\nHere's how it outputs:\nall loop: 3.430s\nall loop: 3.431s\n\nThis doesn't show a diff but has highly accurate timings. The first output is after the slow code, and the second is after the poop. (Sorry, I'm basically 12 years old.)\nSecond Attempt - Debugging\nFor my second attempt, I remembered that Eleventy would report timing information in aggregate when doing a build, for example:\n[11ty] Copied 38 files / Wrote 6399 files in 35.28 seconds (5.5ms each, v2.0.0-canary.16)\n\nAnd I also remembered it would &quot;flag&quot; data files that took too long. But I was curious if there were more options available via the CLI. Turns out, there's a DEBUG value you can use at the CLI as documented here: Performance\nBefore I continue, let me say that it is FREAKING REFRESHING for a technical site like the Eleventy docs to provide instructions for both Mac/Linux and Windows. I'm really tired of sites that assume Mac/Linux and don't provide help for Windows users, especially in this case where the syntax is different.\nIn my case, I'm on WSL, so I used this command:\n\nThis returns a lot of information, but here's a snippet:\nEleventy:Benchmark Benchmark      2ms   0%     2× (Aggregate) &gt; Compile &gt; ./_posts/2021/05/16/2021-05-16-building-a-choose-your-own-adventure-site-with-eleventy.md +0ms\nEleventy:Benchmark Benchmark      2ms   0%     2× (Aggregate) &gt; Compile &gt; ./_posts/2021/11/13/2021-11-13-congratulating-yourself-with-pipedream-and-microsoft-to-do.md +0ms\nEleventy:Benchmark Benchmark      2ms   0%     2× (Aggregate) &gt; Compile &gt; ./_posts/2022/06/18/2022-06-18-building-a-quiz-with-eleventy-and-eleventy-serverless.md +0ms\nEleventy:Benchmark Benchmark      2ms   0%     2× (Aggregate) &gt; Compile &gt; ./",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Quick LiquidJS + Eleventy Example - All Posts",
		"date":"Wed Nov 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1668016800,
		"url":"https://www.raymondcamden.com/2022/11/09/quick-liquidjs--eleventy-example-all-posts",
		"content":"So, on a whim today I decided to add a page to my blog to display every single post, separated by year. This was not meant to be used by anyone (hence me not linking to it in the nav), but something I've wanted around for a while. I've got a nice search form here, but sometimes I want to search for something I blogged a few weeks ago and having a simple list of posts would be useful. I didn't want to build &quot;proper&quot; pagination, just one giant list in on an HTML page. That's not the best UX but as I'm building this for me, I approve. I thought it would be a quick little script, but as I built it, I ran into a few interesting issues.\nFirst, let me state that I prefer Liquid for my template language, but when doing &quot;data centric&quot; type stuff I'll usually go to EJS. EJS isn't pretty, but it's flexible as heck. If you want, you can peruse the awesome-not-ugly-at-all code I wrote for my stats page. I figured my logic wouldn't be too crazy here so Liquid should be fine.\nI began by figuring out my year range. I could have hard coded the first year (2003) as that's not going to change, but I wanted to do things the &quot;right&quot; way as much as possible.\n\nIn the snippet above, I get all my posts, generate the current year, and then get my first blog post year. I wanted to do those two last lines in one call, but as far as I can tell, Liquid doesn't have a filter that can take an object and return just one particular key.\nNext, we need to loop over each year:\n\nThe for loop logic needs to go from now till the first year, but there isn't a step value for looping in Liquid. Instead, you can simply apply the reversed keyword to the array created by the two years.\nI decided to use details tags for my display so that I wouldn't have a giant list of six thousand links. To be clear, the web page is still downloading a giant list of links, but as I said, I'm building this for me and I'm fine with that:\n\nNow came the fun part. I've got a loop iteration for every year and I need to render the posts from that year. The proper thing to do here would be to build a filter I could pass my posts to and iterate over a result of posts for one particular year. I did not do the proper thing. Instead, I looped over everything:\n\nIgnoring how horribly inefficient this is, make note of this: | plus: 0. Initially my conditional never returned true. It occurred to me that perhaps postYear was a string and I needed to cast it. There isn't a cast-type function in Liquid, but you can perform a mathematical operation on it, in this case adding zero.\nThis worked great - again - if we ignore how dumb it is to loop 20ish times over six thousand entries. Or so I thought. Locally I work with a much smaller set of posts so reloads are quicker. When I deployed to production my build failed with a Maximum call stack size exceeded error. This really surprised me. I mean, I knew it wasn't efficient code, but 20ish loops over six thousand objects shouldn't be too bad. Or at least I thought.\nI was going to start reworking the code when I decided to try something else - moving to Eleventy 2 - well the Canary version. I wouldn't normally deploy unreleased code live, but as it's just my blog I figured why not. Even better, it fixed the issue.\nIf you want to see the final result, you can see it live here: https://www.raymondcamden.com/all\nAnd here's the complete template:\n\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Nov 06 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1667757600,
		"url":"https://www.raymondcamden.com/2022/11/06/links-for-you",
		"content":"Happy DST Day! Or is it DST Ends day? Who knows. (And I'm not motivated enough to look it up.) This week I'll be heading to Connect.Tech, one of my absolute favorite conferences. I always see a few good friends there and the content itself is incredibly well done. This is my second to last conference of the year so I'm going to do my best to get the most out of it. If you read this blog and will be attending, be sure to come tell me hi! Alright, let me get started with what I want to share this week.\nMastodon Intro\nThis past week has been quite tumultuous for Twitter. Those who know why don't need to me to explain it and those who have no idea are probably better for it. This week has seen a lot of people giving Mastodon a try and while it's a pretty cool system, it's also really different in important ways.\nLuckily, Dr. Axel has written up an incredibly useful post for folks who want to give it a try and I absolutely recommend giving it a read: Getting started with Mastodon\nIf you've never encountered Dr. Axel before, than I strongly suggest bookmarking his blog. When it comes to web development, he writes some of the most in-depth articles around. I'll admit that sometimes his writing is a bit too high level for me (and that's my fault, not his), but his Mastodon article is easy reading and will be a huge help for folks who are looking forward to tooting with the rest of us nerds. (Yes, tooting.)\nIf you want to follow me, you can find my profile here: https://mastodon.social/@raymondcamden\nMore WebC Information\nI've only begun playing with WebC, Eleventy's web component feature, but it's beginning to look like an incredibly powerful addition to the Eleventy platform. The last time I did a &quot;Links For You&quot; post I shared links to the docs and a presentation by Zach, today I want to share two really good links on the subject, both on the excellent 11ty Rocks site.\n\nIntroduction to WebC - start here, of course...\nUnderstanding WebC Features and Concepts - and then read this\n\nAs I mentioned in my own post on the topic, I struggled a bit with the core 11ty docs on the subject, so these two posts were really useful. (And on that, there have been updates to the core 11ty docs lately that help clear things up!)\nBardcore - The Music Genre You Didn't Know You Needed\nI discovered this a few weeks ago, but apparently people are making medieval covers of modern songs under the genre name of Bardcore. You can read more about it here: People are Making Amazing Medieval Style Covers of Pop Songs and Calling It Bardcore\nI love this. I've got a thing for covers and mashups in general, but this is just really freaking cool to me. It reminds me of (spoiler for West World Season 1) the covers used in West World. I didn't realize they were doing it until I noticed that the song being played in the saloon sounded awfully familiar.\nIf you would like a sample, check out the tune below. It's beautiful.\n\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Responsive Images with Cloudinary",
		"date":"Fri Nov 04 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1667584800,
		"url":"https://www.raymondcamden.com/2022/11/04/adding-responsive-images-with-cloudinary",
		"content":"In case you can't tell, I've been enjoying playing around with Cloudinary these last few weeks. As part of my research, I recently looked around in the docs for things I wanted to dig deeper into. One of the features I thought was fascinating was Cloudinary's remote image support. What does that mean?\nIn the examples I've shown so far, I've made use of Cloudinary's media library to store my files. That works well enough and has an API to let you automate working with it, but what if you have existing images you don't want to move? Cloudinary lets you support them by simply including them in the URL request to their server!\nSo for example, the picture of me in the upper left side of the blog (wow was I skinnier then) may be found at this URL:\nhttps://www.raymondcamden.com/images/avatar2.jpg\nThis same image, with no transformations, may be found here:\nhttps://res.cloudinary.com/raymondcamden/image/fetch/https://www.raymondcamden.com/images/avatar2.jpg\nExcept that now it's cached and served via Cloudinary CDNs. I can also apply transformations just like any other Cloudinary asset. So if I want to apply sepia (of course I do), I'd just use:\nhttps://res.cloudinary.com/raymondcamden/image/fetch/e_sepia/https://www.raymondcamden.com/images/avatar2.jpg\nAnd it would render as:\n\n\n\nCool! So let's see if we can use this to add a bit of responsiveness to my blog. Let me preface this by saying that I've been &quot;adjacently&quot; familiar with responsive design for years. By that I mean, I know the very high level basics, I recognize it in code when I see it, but I haven't actually built sites with it myself. For this blog post, I used the excellent article from MDN, Responsive Images. I want to be clear that what I did was just a test (although it is deployed right now!) and it could most likely be done better. Basically, blame my ignorance, not Cloudinary.\nEach blog post has a header image on top. I usually source these from Unsplash and manually resize them to 650 pixels wide. As an example, this is how a banner image is rendered in HTML here:\n\nI decided to modify this HTML such that:\n\nAt screen sizes 800px and higher, just render as is.\nAt smaller screen sizes, render an image that's been scaled down to 300 pixels wide.\n\nGiven a URL of https://static.raymondcamden.com/images/banners/cat_sleeping.jpg, the resized Cloudinary version of that is https://res.cloudinary.com/raymondcamden/image/fetch/c_fit,w_300/https://static.raymondcamden.com/images/banners/cat_sleeping.jpg\nNotice I've added the c_fit,w_300 directive in there to handle the resize. Remember, this is done one time and it's cached, so while I don't have these images prepared as I type this, as soon as one person requests the image it will be built and then served from cache from then on.\nI modified my HTML like so... (Note - I use LiquidJS with Eleventy to build my site, but I figured people don't need to see the Liquid portion in order to get what I'm showing here. As always, my blog's repo is available for perusal!)\n\nLike I said above, I'm still fairly new to this and I know I could provide even more options for different viewport sizes, but I figure this one small change could be really helpful.\nAs a quick test, I opened up dev dtools and tested with this blog post, which right now is using https://static.raymondcamden.com/images/banners/welcome2018.jpg. This image clocks in at 82.3KB. When I resize my viewport down, I get the Cloudinary version which is 13.0 kb. (The 0 size for the first request shows it was being loaded via cache.)\n\n\n\nSo just to recap. Given that I could do this better, definitely, but it took maybe 30 minutes to add a responsive header to my blog and I didn't have to touch one of my older media files. Cloudinary did all the work, I literally just crafted a URL. Freaking awesome!\nPhoto by Taras Shypka on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Integrating Cloudinary Notifications with Pipedream",
		"date":"Tue Nov 01 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1667325600,
		"url":"https://www.raymondcamden.com/2022/11/01/integrating-cloudinary-notifications-with-pipedream",
		"content":"As I continue my journey into learning the awesomeness of the Cloudinary platform, today I decided to take a look at their notifications support. Cloudinary lets you specify a webhook URL that will be hit on different types of events. I whipped up a quick example of using this with Pipedream, my favorite service for processing webhooks. Here's how I did it.\nSetting Up the URL\nIn order to use webhooks, you can either specify a Cloudinary-wide URL or specify it when using certain API methods. For my test, I began at Pipedream, created a new HTTP-triggered workflow, copied the URL, and then pasted it into my Cloudinary account settings:\n\n\n\nTesting Events\nBy default, Cloudinary will fire the webhook on:\n\nUploading, renaming, and deleting an asset\nModifying metadata for an asset\nModifying tags for an asset\nModifying access control for an asset\nMaking a new folder\n\nSince I had my Pipedream workflow set up, I quickly tested using Cloudinary's Media Library. I made a new folder and just started dropping a few files in.\n\n\n\nAfter uploading, I confirmed that I saw an event on the Pipedream side and confirmed the body matched what Cloudinary documented as part of their payload.\n\n\n\nCool! Now let's build on it...\nVerifying Events\nThe Cloudinary docs suggest that you validate the webhook to ensure it really came from them. This is a mixture of checking various headers and the body and such, but honestly, their SDK makes this easy... for the most part. So here's their simple Node.js example:\n\nThis made sense, but I wasn't sure how to handle valid_for. I could absolutely tell what it meant, &quot;If the webhook was sent at time X, it's only valid within a time range Y&quot;, but I was not able to find documentation on this argument. I ended up going to the GitHub repo for the SDK and finding it here:\nhttps://github.com/cloudinary/cloudinary_npm/blob/e43ba794e6dec691019d02e93d89df187d684dcb/lib/utils/index.js#L1059\nIt's in seconds, and it defaults to 7200, so I don't need to bother setting it. I added this to my workflow as a new code step with the following logic:\n\nOutside of being unsure about the time, two other things tripped me. First, Pipedream lowercases HTTP headers. Notice in the sample above I'm using the lowercase version of the headers Cloudinary sends. (FYI, you can get the raw headers if you want.) Secondly, the method wants a JSON version of the body. Pipedream automatically parses it to data which is certainly what I'd want most of the time, but in this case, I had to transform it back to JSON.\nAn Example\nA basic Pipedream workflow would need the HTTP trigger and the verification step, outside of that you would do... well whatever makes sense. I decided on a workflow that would:\n\ncheck to see if the event was an upload\nsend me an email with a copy of the picture\n\nLet's tackle the first one by using a Filter step that continues if a condition is true. We want to check the notification_type value of the body sent to the workflow and want to continue when it's set to upload. Here's the Filter step I used:\n\n\n\nAs a reminder, I could have done this in a code step. Heck, I could have done it in the verification step too. But I like my workflows to be descriptive, clear of purpose, and use the built-in stuff, like Filter, whenever possible.\nYou can quote me on this - great tools adapt to your preference and don't force you to do things only one way.\nNext, I added a step to create my HTML string. This was slightly complex as I used the Cloudinary SDK again to create a thumbnail of the image that was just uploaded. Here's the entire step:\n\nFinally, I added the built-in Pipedream step that emails you. I say this every single time I blog, but I used the same value for the text body of the email as HTML and you should not do that in production.\n\n\n\nAnd here's the final result:\n\n\n\nWant to try this yourself? You can make a copy here: https://pipedream.com/new?h=tch_3brfvn\nEnjoy, and as always, let me know what you think.\nPhoto by Prateek Katyal on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "pipedream",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An Alpine.js Template for Glitch",
		"date":"Fri Oct 28 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1666980000,
		"url":"https://www.raymondcamden.com/2022/10/28/an-alpinejs-template-for-glitch",
		"content":"I discovered Glitch a few months ago, and while I haven't used it a lot since then, the more I do, the more I really dig it. When working on my blog post yesterday, I shared the demo as a Glitch project, you can see it here if you would like. While setting up the project, I fumbled a bit with the right order of script tags, which was entirely my fault, but knowing my memory I wanted to note what worked for me, and share it with others. Let's start with the code first.\nHere's an incredibly basic HTML page that loads up Alpine, a style sheet, and a JavaScript file:\n\nI pretty consistently use app for my Alpine applications, but certainly, you can name it a bit better. My style sheet only contains the x-cloak definition:\n\nAnd here's script.js:\n\nWhen I write Alpine, I don't like including code in the HTML, so I do everything inside an alpine:init block. I also almost always use the init method, so I've got an empty one there.\nFinally, and I forgot this part when I published the blog post a few minutes ago, I added an .eslintrc.json file so that the Glitch editor wouldn't complain about the Alpine global:\n\nCool. So how do you use this? You could copy and paste, but Glitch makes this easier. Click New Project in the upper right-hand UI:\n\n\n\nAnd select Import from GitHub. In the prompt, enter the repo I created for my template: https://github.com/cfjedimaster/glitch-alpine\nI didn't include a README in there because it would just be something you would have to remove or edit in your project. All in all, it's pretty minimal, which honestly is what I prefer when starting new projects. Anyway, enjoy!\nPhoto by Daniel Leone on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "alpinejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Cloudinary with Alpine.js",
		"date":"Thu Oct 27 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1666893600,
		"url":"https://www.raymondcamden.com/2022/10/27/using-cloudinary-with-alpinejs",
		"content":"A few days ago I blogged about building a public API for a Cloudinary folder. I mentioned then that the impetus for that post was another post I had planned. Today I'm finally getting around to writing it. As folks know, I've been quite smitten with Alpine.js lately, and I thought it would be interesting to share a demo of using Cloudinary's Image transformation services in an Alpine app. While the implementation was pretty trivial, here's what I came up with.\nFirst off, my Alpine application is going to display images from a specific folder of assets stored at Cloudinary. For that, I built the endpoint described in my last post: https://eoghaym28jbb0zf.m.pipedream.net/.\nFor each image in that folder, I want to display a thumbnail and then a &quot;web-sized&quot; version (basically something nicely sized but not huge). To display these images, I'm going to use a library I've used before, Parvus. Parvus is a lightbox library where you can view an image in a 100% full overlay above the rest of the page. It's fairly lightweight and simple and was easy to use last time, so I figured I'd fire it up again. Here's how the application renders:\n\n\n\nNote the use of sepia. I used sepia because that's what real designers do. I'm kidding. Mostly. Seriously though, please don't take my horrible design skills as indicative of what you can build with Cloudinary and Alpine. I'm where good design goes to die.\nClicking on an individual image opens it up:\n\n\n\nYou can see that Parvus adds navigation, a counter (X of Y), and a &quot;close&quot; button. Let's look at the code.\nThe HTML, ignoring header and footer stuff, is pretty minimal:\n\nI'm looping over photos, and for each, I link to a webversion function called on the URL and display a thumbnail function version of the URL. In this case, URL is the original version of the asset, but as I said, I want a small (and sepia, don't forget the sepia, all professional designers know it) thumbnail and a 'web' version of the image.\nHere's the JavaScript:\n\nFrom the top, my Alpine application starts by fetching the list of images from my Pipedream API. Once I get the images, I just assign them to my photos variable. Parvus needs to work with items in the DOM, and Alpine has a nextTick function much like Vue which works perfectly for that.\nMy two image formatting functions are just using string replacements on the original URLs. I had considered using the Cloudinary SDK (and you can see a great forum post on how that's done here) but figured with this incredibly simple use case, one line of code was better than another library.\nAnd that's it! I said this was simple, remember? I used Glitch for the demo again which means you can play and fork with the demo here: https://glitch.com/edit/#!/ancient-tall-mustard. If you only want to view the demo, check it out here: https://ancient-tall-mustard.glitch.me/\nPhoto by Ludemeula Fernandes on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "alpinejs",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building an API to List Cloudinary Images in a Folder",
		"date":"Mon Oct 24 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1666634400,
		"url":"https://www.raymondcamden.com/2022/10/24/building-an-api-to-list-cloudinary-images-in-a-folder",
		"content":"I was preparing to work on a Cloudinary and Apline post when I realized I needed something before I could build that demo - a list of images in a Cloudinary folder. While this is directly supported by their SDKs and REST APIs, I needed something that could be used in a public-facing web application. So with that in mind, I turned to Pipedream to build a serverless endpoint. Here's how I did it.\nBefore I start talking about code, I began by logging into my Cloudinary account, going into my Media Library, and creating a new folder named cats. In there, I dragged and dropped a few cat pictures from my personal collection. (Do not ask how many of these I have.)\n\n\n\nIn Pipedream, I created a new workflow with the HTTP trigger. I've talked about Pipedream quite a bit here, but for those unfamiliar with the service, this is their way of creating a workflow that's executed by an incoming HTTP request.\nBecause I like my workflows to be as flexible as possible, I specified an &quot;Export Variables&quot; step which is a simple way to define key/value pairs in your workflow. In my case, I defined a key of folder with the value, cats.\n\n\n\nNow I need to get my images from the folder. I began by seeing what Pipedream had in terms of Cloudinary support. I added a step and searched for Cloudinary, and of course, they had stuff!\n\n\n\nUnfortunately, none of the built-in actions supported getting a list of images from a folder:\n\n\n\nFortunately, and as with other services, you can write custom Node/Python code as seen in the screenshot above. I've mentioned this before, but the beauty of this is that Pipedream handles authentication for you (once you log in of course):\n\n\n\nOk, so while I don't have to worry about authentication (which to be honest, their SDK makes incredibly easy anyway), I did have to dig a bit to find out how to list images from a folder. Turns out, there are multiple ways documented on Cloudinary's support site in this useful article: Listing all assets within a folder\nFor me, it came down to a URL of the form:\nhttps://api.cloudinary.com/v1_1/MYCLOUDNAME/resources/image?max_results=50&amp;prefix=MYFOLDER&amp;type=upload\nNotice I've specified a max result of 50. I didn't check, but I know there's a cap on how high that value can get, so I'll warn folks that my solution may need tweaking if you need to support repeating calls to handle paging. Anyway, here's the Python code I used - which was very slightly modified from Pipedream's default code:\n\nCool. So at this point, I could have just returned my data, but I realized that there was a lot of information there that I didn't need, and maybe shouldn't be public. I added another Python step just to transform my results. This was my first time using a Python lambda and the map function.\n\nI decided to return the URL, height, and width. I figured that was enough.\nFinally, I needed to return the result. I had to switch back to Node as it's not possible to return a custom HTTP response in Pipedream's Python support. (Not yet anyway.)\n\nAnd that's it. You can see the output here: https://eoghaym28jbb0zf.m.pipedream.net/.\nAs a reminder, the modern version of Pipedream's workflow editor is still working on supporting sharing.\nYou can create a copy of my workflow here: https://pipedream.com/new?h=tch_ORVf6y, but note that you will need to click &quot;Add App&quot; in the cloudinary_get_cats section to add a Cloudinary connection.\nP.S. As a completely unnecessary aside, I struggled a bit with whether or not I should return just the array of images or the object of an array as I did. On one hand, it feels silly to have an object with one key being the result - just return the value. On the other hand... it just feels... &quot;more right&quot; I suppose, to return it as an object. This is totally not important in regard to what I was doing, but just thought I'd share. ;)\nPhoto by Viktor Talashuk on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "pipedream",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Integrating Cloudinary into Eleventy",
		"date":"Thu Oct 20 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1666288800,
		"url":"https://www.raymondcamden.com/2022/10/20/integrating-cloudinary-into-eleventy",
		"content":"I've had on my &quot;to-do&quot; list for months now to take a look at Cloudinary and their media APIs. I got some time this week to play around with it and I wanted to share my experience. TLDR - it's freaking incredibly well done and surprisingly powerful. Here's what I found.\nGoing into my research, I knew that they had APIs related to image (and other media) and made it simple to serve up different-sized images. So you could use one core, large, image as a way to serve multiple other versions better optimized for the web. I signed up for their free tier (you can find details on their pricing page) and began to poke around.\nI started in the Image transformations portion of the docs. Here's where I began to be really impressed. Given a core URL for an image hosted with them, you can apply a huge amount of different transformations. There's a bunch for crop and resize, of course, I expected that, what I didn't expect was the ability to do things like, &quot;make a thumbnail of an image focused on a face&quot;. That's next-level awesome. Here's a practical example.\nThis URL points to an original image (when you sign up with Cloudinary, they seed your media library with a bunch of samples:\nhttps://res.cloudinary.com/raymondcamden/image/upload/v1666103328/samples/animals/cat.jpg\nIt's a large image so I won't render it as an image here, but you can copy the URL and view it in another tab. To apply a scale to it, you add in information in the URL before the vXXXXX part, like so (note - I've added a space to the following URLs so they wrap a bit nicer in the post):\nhttps://res.cloudinary.com/raymondcamden/image/upload/ c_fit,w_200,h_200/v1666103328/samples/animals/cat.jpg\nIn this case, it's scaling to fit within a box 200x200. It's a scale, so the aspect ratio is kept. Here's the image:\n\nAlong with size-based transformations, they have a bunch of artistic ones as well. Given the previous URL, I can add sepia by using e_sepia:\nhttps://res.cloudinary.com/raymondcamden/image/upload/ c_fit,w_200,h_200,e_sepia/v1666103328/samples/animals/cat.jpg\n\nFinally, it's also easy to apply watermark images and text on an image. Here, I've added text with styling information:\nhttps://res.cloudinary.com/raymondcamden/image/upload/ co_rgb:FF0000,l_text:Arial_120:Cat/fl_layer_apply/c_fit,w_400,h_400/v1666103328/samples/animals/cat.jpg\n\nIf you want to play with these images (and why not, that cat is adorable), feel free to run this CodePen and play with the URLs yourself:\n\n  See the Pen \n  Cloudinary by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAll in all, fairly simple, and I only scratched the surface, but I figured at this point I knew enough to build a simple Eleventy demo. Now before I start, do note that there is both a Netlify Cloudinary plugin as well as an Eleventy Cloudinary plugin. I wanted to try things out myself so I didn't use any of them. For my demo I wanted to accomplish the following:\n\nUse a directory of images as a source - ensuring that they all exist in my Cloudinary media library\nGenerate Eleventy data for each image\nInclude a URL for a thumbnail\nInclude a URL for a version of the image that's sized reasonably for the web and has a 'copyright' notice watermark.\n\nHere's how I built it.\nI created a new Eleventy site, and in there, I created a folder called photos that included a bit over twenty different photos from my own personal collection.\n\n\n\nNext, I installed the Node.js SDK for Cloudinary: npm i cloudinary. Once installed, I created a data file named photos.js. I began by configuring the Cloudinary object with my credentials:\n\nNext, I specified my input directory:\n\nThen I defined my data to be returned to Eleventy:\n\nFrom the top, I specify a set of options for uploading to Cloudinary, the important bit is the overwrite flag which should mean (more on that later) that it won't keep uploading images that were already stored. I loop over the images in my directory, and for each, run the upload method on them. I return an array of objects that consist of the ID of the image (a unique identifier) and the thumb and web versions of the URLs.\nFor the functions that generate my thumb and web versions, I initially began writing code to manipulate the string. I figured that would be easy enough. But when I looked deeper into the Node SDK, I saw all of that was baked in! So for example, here's how I get the thumbnail URL:\n\nAnd here's how I get the web version that includes copyright text:\n\nThat second function is probably a bit more verbose than one string replace call, but it's a heck of a lot more readable.\nIn use, I had 2 Eleventy templates. First, the list of thumbnails:\n\nMy Photos page makes use of pagination to create one HTML page per photo:\n\nAll in all, this took me maybe thirty minutes to write, not much time at all, and you can see the final result here: https://cloudinarytest1.netlify.app/\nNote that the copyright watermark is rather small, although one could argue that makes it less obtrusive. Obviousl",
		"tags":[
	        
            "javascript",
            
            "eleventy",
            
            "cloudinary"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "First Experience Building with Eleventy's WebC Plugin",
		"date":"Sun Oct 16 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1665943200,
		"url":"https://www.raymondcamden.com/2022/10/16/first-experience-building-with-eleventys-webc-plugin",
		"content":"A few weeks ago, Zach Leatherman began discussing his plans to add web component support to Eleventy. Starting with his announcement post, Adding Components to Eleventy with WebC, developers can now start working with a plugin, WebC, to test out this new support. The plugin docs have a great explainer as to why this support is being added, but to me it mainly boils down - running our web components during server build and not on the client-side with JavaScript. Now, so far, my own experiments with web components have been fairly simple, small things, so I'm not too concerned about the client-side impact, but heck, if we can ship no JavaScript, that's better than a small amount.\nI will say that while the WebC docs are fairly extensive, I am struggling to wrap my head around it completely now. To be honest, I struggled with Eleventy Serverless too, so I figure I just need to build out a few demos and I think I I'll grok it better. As always, I blog what I learn so I hope this entry (and any further ones) helps others.\nI don't want to repeat the docs so I won't walk you through installation and the like, that's all fairly simple, but I do want to point out a few things that caused me trouble at first.\nBy default, when you add WebC to an Eleventy project, it does not scan for and automatically add in web components. This is covered but (imo) rather late in the docs. When I'm learning something I absolutely do not read to the of the page. As soon as I feel like I can play, I do, and if an important detail is a bit late in the doc, I typically end up screwing something up. ;)\nYou can add support for a specific or glob of web components three ways, but for now I'll focus on two. You can either define a glob of web components in front matter (at the template or directory level), or as a configuration option. While the first code sample shows the default behavior:\n\nYou will most likely want to start off by having Eleventy automatically scan a folder of web components like so:\n\nBy the way, the exact folder doesn't really matter. I used the above based on examples from the docs, but I think going forward I'll use something like _components to match the style used by data and include files in Eleventy.\nThe next thing that caused me trouble was creating a web component that was dynamic based on attributes. I think my issue was that I was thinking that Liquid directives and tags would be supported, but that's not the case. You use a new file type, .webc, and while it has support for binding attributes via a :name, it isn't really a template language like Liquid. So for example, I couldn't do basic things like conditionals. Here's an example of the simple level  you can do with a .webc file, in this case hello-world.webc:\n\nIn this case, I'm binding the alt attribute (which isn't really an attribute of span afaik) as well as defining the inner HTML of a block by using the attribute passed via name, so for example:\n\nThis all outputs to:\n\nBut like I said, I wanted more flexibility in my component, what I really wanted was Liquid. There is a way to do that, just wrap your component with:\n\nI think for now my plan is to just use this as a basis for my components, unless I know for sure I don't need conditions and looping.\nAs I said - I'm pretty new to this so I expect how I do things will change as I learn.\nThat's pretty much the main things that causes me problems so far, so let's look at a demo!\nA Placeholder WebC Example\nBack a few days ago, I blogged about a placeholder web component that made use of dynamic SVG to render simple placeholder images. It was pretty simple, but actually practical I think. How would I convert that code to a server-side rendered WebC component? I basically just switched from JavaScript to Liquid:\n\nFor comparison's sake, here's the JavaScript one:\n\nI have to say - as much as I don't mind the JavaScript interface for working with web components (and I still have a lot of exploring to do), that Liquid/HTML version is a heck of a lot simpler! Basically - check and default my height and width and just output SVG, with a conditional block inside.\nI added this to my components directory, and then in an Eleventy index.webc file, used it like so:\n\nI did NOT include a JavaScript file, I don't need to. The output in _site looks like so:\n\nYou'll notice a lot of white space in the output above. While you can fix that with a HTML post processor (see the super simple Eleventy sample code example), it's something I want to see if I can fix myself later. Most importantly though - not a lick of JavaScript!\nWant to see it yourself? I decided to give it a try on Glitch, a service I discovered a month or so ago. You can browse a live, working Eleventy site on their service here: https://glitch.com/edit/#!/placeholder-demo. You can view the running version here: https://placeholder-demo.glitch.me/ Give it a shot and let me know what you think.\nEdit on 10/17 at 1:13PM: Just a quick note. I got an email from Rian Murnen",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "eleventy"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with Slots and Web Components",
		"date":"Thu Oct 13 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1665684000,
		"url":"https://www.raymondcamden.com/2022/10/13/working-with-slots-and-web-components",
		"content":"Before I begin a warning. As I've made clear over the last few posts on web components, I'm still very much in the &quot;learn and try things out&quot; phase of my education with web components. This post in particular is one I'm a bit unsure of, but as I learn, I like to share, and as always, I'll post back later with corrections, updates, and further learnings. Alright, with that out of the way, let's get started.\nI've been raving about MDN Web Docs for a very long time. It is absolutely, unequivocally, the best source for learning about the web platform. That being said, this was one of those very rare cases where their documentation just really didn't work for me, specifically the Using templates and slots section of their otherwise excellent Web Components reference. It's most likely my fault, and I'm fine with that, but I'm also going to see if I can contribute back some edits that may make things easier for me. It was a real struggle, but I think I've got a basic idea now of how slots work with web components.\nFirst - the Why?\nBefore we begin talking about slots, let's talk about what problems they can solve. As you may know, web components support the passing of arguments to help define their behavior, just like baked-in HTML elements. You can see an example of this in my blog post where I build a placeholder component that accepts arguments for width, height, and text. In that case, arguments work perfectly:\n\nOk, but what about arguments that may be longer, and more complex? Consider the typical &quot;Card&quot; UI component. Here's an example from the Vuetify library:\n\n\n\nLooking at that component, you can see it's got multiple different parts. A header image, header text, main body text, a row of buttons (well, one button) at the bottom. All of this could be passed to a web component tag, but it's going to get a bit messy. So example:\n\nSlots can help here. Instead of passing a lot of arguments, possibly ones with complex values, we can use elements inside our web component with defined slots:\n\nIn the above example, I'm still using arguments for the simpler stuff, but the more complex values are inline, with a slot argument specifying what their role is.\nObviously, the use of slots versus arguments will change on a case-by-case basis, but flexibility is always a good thing!\nSecond - ok but how?\nThis is where I ran into the most issues with the MDN documentation. The critical part missing for me was how the web component used the slots. Turns out it's rather easy. A web component can use a slot by just using the slot element. Here's an example:\n\nIn this case, I'm looking for a slot named testslot and rendering it inside a p element. Even cooler, you can provide default text inside that only shows up with the slot isn't used. Here's a component that makes use of this.\n\nYou'll notice I'm not using a bunch of createElement calls, just one to make a div and then I use a template string. This is a much simpler approach and I'll probably use this more going forward. Thanks to Nolan Erck for the inspiration here, I saw something similar in his web component presentation.\nOk, given the above component, I can then do:\n\nWhich returns the following:\n\n\n\nYou can play with this below:\n\n  See the Pen \n  Slot 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne interesting side effect is that as soon as a web component makes use of text, any content inside your component instance will not be rendered. So if I do:\n\nIt won't render. Why? Anything not in a slot is considered the unnamed slot. I can add it to my web component DOM:\n\nAnd then it starts working. You can see this version here:\n\n  See the Pen \n  Slot 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThird - Time for a Useless Demo!\nOk, so once again, I built something fun. It's fragile, not really something I'd use in production, but was fun as a &quot;can I build this?&quot; exercise. Check out the following:\n\nCan you guess what it does? I'm calling a component that points to a remote API that returns an array of records. My text inside, which will become the default slot, is a template where each item in brackets will be replaced with a real value. Here's the web component:\n\nIn theory, I could have used the technique I blogged about using inner text of a component, but in this case I wanted to have a formal slot so my component has that... and just that.\nIn connectedCallback, I do a fetch against the URL, find my template and then begin iterating over the remote data. For each record, I check every key inside the object, and see if I have a corresponding value in the array. If so, it's replaced. I could get fancy and actually dynamically load in Handlebars instead, but this was just for fun. Here's a CodePen showing this in action. The template shows up while loading, and I believe I could fix that, but I had trouble getting that working well. It was easy to hide with CSS, but when I tried to display the result it failed. I know I could work around t",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with Custom Events and Web Components",
		"date":"Mon Oct 10 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1665424800,
		"url":"https://www.raymondcamden.com/2022/10/10/working-with-custom-events-and-web-components",
		"content":"A few days ago I shared a web component I built that wrapped Adobe's PDF Embed library. As I said then, my intent was to build it as a quick prototype as I learn about web components, but this morning I got to thinking about web components and events. The PDF Embed library has a large set of events you can listen to and respond to, so I was curious how I'd actually do that within my wrapper. Here's what I came up with.\nRound One\nI knew that working with events and PDF Embed is a bit complex, and while I hoped my web component could make it simpler, I decided to start with a completely arbitrary fake event. My web component has a loadPDF method that's run when the core library is loaded. I modified it to fire an event after two seconds:\n\nThe first half is from the previous version and the setTimeout is where the real work begins. There are two crucial things here.\nFirst, your custom event has to use the composed property. I found this on StackOverflow and you can read more about it on MDN.\nSecondly, and this applies to my particular web component and not web components in general, but notice I'm dispatching it from shadowRoot. In my blog post detailing my first version of the component, I mentioned specifically I didn't use shadowRoot because my div needed to be &quot;top-level&quot; in order for the PDF Embed library to work. I'm still doing that (manipulating the root DOM to add my div), but I know use the shadowRoot to be my broadcaster.\nWith this in play, I could then listen for it in my HTML. First, I assigned an ID to the component:\n\nIf you remember, I had to generate unique IDs for my divs when generating the PDF Viewer. I'm still doing that, and I still think it's a good solution. This ID will apply to the actual component, not the DIVs it spits out.\nThen in JavaScript I can listen for it:\n\nThis worked well, so I then began working on the &quot;real&quot; version.\nRound Two\nAs I alluded to above, PDF Embed has an event system but it's a bit tricky to use. For example, we have three 'groups' of events. That's not terribly weird, but to listen for a particular event, you have to enable the right group and then, if you care, specify the precise event you want to listen to. All in all, I get the idea behind it - you don't need to listen to everything, but from a practical viewpoint I always found it a bit weird to use.\nSo let's fix it!\nFirst, in loadPDF, I decided to listen to everything, like so:\n\nIn the code block above, the three 'enable' settings are referring back to the event groups I mentioned before. Our API says that if you don't ask for specific events, all are fired, so the net result of the above is - for every single event possible, fire it and run my handler. Now let's fill out that handler:\n\nIn the above code, I take the original event, and recreate it as a new CustomEvent, ensuring I set composed to true and passing on the original data as the detail.\nThis then allows for:\n\nHere's how it looks in devtools. It's in dark mode so you know it's legit:\n\n\n\nYou can find the latest version here (as I mentioned earlier, I'm slowly migrating my web components to a new repo) - https://github.com/cfjedimaster/webcomponents/tree/main/pdfembed\n",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Oct 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1665338400,
		"url":"https://www.raymondcamden.com/2022/10/09/links-for-you",
		"content":"Welcome to another Sunday post of links. I don't know about you, but I'm enjoying gathering these and sharing them, so I hope yall find value in them as well. Let's get started!\nJasper, Static Site Generator for ColdFusion\nFor readers who may be new here, I was heavily involved with Adobe ColdFusion for a good twenty years or so. I don't really do much with it now, but I just got back from speaking at the Adobe ColdFusion Summit and it was a great opportunity to meet with a great community. My buddy Robert Zehnder has been working on a ColdFusion Static Site Generator for a few months now and it's a great project. He's been blogging about it and you can browse the repository as well. He recently added CommandBox support which makes installation simple. Check it out!\nWebC, Web Components For Eleventy\nI've been blogging about web components a lot lately and I'm pretty excited about the technology. I was thrilled to hear that Eleventy's creator Zach Leatherman has a new plugin for Eleventy, WebC. WebC lets you use web components in your Eleventy projects and render them completely server-side. He's got a good introduction article here: Adding Components to Eleventy with WebC. I plan on digging into this very soon.\nHere's a quick video introduction:\n\nMarkdown Guide\nTypically I try to have these links focus on other people and projects, but I also like to highlight writing I do off of this blog. A good month or so, I wrote an introduction to Markdown for Verpex: Markdown Guide. I've been using Markdown for quite some time now but I'm still learning things here and there. Markdown has that great combination of power and simplicity that I love.\nThat's all for today - have a great week!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Placeholder Web Component with No External Dependencies",
		"date":"Thu Oct 06 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1665079200,
		"url":"https://www.raymondcamden.com/2022/10/06/building-a-placeholder-web-component-with-no-external-dependencies",
		"content":"As my readers can tell, I'm on something of a web component kick, and while I'm enjoying building silly examples, today I wanted to share one I thought might actually be useful - a placeholder component that doesn't use any external services.\nAs you probably know, there are about five million placeholder services out there that let you simply craft a URL and generate an image of specific size and other attributes. For example, placeholder.com lets you use a URL like so, https://via.placeholder.com/350x150, which generates:\n\nOf course, that's boring, what you really want to use is placekitten.com - given https://placekitten.com/600/300 you get:\n\nCool, but each of these services requires using an external service. While there's nothing necessarily wrong with that, I thought it would be interesting to build a web component that created the placeholder via SVG. This would remove the external dependency and improve the performance of the page. Obviously, placeholders aren't (typically) used in production so the performance of such a thing may not be that big of a deal, but I thought it would be fun to build. Here's what I came up with.\nFirst, I'm not terribly familiar with SVG, but I found you could create a simple rectangle of a particular size like so:\n\nIn theory, all I need for my web component is the ability to spit out those tags in the shadow DOM. Here's the first version of what I built:\n\nFor the most part, I think this is pretty vanilla web component stuff, except for the use of createElementNS. Per the MDN docs, this creates an element with a specific namespace, which our SVG tags require. My web component took three attributes: width, height, and bgcolor, each of which has a basic default. Here's it in use:\n\nI'd share a screenshot but it's literally three boxes so you'll have to wait for the end. ;)\nI then thought it would be nice to support custom text. Placeholder.com does this and it's a useful way to mark what a placeholder is - well, taking place of so to speak.\nIn SVG land, this tag will create a horizontally and vertically centered text:\n\nTo support this, I added a new condition to the web component:\n\nWhich then lets me do:\n\nNice! So, since I've been playing with web components a lot, I decided to start using a new repository: https://github.com/cfjedimaster/webcomponents. You can find the source code for this demo here: https://github.com/cfjedimaster/webcomponents/tree/main/placeholder_svg. Later this week I'll start moving over some of my earlier demos so I have them in one nice place. Finally, if you want to play with this yourself, use the CodePen below:\n\n  See the Pen \n  Placeholder (SVG) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nLet me know what you think. I believe this one may actually be - dare I say it - useful?\nPhoto by Kelly Sikkema on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Web Component Experiment - Manipulating Inner Text",
		"date":"Tue Oct 04 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1664906400,
		"url":"https://www.raymondcamden.com/2022/10/04/web-component-experiment-manipulating-inner-text",
		"content":"I've been thinking a lot more about web components lately, and this especially got a kick up after seeing a great presentation on the topic by Nolan Erck. Yesterday I was curious if web components could access, and manipulate, content between the opening and closing tag. So for example, consider this code:\n\nWhat I'd like to do is read the text inside (Here is text about a cat.) and manipulate it. I'm not talking about the slot feature, which looks like a good feature, but a simpler approach where I can just grab the text between the paired tags.\nI was able to figure it out, and I'm going to share my findings below, but please remember that I'm new at this and maybe what I'm proposing is a bad idea. To be 100% clear, my third and final example is a very, very bad idea and you should not copy it. Seriously.\nAttempt One\nFor my first example, I checked to see if I could read and edit the textContent property. I created a quick web component named inner-text, and tried this code:\n\nBasically - read textContent, reverse it by using the convert to an array trick, and return it to the div I'm adding in the shadow DOM. I tested like so:\n\nAnd voila, it worked:\n\n  See the Pen \n  InnerText by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAttempt Two\nSeeing how simple it was, I then decided to build something real, and maybe a tiny bit practical. How about a Markdown Renderer?\n\nIn the code above, I load in the marked JavaScript Markdown library which lets me simply use the parse command on text to convert it to HTML. Using input like so:\n\nI get valid HTML out. Here's it in action:\n\n  See the Pen \n  Markdown by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nOk, stop reading now, honest.\nAttempt Three\nStill reading? Why, I warned you! So, about five years ago, I blogged a completely dumb experiment I did on OpenWhisk, serverless BASIC support: Serverless Basic. I've got a fond memories of BASIC, specifically AppleSoft Basic, as it was my first programming language. Wouldn't it be cool, and completely useless, to build a web component that allowed for this?\n\nOr even:\n\nYes, of course it's cool! I used the same library I used in the past, jsbasic, and built it into the following web component:\n\nThis one's a bit more complex. First, I load in the library via a script tag added to the DOM, and then use an interval to see when the global object, basic, exists. Updating the content was a bit more complex. Since my BASIC parsing was finished after the component was loaded and rendered, I grab a div via querySelector and update it that way.\nIn case it isn't obvious, this whole thing is a really bad idea. But it works. To be clear, it works if you stick to non-interactive BASIC, but heck, even the infamous goto and gosub work. You can play with it here:\nUnfortunately I can't show this on CodePen as it needs access to the BASIC library in a CORS friendly manner, but you can test the running app here: https://cfjedimaster.github.io/webdemos/webcomponents/basic.html\nAll of the code shown here, and my other web components, may be be found here: https://github.com/cfjedimaster/webdemos/tree/master/webcomponents\nPhoto by Ilja Tulit on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A PDF Embed Web Component",
		"date":"Sun Oct 02 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1664733600,
		"url":"https://www.raymondcamden.com/2022/10/02/a-pdf-embed-web-component",
		"content":"I'm still pretty new to web components (see my post back in May, My First Web Component), but I've been playing with them, and other libraries that wrap them, off and on. Recently I decided to revisit something I had tried at the time I first played with the technology, a wrapper for Adobe's PDF Embed library.\nAt the time, I couldn't get my code working because the PDF Embed library requires the ID value of a div to use when rendering the PDF. When using a web component, you typically work with the Shadow DOM, a DOM tree &quot;hidden&quot; inside the web component. While it's possible to make the DOM tree accessible to outside code, the PDF Embed library still couldn't use it. It requires a string for the ID and it has to be available via 'regular' means, i.e. document.querySelector('#TheIDValHere'). I've already filed an ER with our engineers to add support for passing in an HTML element instead, but in the meantime, I just moved on.\nUntil today. When randomly, this popped back into my head, and I wondered - can a web component add stuff to the wrapping DOM around it? Turns out it certainly can. It's frowned upon, and you should not do it typically, but I think this is a case where it makes sense. In my case, I decided to simply append the DIV element I need immediately after my web component.\nLet me share the code, and then I'll show it in action. Here's the entire component:\n\nI begin by importing the uuid package. This is something I've used in Node quite a bit, but it's the first time I've used it in a client-side application. I'm using this to generate a unique ID for my DIVs. This way if a person makes 2, or more, PDF Embed instances on their web page, the div will always have a unique ID. I could have made it an tag attribute, but why make the user do work they really don't need to? (Although literally as I write this, I do see why allowing the user to set it could be good - for styling purposes for example.)\nGoing on - my component requires the URL of the PDF and the client key (PDF Embed is free, but requires a domain-locked key). I then have optional arguments for width, height, and embed mode. (See our docs for examples of these different modes.)\nFinally I have the important part - where I drop the div in the parent:\n\nAgain, this is not what you would normally do, but it worked just fine for me. Continuing on, next look at connectedCallback. This will be fired when the component is loaded and is how I handle loading in the external library. As the long-winded comment says, I did try to load this only once, but in my testing of two embeds right next to each other, my check did not work. It was expected, and as the comment suggests, I believe the optimization will work correctly if embeds are loaded later via JavaScript.\nGoing back up, loadPDF is vanilla PDF Embed code to render the document in the DIV. Oh, our library also requires a &quot;name&quot; value for PDFs. I think that's silly so I just create it myself based on the URL.\nOnce this is included in your web project, I freaking love how easy it is to use:\n\nOr with height, width, and mode:\n\nIf you want to see the component or possibly even make some edits, you can find it here:\nhttps://github.com/cfjedimaster/webdemos/blob/master/webcomponents/pdfembed.js\nEdit on 10/10/2022: New location: https://github.com/cfjedimaster/webcomponents/tree/main/pdfembed\nI also put it up on CodePen here:\n\n  See the Pen \n  PDF Embed WC by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs always, let me know what you think, and remember I'm still new at this, so be gentle. ;)\n",
		"tags":[
	        
            "javascript",
            
            "web components",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using the Adobe PDF Embed API with Vue 3",
		"date":"Fri Sep 30 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1664560800,
		"url":"https://www.raymondcamden.com/2022/09/30/using-the-adobe-pdf-embed-api-with-vue-3",
		"content":"A long time ago, ok, February of last year, I posted about using the Adobe PDF Embed library with Vue.js: Using the PDF Embed API with Vue.js. The main issue with our Embed library and libraries like Vue is a &quot;chicken and egg&quot; issue. Basically, our docs tell you to add an event listener for our library to load, but it's possible that the library has loaded before you add the event listener.\nIn my previous post, I talked about how you can still use the event listener, but also look for window.AdobeDC to see if it's loaded. This method would apply to any framework wanting to make use of the library so it's a good tip in general.\nToday, a user on our forums posted that they were having issues with the library and Vue 3, although it was something completely different.\nFirst off - they didn't have the &quot;chicken/egg&quot; issue I described above. They were loading our library dynamically in mounted:\n\nI like this approach! Anyway, they had no problem creating an AdobeDCView object for their PDF. They used a watch on this.adobeApiPDFReady:\n\nSo far so good. They then used a button to trigger displaying the PDF:\n\nAnd here's the method:\n\nThis is all boilerplate per our docs, just slightly modified for Vue, for example, this.adobeDCView to represent Vue's data. However when previewFile was called, this error was returned:\n\n\n\nHonestly I was completely at a loss as to why this error was being thrown. Nothing seemed amiss. I ensured that the div element was being found correctly. I ensured that the library was really loaded. Nothing made sense.\nThen - purely on a whim - I changed adobeDCView from a Vue value (i.e. in the this scope for the component) to window.adobeDCView. It started working!\nI then tried this:\n\nAnd inside my code calling previewFile, did:\n\nAnd saw this:\n\n\n\nNote how the Vue object is a Proxy, which makes sense - Vue's data is reactive and all. Honestly I'm not sure why I didn't have this issue before, but certainly Vue 3 is a significant update from Vue 2. So, I didn't want to use a window object as it felt... wrong. I did a bit of searching and found this StackOverflow answer: How to make a template variable non-reactive in Vue which led to a note in the official docs on the use of Object.freeze().\nI literally just changed this:\n\nAnd it worked! Here's an embedded CodeSandbox showing it in action:\n<iframe src=\"https://codesandbox.io/embed/adobe-vue-embed-api-forked-l1quyk?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"adobe-vue-embed-api (forked)\"\n     allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n     sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n   >\nAs always, let me know if this doesn't work for you!\n",
		"tags":[
	        
            "adobe",
            
            "pdf services",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Converting Markdown Code Blocks to Gists",
		"date":"Wed Sep 28 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1664388000,
		"url":"https://www.raymondcamden.com/2022/09/28/converting-markdown-code-blocks-to-gists",
		"content":"File this under the &quot;I have no freaking idea who this will be useful for&quot; bucket, but I wrote up a script to help me with a problem concerning authoring on Medium and figured I'd share it. It also allowed me to play more with GitHub's APIs and that was definitely useful for me, so hopefully it will be useful for you. Why Medium? I'm not a fan of the platform myself, but at work, we use it for our developer blog so I have to use it. In general, it's an OK platform, but one thing it doesn't do well is code blocks.\nSpecifically, it doesn't have support for any color coding of your code samples. For very short blocks of code that's ok, but for any reasonably sized code snippet, the lack of syntax coloring really begins to make it difficult to parse.\nThe &quot;standard&quot; solution is to make a GitHub gist, get the URL of the gist, paste it into Medium, hit enter, and it will replace the URL with an embed. That works but is really annoying. In my last post, I had 16 of these and decided I had had enough and it was time to look at an automation tool. Here's what I built.\nThe First Solution\nMy initial attempt (which, technically worked perfectly, until it didn't, I'll explain why later) used this process:\n\nFind all the code blocks in a Markdown file\nFor each, attempt to identify the type based on any characters after the ```.\nFor each, get the beginning and end character positions\nFor all the matches, create a new Gist using a filename based on the type of code block\nFor the newly created Gist, create an embed string and replace the text in Markdown\n\nI was initially going to use GitHub's REST APIs but then discovered octokit.js, a utility library that makes it incredibly easy to use.\nAlso, note that the code I built requires a personal access token. You can generate those quickly via GitHub's settings. When it comes time to select the scopes and permissions, just select gist as that's all the code demonstrated here needs.\nAlright, let's get started. First, my script takes two inputs - the location of the input markdown and the location of where it should be saved:\n\nI then read in the input file and request the code blocks:\n\nThe getCodeBlocks function looks for code block markers (three backticks). It attempts to find a language type and it gets the range for each one:\n\nNote that when a type is not defined, I set it to &quot;plain&quot;.\nOnce I have my result, I can check to see if any were found:\n\nNow I need to process them. I'm going to go from the last block to the first because I'm going to be modifying the file contents in a string and if I did it first to last, my ranges would be off.\n\nLet's first look at createGist:\n\nThe API to create a Gist requires a filename. I sniff the type and use generic names based on the type. As the comments say, I could make this a bit more flexible.\nI then remove the backticks, and call octokit. Notice how simple that part is - one quick API call. The result is a Gist object that includes an html_url value I use in toGistEmbed:\n\nIt's possibly a bit silly to have a function for such a simpler operation, but I figured what the heck. I then write out the file. Here's the full script, as a Gist, because the backticks ended up messing my blog's processing a bit:\n\nSo... this worked well, but I ran into a problem. If I copied and pasted the result into Medium, it automatically escaped the script tags and treated it as code to show. On to the second solution!\nThe Second Solution\nTo get this working right in Medium, I made one incredibly small change:\n\nYeah, the function does nothing now, which makes it even more silly, but I ended up with a result that had my Gist URLs in the text. When I pasted this into Medium, I then went to each one, put my cursor at the end, and hit enter. Still a bit of manual work, but far easier than creating the Gists by hand, one by one.\nAs always, let me know what you think and if you find this useful, give me a shout-out!\nPhoto by David Travis on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Sep 25 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1664128800,
		"url":"https://www.raymondcamden.com/2022/09/25/links-for-you",
		"content":"Happy Almost October! It's Fall here, and by Fall, I mean still incredibly hot and humid and reminding me why I can't wait to move out of this state (for more reasons than the weather of course). Here's a few links for you to enjoy. Have a great week!\nPlaying with Data in Jupyter Notebooks with VS Code\nFirst up is a great presentation by Dr. Sarah Kaiser about Jupyter Notebooks and VS Code. I've only recently discovered Jupyter Notebooks myself and find them absolutely incredible.\n\nAfter you watch this video, you can read about my experience with them and PDF Extract (Working with PDF Extract and Jupyter Notebooks).\nWhat’s New With Forms in 2022?\nNext up is an excellent article by Ollie Williams. As you can tell by the title, it's a look at recent changes to forms on the web. This has been something I've been passionate about since the days of HTML5 and this article taught me a few things as well. Read it here: &quot;What’s New With Forms in 2022?&quot;\nMonitor Events and Function Calls via Console\nA short tip for something probably incredibly useful to those of using developer tools (and if you aren't, why not?) - in this tutorial, David Walsh covers how to monitor events and function invocations in your console. I've been using dev tools for what feels like forever and I didn't know about this one. Read about it here: Monitor Events and Function Calls via Console\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "JavaScript Quickie - Add Days But Prefer Business Days",
		"date":"Fri Sep 23 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1663956000,
		"url":"https://www.raymondcamden.com/2022/09/23/javascript-quickie-add-days-but-prefer-business-days",
		"content":"Sometimes when thinking about something I want to post, a particular part of it grabs my attention and I decide to rip it out and write something focused on just that one aspect. That's what happened today when I was thinking about a particular way of doing date math and I wanted to see if it would make sense.\nGiven a particular date, I want to add a certain number of days to it (or weeks, or months), but I want the result to &quot;prefer&quot; business days. This is not the same as adding business days. So for example, 20 days from now is pretty different from 20 business days from now. Rather, I want the main difference to be close to my desired duration, but just &quot;adjusted&quot; to fall on a business day.\nTo test out my theory that this was possible, I decided to use the date-fns date library.\n\n\n\ndate-fns has a wealth of useful date utilities, including a couple I'd need to implement my logic:\n\nadd - lets you quickly add durations to dates\nisWeekend - returns true if a date falls on a weekends\nnextMonday - fires up a Web3 crypto miner via Docker and Lambda to generate machine-driven art. No, actually it just returns the next Monday after a date\n\nI began with a simple function that takes an input date and a duration in days:\n\nPretty straightforward I think. I wrote a quick test that started with today and added 0 to 14 days. Here's how the results looked:\nFri 2022-09-23, plus 0 days, using addDayPreferBusinessDay: Fri 2022-09-23\nFri 2022-09-23, plus 1 days, using addDayPreferBusinessDay: Mon 2022-09-26\nFri 2022-09-23, plus 2 days, using addDayPreferBusinessDay: Mon 2022-09-26\nFri 2022-09-23, plus 3 days, using addDayPreferBusinessDay: Mon 2022-09-26\nFri 2022-09-23, plus 4 days, using addDayPreferBusinessDay: Tue 2022-09-27\nFri 2022-09-23, plus 5 days, using addDayPreferBusinessDay: Wed 2022-09-28\nFri 2022-09-23, plus 6 days, using addDayPreferBusinessDay: Thu 2022-09-29\nFri 2022-09-23, plus 7 days, using addDayPreferBusinessDay: Fri 2022-09-30\nFri 2022-09-23, plus 8 days, using addDayPreferBusinessDay: Mon 2022-10-03\nFri 2022-09-23, plus 9 days, using addDayPreferBusinessDay: Mon 2022-10-03\nFri 2022-09-23, plus 10 days, using addDayPreferBusinessDay: Mon 2022-10-03\nFri 2022-09-23, plus 11 days, using addDayPreferBusinessDay: Tue 2022-10-04\nFri 2022-09-23, plus 12 days, using addDayPreferBusinessDay: Wed 2022-10-05\nFri 2022-09-23, plus 13 days, using addDayPreferBusinessDay: Thu 2022-10-06\nFri 2022-09-23, plus 14 days, using addDayPreferBusinessDay: Fri 2022-10-07\n\nGiven that today is Friday, you can see how the next few days all bump to Monday, and do so again next week.\nFor the heck of it, I decided to compare this to strictly adding business days. date-fns has a function for this, ]addBusinessDays](https://date-fns.org/v2.29.3/docs/addBusinessDays). Here's how those results look:\nFri 2022-09-23, plus 0 days, using addBusinessDays: Fri 2022-09-23\nFri 2022-09-23, plus 1 days, using addBusinessDays: Mon 2022-09-26\nFri 2022-09-23, plus 2 days, using addBusinessDays: Tue 2022-09-27\nFri 2022-09-23, plus 3 days, using addBusinessDays: Wed 2022-09-28\nFri 2022-09-23, plus 4 days, using addBusinessDays: Thu 2022-09-29\nFri 2022-09-23, plus 5 days, using addBusinessDays: Fri 2022-09-30\nFri 2022-09-23, plus 6 days, using addBusinessDays: Mon 2022-10-03\nFri 2022-09-23, plus 7 days, using addBusinessDays: Tue 2022-10-04\nFri 2022-09-23, plus 8 days, using addBusinessDays: Wed 2022-10-05\nFri 2022-09-23, plus 9 days, using addBusinessDays: Thu 2022-10-06\nFri 2022-09-23, plus 10 days, using addBusinessDays: Fri 2022-10-07\nFri 2022-09-23, plus 11 days, using addBusinessDays: Mon 2022-10-10\nFri 2022-09-23, plus 12 days, using addBusinessDays: Tue 2022-10-11\nFri 2022-09-23, plus 13 days, using addBusinessDays: Wed 2022-10-12\nFri 2022-09-23, plus 14 days, using addBusinessDays: Thu 2022-10-13\n\nI think you can really see, especially at the end, how things begin to diverge. Is my way &quot;better&quot;? For what I want to use it for, I think it is. Obviously, time will tell, and like always, I'd love to know if people think this is useful. Note I used another useful date-fns utility to render my dates, format\nYou can see the results yourself using the CodePen below:\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Web View of a Public Google Drive Folder",
		"date":"Sat Sep 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1663437600,
		"url":"https://www.raymondcamden.com/2022/09/17/building-a-web-view-of-a-public-google-drive-folder",
		"content":"I'm working on a project to help with local initiatives and as part of that effort, I needed to look into creating a nice way to display, make available, etc., files stored in Google Drive. Google Drive lets you make a folder public, and to be honest, the interface isn't too hard to use. I've got a folder you can open yourself at https://drive.google.com/drive/folders/1FYLaoscxWBV_BU5sFouf7XCrv7cKktBY?usp=sharing. Here's how it looks if you don't want to click.\n\n\n\nIt's a pretty simple interface. You get nice thumbnails, you can view pretty much any kind of file, and you can download it. It's good, but we can do better, right? I decided to look into using the Google Drive API via Pipedream if I could get a data-centric version of the files. I could then render them on my website and have more control over the experience. I could do filtering, sorting, provide context, and so forth, while still having a dynamic list of files. I've got an initial version of this I'm sharing today, but have a follow-up planned with a few more advanced features. Ok, let's get started.\nStep One - Make/Decide on the Folder\nYou're going to need a folder of some sort to test with. I made a new folder in Google Drive, went to the Sharing options, and enabled public access to view items in the folder. That lets anyone see and download files but doesn't let them have control over it. While doing so, I made note of the URL,\nhttps://drive.google.com/drive/u/0/folders/1FYLaoscxWBV_BU5sFouf7XCrv7cKktBY\n\nSpecifically the value after folders, which I assumed to be a unique folder identifier.\nStep Two - Create the Pipedream workflow\nIn Pipedream, I built a new workflow using the HTTP request trigger. Don't forget Pipedream recently added support to automatically filter out requests to favicon.ico and you should absolutely turn that on.\nI then went to add an action to get my Google Drive folders. I knew that Pipedream had built-in actions for working with Google Drive, and quite a few of them:\n\n\n\nUnfortunately, and a bit odd, they don't yet have an action for &quot;List Files in Folder&quot;. I raised this in their Slack (track the issue here!) and there's a good chance that by the time you read this it will be supported, but luckily, Pipedream made it incredibly easy to do. Just select one of the first two options, &quot;Use any Google Drive with...&quot;. I chose Node.js and you get code like so:\n\nAfter you authenticate in the step with your account, you literally just need to change url. That's it! Most times you need to add some query parameters and such and that can be additional work, but 100% of the authentication is handled by Pipedream. I've said it before and I'll say it again - I freaking love that the boring, hard-to-do part is done and I can just focus on building.\nI began by looking at the Files/List API. I needed to figure out two things here.\nFirst, how do we filter to a folder? This is done by using the syntax:\nq={FOLDERID} in parents\n\nI mentioned needing the folder ID from step and that's where it comes in. So my code then looked like so:\n\nAs a reminder, Pipedream supports dynamic props in actions, and I could move folderId outside of the code and make it a step parameter if I wanted. I was lazy this time and didn't bother.\nThis by itself was enough, but by default Google Drive returns maybe 3-4 fields per file. To add more, you can specify a list of fields. I used fields=* to see everything, and then decided to get:\n\nname\nmime type\nview, download, and thumbnail links\nsize\n\nHere's my code now with that update:\n\nTo be clear, all I did was change URL. It took more time for me to research the API than it did to actually write code, thanks to Pipedream.\nI added one more code step just to return my information:\n\nAnd that's it. I still can't share new Pipedream workflows, but you can see the endpoint yourself here: https://eoemdgkqfhrtf27.m.pipedream.net/\nStep Three - Build the Front End with Alpine.js\nFor the front end, I decided on a simple interface built with Alpine.js. For this first iteration, I'm going to get the files and render them in a table. Let's start with the HTML:\n\nI have a table showing name, type, and size, with a fourth column for viewing and downloading. Viewing is done on Google Drive, so I use a new tab to view it, whereas download will just plain work. For my JavaScript, I kept it rather simple - on load, hit the endpoint.\n\nI really should have some error handling here, and I definitely should have a loading indicator, but here's the first draft in action:\n\n  See the Pen \n  Render Google Drive Files by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nFor my second draft, and with the mindset of having non-technical people use this, I made three changes. First, I changed the size to a slightly more human-readable form. I found an excellent little function on StackOverflow named humanFileSize and updated the table cell to use it:\n\nNext, I looked into changing the mime type. There were a few options out there but",
		"tags":[
	        
            "pipedream",
            
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Discover New Music with the Spotify API and Pipedream",
		"date":"Tue Sep 13 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1663092000,
		"url":"https://www.raymondcamden.com/2022/09/13/discover-new-music-with-the-spotify-api-and-pipedream",
		"content":"Frequent readers here will know I'm somewhat fascinated by randomness. As a few examples, I've built demos that rely on generated text: @TBSHoroscope and @MonsterConflict. I've also built demos that randomly select from an existing data set, including @RandomComicBook and @NPSBot. All of these accounts make me smile when I see them show up in my timeline, and they've been informative as well. The @RandomComicBook account has really surprised me with how much art styles have changed at Marvel over the decades as well as just how old some characters are.\nI'm a huge Spotify fan, and one of my favorite features is asking it to play one song and then seeing how Spotify riffs off it and plays similar music. This got me thinking about using Spotify's APIs to really riff by getting a completely random track. While I have genres I prefer, I love music in general and would be willing to give anything a shot once. With that in mind, I've built a Pipedream workflow that emails me one random track every morning.\nSo far I've gotten:\n\n\n\nI've heard of Slipknot before but have never actually listened to any of their music. Guess what - I don't like em! But I really did enjoy playing the track (and a few others afterward).\n\n\n\nI've heard of Pharrell, but not Lil Uzi Vert. Again, not my primary style, but I liked this one better than the Slipknot track.\n\n\n\nAnd in a completely different vein, a track with multiple composers. I've heard of Hans ZImmer and Ramin Djawadi before, but not the others. This was my favorite of the bunch.\nSo how was it built?\nGetting a Random Spotify Track\nSpotify has a rich developer ecosystem. I first covered it back in February (Testing out the new Pipedream to Get Trance Releases) and I was impressed by how easy it was to use. Unfortunately, there is no API to get a random track. However, I came across a great tutorial on how to fake it: Getting random tracks using the Spotify API.\nHis technique basically comes down to using random search strings and random offsets. His method worked, except that at the time of publication, Spotify allowed an offset of up to ten thousand. Currently, the max is one thousand instead. I decided to build my version on Pipedream and make use of Python as much as possible.\nI began with a step that generated a random search string. This is done by selecting a random letter (a-z, technically a-zA-Z) and then returning it with either a percent sign at the end, or in front and at the end. Here's the Python I used:\n\nNext, I selected an offset:\n\nBy the way, I have both of the above Python scripts in unique steps, nicely named (create_search_string and select_offeset). When working with Pipedream, I try to make each step as unique and atomic as possible. Pipedream doesn't care if I do two or more things in one step, but I feel like it's better architected this way.\nThe next step is to perform a search against Spotify. I added a new Spotify step and used Python to hit the API with my random search and offset:\n\nThe important bit is the params block where I access the earlier steps. The result of this is a 'page' of tracks based on the search, an array. Here's how that's rendered in Pipedream:\n\n\n\nAll we need to do now is get a random track from that step. While building this, Zalman Lew on the Pipedream Slack let me know there's actually a Pipedream step built in that lets you point to an array of data and have a random one returned. (You can find it in Helpers, named &quot;Random Item from List&quot;.) So while I could have done it in a few lines of Python, I went with the built-in step:\n\n\n\nAt this point, I've got a randomly selected track value I can use. How did I use it?\nSend the Email\nI decided to keep it simple and have Pipedream send me an email. You saw the screenshots above and yes, it could certainly look nicer. I first created a Python step to generate an HTML email string:\n\nSpotify's API returns a lot of information about the track, but I figured the title, artists, and album cover was enough. With my HTML string complete, I then just added a built-in Pipedream &quot;send the account owner an email&quot; step, and as always, I used the HTML value for the text value, and as I always say, don't do this in production, but for my testing, it worked fine.\nUnfortunately, you still can't publicly share Pipedream V2 workflows, but if you want to see more of the workflow, feel free to reach out directly. Let me know if you've got any questions! Edit: Dylan Pierce of Pipedream was able to make a sharable version of my workflow - so if you want, you can grab it here: https://pipedream.com/new?h=tch_ORVfyO\nPhoto by Adrian Korte on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Using Google Maps with Alpine.js",
		"date":"Fri Sep 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1662746400,
		"url":"https://www.raymondcamden.com/2022/09/09/using-google-maps-with-alpinejs",
		"content":"It's been a little while since I've blogged about Alpine.js, and I thought an example of integrating\nGoogle Maps with it would be a good way to continue my path to becoming comfortable with the framework. I imagined it would be fairly simple, but in building a few demos I ran into some interesting issues that helped me learn a bit more about Alpine. Let's take a look.\nMy Data\nAll of my examples are going to use the same set of data - a few Spirit Halloween stores close to my location. I went to their site, opened up dev tools, and copied the location of seven stores. I tweaked the data on one that was super close to another location and added a value that represents if a store is open twenty-four hours a day. Spirit stores don't do that, but I had a plan for it later.\n\nStarting Simple - Static\nFor my first example, I decided to use the Google Maps Static Map API. This has been a favorite API of mine for years as it's less an API and just a URL. For instances where you don't need a dynamic map, the Static Map API is simple as hell and just requires you to craft a URL with location information in it. I began by building a new Alpine application that would render the location of each store along with a map. I'm displaying the latitude and longitude values which you would probably never do as 'regular' people don't really care. Instead, I'd display a street address.\n\nFor each store, I'm calling out to a method, getImageUrl, that will return the Static Map API URL I need for my images. Here's my Alpine application in its entirety, except with the JSON trimmed a bit for size.\n\nAs an example, the URL for the first store is:\nhttps://maps.googleapis.com/maps/api/staticmap? \ncenter=30.175776,-92.077008&amp;zoom=15&amp;size=400x400&amp;maptype=roadmap\n&amp;markers=color:blue%7C30.175776,-92.077008\n&amp;key=AIzaSyC3hC35ehz1oAfUll7q7qzUlPa27Gz5g5g\n\nYou can see that blow:\n\n\n\nNotice that I center my map at the location and add a marker so it's really obvious what I'm pointing out. Markers can have different colors and labels, but I'm keeping it simple for now. Here's a CodePen showing the complete application.\n\n  See the Pen \n  Alpine + Google Maps 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nA Dynamic Map\nFor my second example, I wanted to do a proper dynamic Google Map, but just that, and nothing more. Normally I would not use Alpine.js if my only goal was to render a map and nothing else. The Google Map JavaScript library isn't hard to use and if I don't need Alpine, then there's no reason to load it.\nBut in working on this demo, I ran into some interesting issues. First off, both Google Maps and Alpine load asynchronously. If in my next version I want to add Alpine functionality that is integrated with the map, I'd need a way to handle that. I did some Googling and came across this helpful resource: Calling Alpine.js methods from third-party scripts. The blog entry describes a method by which you fire off a custom event from your Google Maps code that Alpine will listen to. Their code didn't work for me right away, I had to tweak it a bit, but let's take a look.\nFirst, here's how you add the Google Maps JavaScript SDK (with some additional line breaks for clarity):\n\nNotice two important things here - first my key (which I've locked to a few domains so it's safe to be here) and more importantly, the callback function. This will be called when Google Maps is ready.\nHere's my initMap, taken from the Google Maps docs and modified slightly:\n\nNotice how I'm dispatching a custom event? Back on the Alpine side, I can then add an app-wide listener for that like so:\n\nNow let's return to Alpine:\n\nFirst, it's possible Google Maps loads before Alpine, so in init, I check for it and if it's there, I do my map stuff. That function, doMapStuff, is the same one called by the Alpine event listener. So either way - I'm covered. In this case, I just add a marker to the map. You can see the complete demo below.\n\n  See the Pen \n  Alpine + Google Maps 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nMap with Alpine Data\nAlright, so let's combine that map data from the first sample with the dynamic version from the second example. First, I modified the HTML a bit to include a new control that lets the user filter to stores open twenty-four hours a day:\n\nIn the JavaScript, I began by modifying doMapStuff to render one marker per store:\n\nNotice that I'm creating a marker object and storing it in my store data. Why? This allows me to toggle visibility when the checkbox is changed. To support that feature I used a watcher:\n\nWhen alldayfilter changes, I either set every marker to visible or conditionally hide those that aren't open all day.\nOnce again, here's the CodePen:\n\n  See the Pen \n  Alpine + Google Maps 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThat's it - let me know what you think, and if you've used Google Maps with Alpine, give me a shout-out so I can see it in action!\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Sep 04 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1662314400,
		"url":"https://www.raymondcamden.com/2022/09/04/links-for-you",
		"content":"Another Sunday and another few posts to share with yall. I got ten hours of sleep last night so today is the best day ever.\n\n\n\nOne Serverless Principle to Rule Them All: Idempotency by Adrienne Braganza Tacke\nRaise your hand if you know what idempotency means. I certainly didn't. I've heard the term before but never got around to investigating what it meant. In this presentation, Adrienne does an incredible job explaining what it means and provides practical examples of how it would be implemented. Also, she has some incredible slide designs so those of you who give presentations can get some inspiration as well.\n\nMySQL Window Functions Part 1 by Scott Stroz\nOne of my oldest friends, Scott Stroz, recently got his first developer relations role as an advocate for MySQL at Oracle. He's already off to a great start and producing some awesome content. I've been mostly a NoSQL user for some time now, but I've got a huge amount of respect for SQL and always found MySQL to be one of the easier databases to work with.\nIn this article, he introduces the concept of &quot;window functions&quot;, which are not related to paging as I would have guessed, but related to working with aggregate functions. Seriously, read the post, it's a fascinating feature.\nRead more here: MySQL Window Functions Part 1\nSearch and the Jamstack\nThis is a talk I gave a few months ago talking about search options in the Jamstack. I covered multiple different options and the code is available for anyone who needs help getting started.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Investigating IndexedDB Wrapper Libraries - Part Three",
		"date":"Mon Aug 29 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1661796000,
		"url":"https://www.raymondcamden.com/2022/08/29/investigating-indexeddb-wrapper-libraries-part-three",
		"content":"Welcome to the third and final (for now) entry into my series looking at wrapper libraries for working with IndexedDB. I began this series earlier this month demonstrating a simple Contacts database implemented with IndexedDB. In the second entry, I demonstrated how the Dexie library made working with IndexedDB much simpler. Today I'm going to look at my last &quot;planned&quot; entry (I may revisit this again if I find more) in this series - using DPP, or Deep Persistent Proxy Objects for JavaScript.\nDPP makes use of the JavaScript Proxy Object feature. This is a low-level feature that lets you control how access to an object is provided. I'd like to say this is 'new' but it's been discussed for some time. It's got great support so it's safe to use, but honestly, it feels like something more tailored for library/framework developers versus &quot;day to day&quot; coding. Of course, this is exactly the kind of case that DPP covers - a library on top of IndexedDB.\nHow does DPP work? You begin with one asynchronous call to initialize your object. The simplest example would look like so:\n\nThis will create a store, MyDataStore, under a default IndexedDB database named DPP. You can also specify a custom name:\n\nOnce you've initialized DPP, you then ask for the data like so:\n\nJust to be clear, here's a &quot;full&quot; example:\n\nAt this point, myObj is persisted. What does that mean? I can do:\n\nAnd... I'm done. That's it. The object with two keys will be persisted and the next time I work with it, I can just use it. For example:\n\nNotice this is all done with regular synchronous calls. The library handles everything behind the scenes using web workers. (You can get more details on the project's readme docs.)\nSo how does this impact our example application? As before, I'm going to skip over discussing DOM stuff and just focus on the bits related to persistence.\nInclude DPP\nIncluding DPP requires using an import statement. I'll be honest and say this still kinda confuses me in client-side JavaScript. I need to learn more about it.\n\nInitialize the Database\nInitializing the database just uses the two lines I showed above. The result is an object, not a database, so I changed from initDb to initContacts. So in my init function, I've got:\n\nAnd initContacts is:\n\nNotice I check for contacts under the main object. I'm working with an array of data so I'm going to store it as contactsOb.contacts.\nWorking with Data\nNow for the fun part. In my previous two blog posts, I had one function for each use of working with DOM stuff, like rendering contacts, editing, etc, and one function each for persistence. That made it easier to switch from &quot;pure IndexedDB&quot; to Dexie. However, all of that is gone now. I'm not doing the persistence, DPP is.\nSo for example, I don't need a function to get contacts. I already have it. And when I want to render them, I just do:\n\nIf I want to save a contact, I either update an existing contact by its index or add it to the end of the array:\n\nDeleting a contact is just a splice call:\n\nAnd... I'm done. Honestly, it's shocking how cool DPP is. My original version had 181 lines of JavaScript. My Dexie version brought that down to 106. The DPP version goes down even more, to 97. While not as dramatic of a drop, there's not one line of IndexedDB code in there as it's all handled by the library. Here's the complete version for you to see:\n\n  See the Pen \n  IDB DPP by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAgain, please pardon the kind of ugly formatting of the display in the embed, but I'm blown away by how simple DPP makes the application. If you've used it, I'd love to hear about it so please drop me a line. Also, if you've got suggestions for other IndexedDB libraries, please share them with me.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding Social Share Links in Eleventy",
		"date":"Mon Aug 22 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1661191200,
		"url":"https://www.raymondcamden.com/2022/08/22/adding-social-share-links-in-eleventy",
		"content":"One common feature of content sites (including this one), is links/buttons/etc to share a piece of content on social media. Typically this is Twitter, but many sites will include ways to share links on Facebook, LinkedIn, and more. A reader asked me a few days ago about how this could be done in Eleventy and I thought I'd share a quick example.\nThe first thing to know is that for many sites, they would love for you to embed a JavaScript library on your page to give you an 'enhanced' experience. I read that as a bunch of additional crap/load/privacy-stealing stuff I don't need. That being said, because most docs lead folks to the 'widgets', it's sometimes difficult to find the simpler vanilla HTML solutions.\nWhile researching this, I discovered a great GitHub repository, social-share-urls. This provides helper utilities in multiple languages to make it easier to raft these URLs. They also include public domain icons. But what's really cool is that if you just want to get right to the URLs, they provide a gist:\n\nLovely, right? As you can see, each one will always require a URL of the page you want to share and some include additional things like the page title and more. Let's look at an example of how we could craft these URLs with a focus on Facebook, Twitter, and LinkedIn. (Personally, I share my content on Twitter and LinkedIn, and keep Facebook to pics of my kids and LEGOs.)\nFirst off - we need a URL - and that's actually not as simple as you may think. Eleventy provides a page.url value, but it's the relative URL, not the complete URL. If you want a full URL, you will either need to use a bit of code to sniff it, an environment variable possibly (Netlify provides one), or a hard-coded value. Personally, I think a simple hard-coded value is the best solution and can be handled in one line in your configuration file:\n\nGiven this, imagine we've got a blog written in Eleventy where all the posts use a post layout. I'd add social sharing links at the bottom, and craft them like so:\n\nIn the sample above, I'm creating a &quot;full&quot; URL by first using the rootURL value and then page.url. For both, note that I pass them through the url_encode filter to make them safe in the query string. (Although modern browsers tend to handle this for us, with it being so easy to use, we might as well, right?) Notice that the Twitter link also includes the title value.\nAll in all, not difficult at all, but the code is a bit messy. We can make use of a Liquid tag to make it a bit nicer. Consider this version:\n\nIn the code block above, I made use of assign to create two variables, pageUrl and stitle. I craft my complete URL using prepend and still URL encode the values. For the title, I just encode it. (It may make sense here to include the name of the site as well.) I added Reddit in this example just because.\nAs always, let me know if this doesn't make sense! You can find the complete source code here: https://github.com/cfjedimaster/eleventy-demos/tree/master/sharelinks\nPhoto by Elaine Casap on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links for You",
		"date":"Sun Aug 21 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1661104800,
		"url":"https://www.raymondcamden.com/2022/08/21/links-for-you",
		"content":"Happy Sunday subscribers! As always, thank you for hanging around and as always, if you've got any feedback about these\nposts (or the blog in general), just send me an email at raymondcamden@gmail.com. Alright, let's get started.\nGetting Started with Live Streaming and Amazon IVS\nMy buddy Todd Sharp recently started a new gig at Amazon as a developer advocate for Amazon Interactive Video Service. IVS is the service that powers Twitch. IVS allows organizations to set up their own live streaming service powered by AWS and frankly, it's kind of shocking for accessible the setup is. Todd explains all of this and more in his post: &quot;Get Started Live Streaming in the Cloud with Amazon IVS&quot;. Give it a read and check it out. I know I was surprised.\nWebmentions Eleventy plugin\nI use Webmentions here as a way to show how folks are responding to my blog posts. It wasn't difficult to add to my site, but Luke Bonaccorsi has a great way to make it even easier, with an Eleventy plugin. Here he announces the plugin and links to more information: &quot;No Comment 2: The Webmentioning&quot;\nMy interview on Breaking Changes\nA few weeks back, I was honored to be interviewed on the Breaking Changes talk show, an event run by Kin Lane of Postman. It was a great conversation and I hope you enjoy it as well.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Investigating IndexedDB Wrapper Libraries - Part Two",
		"date":"Thu Aug 18 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1660845600,
		"url":"https://www.raymondcamden.com/2022/08/18/investigating-indexeddb-wrapper-libraries-part-two",
		"content":"In my post yesterday I spoke about how I was interested in digging into IndexedDB again, and specifically, how I wanted to investigate some libraries that make using the feature a bit easier. In the first post, I described a basic &quot;Contacts&quot; application and demonstrated the code required to add persistence with IndexedDB. Today I'm going to update my application to make use of a wrapper library named Dexie.\nDexie is an incredibly simple wrapper for IndexedDB and has reactive support for frameworks including Vue, React, Svelte, and Angular. Dexie is a free library, but they also sell a commercial sync service called Dexie Cloud. It makes use of Promises which makes using it with async/await even simpler. I suggest taking a look at the extensive docs as I'm going to focus on just the parts I need to convert my application.\nAs before, I'm going to skip over discussing DOM stuff and just focus on the bits related to persistence.\nInclude Dexie\nIncluding Dexie is as simple as adding a script ag and pointing to the CDN, https://unpkg.com/dexie/dist/dexie.js. I'm using CodePen for my demos so I added it as an external script.\nInitialize the Database\nRemember when I said working with Dexie made IndexedDB simple? Nothing could be more indicative of that than initialization. For comparison's sake, here's the original code:\n\nRemember, you have to open the database and then listen for an upgrade event (which is also fired on the first visit) and do any structural setup there. This involves creating stores and indexes. My demo isn't doing any searches so I don't have to worry about that.\nHere's the Dexie version:\n\nThat's shockingly smaller. To be fair, this doesn't actually create the database, it just &quot;prepares&quot; your web page. Dexie smartly delays doing anything until you try to work with data. But it's important to note this if you have devtools open and are looking at your databases, you will not see anything at first.\nThe second line defines a store named contacts. The string value is how you define primary keys and indexes. As I don't have any indexes just a primary key, it's relatively short. If I wanted to build an index on the last name property, it would look like so:\n\nWill the simplicity continue???\n\n\n\nGet All Contacts\nNext up is the call to get all contacts. Here's the original:\n\nAnd now the Dexie version:\n\nIf you aren't impressed yet, I don't know what will impress you!\nGet One Contact\nOnce again, the previous version:\n\nAnd with Dexie:\n\nSaving Contacts\nIf you remember, in the previous version we used the put API to allow us to have one method for storing new contacts as well as updating existing ones. Here's the original:\n\nAnd then Dexie:\n\nDeleting Contacts\nFinally, let's look at deleting a contact. Here's the original again:\n\nAnd you get one guess as to how many lines it is in Dexie:\n\nThe Whole Enchilada\nBelow you'll find the complete application. Honestly, I almost considered removing my persistence methods with Dexie being so simple. But I also kind of assumed that it was nice having one method to handle DOM events and one method focused just on persistence. Those functions may be one or two lines each, but having it abstracted like that does enable me to make architectural changes later. Here's the final application, and again, forgive the slightly wonky formatting in the embed.\n\n  See the Pen \n  IDB1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Investigating IndexedDB Wrapper Libraries - Part One",
		"date":"Wed Aug 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1660759200,
		"url":"https://www.raymondcamden.com/2022/08/17/investigating-indexeddb-wrapper-libraries-part-one",
		"content":"Many years ago, in fact, during my first stint at Adobe, I got pretty deep into client-side storage mechanisms for the web. At the time, &quot;HTML5&quot; was the buzzword and a lot of people were talking about improved capabilities for the web, but it seemed to me that a lot of the talk was focused on more visual components like Canvas. For me, I got more excited about things like new form fields and storage. I spent a lot of time digging into various ways of storing data, I even wrote a book on the topic. But after spending a lot of time digging into it I moved on to other topics.\nNow - many years later - it's on my mind again, specifically IndexedDB. When I first got deep into the topic, I focused on using it as is, just the raw API, and didn't dig into helper libraries. I thought it would be a good time to look into some of the options out there and see which fit best for my development. IndexedDB isn't necessarily hard to use, but it's a bit complex and requires a bit of planning. When compared to LocalStage it's much more difficult, but still absolutely usable with some practice. However, it can certainly be simpler with a nice utility library.\nAs I've already covered how to use IndexedDB here in the past I won't go into it again, but I'll use this opportunity to link to the best resource for learning anything web related, the mdn web docs. Their IndexedDB docs are great and go deep into the concepts and APIs.\nFor this first blog post, I'm going to demonstrate a simple application that uses IndexedDB without any helper libraries at all. It's a simple &quot;Contacts&quot; application (see my earlier post for something similar) that stores a list of people, including a first name, last name, and email property. The interface lists contacts and provides a form to the right. The form on the right can be used for creating new contacts or editing existing ones.\n\n\n\nI'll share the complete code for the application at the end, and I'm going to skip talking about DOM methods and the such. Instead, let's focus on the IndexedDB portions.\nInitialize the Database\nAny use of IndexedDB requires opening a connection to a database and handling initial object store creation. To further complicate matters, IndexedDB supports versioning and you're only allowed to make changes to a database on a version change. I handle all of this in one function:\n\nYou'll notice I'm returning a promise so I can use it easier. I open the database, listen for an onupgradeneeded event, which will fire the first time the user hits the page, and set up the object store. For my contacts, I want an auto-incrementing key named id. Not too bad, but outside of this function I can just do:\n\nI love async/await like I love a good cookie.\n\n\n\nNow let's look at the various CRUD (Create/Read/Update/Delete) functions.\nGet All Contacts\nGetting all my contacts so I can render them in a table isn't too difficult since there's a getAll() API for IndexedDB.\n\nBack in the calling code, I can just do:\n\nThe result is simple JavaScript objects in an array, so it's not difficult to use.\nGet One Contact\nGetting one contact requires a primary key. In my case, I used the id property so once I know that, I can use get(key) to fetch the record. Here's that function:\n\nBy the way, you'll notice that sometimes I wait for the transaction to fire a success message and sometimes I wait for a particular request to fire a success. That's a bit inconsistent but I also think it's ok too. Again though it just speaks to the complexity of working with IndexedDB. Here's an example of it being called:\n\nSaving Contacts\nOne way IndexedDB is a bit simple however is with storing records. While there's an API to add records, there's a record to update that nicely handles creating new records when it needs to. This is done via put and all I need to do is either pass an object with an id value or not - and it just does what it needs to:\n\nOnce again, using this is simple:\n\nDeleting Contacts\nFor the last and final CRUD method we need, I set up a delete method. This requires a primary key.\n\nAnd here is the code using it:\n\nThe Whole Enchilada\nAll in all, once you encapsulate the complexity away into functions, it's not too difficult to use IndexedDB, and again, async/await makes life so much easier once you get the hang of it. But I'm really curious to see what happens after I start using a helper library or two. For the complete demo, you can use the CodePen demo below. Please forgive the slightly wonky formatting - it looks better on the CodePen site. See you in the next part!\n\n  See the Pen \n  IDB1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Support Draft Blog Posts in Eleventy",
		"date":"Sun Aug 14 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1660500000,
		"url":"https://www.raymondcamden.com/2022/08/14/support-draft-blog-posts-in-eleventy",
		"content":"Last week, I was helping someone over email and we began talking about how to support &quot;draft&quot; posts in Eleventy. A draft post, at least for the purpose of this post, refers to content that should not be rendered on the published site. It may be a blog post that needs time for edits or perhaps just something the author needs time to chew over. It may be committed to a repository, but should not be displayed on the actual website. I did a bit of digging into this and came up with a couple of different solutions I'd like to share, but as always, please let me know what you think or how you've accomplished the same thing.\nFor this demo, I'm using a simple blog application where posts are all stored in a posts directory. I've got two posts, alpha.md and beta.md, and my intent is for beta.md to be a draft. Initially, each blog post contains front matter for title and layout. Alright, let's get into it!\nIteration One\nThe first, and simplest, way to suppress content is to use the permalink feature to disable writing to disk. So given a post I want to be in draft mode, I could just do this:\n\nSetting permalink to false will stop the file from being published. Sweet and simple, right? But what if you are using a directory data file to specify your collection, or getFilteredByGlob? In that case, the template would still be considered part of the collection even though it wasn't stored. So for example, imagine our posts directory had posts.11tydata.json:\n\nYou would normally do this to save yourself from having to tag every blog post. If you had done this and then iterated over collections.posts, the draft post would show up. The url property would be false, but it would still show up. As an example:\n\nAnd the result:\n\nThe other issue I have with this iteration is that - while it halfway works - I don't like the fact that a new developer to the repository may not get why we're using permalink like this. Yes, I'm being picky, but the code isn't being clear about what it's trying to do here. Let's make it better.\nIteration Two\nIn the second iteration, I want to fix two problems from the previous version. First, I want to use better front matter to more clearly express my intent, and more importantly, ensure the draft post isn't showing up in collections.\nFirst, let's switch from using a data JSON file to a data JavaScript file so we can use some code. I renamed my JSON file to posts.11tydata.js and used this code:\n\nI'm making the permalink value dynamic based on the value of draft in front matter. The tags value as well is dynamic, this time only setting it to posts when draft isn't being used. In beta.md, I switched to this:\n\nThis looked perfect, but something interesting happened. First, permalink worked perfectly, beta.md wasn't published. But, when I looped over collections.posts - oddly nothing was output! In fact, check this out:\n\nAs you can see, I'm looping over my posts and the global all collection. In both, I output the url, title, and tags. Here's the output:\n\nSo yeah, my non-draft post was correctly tagged but didn't show up in the collection. I'm not sure what to think about that and possibly it's a bug (after publishing this I'll file - update - I filed it here), but I took another approach.\nIn .eleventy.js, I added a custom collection:\n\nAnd guess what? This too didn't work! It's got to be a timing/chicken+egg/etc type issue. Finally, I switched to a file system solution:\n\nOddly, in this scenario I was able to get my posts, Eleventy properly processed the dynamic tags aspect, and correctly returned a filtered list of non-draft posts. I switched my 'blog post display' logic to:\n\nPerfect! Note that you want to ensure you consistently use this new collection. So for example, if you have an RSS feed\n(you do, right?) or a search interface, you'll want to use blogPosts, not posts.\nIn theory, you can stop reading now, but I had another idea as well.\nIteration Three\nWhat if you wanted to have 'draft' posts that were published, but not linked to from your list of posts, RSS, and so forth? This would let you publish a draft post and let other folks take a look for review purposes. To be clear, you could just share a link to your repository as well, but if you wanted a non-technical person to do the review, see the post in the context of your website, etc, then publishing, but not including, a draft could be helpful.\nWe can support this by simply removing the permalink feature:\n\nTagging is still done correctly, at least when combined with our custom collection, and beta.md will be written out to the file system. This means you could then share a URL with others to get their feedback on the content.\nAs a final note, you could also use all of this to support future posts. Ie, don't link/publish if the data is in the future. But you would need to combine that with a scheduled build system to ensure that when it is time for the post to go live, a build is fired off. That could be done with a daily schedule if you w",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Web-Based Badge Scanner",
		"date":"Thu Aug 11 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1660240800,
		"url":"https://www.raymondcamden.com/2022/08/11/building-a-web-based-badge-scanner",
		"content":"I'm at a conference now working a booth (well, at least when I started writing this), and I really didn't realize how much I enjoyed this part of the job. While I've attended a few conferences post COVID (err, well, &quot;post&quot; may be too strong of a word), this is the first booth I've worked at in years. One of the first things I did when I arrived was check and see how we were going to get contacts via badge scanning. Not surprisingly, the conference organizers suggested a native app. Me being me - I immediately thought of how the app's features could be accomplished via the web. There's nothing wrong with the native app (actually, it's pretty buggy at times), but I dislike installing native apps for events. Nine times out of ten I forget to delete it off my phone even though I'll never use it again. I've now built a web-based version of the application, and while it's certainly ugly as hell, I thought I'd share how I did it.\nThe web app has the following features:\n\nVia user interaction, start a camera feed so you can point it at a badge and scan the QR code.\nParse the results from the QR code and let you store the contact persistently.\nRender the list of contacts so you can see who you have scanned.\nFinally, let the user click to download the contacts as a zip file.\n\nLet's go into detail on how I built each of these parts.\nThe QR Scanner\nFor the first part of the application, I needed a QR scanner. I knew that a web page could get access to a user's camera (via getUserMedia, an API I've used in the past) and I knew it could render it to screen via a video tag. The hard part would be looking at that stream and trying to find a QR code.\nLuckily I came across a great library that simplified most of that work: https://github.com/nimiq/qr-scanner. The library handles getting camera access, displaying it on screen, and trying to find and parse the QR code. As an FYI, there is a native API for barcode detection that supports QR codes, but it's pretty much a Chromium thing only now. The QR Scanner library I used will make use of it if it exists though.\nAfter grabbing the required JS library, here's how I used it. First, I began with a video tag in my layout:\n\nIn JavaScript, there are a few steps. First, I get a pointer to the DOM element:\n\nNext, I make an instance of the scanner:\n\nscanResult is a success handler. To start scanning, you use this method:\n\nFor my app, I tied this to a button you could click to start the scanning process. The success handler is passed an object that will contain, surprise surprise, the result of the scan as text. Now came the fun part.\nParsing the Results\nWhen I tested my badge at this conference, the QR code contained vCard info. A vCard string is contact information in a somewhat simple format. (You can read more about it at the spec). Here's an example (source from https://docs.fileformat.com/email/vcf/):\nBEGIN:VCARD\nVERSION:2.1\nN:Gump;Forrest;;Mr.\nFN:Forrest Gump\nORG:Bubba Gump Shrimp Co.\nTITLE:Shrimp Man\nPHOTO;GIF:http://www.example.com/dir_photos/my_photo.gif\nTEL;WORK;VOICE:(111) 555-1212\nTEL;HOME;VOICE:(404) 555-1212\nADR;WORK;PREF:;;100 Waters Edge;Baytown;LA;30314;United States of America\nLABEL;WORK;PREF;ENCODING#QUOTED-PRINTABLE;CHARSET#UTF-8:100 Waters Edge#0D#\n #0ABaytown\\, LA 30314#0D#0AUnited States of America\nADR;HOME:;;42 Plantation St.;Baytown;LA;30314;United States of America\nLABEL;HOME;ENCODING#QUOTED-PRINTABLE;CHARSET#UTF-8:42 Plantation St.#0D#0A#\n Baytown, LA 30314#0D#0AUnited States of America\nEMAIL:forrestgump@example.com\nREV:20080424T195243Z\nEND:VCARD\n\nIt's not a terribly difficult format and I was able to find a few pre-built JavaScript libraries out there, but there were all a bit flakey. I decided to build my own, and while it's probably not completely robust, it does the job. My intent was to parse the fields as well as give them nicer names where possible. Here's the function I wrote:\n\nFor the most part, this is just string parsing, but note that some fields in a contact record have types, like addresses and phone numbers. The result of this function is a nice JavaScript object that's an array of fields with nicer names, values, and where it exists, types.\nSo going back to the scan operation, this is how I handle it:\n\nI turn off the current scanner. Parse the data and save it as well as the original string in a global variable, and then update the DOM to reflect a new scan that came in. I use the name value as a label.\n\n\n\nDid I mention that the UI wasn't pretty?\nSo, as a quick test, I asked my two best friends to send me pictures of their badges from recent conferences. One had a vCard and one did not, instead having some other weird ~ delimited format.\n12688~Scott~Stroz~noyb@noyb.com~MySQL Developer Advocate~Oracle~5559755049~12345\n\nAlright, so at this point, my app can scan a badge, parse the vCard, and now we need to save it.\nPersisting Contacts\nTo handle persistence, I decided to make use of IndexedDB. A few years back, I went hardcore deep into",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "How to Migrate from Lunr to Algolia - a Technical Guide",
		"date":"Tue Aug 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1660068000,
		"url":"https://www.raymondcamden.com/2022/08/09/how-to-migrate-from-lunr-to-algolia-a-technical-guide",
		"content":"Search is an incredibly important aspect of any site hosting even a small amount of\ncontent. How quickly your site can provide search results and how well your results\nmatch the user's intent is critical. There are multiple search options available for\ndevelopers, and so sometimes in the pursuit of speed and relevance, a site will migrate\nfrom one service to another. In this article, I'm going to present a use case for why a site\nmay move from a self-hosted solution like Lunr to a server-based solution like Algolia. I want to stress that this is not meant to be an attack on Lunr, but more where it would be\nappropriate for a site and what the benefits were of moving to Algolia in my particular\nuse case.\nHow Lunr Works\nLunr is an open source JavaScript-based solution for search services. With Lunr, developers\nstart by creating an index of the content they want to be able to search through. This\nneed not be a one-to-one copy of site content, but instead can be tailored to focus what\nwill be most useful for search. This data can come from anywhere, but must be provided\nto Lunr as an array of objects. Let's consider a fun hypothetical dataset: cats available\nfor adoption. Their data may look like this:\n\nLooking at this data, we can make the executive decision now that we'll let our users\nsearch by name, gender, breed, and history, but not by birth date. With the Lunr library\nloaded, code to generate that index would look something like this:\n\nWith the index created, search then becomes one line:\n\nWhere Lunr Might Not Work Well\nLunr is simple to use, but there are a few areas of concern. Here's a few things to keep\nin mind that may make a migration more desirable.\n\n\nFirst, as your data grows, so will your index. Lunr needs time to create the index\nand that time grows with the size of the content being indexed. Lunr does support\npre-building the index, but that pre-built index must still be served up to the user.\n\n\nAnother issue is that Lunr results contain just a reference to the original data. So for\nexample, in the cats data above, we defined the reference as the name of the cat.\nIn order to return a meaningful result to the user, we still need to use that reference\nto go fetch the full data on the cat from our data storage.\n\n\nIt is entirely possible to use Lunr on the server, but that then requires setting up the\nserver, or serverless functions. All absolutely possible, but more work on the\ndevelopers side.\n\n\nLunr has a lot of incredible features like stemming, but there are still a few absent must-haves, like typo handling and synonym matching. Another example: how can\nwe get Lunr to factor the user’s location into result relevance scores?\n\n\nFinally, and this is the big one: we need to roll our own analytics. Again, it’s certainly\ndoable, but this will require more code, more data storage, and more maintenance.\nThese stats can be incredibly important because they reflect what your users need\nand what they're having trouble finding.\n\n\nMigrating to Algolia\nSo let's now assume that you've identified you need to migrate from Lunr. What does moving to Algolia look like?\nFirst off, we need to calculate what your cost will be, if any. Algolia's pricing page goes\ninto detail about with, basing an estimate on a combination of your index size and\nsearch count. Currently, the free tier offers up to 10,000 search requests per month,\n10,000 search recommendations per month, and 10,000 documents in the index at any\ngiven time.\nIf you’re good with the price, the next step is signing up and creating your account. After confirming your email address, you will be dropped into the Algolia dashboard.\n\n\n\nThe first page directs you to create an index, a necessary part of building a search\ninterface. Your account can have as many different indexes you need — it's absolutely\npossible one site may need multiple, depending on the size and complexity. As developers,\nusually naming things is one of the harder parts of our job, but feel free to just use the\nname of your web site. For my example, I used &quot;cats&quot;.\nAfter naming the index, you are prompted to add your data — you don't need to do this\nimmediately, but it’s definitely helpful while you get set up. This is where we run into an\nimportant distinction if you’re coming from Lunr: with client-side solutions, you generate\nan index every time search is used, or at build-time for pre-built indexes. With Algolia's\nservice-based approach, an index is like an empty database. You create the index as an\nempty bucket and then can add, edit, and delete data within it. As an example, if you\nadd 100 documents to your index right now in Algolia, then that index is already seeded\nand ready to search without a time-consuming build on the client. You’ll only need to\nupdate the index to reflect changes in your dataset, but never to reload data that you’ve\nalready given to Algolia. Initially, Algolia will offer a few options as far as seeding goes:\n\n\n\nAt this point, how you seed the index is ",
		"tags":[
	        
            "algolia"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Responding to Severe Weather Alerts with Pipedream",
		"date":"Mon Aug 08 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1659981600,
		"url":"https://www.raymondcamden.com/2022/08/08/responding-to-severe-weather-alerts-with-pipedream",
		"content":"A few months ago I wrote about a (possibly) useless example of custom events in Pipedream, &quot;Kicking Off a Pipedream Workflow on a Full Moon (Because Why Not?)&quot;. While not terribly practical, the article demonstrated one of the cooler features of Pipedream, the ability to create workflows on any particular piece of logic. In the case of that article, I defined a custom &quot;event source&quot; (what Pipedream calls a way to begin a workflow) based on a daily check on the current moon phase. Today I'm sharing another example of this feature, possibly quite a bit more useful - severe weather alerts.\nI'm doing a bit of research into Microsoft's mapping solutions, and while I was looking at the docs, I discovered their Severe Weather Alerts API. As you can imagine, given a location, it will return information about any possible severe weather in the area. If there aren't any, it simply returns an empty array. Here's an example (taken from Microsoft's docs) of how this could look:\n\nIn this example, two different alerts were returned for the same area. Calling the API is relatively simple. Given that you have a key, the endpoint looks like so:\nhttps://atlas.microsoft.com/weather/severe/alerts/json?api-version=1.1&amp;query=48.057,-81.091&amp;subscription-key=X\n\nLocation is in longitude,latitude format and is passed to the query value. The only other optional arguments supported are to truncate the results or change the language. So given how easy it is to use the API, how do we use it as a Pipedream event source?\nI began by creating a file, severeweather.js, and used the format required for an event source. You can find this documented on the Pipedream site, but at a minimum, it looks like so:\n\nBasically - metadata and code. The $emit part is what creates an event and what would be used to fire off workflows. The Pipedream CLI can then be used to publish the code to your account. Here's how my event source is built:\n\nLet's take it from the top. The name and description fields simply describe the source. The props section define the properties for the source, specifically the fact that it's based on a schedule (in this example, once an hour), and that it requires two unique values, an Azure Maps key, and a location.\nIt's important to note that these properties will be unique per instance of the event source. So this code would be used in different workflows with different values.\nLet's skip dedupe for a bit and look at the run block. I use node-fetch to hit the Azure Severe Weather endpoint and fetch the results. For each alert, I want to emit it as a different event. However, it's possible an alert could be older than an hour and my event may fire more than once for one specific alert. Pipedream makes this easy to fix by using a dedupe strategy. When I specified unique, I was then responsible for creating a primary key for each event. You can see this in the second argument to this.$emit. I also add the optional summary value to describe the event. But the cool part is - I don't have to do anything else to enforce uniqueness. Pipedream itself will suppress any alert that was already emitted.\nWhen I deployed this via the command line, it prompted me for my two values, and then simply created the configured source and made it available. So for example, I could create a new workflow, and look for it as a source:\n\n\n\nAt this point, you can do anything imaginable with the source. You could send an SMS message. You can send a notification to a device. You could email a listserv of people who may be in the area as a warning. As the simplest example, I created a workflow that simply sent me an email. I added a Python step that took the alert (notice I said alert, not alerts, my event source is going to fire one time for each unique alert) and created a string appropriate for email:\n\nAnd then this was passed to an email step. Pipedream has a &quot;send an email to the owner&quot; step that is super easy to use but note that if I wanted more fine-grained control, I could easily use something like Sendgrid instead. Here's how that configured step looks:\n\n\n\nAs a quick note, I'm using the same value for plain text and HTML emails, which isn't good, but as I know I'm the person getting the email and my email client supports HTML, I don't worry about it.\nI did a quick test where I used Accuweather's severe weather page to find active events. Here's an example of the email I got:\n\n\n\nAs always, if any of this doesn't make sense, just let me know!\nPhoto by Josep Castells on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Mon Aug 01 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1659376800,
		"url":"https://www.raymondcamden.com/2022/08/01/links-for-you",
		"content":"It's been a little while since I've shared a links post, and while I usually save them for the weekend, I thought it would be nice to start off August with one. Also, yesterday I was busy being incredibly lazy.\nLearn to Use Pipedream\nReaders here know I'm a huge fan of Pipedream (you can see my posts on it here). Recently they released Pipedream University, a set of free online lessons that cover how to get started building workflows. If you've been looking to give it a try, this is a great way to learn what you'll need.\nEleventy 2.0 Goodies\nA few weeks ago Zach tweeted about some things coming to the next major version of Eleventy:\nNew Eleventy 2.0.0-canary.13 🎈🐥🐀🌍🆕 i18n Plugin https://t.co/Du7zYEqpmr📅🆕 `date: git Created` option https://t.co/gRGAxaRgFG⚙️🆕 URL Transforms feature: https://t.co/zw4onBq9KXWith excellent contributions from @Snapstromegon and @KyleMitBTV!&mdash; Eleventy 🎈 2.0.0-canary.14 (@eleven_ty) July 15, 2022 \nOf interest here (to me anyway) are the Internationalization updates. There's a new plugin to help with linking as well as new docs about setting up a project properly. This is further helped with a new URL transforms feature.\nIf you weren't aware, the Eleventy project is using milestones to track their work on 2.0 and you can see the complete list here: https://github.com/11ty/eleventy/milestone/38\nAndor Trailer\nAnd now for something completely unrelated from tech - the new trailer for the next Star Wars show to arrive on Disney Plus, Andor:\n\n&quot;Rogue One&quot; is my favorite Star Wars film so I'm really looking forward to this!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building Related Selects in Alpine.js",
		"date":"Fri Jul 29 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1659117600,
		"url":"https://www.raymondcamden.com/2022/07/29/building-related-selects-in-alpinejs",
		"content":"One common UX/UI metaphor in web design is the idea of &quot;related&quot; selects or drop-downs. What I mean by this is the idea of having one select field of options, and when you select something from there, it drives the contents of another (or related) select field. An example of this could be a drop-down of car makes. When a particular make is selected, you then get a drop-down of car models. I thought it would be fun to build an example of this in Alpine.js.\nTo begin, I created two utility functions that would create mock data for my selects. The first returns a list of states (not all of them, and that's ok):\n\nNotice each state has an id and label value. Each state has a set of related cities. For that, I built a method to generate them when requested. To demonstrate returning different sets of data for each state, both the names of the cities and the number of cities are dynamic:\n\nAlright, let's look at the HTML I'll use for this:\n\nI've got two main parts to this - one for states and one for cities. For states, I've bound the select to a state variable and the options come from states.\nFor cities, it's pretty similar, but note the use of x-show. The idea here is to only show the city drop-down when a state is selected.\nNow let's check out the JavaScript:\n\nAs this is a very simple demo, the code isn't long. You can see states making use of the function I defined earlier. Ditto for cities, but notice how it uses this.state. This will re-run anytime the value of state changes. I don't have to do anything else. You can play with this yourself here:\n\n  See the Pen \n  Related DD by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFor the heck of it, I decided to build another version, this one where the call to get cities for a state was asynchronous. You could imagine this being the result of calling an API for example. As a hackish way to fake that, I simply used setTimeout. Here's the new version:\n\nThere's one more slight difference in that I check if the id value is null, which it will be on load, and if so, return an empty array.\nNow - at first - it was an easy modification to use this. I literally just did this to the cities definition in my Alpine app:\n\nAnd it just freaking worked. But there wasn't any feedback to the user that something was happening when a state was selected. I decided to add a loading message and ensure the cities select was hidden during this process. That ended up being a bit more complex.\nFirst, I modified the second half of my HTML:\n\nFirst, I've got a span that shows up when a new value, loadingCities, is true. You'll see that in a second. I then modified the label to check for both state and loadingCities to be false. So far so good. But here's where things got odd. In my mind, this should have been enough. But when I had &quot;City&quot; by itself, not wrapped in a span, I would see it the very first time a state was selected, even though loadingCities was true. On the second, and so forth, selection, it worked as expected. I also had an issue with the select showing up, empty.\nI'm not sure why, but I added the span, used x-show for both inside, and even though it feels like I had to do more work than expected, it seemed to work well.\nHere's the updated JavaScript:\n\nYou can see cities is a bit more complex. I've got to get the result and then set my loading value back to false, then return it. All in all, it worked, but as I said, it was tricky to get the timings right on everything. Here's this application for you to play with:\n\n  See the Pen \n  Related DD (Async) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\nPhoto by Adrian Schwarz on Unsplash\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Generating Zips in an Eleventy Site",
		"date":"Sat Jul 23 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1658599200,
		"url":"https://www.raymondcamden.com/2022/07/23/generating-zips-in-an-eleventy-site",
		"content":"Here's an interesting question. Given an Eleventy site that has dynamic resources of some kind, how could you provide a way to get those resources in one simple zip file? Here's how I solved that problem.\nFirst, I decided that my &quot;dynamic resources&quot; would be a set of cat pictures, because, of course I did. In a new Eleventy site, I created an images directory, and under that, a subdirectory named cats. In this directory, I dropped a random bunch of cat pictures. (I've got a OneDrive folder of about a hundred of them.) As I wanted Eleventy to be aware of them, I created a _data directory file named catpics.js:\n\nBasically, select all the JPGs and return them in an array, while also removing /src from the result so I end up with an array of paths I could use in HTML later. Here's how I used that:\n\nThe final bit was to ensure my images were copied into the source directory. I did that in my .eleventy.js file:\n\nHere's how this looks when rendered:\n\n\n\nHonestly, I don't really need to show you this picture but I couldn't resist. Alright, so the next part was to generate the zip file. While I had a good idea of how I was going to create the zip, I struggled with what was the &quot;best&quot; or more appropriate place to handle that logic. In the end, I decided to use Eleventy's eleventy.after event. That event fires after a build is complete. Here's how I used it in my .eleventy.js file:\n\nI make use of adm-zip, a nice JavaScript zip library I've used before. I make a new zip, add each picture, and when done I wrote it out to my output directory as catpics.zip. Back in my HTML, I then just added a link to it:\n\nThat's it. In theory, now you can drop new images in the cat directory, run a build (hopefully that's automated), and the zip file will be updated when the build is done. If you've done something like this, I'd love to see a real-world example, just let me know. If you want the complete source to this demo, you may find it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/autozip\nPhoto by Tomas Sobek on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "An example of Algolia Search with Alpine.js",
		"date":"Tue Jul 19 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1658253600,
		"url":"https://www.raymondcamden.com/2022/07/19/an-example-of-algolia-search-with-alpinejs",
		"content":"As my readers know, I've been falling in love with Alpine.js lately and am always on the hunt for more ways to practice using the framework. I thought I'd share an example of how you could use it with Algolia's JavaScript client. I use that on my search page here with Vue.js, so it wasn't a terribly difficult thing to rebuild a similar interface in Alpine.js. Here's how I did it.\nThe Layout\nFor the layout, I went with a simple search interface and results that displayed the title, date, and a snippet for each result. Here's that HTML with Alpine.js directives throughout:\n\nFrom the top, the first two elements are my search field, using x-model, and a button that will initiate the search. I've got it disabled based on a value searchReady that you will see soon.\nThe next block handles cases where no results were found.\nAnd then I have a block that shows up when results are ready. I render the total number of hits as well as how many I'm showing. (I could do paging here, and if folks want to see that, just ask.) I then loop over my results making use of the url, title, date, and snippet values.\nThe JavaScript\nNow let's look at the JavaScript code:\n\nOn top, I've got three constants related to Algolia. An application ID, my API token which only has read access, and the name of my index. When Alpine initializes the application, I create an instance of the Algolia index wrapper, and then set my searchReady boolean to true. This will enable that button in HTML.\nFor search, I do a quick validation of the value, and then just pass it to Algolia. The commented-out line shows how simple this can be if you want the defaults, but I wanted Algolia to create a snippet on the content field so the search results would be a bit nicer.\nFinally, I do a bit of work on the results. If none were found, I set the value so that it will get flagged in the HTML. If we have results, I copy over the total hits and per page values. I then map the results to make things a bit easier in the HTML. Specifically, I copy over the snippet to an easier-to-use key, and then I use the Intl object to make the dates a bit nicer.\nHere's an example of how it looks, and please note that it's me doing my best at design. Don't blame Alpine or Algolia. ;)\n\n\n\nIf you want to give this a try yourself, play with the CodePen below, and as always, let me know if you've got any questions!\n\n  See the Pen \n  Algolia + Alpine example by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "alpinejs",
            
            "algolia"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Getting Images from a Twitter Account (2022)",
		"date":"Fri Jul 15 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1657908000,
		"url":"https://www.raymondcamden.com/2022/07/15/getting-images-from-a-twitter-account-2022",
		"content":"Some time ago, back in the &quot;before time&quot; of 2016, I wrote up a demo of a simple idea - grabbing the media (pictures specifically) from a Twitter account. I follow several Twitter accounts that simply post cool pictures. Given how toxic online platforms can be, just seeing cool pictures can be a bit relaxing. The demo made use of Node.js and ran on IBM's Bluemix platform. You can read that old post here if you desire: Getting Images from a Twitter Account. I followed up this post two years later with an example of the same idea running via Azure Functions - My First Azure Function App - Twitter Image Displayer. A few weeks back someone DMed me asking about an update and I thought I'd take a stab at it. If you just want to see the demo, you can see it here:\nhttps://cfjedimaster.github.io/webdemos/tweetimages2/\nHere's a beautiful example of getting the images from @randomcomicbook\n\n\n\nAnd here's how I built this new version. There's a backend and a frontend, of course, so let's start with the backend.\nServer\nTo start off, I created a new Pipedream workflow that fired on an HTTP request. I then added a code step to validate that an account was passed in the query string:\n\nI named this get_account and you'll see me reference the result later.\nThe next step actually does the work with Twitter. In the example from six years ago, I built a Node.js application and made use of OAuth. What this means is that to use the application, you needed a Twitter login and had to authenticate with Twitter first. This means that the application itself was limited to a quota of calls but that limit was based per user. For the version I built this week, I decided to make use of a Twitter application instead.\nThe docs for Twitter's search API specifies that unless you have academic access, it will only search the past week. So keep that in mind when testing. If an account hasn't posted an image in a week, then no results are going to be found.\nAlso, for rate limits, an app has a limit of 450 requests per 15-minute window. For users, it's 180. I made the judgment call to go with an app to keep things simpler. I also figured that if this blog post &quot;takes off&quot;, I'd still probably be way under the rate limit so I'm not too concerned, but keep that in mind if you build your own version of this.\nI created the app via Twitter's developer console, which has gotten really good over the past few years. With the app created, I then went to Pipedream.\nPipedream has excellent support for working with Twitter APIs, and I've built way too many Twitter bots with them. However, normally I've used their support based on a particular account. So by that I mean - I set up my bot to post on a schedule, and the action to send a tweet is based on the bot's account. In this case, I wanted to use Twitter APIs based on my Twitter app, and for that, you use a different action - &quot;Twitter Developer App&quot;:\n\n\n\nAfter selecting that, you then pick the &quot;Use any Twitter Developer App API&quot; option:\n\n\n\nAfter selecting it, you are then given a step that lets you configure the Twitter Developer app. For that, you'll enter your values from the Twitter developer portal. And best of all - once you've done that, Pipedream will remember the connection and let you select it again. The default code shows that it uses the Twit library.\n\nThe cool thing is that all you need to do is modify the last line to start adding your logic. Let's take a look at how I did my search:\n\nI create a search query based on the user you want to scan for images, that's the from portion, I tell it to filter to media, that's the filter part. Finally, and this is crucial, note the tweet_mode value. As the StackOverflow link there points out, if you don't use this, you won't get the full results back.\nOnce I have the results, I then loop over them, look for an image entity, and add it to an array of results.\nThe final step is one more code step that returns the result to the caller:\n\nI absolutely could have included this in the last step, Pipedream won't complain, but I try to build my workflows such that each step does one concrete thing. I love that Pipedream lets me pretend to be a better developer when I'm not feeling lazy.\nClient\nFor the front end, I took a look at my demo from six years ago. It made use of jQuery and a jQuery plugin for lightbox functionality. For the 'modern' version, I decided on vanilla JavaScript and an excellent non-framework-based library called Parvus. Here's the HTML, which consists of text explaining what to do and a few DOM elements I need to work with:\n\nAnd here's the JavaScript:\n\nBasically, on button click, get the value (potentially removing the @), and then pass it to my Pipedream workflow. When I get the array back, either render them out (including the code Parvus needs) or report that no results were found.\nThe complete source code may be found here: https://github.com/cfjedimaster/webdemos/tree/master/tweetimages2. At this time",
		"tags":[
	        
            "pipedream",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Auth0 Login with JavaScript - Some Tips",
		"date":"Mon Jul 11 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1657562400,
		"url":"https://www.raymondcamden.com/2022/07/11/using-auth0-login-with-javascript-some-tips",
		"content":"Despite having worked at Auth0 a few years back, I never actually used their main identity product. (When I was there, I was part of a team working on a serverless offering.) It's been in the back of my mind to try the product for some time now, but I never got around to it. This past week Adobe was shut down for the holiday so with a lot of free time, I decided I'd finally give it a shot. I got something working, but had some troubles with their documentation so I figured I'd share what I ran into, and some code as well in hopes it will be helpful for others.\nThe Quickstart - Issue One\nOk, this all began when I used the JavaScript quickstart as my guide. I wanted something that wasn't framework-specific and was as simple as possible. I will say I completely missed the &quot;this is a beta&quot; warning on top, but I've left some feedback via their forums and other means. Most of what I found could be fixed with a bit more documentation, but there's one issue that completely breaks things. I'll do my best to make that clear in the text below.\nOutside of the issues, the quickstart is rather well done. I especially like how it offers to set stuff up for your code right in the guide:\n\n\n\nHowever, if you use this option, it creates the application with one setting misconfigured. Go ahead and let the tutorial create the application, but then go into the dashboard in another tab, open the Applications panel and select your new application. (It will probably be the only one.) You should be in the Settings panel and if you scroll down to Application properties, look for Token Endpoint Authentication Method:\n\n\n\nSee that yellow warning there? When I was going through the quickstart I wasn't able to log in, randomly came in here, noticed that, and figured, ok, let's change it. Change it to &quot;None&quot;, save, and you'll notice it's now disabled:\n\n\n\nI assume it can be re-enabled if you tweak other settings, but all I know is that it was enough to get my demo code working.\nThe Quickstart - Issue Two\nThe second issue I ran into was 100% my fault, but with the assumption that other folks may make the same mistake, I figured I'd share. As you scroll down the quickstart, the content on the right-hand changes. This is super obvious to everyone. I assume. But I was on step three and stuck as the text didn't explain what to do:\n\n\n\nTurns out - I just needed to scroll a bit more:\n\n\n\nYeah... I should have noticed that but as I said, if you're like me, don't repeat this mistake.\nThe Quickstart - Issue Three\nThe third issue I will bring is more generic and can be fixed by fleshing out the docs a bit - but after step three, things get a bit vague. So for example, in step four, they ask you to add a login button, but they don't mention that the default code shown previously requires a login button with a particular ID. Also a logout button. Also a div for the profile. If you are like me and test after every code change, this will throw you.\nSpecifically this is enough:\n\nAnd in fact, this is where the tutorial falls apart for me. I went ahead and downloaded the sample and... well, ok, soapbox coming. The &quot;vanilla&quot; JS example makes use of Node, docker, has a package.json, etc. And... to be clear, there's nothing at all wrong with all of that but my god, all of this could be done in one page (as I'm about to show!), and while all of those technologies are fine (I use Node every day), if you don't need them to illustrate a point, don't use em!!!\nWow, three exclamation marks. I need to chill for a sec:\n\n\n\nA Demo\nAlright, so despite running into issues, I can say when I got it all figured out, I was able to implement login in a simple, one-page application. I took their code and modified things a bit to make it a bit more ready for production. For example, I now show/hide the login and logout buttons depending on the login state. I also run the code in DOMContentLoaded as you would typically do. Here's a complete example, and yes, normally you would want the JavaScript in its own file, but as I said, I was looking for a simple example:\n\nIf you've got any questions about the above, let me know. Note that you can use this code on multiple pages and it works as expected. By that I mean, if you are on index.html, log in, and then go to cat.html, and check login status, it will reflect that you are logged in. No need for a SPA - it just plain works.\nAs always, reach out if you've got a question about the above. This was done in preparation for another demo related to Twitter, and I got to say, the Auth0 experience of connecting a Twitter app to an app login was really well done!\nPhoto by Micah Williams on Unsplash\n",
		"tags":[
	        
            "auth0"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Jul 03 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1656871200,
		"url":"https://www.raymondcamden.com/2022/07/03/links-for-you",
		"content":"Happy (Almost) Independence Day. Adobe (where I slave away every day pounding on my keyboard) is shut down this week so I'll be taking it easy trying my best to do next to nothing. I'll probably fail, but at least I'll get to sleep late. Here's a few links for your perusal.\nFinding Related Posts in Eleventy\nA week or so ago I posted an example of finding content related by day of year. So for an article published on April 8, 2022, you would be able to find articles posted on the same day in earlier years. It's not terribly useful probably, but was fun to build. When I shared it on Twitter, Justin Poehnelt a cool plugin: https://www.npmjs.com/package/eleventy-plugin-related. This plugin will find related items based on customizable properties and weights. It's really cool, and while I don't have plans on adding my silly year based demo to the blog, I may actually add this one. (Just need to figure out a nice way to display it.)\nPower Automate Presentation\nEarlier this week I gave a presentation on using Microsoft Power Automate and our PDF Extract API to automatically parse the contents of PDF documents. As I said it uses our Extract API, but also makes use of Microsoft's Natural Language Processing API:\n\nEleventy Weekly\nAnother Eleventy link, this time to the Weekly video from Zach. Of special note is the discussion on proposed changes to indented Markdown code. (Could that possibly be the most nerdiest thing I've written here? Yes.)\n\n\n\nThat's it for today. Have a safe and uneventful holiday!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Reading Comic Books in the Jamstack",
		"date":"Fri Jul 01 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1656698400,
		"url":"https://www.raymondcamden.com/2022/07/01/reading-comic-books-in-the-jamstack",
		"content":"One day I'm going to write a really good, Enterprise-grade blog post on Jamstack stuff and not talk about silly crap that has no business use. Today is not that day. For those of you who may not be avid comic book readers, you may not know that just like &quot;regular&quot; books, comic books come in digital formats as well. You can view them on your Kindle or via other applications and there's a pretty large market for them.\nOutside of the ones you read on a Kindle, comic books typically use the comic book archive format. This is not a special format, but literally just a compressed file containing the images. You can tell the type of compression by the extension:\n\ncbz - Zip\ncbr - RAR\n\nThere are also versions for 7z, ACE (never even heard of that), and TAR (AKA the format I have to google every time I need to uncompress it).\nI thought it would be fun (I have a weird idea of fun) to see if I could add support for digital comics to Eleventy. It was a bit more work than I anticipated, but I thought I'd share what I built. It's rough... and it's not a great reading experience, but it works. My solution will use Eleventy, but you should be able to use this in other static site generators.\nThe Road Not Taken\nNormally I don't spend a lot of time talking about the approaches I didn't select, but in this case, I spent a lot of time chewing on it and want to share why I decided to not take a certain approach. One of the more cool recent features of Eleventy is the support for custom templates. So for example, I can set up .pdf as a valid template extension and tell Eleventy how it should handle it.\nI really wanted to go this route, but the issue I had is that I knew I was going to have a lot of files related to my comics. So for example, a cover thumbnail. It wasn't just &quot;one to one&quot; (.cbr to .html for example), and my instincts just made me feel like that made the feature a poor fit for what I had in mind.\nAgain, I could be wrong here, and that's why I'm sharing why I didn't use the feature.\nSource Material\nMost comic books are copyrighted material, but due to how long comics have been around, turns out there are quite a few in the public domain. I found an excellent website, Comic Book+, that provides access to over forty thousand free comics you can download. Check it out if for no other reason than that it's a great way to see early comics. For my demo, I grabbed three comics from their collection.\nThe Plan\nGiven a source comic, I decided on the following plan:\n\nFirst, extract the file into a directory of pages. That will let me show the pages via HTML.\nGiven that we've got a directory of pages, assume that the first image is a cover, and create a nice-sized thumbnail.\nStore all of the above in a cacheable location so our builds will go faster.\nFor the front end, I decided on a super simple reading experience - basically buttons to go back and forth and one page, centered, at a time.\n\nParsing Comics\nAs I said above, I decided against using the custom template feature in Eleventy and instead used the global data feature. I created _data/comics.js and started coding. I made more than a few assumptions along the way, first of all being that I would only support .cbr and .cbz. That meant I'd only need two libraries to parse the files.\nMy file is a bit complex, and honestly, could use a bit of refactoring, so let me share it in bits first so hopefully, it will make more sense.\nFirst, a few constants:\n\nNext, initialize my result, and read my directory:\n\nAt this point, we've got an array of files. I looped over them and specified f as the current file:\n\nFor each comic, I need it to have its own cache directory under the main cacheDir defined above. I used the slugified version of the filename for that:\n\nNow we need our first main process, extracting the contents into a 'pages' directory:\n\nFrom the top - we first see if the pages folder exists and if not, create it. We then have two branches. For zip files, I used adm-zip, a relatively simple library for working with zips. The only issue I ran into is that I couldn't use the &quot;one-liner&quot; (commented out in the code sample above) as I needed to 'flatten' the result. So for example, given spiderman01.cbz, the contents inside may be in a subdirectory spiderman (01)\\..... I want a 'flat' set of images which is what the third argument to extractEntryTo does.\nFor RAR files, it was a bit more difficult. I used node-unrar-js which was just a very difficult library to use. As I plainly said, I have no idea why that line there at the end worked, but it did. I'm sure that's totally fine and safe to deploy to production.\nOk, so at this point, we've extracted the comics. Now we need to make thumbnails:\n\nFor this, I used the Jimp, another easy-to-use library. The only real oddity I came across was one archive containing a Thumbs.db file I needed to ignore.\nThe final part takes my data and prepares it for use on the site:\n\nYou'll notice I set a number of pages value ",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Testing the Netlify Cache Plugin with Eleventy",
		"date":"Sun Jun 26 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1656266400,
		"url":"https://www.raymondcamden.com/2022/06/26/testing-the-netlify-cache-plugin-with-eleventy",
		"content":"For months now I've been meaning to check out, and try, the Netlify Caching plugin. This plugin lets you cache resources between builds saving you time when doing builds. I didn't doubt it worked, but I needed to give it a try myself to see it in action. To test it out, I used Eleventy, but note that you can use any static site generator with the plugin. (It just won't be as cool.)\nDemo Site\nFor my demo, I wanted to mimic something that would be have a slow process of some sort. I wanted to keep it simple though so I decided I'd just &quot;fake&quot; the slowness. For my made up process, I'm going to tell Eleventy to scan a directory of PDFs, and for each PDF, it's going to &quot;process&quot; them. That process could be running an extract API to get the contents of the PDF. It doesn't really matter as it's going to be fake.\nTo create this, I built a new Eleventy site (I'll share the repo at the end) and added _data/pdfs.js:\n\nThe data file begins by scanning a directory (sourceDir) for a list of PDFs. For each PDF, it needs to process them. It first checks if a cached file exists for the process (cached_file) and if not, executes my fake process. Note I'm using a setTimeout here to force the process to wait five seconds. When done, it stores the result.\nThe result is a set of data files that have the same name (except for extension) for each of my PDFs. The data is just the file name and a date stamp representing when it was created.\nFor my GitHub repository, I will not be committing the scanned results, that has to happen in production, but I did add a .gitkeep file to ensure the directory was in the repo.\nFinally, I built a simple index.liquid file to render the results:\n\nYou'll notice I output the time the file was created on top, this will be useful for comparison's sake once the cache is working. For each PDF I render the name and the time it's data was made.\nOnce I confirmed this was working locally, I deployed to Netlify. I did a few deploys and in general it averaged around a minute and fifteen seconds:\n\n\n\nIf I keep repeating the builds, the times don't change and if I look at the logs, you can see that the cached results from the fake process never exist:\n4:45:28 PM: The cached file does NOT exist. Getting my data.\n4:45:33 PM: got my parsed pdf info  {\n4:45:33 PM:   input: 'a-midsummer-nights-dream_PDF_FolgerShakespeare.pdf',\n4:45:33 PM:   made: 2022-06-26T21:45:33.617Z\n4:45:33 PM: }\n4:45:33 PM: pdf source input alls-well-that-ends-well_PDF_FolgerShakespeare.pdf\n4:45:33 PM: cached_file location ./scanned_pdfs/alls-well-that-ends-well_PDF_FolgerShakespeare.json\n4:45:33 PM: The cached file does NOT exist. Getting my data.\n4:45:38 PM: got my parsed pdf info  {\n4:45:38 PM:   input: 'alls-well-that-ends-well_PDF_FolgerShakespeare.pdf',\n4:45:38 PM:   made: 2022-06-26T21:45:38.621Z\n4:45:38 PM: }\n\nCache the Things!\nAlright, now for the cool part. To get caching enabled, you need to do a grand total of two things. First, I &quot;installed&quot; the plugin via netlify.toml:\n\nThe plugin defaults to a particular folder (.cache) but as you can see, I changed to match the cache directory my code was looking for.\nThe second step, and a crucial one, is to install the plugin locally so it shows up in your package.json:\n\nNow - when I deployed the first time, it didn't show any improvement in caching because it needed to be setup, but the second and further builds showed a dramatic improvement (well, relatively):\n\n\n\nNow I'm seeing builds around thirty-five to forty seconds. And the logs confirm the cache is working:\n4:54:07 PM: running pdfs data file\n4:54:07 PM: pdf source input a-midsummer-nights-dream_PDF_FolgerShakespeare.pdf\n4:54:07 PM: cached_file location ./scanned_pdfs/a-midsummer-nights-dream_PDF_FolgerShakespeare.json\n4:54:07 PM: The cached file DOES exist.\n4:54:07 PM: pdf source input alls-well-that-ends-well_PDF_FolgerShakespeare.pdf\n4:54:07 PM: cached_file location ./scanned_pdfs/alls-well-that-ends-well_PDF_FolgerShakespeare.json\n4:54:07 PM: The cached file DOES exist.\n4:54:07 PM: Creating deploy upload records\n4:54:07 PM: pdf source input antony-and-cleopatra_PDF_FolgerShakespeare.pdf\n4:54:07 PM: cached_file location ./scanned_pdfs/antony-and-cleopatra_PDF_FolgerShakespeare.json\n4:54:07 PM: The cached file DOES exist.\n\nIt Just Works\nI'm always happy when things just work and work well, and honestly this is a super powerful plugin for such little work to enable it. I do find it surprising that it isn't simply a setting in your Netlify site admin, but maybe it isn't something a majority of people need. If you want to see my live site (I probably won't update it again, sorry :), you can find it here: https://cache-test-camden.netlify.app/. The repo may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/cache_test\nPhoto by josh Glauser on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Related Content by Day of Year in Eleventy",
		"date":"Thu Jun 23 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1656007200,
		"url":"https://www.raymondcamden.com/2022/06/23/related-content-by-day-of-year-in-eleventy",
		"content":"Ok, chalk this up to something that is probably useful to one out of ten of my readers, but the idea's been bouncing around my brain for a few months now and I finally took the time to build it out. Imagine a content site that's been around for a while, for example, this blog (twenty years next February). It may be interesting to tie articles to content written in the past, specifically, on the same day in previous years. This requires a site with years of content and enough content such that there would actually be a decent chance of that happening, but I could see newspaper sites or other news organizations being able to meet that criteria. For my demo, I took three years of content from this blog and got to work.\nI began by creating a &quot;post&quot; template that would check for, and optionally include the content:\n\nThe template calls a filter, onthisday, and passes the date of the article itself, and all the content it needs to check, in this case a collection of posts. Let's look at that filter:\n\nBasically - check the year and ensure it's less than the current year, then check for matches on month and date (which is the day of the month). Here's an example of how it looks when content is found:\n\n\n\nThat's it. I do think you could make it a bit better if you perhaps allowed for a bit of wiggle room - like maybe return results that match the day, but also one before and one after. That would help find matches near weekends when publishing may slow down. If you want a copy of a complete demo of this, you can find it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/onthisdayfilter\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Quiz with Eleventy and Eleventy Serverless",
		"date":"Sat Jun 18 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1655575200,
		"url":"https://www.raymondcamden.com/2022/06/18/building-a-quiz-with-eleventy-and-eleventy-serverless",
		"content":"A few days ago, I was thinking about what a &quot;quiz&quot; would look like in Eleventy. I put that in quotes because there's a lot of different ways you can think of quizzes and how they're built. For my demo, I set my list of features to the following:\n\nI defined a quiz as a set of questions. Each question has a right answer.\nQuizzes would be defined in JSON, letting you add new quizzes by simply dropping in a data file. In the &quot;real world&quot;, I think you could imagine a user friendly editor that uses the web to add/edit/delete quiz data with JSON files being how they are persisted. Or heck, store them in a database and have a process to export the tabular data into JSON.\nWhile a quiz could be done in client-side JavaScript, I wanted a non-JavaScript, pure HTML approach. So our quiz will simply display the list of questions, submit to an Eleventy Serverless process, and return the result.\nA quiz is essentially a form, and building a &quot;dynamic form renderer&quot; can be the very definition of going down the rabbit hole. To keep things simple, I decided to support single choice questions, multiple choice questions, and true/false questions. No branching or conditional logic, just three types of questions.\n\nWith that in mind, let's me describe what I built.\nDefining Quizzes\nAs I said above, I went with a pretty simple quiz setup in terms of what I supported. There's three type of questions only: single choice, multiple choice, and true/false. A quiz should have a name and description, to help define itself, and then a list of questions. Here's an example quiz, built in JSON:\n\nEach question must have a text value at minimum. The type value defaults to single so it can be left off. Each question, except for &quot;true/false&quot; types, have an array of answers. Finally we use correctAnswer to define the right answer for the question. Note that for multiple, this is an array. (Although you could have an array of one item.)\nIn order to keep the quizzes separate from the rest of the site in terms of file structure, I made a folder named quizzes and dropped a few json files in there.\nLoading Quizzes\nWith a format defined for creating quizzes, how do we load them? I created a _data file named quizzes.js:\n\nFrom top to bottom - you can see it begins by reading my directory of quizzes. It reads each in and parses the JSON. As stated in the comments, this would be an excellent place to make use of JSON Schema. Not only would this let me provide code hinting for editing quizzes (although like I said, I'd imagine a web-based editor for non-technical users), it would let me validate quizzes before trying to load them.\nMy code does one small bit of manipulation and handles the default value of single when type is not defined for questions.\nAt the end, I've now got a quizzes object available in data.\nRendering Quizzes\nOk, this is the complex part. Even with keeping my quizzes to three different types of questions, it's still requires a bit of code to handle it. I used Eleventy's pagination feature to render one page per quiz:\n\nFrom the top, I specify that for each item in my quizzes data array I should have one unique file. The path is based on the quizzes name, passed through the slugify filter. Notice my form is submitting to submitQuiz which I'll cover next. Also note I specify method=&quot;get&quot;. This is required as Netlify Serverless functions don't get POST parameters (note - I'm double checking that and I may be wrong).\nInside the form, I loop over each question. This is where the real complexity is. I begin by getting the current loop index. I need this so I can &quot;name&quot; my questions in the form. You will see each one is named q{{qIndex}} which will output to q1, q2, and so forth.\nI then give a unique ID value to each answer. This is based on the question name and the index of each answer. So for example, the first one would be q1_1. I do this so I can assign a label for each answer. (I only recently discovered that you can skip for if you  wrap the field and text in &lt;label&gt; tags. I probably would have done that to keep things simpler.) For each answer, I output the dynamic text. Finally, the &quot;true/false&quot; one is a bit more static as the answers aren't dynamic.\nHere's my initial quiz displays:\n\n\n\nChecking Quizzes\nThe final step involves taking the user input and checking how well they did. If you remember from above, my quiz points to /submitQuiz/. For that, I'm going to use an Eleventy Serverless function. (If you need a refresher on how it works, check the core docs as well as my introduction.)\nI followed the directions to scaffold Eleventy Serverless support and then built my template:\n\nIt's rather short because most of the logic is done elsewhere. Eleventy Serverless templates have access to the query string, which based on the form, would look like so:\nhttp://localhost:42357/submitQuiz/?quiz=alpha&amp;q1=3&amp;q2=1&amp;q2=2&amp;q3=true\n\nI've got the name of the quiz (slu",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Tue Jun 14 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1655229600,
		"url":"https://www.raymondcamden.com/2022/06/14/links-for-you",
		"content":"Normally I save these &quot;Link&quot; posts for the weekend, but I'm just getting back from a week-long vacation (we went up to\na place in the mountains south of Chatanooga) and I've been swamped trying to catch up with emails and stuff, I haven't had time to post a &quot;real&quot; entry here. I thought I'd share a few things on my mind in hopes it's of interest to my readers. (I know I ask this every now and then, but if you are getting these emails and you are enjoying these, let me know, please. Or heck, if you hate em, let me know too. ;)\nGlitch is Amazing\nI've been hearing about Glitch for some time now, but never had the chance to try it out myself. They came across my news feeds recently with their acquisition by Fastly so they've been on my mind recently. Glitch is an online editor to build web apps - from simple static HTML sites to Node applications. It encourages sharing (using the term 'remix' instead of fork, which I dig) and has an incredible editing experience. It has a great free tier and a shockingly cheap pro plan (details on their pricing page). I've got some plans on how to use Glitch for some work projects, but also want to dig into using it for demos. I like and have used CodePen quite a bit and will probably keep doing so for smaller things, but I'm going to see where Glitch fits in with my development style.\nLinting Text with Vale\nLinting refers to checking code for stylistic rules. For example, using tabs versus spaces. (Because that makes sense and anyone who tells you otherwise is crazy.) While generally, linting refers to code, there are also tools to lint text for things like spelling, punctuation, and inclusive text.\nI recently came across a cool example of this in a blog post by Nwokocha Wisdom Maduabuchi, &quot;How to automate linting your documentation using Vale and Github actions.&quot;. The title does a pretty good job of explaining what it covers, but I'll just point out that not only is it a great example of prose text linting, it's also a good job of GitHub Actions. (I blogged about my first experiments with it here: Integrating Eleventy with GitHub Flat Data)\nIf you like it, give him a follow on Twitter here: https://twitter.com/Joklinztech\nSide Effects\nMy wife and I are huge gamers (video, board, card), and we recently picked up a really cool one called Side Effects. Side Effects is a card game based on mental illnesses, and while that sounds a bit disturbing, it's a simple game, it's tongue-in-cheek, and it's got some beautiful artwork. As someone who has personally dealt with a few of the illnesses in the game, I can say it's silly, fun, and a hoot to play. If you decide to pick it up, please consider using the link below as I'll get a few pennies.\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Image Upload Preview in Alpine.js",
		"date":"Fri Jun 03 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1654279200,
		"url":"https://www.raymondcamden.com/2022/06/03/image-upload-preview-in-alpinejs",
		"content":"So as I've said a few times now, I'm on kind of a trend here on rebuilding previous demos in either vanilla (i.e. no framework) JavaScript or my new favorite framework, Alpine.js. In that vein, I've got an update to a post I first wrote nearly a decade ago, &quot;Adding a file display list to a multi-file upload HTML control&quot;. I followed that up with a Vue version here: &quot;Vue Quick Shot - Image Upload Previews&quot;. The idea was to enhance a form that asks for image uploads by adding a simple preview of the image. This helps as it lets the user be sure they've selected the right file.\nAlpine.js is very similar to Vue so a lot of what follows is very close to the previous post. Here's the HTML I used - keep in mind a real form would probably ask for more fields:\n\nOn top, the x-data directive maps the form to the JavaScript I'll show in a bit. I've then got a label and my input field. Note the use of accept to restrict the user to image files. (And as always, don't forget you can't trust stuff like this. Your code handling the upload will need to verify that an image was really uploaded.) The input field uses @change to specify a method to run when the value changes, previewFile. Also, note the x-ref. Later on, my code will need to directly access the DOM item and this is the Alpine way of doing it.\nThe second block handles the preview. I wrap it in an x-if so that the preview is only there when a value is present. That value, imgsrc, is bound to the image tag. Now let's look at the JavaScript:\n\nI begin with the Alpine-specific listener, alpine:init, and inside that, I create the data for my application, named imgPreview. In the application, I've got a grand total of one variable, imgsrc, and one method, previewFile. In previewFile, we first see if a file was selected and if it was an image. If we pass that, we reset imgsrc to null (in case they select multiple times) and then create a FileReader object. We read it in as a data URL and then set it to the imgsrc variable once it's loaded.\nThe final part is a bit of CSS to ensure the image stays relatively small:\n\nWant to give it a try? Check out the CodePen below:\n\n  See the Pen \n  Untitled by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun May 29 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1653847200,
		"url":"https://www.raymondcamden.com/2022/05/29/links-for-you",
		"content":"A few quick links here to end May. This week has been incredibly difficult for many people, myself included. Please remember that therapy can be life saving, it was for me. If you need help, please sure to reach out and get support. Your mental health is just as important as your physical health.\nMySQL developers rejoice!\nFor my first link, I want to share a cool Visual Studio Code extension my buddy Scott Stroz shared, the MySQL Shell for VS Code. This is a dang well done extension for folks who use MySQL. Even better, it's done in a notebook interface which is something I've only really started using since I began playing with Python. To give you an idea of how it looks, I'm going to steal one of their graphics:\n\n\n\nSlick, right? Oracle has been doing some cool stuff lately and I've been meaning to dig into their stack more for quite some time now.\nOutside articles\nI keep track of my &quot;external&quot; writing on my About page, but I realized these blog posts would be a great way to let my subscribers know of recent articles. I write for a few different places, but most recently I've done some posts for Verpex. In particular, here are my recent articles there:\n\nTop Six Things to Avoid in Your Website\nExploring Alternatives to Google Analytics\nJamstack vs. WordPress: My Journey\n\nI've also written for Serialized:\n\nA (Hopefully) Gentle Introduction to Serialized and Event Sourcing\n\nEleventy update\nAnd lastly, here's the most recent update from Eleventy:\n\nFor those reading this on a mobile device, here's a link: https://www.youtube.com/watch?v=oCTAZumAGNc\nAs always, cool stuff, and I'm so incredibly happy I've built my site on it!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Integrate Your Netlify Builds with Tidbyt and Pipedream",
		"date":"Thu May 26 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1653588000,
		"url":"https://www.raymondcamden.com/2022/05/26/integrate-your-netlify-builds-with-tidbyt-and-pipedream",
		"content":"A few months ago I randomly came across a Facebook app for a little device that looked completely unnecessary yet also completely cool: Tidbyt. The Tidbyt is a little hardware LED device that shows different pieces of data, for example, sunrise and sunset:\n\n\n\nOr moon phase information:\n\n\n\nAnd of course, Nyan cat:\n\n\n\nAs I said, this is not something I need in my life, but I'm so happy I got it. It's just a fun piece of hardware. But even cooler is that it's got an API. You can use the API to build full apps or just one-time messages. Their developer documentation is pretty well done, and while I ran into a hiccup or two, it was pretty easy to start sending custom messages to my hardware. The only real nit is that you must send a picture, a 64x32 image. That's kind of a bummer when you want to just send a quick text message, but there are multiple ways to generate images out there so it's not too much of a problem.\nWith that in mind - let me share how I built a Netlify integration!\nWhat I'm Building\nShort and simple - when I deploy my site (this site, the one you are on right now!), I want a notification when the build is complete. Something like so:\n\n\n\nNetlify takes between two to four minutes when building my site, so this way I can get a visual notification when the build is done. Let me share how I did it.\nThe Pipedream Workflow\nThe main part of the process is a Pipedream workflow that uses an HTTP trigger. Essentially a simple serverless endpoint. Outside of the trigger is one code step. Now, here's where things got a bit complex. I knew I needed to generate an image with text. While I've done some work with Node libraries in the past to read and manipulate images, it's been a while since I had to use one. I also ran into an issue where the NPM modules I found didn't work well on Pipedream.\nAt first, I thought about using a placeholder image service. Many of them let you define an image with text on it, already centered, and while I struggled a bit, I did get something working there.\nWhile chatting about this on the Pipedream slack, one of the members there recommended Bannerbear. This is a really cool image API service that has a very simple API. You design a base template using their web-based tools, and can then use an API to say, &quot;Take this template, manipulate it, and return me the result.&quot;\nHere's how I used it:\n\nAs you can see, there isn't too much to it. I specify a template (which for me was just a black rectangle using the proper size for Tidbyt) and then the text I want to use. I send this to Bannerbear and the result is a JSON packet that includes the URL to the generated image. Now I want to be clear, Bannerbear has a heck of a lot of options, I just used the quickest and simplest thing I could to generate my image, but it's definitely a service to check out. Here's what that image looks like when done:\n\n\n\nThe next step was to get that data on my Tidbyt. As I mentioned, they have an API, but you can also find an NPM package: https://npm.io/package/tidbyt. Once installed, and with your keys setup in environment variables, it's pretty quick to use:\n\nNow... this was the first version and you probably noticed. I'm calling an API to generate a dynamic image... with static text. That's kind of dumb. So after I got it working, and sent Bannerbear a virtual high five, I stopped using their API and simply saved the image to my Pipedream workflow attachments. Now the code step is a bit simpler:\n\nThe Netlify Integration\nThe final step was to copy the URL from the Pipedream workflow and tell Netlify to hit it on a successful build. I went into my site's settings, Build &amp; deploy, Deploy notifications. I added a new notification and selected the &quot;deploy succeeds&quot; notification. Lastly, I just pasted in my URL from Pipedream:\n\n\n\nWrap Up\nAs I said, I really like the hardware, and there are a lot of apps you can use with it. The API is nicely done and I could do something a lot more complex, but for a quick integration test, this simple workflow worked well. Let me know if you pick up the device and what you think.\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building Table Sorting and Pagination in a Web Component",
		"date":"Mon May 23 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1653328800,
		"url":"https://www.raymondcamden.com/2022/05/23/building-table-sorting-and-pagination-in-a-web-component",
		"content":"Last week I blogged about my first experience building a simple web component. As I said, this was something I've heard about for years but never got around to playing with. If you read that first article, you'll see it didn't take a lot of work to get started. I didn't need a build process or a framework, just a JavaScript file to define my custom component. If you are a regular reader here, you know I've built the same demo a few times, a basic table with Ajax loaded data that supported sorting and pagination. As a refresher, here are those previous articles:\n\nBuilding Table Sorting and Pagination in Vue.js\nBuilding Table Sorting and Pagination in JavaScript\nBuilding Table Sorting and Pagination in Alpine.js\n\nIn each of these articles, I hit a back-end service (https://www.raymondcamden.com/.netlify/functions/get-cats) that returned an array of cats. Each array instance had a name, age, breed, and gender value. For each of my previous demos, I began with a demo that simply loaded the data and rendered it. I then added sorting. As a final iteration, I then added pagination.\nVersion One - Just Rendering\nSo how would I build this as a web component? I began with a JavaScript file, datatable.js. My plan for the component's API was rather simple. One required attribute points to an API and an optional attribute that would let you specify the specific columns to output. Here's the simplest use case:\n\nAnd here's one specifying the columns:\n\nIn my first iteration, I simply focused on rendering:\n\nFrom the top, my constructor first checks attributes and ensures it has at least the src attribute and optional cols. I wasn't exactly sure what to do when src wasn't passed, but in general, web pages 'break' nicely, and I figured just exiting was the simplest solution.\nI then begin creating my DOM, in this case, a table with a head and body. I create a style sheet to add borders as well.\nThe logic for rendering the table is broken out across a few methods. load handles fetching the data and when done, calls out to render. I broke render up into two more functions, one for the header and one for the body. I was thinking ahead a bit and figured I would not want to re-render the header on sorting or paging, just the body. Finally, note that attributeChangedCallback handles noticing src values and will call load. This works in my &quot;just plain html usage&quot; and would work if I used JavaScript to change the src value dynamically. Check out this version here:\n\n  See the Pen \n  WC Table1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two - Sorting\nFor sorting, I made a few changes. First, in renderHeader, I changed it like so:\n\nI use a data attribute to define the column to sort by and then add an event listener for each header to listen for click events. My sort event is as follows:\n\nI get the sort by column by examining the data attribute of the element that recognized the click event. After that, it's a regular JavaScript sort function and I run renderBody.\nNow, at this point, I ran into an issue. In sort, the value of this no longer pointed to the main scope of my component. I had no idea why. I did some googling and ran into this: This is why we need to bind event handlers in Class Components in React. It seemed like a very similar issue and while I can't promise to understand the issue completely, but it's solution worked well for me. In my constructor, I added this at the end:\n\nAnd it worked like a charm. You can see the updated version here:\n\n  See the Pen \n  WC Table1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Three - Paging\nFor the third and final version, I added paging. In my previous two editions, the 'root' of my component was the table tag. Because I was going to add buttons for navigation, I ended up making a new div to contain them. It didn't occur to me to use tfoot and now I kinda wish I had, but I'm ok with that. Here's the updated constructor with the new DOM elements as well as two new event handlers for navigation. I set the page size to 5 as my array of cats isn't very large.\n\nNotice that I repeat the bind call for my new event handlers. Pagination is done like so:\n\nAnd then renderBody is updated with a filter call to just get the &quot;page&quot; of data:\n\nYou can demo this version here:\n\n  See the Pen \n  WC Table2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWhat's Left?\nSo, all I really did here was build the bare minimum. As long as I've been doing client-side development, there have been frameworks out there with super complex data tables. I could see adding support for things like, &quot;my API returns an array, but it's in a subelement named items&quot;. I could see passing the page size as an attribute too. Maybe even a colLabels attribute to let me specify my header labels. You get the idea. :) If this is helpful, or if you have any questions, let me know!\n",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My First Web Component",
		"date":"Wed May 18 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1652896800,
		"url":"https://www.raymondcamden.com/2022/05/18/my-first-web-component",
		"content":"As a technology, web components have been on my radar for quite some time. From what I can see, the first, or the one of the first mentions of this was way back in 2011, over a decade a ago. In that time, browsers, all of them actually, came around to supporting them (except for one holdout for a part of the specification, and you get one guess as to who the holdout is), so over the weekend I took a quick look at the technology to see how hard it would be to build a simple demo. I've got to say I was rather surprised. I've only scratched the surface of the technology, and I've got a good idea for a follow up post, but I thought I'd quickly share the simple example I built and my thoughts on working with the tech in general.\nWhat exactly is it?\nAt a high level, a web component lets you define a custom HTML element. So for example, I could do this:\n\nThe definition of pet-cat comes externally and can consist of any regular HTML blocks. So the practical result of the above could be:\n\nThese elements act just like regular HTML tags. You can even use JavaScript to create new instances of them and dynamically change their attributes.\nI highly suggest reading the MDN reference for Web Components as it goes into great detail, but the main building blocks consist of:\n\nThe ability to define a custom element (pet-cat above) in JavaScript\nThe Shadow DOM, which sounds really cool, but is basically a way of saying a document tree that is encapsulated inside itself and away from the rest of your document. I saw a great example of this and can't remember the source, but think of the &lt;video&gt; tag and how it has built in controls for working with videos. That's a DOM that's encapsulated within itself.\nAnd finally, HTML templates that are not rendered but used by the web component for layout. I actually did not touch this aspect for the demo I built, so it's not 100% necessary.\n\nWeb components come in two main flavors:\n\nCompletely unique ones like the example I gave above.\nComponents that modify existing tags, recognized via the as syntax: &lt;ul is=&quot;something-else&quot;&gt;. This is where we hit the issue with that one particular browser. Safari does not support this style, and as far as I know, never will. Who knows. To be honest, I find this style less appealing then the previous one so it doesn't bother me too much.\n\nOk, but why?\nRight away I can see that web components would be a great boon to UI libraries. I checked and while Bootstrap doesn't support it, it's on their radar. Having using BootstrapVue, I can tell you the experience of using Bootstrap with components is significantly better than &quot;regular&quot; Bootstrap. As an example, here's a simple tabbed UI:\n\nWhile not difficult, compare it to this:\n\nI can also see this being really useful inside an organization where consistent UI/UX/etc elements need to be built across a large site. Using web components would certainly make that simpler.\nWith what I said above, I don't necessarily think it's going to be something every developer uses on ever little project, but that's ok. We've seen other JavaScript improvements that are more useful to library developers than day to day development.\nHow about an example?\nGive me the kitty...\nFor my first test, I built a quick wrapper for PlaceKitten. I created a file, cat.js, and defined it as such:\n\nI've got a class that extends a base HTMLElement. It must have a constructor that calls super. That shadow variable there defines an 'open' interface which means the parent could &quot;reach&quot; into the DOM if necessary.\nNext I have the logic for the component. Define a default width and height and override it if specified by the user.\nMy DOM is a div tag with an image inside. When I'm done building it, I add it to my shadow I'm done.\nAt the end, make note of the define call. Web components must be kabab-case, ie somegthing dash something.\nIn an HTML template, I just include it and use it:\n\nHere's the result:\n\n\n\nSurely the web gods intended components to be used for cats, right? If you open up devtools, you can see them just as any other element:\n\n\n\nIf you want, you can view it online here: https://cfjedimaster.github.io/webdemos/webcomponents/test1.html\nSo that initial example was so trivial that it wouldn't work really well in a production environment. Specifically it would fail in one respect. If I used JavaScript to make a new instance of the element and then set the dimensions, it would fail:\n\nWhy? Because a web component has to define what attributes it will &quot;listen&quot; to for changes, and has to have custom logic of some sort to implement those changes. Luckily this can be done two methods. First, we define the attributes we want to watch:\n\nAnd then we can use attributeCHangedCallback to handle those changes. It looks like so:\n\nI updated my cat element to make use of this:\n\nNote that I abstracted out the logic to get the image source in a function, getURL. I can then use that in the constructor as well the",
		"tags":[
	        
            "javascript",
            
            "web components"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Kicking Off a Pipedream Workflow on a Full Moon (Because Why Not?)",
		"date":"Mon May 16 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1652724000,
		"url":"https://www.raymondcamden.com/2022/05/16/kicking-off-a-pipedream-workflow-on-a-full-moon-because-why-not",
		"content":"File this under &quot;You probably will never need it but...&quot;, did you know that Pipedream is flexible to the point of allowing you to define truly customized ways to kick off workflows? How flexible? What about the ability to fire workflows on a full moon?\n\n\n\nThis is the email I got yesterday, and yep, I can confirm this happened:\n\n\n\nSources in Pipedream are anything that can start a workflow. So for example:\n\n&quot;On a new file added in Google Drive...&quot;\n&quot;On a new Tweet from a user...&quot;\n&quot;On a particular schedule&quot;\n\nWhat's cool though is that this system is open to customized sources as well. Pipedream's docs describe how to create custom sources from either the UI or CLI. A few months ago I was bored and thinking about the problems werewolves may have on the dating scene and it occurred to me - a simple notification system for the full moon would probably be helpful!\nFollowing the directions on building from the CLI, I built a CRON-based source that made use of the excellent API from visualcrossing. Their Timeline includes a lot of information but more importantly returns a moonphase value that goes from 0 (new moon), to 0.5 (full moon), and finally to 1 (the next new moon).\nHere's the endpoint I ended up using - I had to ask for the moon phase and also asked it to filter the result set to just the moon phase (to make it a bit speedier):\nhttps://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/70508/today?key=${this.visualCrossingKey}&amp;include=moonphase&amp;elements=moonphase\n\nSo how does this look inside a Pipedream source? Here's the complete code:\n\nThe code begins with the metadata related to the source, including the name, and importantly, a props block where I can specify both how it works (a timer) and that it requires a key from visualcrossing. This lets me deploy the source and share it with others as my key isn't embedded in the code.\nOnce deployed, I made a new workflow, configured the source with my key, and ended it with an &quot;email&quot; me action built by Pipedream:\n\n\n\nThis is an older V1 workflow on Pipedream so I can share it here: https://pipedream.com/@raymondcamden/email-on-full-moon-p_V9CaRmP. While my specific example here isn't terribly useful, I hope it's a good demonstration of what's possible. Let me know what you think.\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Rebuilding TBS Horoscope (Again) as a Pipedream Twitter Bot",
		"date":"Fri May 13 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1652464800,
		"url":"https://www.raymondcamden.com/2022/05/13/rebuilding-tbs-horoscope-again-as-a-pipedream-twitter-bot",
		"content":"I've got a problem. Honestly, I do. I keep building stupid Twitter bots. But - I can honestly say that this time - like many times - I kinda did something cool and learned something, and that makes it worthwhile, right? So what did I do this time?\nMany years ago, almost eleven actually, I built a TBS Horoscope application. This application created completely fake and silly horoscopes (the TBS stands for total bull pucky) and made use of Flex Mobile. Remember that? Even better - it was built for the NookColor.\n\n\n\nI miss Flex Mobile as it was a pretty fun platform to build with. Not to be outdone though - a few years later, I then rebuilt it as an Alexa skill. I built this using the OpenWhisk platform which was one of the easiest to use serverless platforms at the time.\nApparently, at some time I also uploaded it to the Amazon app store for 99 cents. I don't think I'll be retiring anytime soon.\nThis morning I decided to see how quickly I could get it running as a Twitter bot. This was done for no special reason and certainly not due to rumors that a certain billionaire was having second thoughts about his purchase of a social network due to the number of fake accounts it had on it. Honest. Would this face lie to you?\n\n\n\nAs I've shown here a few times now, the process of building a Twitter bot on Pipedream is a few simple steps:\n\nFigure out what account will be tweeting. Normally this is a new one so make yourself a new account. Twitter requires email addresses for accounts and I usually just go with the &quot;plus&quot; addressing trick on Gmail.\nCreate a workflow on Twitter that is schedule-based (assuming you want a bot to simply tweet on a schedule, you can build bots that look for keywords or activate on other conditions).\nFigure out what you're going to tweet\nSend the tweet (this is done by Pipedream - you don't need to code it at all)\n\nIn the above, literally, the only code you write is that second to the last step. So for me, this involved taking the code I had written previously to generate random, senseless horoscopes (unlike real horoscopes which are totally sensible) and then output them. Here's how that code looked.\n\nHere are a few examples:\n\nGemini:\nA dark stranger will enter your life. They will have a defeated island. You can buy a lottery ticket or a restaurant. Either is a good investment. Romance is blossoming like a sticky book! \nYour lucky numbers are 3, 9 and toddler.\n\nAnd then...\n\nAquarius: \nYour sign is in the second phase today. This is critical. Consider selling your byte for a good return today. Avoid romantic engagements today. Wait for a sign - it will resemble a magnificent analyst. \nYour lucky numbers are 5, 4 and grandmother.\n\nAnd finally...\n\nAries:\nA melodic boy will give you important advice today. You can buy a lottery ticket or a denmark. Either is a good investment. You will fall in love with a Sagittarius but they are in love with their spoon. \nYour lucky numbers are 3, 6 and insect.\n\nOk, so the code above is the logic for generating a tweet message. Initially, I just returned it as is... and then I realized something. My horoscopes may be too long. On a whim, I tried this in the excellent RunJS:\n\nIn my test, I saw between 30 to 50 strings that were too long. That's not too bad, and I thought... maybe I could simply loop until I got one that wasn't too long? Then I had a not-quite-as-smart thought - what if I just tried a few more times?\n\nThat is lame as you know what - but guess what? In my testing, the number of bad results went down to zero.\nSo back in Pipedream, my code step does this:\n\nHere's an example tweet:\nLeo: Your spirits are high today - but watch our for a powerful hydrogen. Today is a bad day to invest. Stock prices will change. Love is in the air. Unfortunately not the air you will be breathing. Your lucky numers are 4, 10 and parrot.&mdash; TBS Horoscope (@tbshoroscope) May 13, 2022 \nIf you like it, give ole TBS Horoscope a follow, and thanks for reading this far!\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Improved Utility Actions with Pipedream",
		"date":"Tue May 10 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1652205600,
		"url":"https://www.raymondcamden.com/2022/05/10/improved-utility-actions-with-pipedream",
		"content":"If you can't tell, I'm a huge fan of Pipedream, but it isn't the only service of its kind out there. If anything, I like Pipedream even more as it's opened my eyes to alternatives out there and has made me appreciate the &quot;low-code/no-code&quot; space even more. In particular, I'm really enjoying digging into Microsoft Power Automate and it's got me thinking about what could be brought over into Pipedream.\nOne place that Power Automate does a really good job of is providing logical actions for controlling the flow and execution of your workflows. So for example, if you have some weird custom logic that dictates if a workflow should end, you can define this without writing any code. That's certainly been possible in Pipedream as well, but typically via a short code-based step. I don't mind that, but one of the things I had hoped for were more 'utility' type actions that would let us define such things without code.\nIn the past few weeks, the Pipedream folks have released three different features in this space I want to highlight. For the most part, these were all things you could have done before (except one), it's just implemented in a more formal way now. Let's take a look!\nExporting Variables\nOne common thing I've done in Pipedream workflows (and thanks to Dylan Sather for showing me this right when I first started playing with PD) is to define a code step to configure different constants for my workflow. I'm not talking about things like API secrets, for that, you would use environment variables, but rather values you wish to use later in the workflow and want to be defined by themselves so it's easy to change. For example, here's a step from an older workflow that defines one value:\n\n\n\nLater in my workflow, I referenced it like so:\n\nAll in all, relatively simple, but with the new &quot;Export Variables&quot; action, you can skip the code step.\nTo use it, first note it's a tiny bit awkward. When adding a new step, if you search for 'export', you will not find it. Instead, type &quot;helper&quot; in the search field:\n\n\n\nClick on &quot;Helper Functions&quot; and it will be the first item:\n\n\n\nSelect that and you'll have a new export_variables action. Now - as I said - this was a bit awkward. In my mind, this kind of action was like the Code step, just a native part of what's available. I reported this on the Pipedream slack and Dylan said they are aware of this and are working to improve it. So just note if you're reading this sometime after I published, it may be simpler to add.\nAlright, once added, you're given an empty &quot;Configuration&quot; object:\n\n\n\nIf you click in there, you can begin entering name/value pairs. So for example, here are three keys and values:\n\n\n\nAlso, note I renamed the action to myconfig. Once you've done this, you can then refer to the values via steps.myconfig.config. So for example: steps.myconfig.config.name would return ray.\nSimple, right? I definitely recommend this for workflows that need configuration values defined at the beginning for future steps to reference.\nAborting Workflows with Filter\nI'm not sure &quot;Filter&quot; is the best name, but if you've ever needed to dynamically end a workflow based on some condition, the new Filter action will help you. As with the previous example, this is something you could have done before with a short code step. To begin, you can just search for filter:\n\n\n\nSelecting that will bring you to three choices:\n\n\n\nLet's start with the second and third as they are basically the same thing. If you only want to continue if X is true, you would pick the second. If you want to abort if X is true, you would pick the last.\nWhen creating conditions, you begin by specifying the type - either text, number, date, boolean, null, array, or object. This then determines the type of conditions. For example, if you are doing text comparisons, you can select contains, does not contain, matches, doesn't match, starts, or ends with. For numbers, you get what you expect: &lt;, &lt;=, &gt;, &gt;=, =.\nFor my test, I selected text, and I wanted to say: If a previous step name value was inside 'raymond', continue. I configured it as such and tested - and did not get what I expected:\n\n\n\nTurns out the configuration was the opposite of what I expected. The &quot;Value to compare against&quot; string was checked to see if it was inside &quot;Value to evaluate&quot;. Maybe I'm weird, but I thought it would be the opposite. To test, I simply changed &quot;raymond&quot; to &quot;ra&quot; and confirmed it would continue:\n\n\n\nThe first option from the list of Filter actions mentioned custom condition and this gives you a bit more flexibility. You can enter a dynamic reason and a dynamic condition. Here's an example where my condition is based on an age value defined earlier.\n\n\n\nOnce again, this could be done in a code step, but I prefer it as a proper action.\nPutting things off with Delay\nThis last one is also fairly simple, but as far as I know, impossible to do befo",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun May 08 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1652032800,
		"url":"https://www.raymondcamden.com/2022/05/08/links-for-you",
		"content":"Happy Sunday and Happy Mother's Day. Here are three things I want to share for today. As always, the idea is to help share resources with those of you lucky enough to avoid Twitter and to help/inspire/challenge you. Enjoy!\nThe first thing I want to share is something I've been mentioning over the past few years. An excellent way to keep up on what's going on is to sign up for one of the various newsletters run by Cooper Press. Begin by perusing their full list of free newsletters and I'll specifically call out a few I think are awesome:\n\nJavaScript Weekly\nFrontend Focus\nJAMStacked\n\nAgain, all of these are free. Best of all - you get a weekly newsletter of stuff and can just sit on it if you're busy.\nThe second link I'll share is to Dr. Axel Rauschmayer's excellent 2ality. His article on the triple dot syntax was shared in JavaScript Weekly last week and was an excellent reminder of what an incredible resource his site is. Dr. Rauschmayer goes very deep into JavaScript topics and I have to honestly say I don't always understand him, but that's my fault, not his. If you want to get a very detailed look at JavaScript, check out his site.\nFinally, a bit of nontech and tech. Last night I saw &quot;Doctor Strange in the Multiverse of Madness&quot;. I went with my wife, one of my sons, one of my daughters, and my brother-in-law. I loved it. My daughter loved it. The other three were more &quot;so so&quot; on it. That being said, if you're a parent, you should really consider it R-rated, not PG-13. Some of the horror is surprising at times. It didn't bother me but it absolutely surprised me.\n\n\n\nI'll share that one of my most favorite creations ever is the @randomcomicbook Twitter bot. It uses the Marvel API to share random comic covers from the history of Marvel's publications. I love the variety of art styles and seeing just how old some of their content is. If you want, you can check out the complete workflow at Pipedream: https://pipedream.com/@raymondcamden/random-comic-book-p_o7C5v1y\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building Table Sorting and Pagination in Alpine.js",
		"date":"Mon May 02 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1651514400,
		"url":"https://www.raymondcamden.com/2022/05/02/building-table-sorting-and-pagination-in-alpinejs",
		"content":"A few months back, I realized that one of my most popular blog posts (Building Table Sorting and Pagination in Vue.js) would be an excellent opportunity to update for a more plain (or vanilla if you will) JavaScript version. That post (Building Table Sorting and Pagination in JavaScript) was pretty fun to write. As much as I enjoyed using Vue over the past few years, I find myself more and more trying to rely less on external frameworks and sticking to simpler methods of getting things done. That being said... I am also really intrigued by Alpine.js.\nAlpine is lightweight (2 methods, 6 properties, and 15 attributes you sprinkle in your HTML) and considers itself the &quot;jQuery for the modern web&quot;. I was introduced to Alpine by an old friend from the ColdFusion community, Luis Majano, and decided about five minutes into his presentation that I wanted to learn more.\nAs part of that journey, I thought an update to the &quot;table sorting and paging&quot; code would be an excellent way to get some practice writing Alpine code. It should be obvious, but I'm new at this so please do not consider it the best example of Alpine, although honestly Alpine is so simple I feel (mostly) confident I did this right. That being said - reach out with improvements and comments.\nFor my demos, I'll be using a simple serverless function that returns an array of cats. You can hit that endpoint here (raymondcamden.com/.netlify/functions/get-cats) to see the complete list, or look at a subset below:\n\nVersion One - Just Rendering\nIn the first version, I'm just going to load the data and render it in a table. Alpine lets you decorate your HTML to bind it to data and custom functionality. Here's the HTML:\n\nIn the code above, the x-data line is the most important as it binds the table to Alpine data. Alpine lets you define code right inside the attribute, but I find that a bit messy. It's possible... I just don't like it.\nNext note the two template tags. The first will show or hide the loading table row based on whether or not my data has loaded. Next, I loop over my data (you'll see this defined in a bit) to render the various cat properties. There are two big differences here between Alpine and Vue. In Alpine, your IF/FOR constructs must be on a template tag. Secondly, you don't use mustache style interpolation for values, but instead, use either x-text or x-html.\nNow for the JavaScript:\n\nIn this relatively simple example, I only have one piece of data, a cats array, and I use init to automatically fetch my data and store it.\nAs a quick aside, before I share the CodePen below, one of the things I struggled with was a 'chicken and egg' problem around the document event (alpine:init) and Alpine being loaded via the CodePen JavaScript settings. You'll notice that I use a script tag in the HTML (I didn't share that above) to get around this issue. Before that I kept having issues with Alpine being unable to 'find' catData and it was just plain annoying. Anyway, here's the complete demo:\n\n  See the Pen \n  Alpine Sortable Table by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two - Sorting\nFor the next version, let's add sorting. Sorting will be enabled by clicking on a table header. Clicking once will sort in one direction, clicking again will reverse the sort. First, I added click events to the header:\n\nNote the use of the shorthand, @click. Alpine also supports the longer x-on:click style as well. I see no reason to use that. Now for the updated JavaScript:\n\nI added two new pieces of data, sortCol and sortAsc. This helps me keep track of the current sort as well as the current direction. I then added the sort function which handles... well, sorting. And that's it. There is something I forgot to do here so be sure to keep reading. Here's the demo:\n\n  See the Pen \n  Alpine Sortable Table (with sorting) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Three - Paging\nAnd now for the grand finale - adding pagination to the data. Because my data set isn't terribly big, I'm going to use a relatively small &quot;page&quot; size of 4. To handle that, and keep track of the current page, I added two values to my data:\n\nBack in my HTML, I first added a new div wrapper to my code:\n\nI did this because my application is now larger than just the table, it has to accommodate two new buttons for paging:\n\nI realized that I was going to need a new way to loop over the cats and that would involve a virtual property. Alpine doesn't have that, but it supports getter functions for values so it ends up being the same. I'll share the entire JavaScript and then explain the changes:\n\nSo first off, nextPage and previousPage handle moving the user back and forth among the pages of data. The really important bit is pagedCats. It handles figuring out what 'slice' of the data should be returned. It also has to handle the data not being there yet. I didn't think I needed that as I thought init would fire before any read to data, but without it, ",
		"tags":[
	        
            "alpinejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun May 01 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1651428000,
		"url":"https://www.raymondcamden.com/2022/05/01/links-for-you",
		"content":"Happy Sunday, readers! Here's a few links for you:\nFirst up, Eleventy has officially hit version 1.0.1. Want a good description of what's changed? Zach Leatherman gave a good overview in the video below. Note that he helpfully shared with me the starting time for the 1.0.1 description and the embed below starts there, but you may want to watch the whole video as well.\n\nSpeaking of Eleventy, I was on a panel in their last meetup. The panel focused on migrating to Eleventy and had some great insights (even from me). This link here will take you to event page and note that you can also get an excellent &quot;state of Eleventy&quot; presentation from Zach:\nEp. 8: State of Eleventy and Panel on Transitioning to 11ty\nNext up is a cool update from Nuxt. I haven't used Nuxt in quite some time, but this video got me really interested in giving it another look.\n\nThe video is a bit over the top at times, but try to ignore that and focus on the improvements coming to Nuxt 3. Thank you to Nick Medrano for letting me know!\nThat's all for now - have a great rest of the week!\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Writing to Google Photos from Pipedream - Some Tips",
		"date":"Thu Apr 28 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1651168800,
		"url":"https://www.raymondcamden.com/2022/04/28/writing-to-google-photos-from-pipedream-some-tips",
		"content":"A few days ago I blogged about automatically backing up Switch screenshots via Pipedream. In that article I demonstrated automatically copying the photos to a Dropbox folder, but my original plan had been to use a Google Photos album. I ran into multiple issues there so I switched to Dropbox. I've figured out the issues and I'd like to share this with others. This will be a bit rough so I apologize in advance, but if you've got any questions, just ask me and I'll try to help.\nGetting the Album ID\nWhen I initially started to work on my workflow, I made a new album via the Google Photos UI, noted the URL and plucked out what I thought was the obvious ID value. Turns out, the &quot;real&quot; ID of an album is not displayed anywhere in the UI. No, instead you need to write code that makes use of the API. This code will get all your albums, display the ID and name, and let you find the one you want.\nI added a code step to Pipedream just for this purpose. You can get your albums, at most 50 at a time, like so:\n\nNote though that if you have more than 50 albums, you will need to paginate. The URL above takes a pageToken attribute that uses the result of the previous call to get the next page. You could build a fancy recursive function, or do what I did - just hard code it so I could see page 2 which was enough for me.\nGetting the Album ID - Again\nOk, so the next tip is really, really important. The code above is useless. Yes, useless. Why? Because the Google Photos API has a strict restriction on who can can write to albums. Turns out - only the OAuth application that created the album can write to the album. This doesn't apply to reads so it wasn't an issue with my previous experiments.\nLuckily it's rather easy to make an album. Here's just the relevant change you would need to make:\n\nThe result of this call will contain your ID. Note it - then disable the step. (Or delete it from the workflow, you really won't need it again.)\nThe Upload Process\nMy last tip was the most painful as it feels like it took forever to figure out. I want to thank David Lieb for pointing me in the right direction.\nSo, the process to write to an album is mostly straight forward. First, you upload your data to an upload endpoint. This returns a upload token value. Then you hit an API call to create a new &quot;mediaItem&quot; and associate it with the album.\nTwo simple calls - but I was stuck for a while. My issue came about because I was uploading the file incorrectly, and the first call from the upload API didn't make that really clear to me. I'd then do the second call and get a vague error back:\n\nThe docs for the Upload API are pretty clear, but I believe my issue stemmed from my use of Axios, something I don't normally use. You need to post the raw bits of your image to the endpoint. I thought I was doing that, and as I said, the result from the upload call was always kosher (as far as I knew), but it wasn't uploading the right way.\nFor me the trick involved ensuring I was using an ArrayBuffer, and not just a string version of the binary data. One quick way to get an ArrayBuffer if you are using Axios to fetch your data is like so:\n\nI know you can also craft ArrayBuffers from binary input so if you aren't using Axios to fetch the initial image, that should be an option as well.\nOnce you have that, you can then do the upload:\n\nAnd then add to the album:\n\nAnd just to prove it worked - here's the result in the Google Photos UI - back when I was skinny.\n\n\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Store Nintendo Switch Screenshots in the Cloud using Pipedream",
		"date":"Sat Apr 23 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1650736800,
		"url":"https://www.raymondcamden.com/2022/04/23/store-nintendo-switch-screenshots-in-the-cloud-using-pipedream",
		"content":"I've been a gamer for my entire life, starting with the venerable Atari 2600 back when I was almost late for grade school because I was having a really good game of Space Invaders.\n\n\n\nFor a long time, my go-to gaming system came from Nintendo. First the NES, then SNES, and Nintendo 64. But around when the GameCube came out, I wasn't quite as infatuated with them anymore. I started playing the Xbox and Playstation, and generally just felt like Nintendo wasn't making &quot;serious&quot; video game consoles anymore. (Now I realize how silly that sounds. Trust me.)\nMost recently though I picked up the Nintendo Switch and was blown away by how good of a device it was. While it's still my least used game console, it's an incredible machine and I've had a lot of fun with it. A few days ago I picked up a classic, the original &quot;Dragon Quest&quot; (Why? See this tweet.)\nWhile playing the game, I took a screenshot and wanted to share it with my wife. I remember that this was a bit of a weird process, so I did a quick Google and found this: How to Send Nintendo Switch Screenshots to Your PC or Phone\nThis is the &quot;new and improved&quot; process and frankly, it's still somewhat stupid. You scan a QR code on your mobile device, this changes your Wifi to match with the Switch, you scan a second QR code to open a web page, and then you download it. It works, don't get me wrong, but feels like something out of 1995. It's like the process you use to print photos at CVS. This isn't the only way though, you can connect your Switch physically to a machine and copy over USB or you can tweet your photo and grab it there.\nWhen I complained about how silly and obtuse this process seemed, Cory Birdsong responded that he created a second Twitter account just to handle posting his Switch screenshots. And this got me thinking.\nI knew Pipedream had super-simple support for executing workflows when a Twitter account posted a new tweet. Could I automate the process of taking my screenshots and making them available in the cloud?\n\n\n\nStep Zero - Setup a Twitter Account\nSo the first thing I needed was a Twitter account. I've made a bunch of these for my various bots so what's one more? Say Hello to @raysswitch.\nStep One - Trigger on Tweet\nThe first (real) step in building my workflow was a Pipedream trigger that fires on new tweets. When setting this up, you've got multiple options:\n\nWhat account to check (raysswitch)\nInclude or Exclude retweets and replies (I never plan on really tweeting from that account, but I set it to exclude just to be sure)\nPolling interval - this defaults to 15 minutes and I left it at that\n\n\n\n\nRemember that while testing, you can trigger this automatically so you don't have to wait.\nStep Two - Filter and Return Media\nThe next step involved code, and believe it or not, this is the only code I needed to write in the entire process. While my trigger is ignoring replies and retweets, it's possible I may tweet from the account by accident, so I wanted a bit of code to filter out tweets that didn't include a screenshot. I added a Node.js step with this code.\n\nThere's a bit of foreshadowing here as I know I'm going to need the URL to the media as well as the filename. I return both from the step. If no media was attached, I end the workflow.\nStep Three - Post to Dropbox\nSo, originally when working on this I had planned to post to a Google Photos album. I ran into some issues there and I plan on (hopefully) blogging about that later. For now, though I went with copying to a Dropbox folder. Again, Pipedream has this baked in. So I selected the action, configured the folder, and told it where to find the source (the URL from the previous step) and the file name to use (the file value from the previous step):\n\n\n\nAnd... that's it. Pipedream handled all of the hard stuff and I literally had to write about 5 lines of code. Here's an example with a shot I just took a few minutes ago:\n\n\n\nNote this is a screenshot from the Dropbox UI, if I had file system integration on this laptop it would have just shown up. If you want to see the original, here's a link to it. Feel free to make fun of my Diablo character names. As I've said a few times recently, Pipedream currently doesn't allow public sharing of workflows using their new UI, but it's coming soon, so if you want that link and it's in the future (i.e., not today ;), drop me a line.\nPhoto by Enrique Vidal Flores on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "The Jamstack Book - Final Release!",
		"date":"Thu Apr 21 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1650564000,
		"url":"https://www.raymondcamden.com/2022/04/21/the-jamstack-book-final-release",
		"content":"As the title says, I'm incredibly happy to announce the final release of The Jamstack Book:\n\n\n\nThis book was written by both Brian Rinaldi and myself and covers the basics of what the Jamstack is, demonstrates multiple examples, and goes into topics like using a CMS and serverless.\nNow, to be technical, the PDF is available right now. If you want a mobile/Kindle version or dead tree, you have to wait a few more weeks. (And honestly, I understand that - I prefer physical books for technical topics.) But you can still order now if you want, and remember, every book you buy helps me feed my eight kids, three cats, and dog. (Yes, I'm laying down a guilt trip. Is it working?)\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Watching RSS Feeds for Keywords in Pipedream",
		"date":"Tue Apr 19 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1650391200,
		"url":"https://www.raymondcamden.com/2022/04/19/watching-rss-feeds-for-keywords-in-pipedream",
		"content":"Back in the day, I used to run a website called rssWatcher. (If you want, you can read the original launch announcement from 2004.) The idea was simple. You would sign up, then create a list of RSS feeds and corresponding keywords. The service would check this on a schedule and let you know when a match was found. I built this in ColdFusion and I honestly don't remember when I shut it down, but it was in my mind recently and thought I'd take a stab at building a simple version of this on my favorite service, Pipedream. Here's how I did it.\nStep One - The Schedule\nThe first part to my workflow was the trigger which was a simple schedule. This defaults to once an hour, but when it comes to most RSS feeds, that's way overkill so I switched it to 6 hours.\n\n\n\nHonestly, once a day would probably be best for this.\nStep Two - RSS Feeds\nIn the next step, I need to actually gather the RSS data. Luckily, Pipedream has a built-in action for it. In fact, it's one I wrote and contributed to them: Merge RSS Feeds. This action lets you specify any number of RSS feeds. The action will parse them all and either return a date sorted merge list of items, or return them separated by RSS feed. The default is to merge them together but I knew I'd need them separated so I set that option to false. But to be clear, this entire step was done for me. I literally just configured it!\n\n\n\nIn the example above, I merged my RSS feed, Todd Sharp's blog, and Scott Stroz. I recommend subscribing and reading to both of their blogs - they're incredibly smart and cool developers.\nOk, before we get into step three, just keep in mind. I've got a serverless workflow deployed that merges a dynamic list of RSS feeds on a schedule and I haven't written one line of code yet.\nStep Three - Define the Keywords\nFor this step, I'm simply defining the keywords that I want to use in my search. Pipedream doesn't support the idea of workflow level properties, if it did, this would be a great place to use them. I added a code step for the express purpose of providing a UI to enter keywords and then have those values exposed later. I've also floated the idea to Pipedream of having a simpler action for this - ie, let me use the user interface to enter values and have it returned from the step.\nI added a code step and used the props portion to define how and I want to want to enter - an array of strings.\n\nWhen editing the step, I entered 'dog' and 'cat'. You can see this both when editing (it's in the configuration) and when just viewing the step:\n\n\n\nStep Four - Removing Old Entries\nOk, now we get into some real code. We don't want to search against RSS items that have already been scanned once. Therefore we need a caching system. Pipedream previously shipped with a &quot;checkpoint&quot; system (I blogged about this a little over two years ago!) but has recently shipped a more powerful version of state, Data Stores. Data Stores are a key-value persistence system available across your entire Pipedream organization, not just one workflow, and they've got a pretty simple get,set API.\nFor my workflow, I used a data store that would cache when RSS feed X was last scanned. If this cache exists, I use it as a way to filter out previous feed items. This is why I did not merge my RSS items above as I thought it would make it easier to handle. I created a new code step named check_last_hit:\n\nThe first important part of this step is in the props. By defining a prop of type data_store, this will tell the Pipedream UI to ask me to specify a name of a data store to associate with the step. I named this feedCheckCache, and in code, it will be available as rsscache.\nGiven that I have access to this now, I can loop over my feeds, see if I have ever hit it before, and use that as a way to remove items. You can see that in the run portion.\nNote that it makes use of the result from the 'get rss' feeds step, and step results are read only, so I make a copy of it so I can remove items from the array.\nStep Five - Update the Cache\nOk, this could have been in the last code step, but in Pipedream I try my best to make steps as atomic and simple as possible. This makes it much easier to test (especially in their new UI which lets you test individual steps). So I added yet another code step that just sets a last cache check date of today for my feeds:\n\nStep Six - Search!\nOk, at this point, we have a date filtered set of items. Our data is an array of RSS feeds, each with a set of items. We need to search against the feeds and items for matches. Our code will search for matches in any keywords but reports every one. So if you are searching for 2 keywords and both exist, you'll get two results. That felt like a good idea to me at the time, but I could see condensing them as well. Honestly my assumption is that usually only one will match. Feel free to disagee. :) Here's the code:\n\nThe end result is an array of matching items. It includes the feed, the item, and the matched keyword.\nStep Seven ",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Links For You",
		"date":"Sun Apr 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1650218400,
		"url":"https://www.raymondcamden.com/2022/04/17/links-for-you",
		"content":"As I've said a few times now (and sorry regular readers, I'm probably repeating this too often), I plan on sharing more short-form blog posts of stuff that I also share on Twitter. Yesterday I shared a few good links in a thread and I thought it would be a perfect kind of thing to share here with my subscribers. As always, I'd love your feedback, both good and bad, if you like this kind of content!\nThe first link I'll share involves one of my favorite services, Pipedream, and another I'm becoming a huge fan of, Algolia. In this post, Dylan Piece of Pipedream shares how to automate Algolia site crawling: Trigger an Algolia Crawler to reindex on Github Repository Releases\nAs an FYI, I covered Algolia site indexing a few days ago: Working with Algolia's Crawler\nFor the second link, here's a good article that compares Fetch and Axios. I've known about Axios for a long time but have generally preferred using Fetch. This article by Meticulous gives a good overview of the differences: Axios vs Fetch for beginners.\nAnd lastly, I pointed out Alpine.js. Alpine.js is a lightweight JavaScript library that reminds me a lot of Vue 2, specifically in terms of its usefulness in progressive enhancement. Alpine won't be your go-to library for building large SPAs, but for just enhancing your JavaScript development in general it looks really appealing. I've done some playing with it and I'm very excited about it. You can expect some blog posts on it soon.\nPhoto by JJ Ying on Unsplash\n",
		"tags":[
	        
            "links4you"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Use Your Saffron Recipes in the Jamstack",
		"date":"Mon Apr 11 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1649700000,
		"url":"https://www.raymondcamden.com/2022/04/11/use-your-saffron-recipes-in-the-jamstack",
		"content":"Like a lot of people, I took up baking during the pandemic. This was particularly difficult for me as I have a lot of anxiety when it comes to new things. I tend to stress over ensuring I get everything perfect and my worry about cooking is that if I do one thing wrong, I'll ruin it. While I'm not over that particular anxiety, I have had the chance to try making many things and while I'm not that good at it, I enjoy it, and can make some delicious items at times.\nTo help keep track of what I like to make, I initially used Evernote, but then discovered Saffron. Saffron is a web/mobile app utility for importing and organizing recipes. When it comes to importing, Saffron is near magical. You can point it at the typical recipe online full of useless junk and backstory and it will masterfully parse it down to the essentials. While I don't want to oversell it as perfect, I think I've had to do manual edits maybe one in ten times. You can manually enter your own recipes too of course and it's just an overall great service. It has a free tier that stores 25 recipes and while I'm not quite at that max yet, I plan on subscribing later today to the paid plan. It's that dang good and I want to support it.\nWhile thinking about an entirely different blog post I want to do about the Jamstack and recipes, I decided to take a quick peak to see if Saffron would let me export my data. Not surprisingly, it did, and made it painless. Upon exporting your data, you get a zip of text files, one per recipe. An example looks like so:\n\nAs you can see, it's using a format where each part of the recipe begins with a field name, followed by a colon, and then the value. Multiline values are tabbed over.\nNow, if I were going to stop using Saffron, I'd write a Node script to parse these files and rewrite them into a Markdown file with YAML front matter. But I don't have any plans on quitting Saffron so I decided to take a stab at a solution that would work with the files as is. Here's how I did it. As usual, my solution is using Eleventy, but you can adapt this to any static site generator.\nStep One - Support .txt Files\nEleventy doesn't support .txt files out of the box, but it's relatively easy to add it by using the custom template feature. In my .eleventy.js I did it like so:\n\nThe first function, addTemplateFormats, tells Eleventy to start processing .txt, and the addExtension block configures how it will be support. The compile function is passed the file input and will need to parse it and reformat it, but for now, all I do is return it as is.\nThis is enough to get Eleventy to start using the recipe files, but I also added a JSON file in the directory to specify a layout and tag:\n\nOnce this is done, you can see the files being parsed into HTML:\n\n\n\nOf course, the result isn't pretty, it's just a block of text. To really start displaying the recipes correctly, we need to parse those files\nStep Two - Parse the Recipe\nBefore I started the next part, I opened up the RunJS app. RunJS is a great way to quickly write code for testing things out, and as far as Eleventy is, I knew working in RunJS would be much better. While probably not the best function in the world, here's how I parsed my recipe text files:\n\nThis returns an object of keys and values, and correctly handles the multiline values for ingredients and instructions.\nWith this done, I then used another feature of Eleventy's custom template support, the ability to set data. This is done by adding a getData key inside the addExtension call:\n\nFor the most part this just chains to the utility function I wrote, but I do a bit of manipulation of the keys to make them more 'code friendly', namely lowercasing them and removing spaces. And believe it or not, that was basically it! My recipes were using a recipe.liquid layout, so I went there next:\n\nI basically go through the various keys of my recipe and output them in an order that makes sense, well, to me anyway. I also wrote a quick filter to handle the multiline values:\n\nThat isn't the most elegant solution, but it works. Note that my template does not make use of all the data from the Saffron export, but that could be done later. Also note I never make use of the actual contents. In theory, I could parse the recipe in compile and return the instructions as my content, but I was fine ignoring the contents. I reserve the right to rethink that later, but for now, it's working:\n\n\n\nAnd that's it! Honestly I was incredibly impressed by how Eleventy supported custom template formats, especially with the getData feature as it made it so easy to use my content in my templates. Also, the whole reason for this post is that I've got another idea related to the cooking and the Jamstack and I needed some sample content to play with. You can find the complete source code for this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/saffron\nPhoto by Theme Photos on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Thoughts on the Jamstack and Content Metrics",
		"date":"Wed Apr 06 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1649268000,
		"url":"https://www.raymondcamden.com/2022/04/06/thoughts-on-jamstack-and-content-metrics",
		"content":"Please forgive the (possibly) unclear title! Let me try to explain what I mean and you can then decide on whether or not you keep reading. As I was walking my dog yesterday (typically the best way to get ideas for my blog), I started thinking about the ways I keep tabs on my blog, specifically my content. This involves both the nature of my content as well as things like my publishing cadence. I've developed tools over time to help me check the status of this and I thought it might make sense to share some thoughts in this area with others. While the technical aspects of this post will use Eleventy, it's really meant to be more generic for the Jamstack. (Although honestly, this would probably apply to any content site.) Hopefully, this clears things up a bit and hopefully, you're sticking around for the rest of the post!\nUnderstanding the Nature of Your Content - Categorization\nThis may seem a bit silly, especially for folks who are just starting and have a small pool of content, but after you've been around for a while and have a large set of content, it helps to understand what your content covers. There are a couple of stats you can check to help in this area.\nJamstack sites typically support a way to categorize your content, and typically via front matter. For my site, I use categories and tags. Now, Eleventy treats tags as a way to organize content into collections. I basically skipped this entire feature for my site as I already used tags in a descriptive way for my blog posts. So for example, here's the categorization for this post:\n\nOne of the first tools I built for my own stats was to list out every category and every tag. I do this in a bit of EJS code:\n\nThis gives me an object containing the name of a category/tag, the count of posts. I also keep track of the total number of each. That count can be important for helping you focus I think. Now, to be fair, my blog here has been around nearly twenty years and the focus has changed. But typically I'd imagine a content site would try for a focused approach on some topic or general area, and therefore the total number of unique categories and tags should be kept to a &quot;reasonable&quot; limit. I put that in quotes because no one number will work for everyone, but I think it's an important thing to keep in mind as your content grows.\nWhen you have a list of categories and tags with counts, you can then get a good idea of what you're covering, which again, for a brand new site is something you can do without the 'work' of gathering stats, but later on will be more difficult. It also makes it easier to find places where it may make sense to update your organization. Here's my current set of categories:\n\n\n\nRight away I notice something interesting. While I try to keep my categories generic and my tags specific, I see I've got a jquery category. Most likely the posts in that category should be using javascript and jquery should be a tag. At the same time, when I first started blogging, I was almost entirely writing about ColdFusion, so I'm not sure I'd move coldfusion to a tag.\nAs you can see, the rules here need to be flexible, but in just writing this post I've identified an optimization I can do. (Issue filed!)\nAnother useful way to work with this data is by renaming. I recently renamed my &quot;static sites&quot; category to Jamstack. I used my stats to ensure I didn't miss any edits to my content.\nYet another useful thing to look for is really low values in stats. Whenever I look at my stats, the categories/tags with super low numbers stand out to me. Do I need to write more there? Do I need to 'migrate' that category/tag into another more appropriate area? You get the idea.\nFinally, having the stats makes it super easy to find typos:\n\n\n\nOops.\nUnderstanding the Nature of Your Content - Size\nBy size, I mean word count obviously, although images and other media make up part of the size too. I've got a &quot;general&quot; word count target I go for that I think strikes a good balance between depth and simplicity - 1 to 1.5K words. I include code in that count as well because my expectation is that people read the code samples as well, and heck, sometimes that can be much more difficult reading than explanatory text. I track word count in two places.\nFirst, in my stats, I count the words for each post and generate an average. Using the same loop as you saw above, I also count words using a simple, and definitely not 100% perfect, count of words based on spaces.\n\nThis currently reports an average of 366, which probably reflects the fact that for a long part of my blog's history, I would publish numerous small posts covering things like cool links and event announcements. This was pre-Twitter so I would regularly use my blog for very short notifications. This is most evident in the fact that in 2007, I had 825 blog posts. That's... crazy, I know. I can say that fairly recently I've decided to start doing more shorter posts. Honestly, it's near impossibl",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Including RSS Content in your Eleventy Site - Part 2",
		"date":"Sun Apr 03 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1649008800,
		"url":"https://www.raymondcamden.com/2022/04/03/including-rss-content-in-your-eleventy-site-part-2",
		"content":"A few weeks ago I blogged about how to include RSS data in your Eleventy site: Including RSS Content in your Eleventy Site. Last week, I had the honor of giving my first presentation to the Eleventy Meetup and for that talk, I took my earlier code and iterated on it a bit to show more examples and add a bit more usefulness to the tip. If you want to watch that presentation, you can do so below (and see @jeromecoupe excellent tal too!). I thought I'd also share the updates here for folks who prefer reading over wathing a video.\n\nAlright, before I begin, be sure to read the first post so you understand the basics. Basically I use a simple npm package (rss-parser) to grab the content in an Eleventy _data file and then use it in a template. That simple usage worked well enough for my need (see the list of Medium articles on my About page), but for the meetup, I wanted to take it a bit further.\nBetter Dates\nIn my initial example, I only used the link and title from the RSS feed. Adding the date would have been nice, but I would have needed to parse the ISO date. Turns out there's a real nice library that helps with this, date-fns. Using this, I modified the _data file to massage the dates.\n\nThen I could use it in my output like so:\n\nSimple and effective. Here's how it looks:\n\n\n\nShow Full Content\nSome RSS feeds contain the full content of the article. For my next demo, I used Eleventy's pagination support to turn the RSS data into dynamic pages. First, the pagination template:\n\nI'm using a permalink under a syndicated-articles and I turn the article title into a URL safe slug. Now I also make use of the content variable. Here's where rss-parser acted a bit. For my Medium feed, the content field did not exist, but instead, it used content:encoded. Nothing in the docs said this should happen (and I plan on filing a bug report when I get a moment), so I modified my _data file to use a bit of logic to normalize it for me. Here is a snippet from the file from the loop:\n\nAgain, I don't see why this happened and why Medium's field caused this, nor did I see it documented, but I plan on doing the right thing and reporting it.\nAlso note that if you do this, the HTML of the feed may not work well with your site, and obviously, ensure you have permission to replicate content!!\nMultiple Feeds\nThat last thing I did was rewrite the _data file to support multiple different RSS feeds. Note that this makes it take a bit longer, but as long as you don't go crazy and add hundreds of feeds, you should be ok. Here's the updated _data file.\n\nThere's a few things I do here outside of grabbing N feeds. First, I include the metadata from the individual feed in each feed item. I do this so that when I display the feed items later, I can note where the item comes from. It's a bit of duplication but I'm fine with that. I'm also still parsing the date and handling content:encoded where necessary. Finally, I sort everything by the date. Here's the updated display:\n\nNote that the main change here is including the blog where the item came from. Here's how this looks:\n\n\n\nAll of this may be found in my Eleventy demos repo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/use_rss\nLet me know if this helps!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "A Snippet for Getting DZone Article Stats",
		"date":"Wed Mar 30 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1648663200,
		"url":"https://www.raymondcamden.com/2022/03/30/a-snippet-for-getting-dzone-article-stats",
		"content":"Over the years of running my blog, I've off and on aggregated my content to DZone. With Adobe, we've been aggregating our Medium tech blog articles to DZone as we're seeing a lot more traffic. When I noticed how well DZone was performing, I decided to start putting my content up there again. (Not all, but some anyway, and I'm curious if they would accept this one. :)\nBeing a stat junkie, I've been checking my profile page often to see how my content is doing. On that page, you can see an infinite scroll list of your most recent articles and the page views each has gotten:\n\n\n\nI was curious if I could get those stats in purely data form so I could sort by page views. Like any good web dev, I opened up my devtools, switched to the Network tab, filtered to Fetch/XHR, and was able to see the endpoint they use to fetch the data:\nhttps://dzone.com/services/widget/article-listV2/list?author=201258&amp;page=1&amp;portal=all&amp;sort=newest\nLooking at that URL, the author value matches what I see in the URL of my profile, https://dzone.com/users/201258/cfjedimaster.html. The result of the endpoint looks like so - minus the actual article data:\n\nEach page of data contains up to twenty article objects in nodes. Here's an example of one:\n\nAs you scroll down the profile page, if you have more than twenty articles, the front end will simply keep requesting more, increasing the page value. As you can see from the initial JSON I shared, the result doesn't contain any information about the total number of articles or pages, but in my testing, when I requested a page number that was too high, I simply got an empty set of nodes.\nWith what I found so far, I determined I could build a generic recursive function to get all my articles. Here's what that looks like:\n\nTo test this, I used the following:\n\nI do two manipulations to the result. First I sort by views. Then I turn the articleDate into real dates. Oddly, like one of ten of my articles was, the rest were in time since epoch. Shrug - I've got no clue. All I know is that this worked. Here's my result in RunJS, one of the best dang apps out there for testing JavaScript:\n\n\n\nThat's only the first bit. I've got 368 articles on DZone so it takes about 10 seconds or so for the script to run. If you want to see a slightly nicer version of this and adapt it for your own DZone profile, I've got a CodePen below. Just edit the authorId value to match yours.\n\n  See the Pen \n  DZone Article Data by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nLet me know if you find this useful, and obviously, if you're reading this in the future, a) I've always supported, and always will, our robot overlords, and b) the DZone endpoint may change and break this. Use with caution.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Saving Form Data in Client-Side Storage",
		"date":"Sun Mar 27 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1648404000,
		"url":"https://www.raymondcamden.com/2022/03/27/saving-form-data-in-client-side-storage",
		"content":"Today's post is one of those that started off with me worrying that it was going to be too simple and quickly turned into a bit of a complex little beast. I love that as it usually means my expectations were wrong and I've got a chance to expand my knowledge a bit. This post came from a simple idea: While working on a form, can we save your form data for restoring later in case you navigate away, close the tab by accident, or perhaps get &quot;surprised&quot; by an operating system update. While this is not something you would want to use in every situation (for example, storing a new password field), there's plenty of examples where this could be helpful, especially in a larger form.\nFor our demo I will only cover client-side storage, which means the data will be unique to one browser on the device. Although what I described here could be tied to a back-end service for storing temporary form data as well.\nInitially I tried to build a generic solution that would apply to any and all forms but quickly discovered that it became Non-Trivial to the point where I decided a more hard-coded solution would be better. My assumption here is that my readers can take these techniques and apply to them to their site with a bit of work. As always, if you have any questions, just let me know!\nThe Form\nAlright, let's start off by looking at the form I'll use for the demo. While covering every unique kind of form field would be overwhelming, I tried to cover the main ones: A few text fields, a set of checkboxes, a set of radio fields, and a textarea. Here's the HTML:\n\nIt's not terribly exciting, but gets the job done in terms of demonstrating multiple types of form fields.\n\n\n\nSaving Form Data\nLet's begin with how to save the form. Here's the high level approach I'm going to use:\n\nThe data will be stored in LocalStorage. This will let it persist forever (not really, but close enough) and will be an incredibly simple API to work with. IndexedDB can store a lot more data, but all we're storing is a form.\nI will persist the data on every change. We could get fancy and save on an interval, but it's relatively inexpensive to just save on every change.\n\nTo begin, I setup my code to fire some logic on DOMContentLoaded as well as create some global variables:\n\nNow let's look at init:\n\nAs I mentioned above, I'm not going for a generic solution, but rather one tied to my exact form. You can see then I create a variable representing the DOM item for each of my fields. depts and cookies ae special as they are a set of items, not just one.\nBut while I'm not going dynamic to set up the variables pointing to the form fields, I did go dynamic to set up the event handler. I could have added an event listener for each of my variables (and ensuring I handled depts and cookies in a loop), but this shortcut handles matching any form fields inside my form and then letting me quickly assign the handler for each.\nNow that we've got event handlers, we can build logic to persist the form. This handler will fire on any change in the fields, but as I said above, we'll get all the data and persist.\n\nI create an object, form, to store my data, and then get the &quot;simple&quot; ones where I can just check the value. This works for the select tag too. For the radio and checkbox ones, I handle them a bit differently. The radio one, depts, will either have nothng selected ore one, so if nothing is picked, it's never saved, or it's a value. For cookies, I'll always have an empty array at minimum, but will fill it with the values when selected.\nFinally, I take the data and pass it to another function. The code to use LocalStorage is very simple, but I wanted it abstracted in case the decision was made to change to something else in the future. Here's that function:\n\nRemember that LocalStorage only takes simple values, so the object is serialized to a string first.\nWoot! Ok, at this point, I can type in data, and confirm it's working in DevTools:\n\n\n\nRetrieving the Data\nNow that there's a way to store the form, let's look at fetching the data (if it exists) and use it. Back in init, I added the following:\n\nI begin by fetching the form (I'll show that in a second) and if the cache exists, I make use of it. For all the simple values, and the select, it's easy to set. For the checkbox and radio ones, it's slightly more complex. depts will either be null or a value, but cookies will be an array (technically it will always exists so the if there isn't really necessary) and I make use includes to check the cached array.\nAs with saveForm, I wanted to wrap the cache retrieval logic to handle updating the storage in the future. Here's getForm:\n\nThere's one last thing to do. When the form is submitted, it makes sense to clear the cache. I added this to the end of init:\n\nThis is nice and simple, but I'm being a bit inconsistent here by not abstracting out how I work with persistence. It's one line, and I feel kinda bad about it, but I'm also fine leaving it for now.\nHere",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Speaking at Eleventy Meetup Next Week",
		"date":"Thu Mar 24 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1648144800,
		"url":"https://www.raymondcamden.com/2022/03/24/speaking-at-eleventy-meetup-next-week",
		"content":"First off, a quick note to my email subscribers and RSS followers - I'm considering doing more short, &quot;FYI&quot; style posts like this, along with using my email list more often, to help cover people who manage to not live on Twitter. I'd like to be able to share quick updates here along with more in-depth blog posts. I reserve the right to change my mind on that too (grin) but consider this a quick heads up.\nNext week, on Monday, March 28, I'll be giving my first presentation to the Eleventy Meetup. If you remember a few weeks ago I blogged on including RSS content in your Eleventy site. At the meetup, I'll be talking about that, and going a bit beyond what I showed in the initial blog post. If you can't make it, I plan on sharing those updates in a blog post as well.\nDetails and more may be found here: Ep. 7: Structuring 11ty Projects and Rendering RSS Content\nI hope to see you there!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Hosting an Alexa Skill on Pipedream",
		"date":"Thu Mar 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1647540000,
		"url":"https://www.raymondcamden.com/2022/03/17/hosting-an-alexa-skill-on-pipedream",
		"content":"I've been a big fan of the Amazon Alexa developer experience for a few years now. I haven't done any active skill (what they call apps) in a while, but I thought I'd take a quick look at what's required to host a skill on Pipedream. While this won't be a &quot;How to build an Alexa skill&quot; post, I will share a bit of background information about the process, because honestly it's pretty dang cool.\nAlexa Skills - The 60 Second Overview\nAlright, so at a high level, an Alexa skill involves the following parts:\n\nFirst is the invocation name, how you 'start' your skill. When you say, &quot;Alexa, ask foo about goo&quot;, foo is the invocation name.\nSkills have intents, which are the broad categories of what people can do with your skill. When you go iinto a coffee shop, you will probably only do two things - ask what's on the menu, and make an order. Those would be your intents.\nIntents have utterances, which are examples of intents. You do not have to enumerate every single possible version of a way to do execute an intent, but the more you do, the better Alexa is at routing input. So for example, I may use the following utterances for orders: &quot;I'd like to order a coffee.&quot;, &quot;I want a coffee.&quot;, &quot;Give me a coffee now!&quot;.\n\nAll of the above is part of the metadata for your skill (and not everything to be clear), but you can also specify an endpoint. This is where your code comes in. Obviously, Alexa pushes you to Lambda, but you do not have to use Lambda. Most of my testing with Alexa was on OpenWhisk.\nThe code is fairly simple to write for the most part. Every request is a JSON object describing the intent as well as any variable data. So for example, when you define an &quot;order&quot; intent, Alexa will figure out what you are ordering and pass that as data. You don't have to do any parsing of the original text at all, Alexa handles all of that for you. You literally write code that a) recognizes the intent, b) gets the arguments, and c) does whatever makes sense for the logic.\nThe best part of developing for Alexa though is that you can create &quot;personal&quot; skills. These are skills that only work on your own devices. I had a lot of fun building stupidtotally serious skills for myself and my family. Last time I checked, Google did not allow this for their voice assistant. Well they did, but every use of the code had a &quot;test&quot; message that pretty much ruined the experience. Maybe that's gone now and if so, let me know.\nAlexa on Pipedream\nOk, so let's talk about hosting a skill on Pipedream. First off, since you need a URL for Alexa to hit, you'll want to create your workflow with the HTTP trigger. This will give you the URL you need to provide to Alexa. Here's the important bit. When you add your endpoint in the Alexa console, ensure you select: &quot;My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority&quot;\n\n\n\nWhat follows next is how I built my workflow. These specifics could be changed based on your preferences. I try to have small concrete things in individual Pipedream steps, but Pipedream is fine with one big code step too if you want. So first I built a step that simply got the intent out from the request object.\n\nThis was in a step named steps.determineintent which meant later in my code I could check steps.determineintent.intent. That's not much shorter than the original, but it felt right. My test skill didn't use any arguments but I'd probably use the same step to get those args out as well. Basically, I'm thinking of this as a &quot;Alexa sent me this big JSON, let me break it down into the parts I care about&quot; type step. The second line there handles the case where a person says, &quot;ask alexa foo&quot;, basically opening a skill with no other input. My thinking here is to treat it like a help request.\nNext, I added a few steps to handle required/default intents in Alexa skills. As part of the certification process (which again, is only required if you go live), your skill has to respond to a 'launch' request (ie, just opening it up, see the text), a 'help' request, and an 'end' one.\nAs I said above, I treated the launch request the same as help, which obviously wouldn't always work but was fine for me. So here is the step for that:\n\nAt the time I'm writing this, Pipedream doesn't have a concept of conditional logic for steps, so instead I simply leave the current step via a return statement. Alexa responses are JSON packets, and while they can get pretty complex, above you see a simple example of one.\nMy next step handles both canceling and stopping:\n\nAnd then finally, I added the logic of my skill. In this case, I rebuilt a stupid horoscope simulator I converted from an old Flex Mobile app (&quot;Rebuilding a Flex Mobile App as an Alexa Skill&quot;). I'll share the code, but note it really doesn't matter. The code you would use would match your business logic (but won't be as cool):\n",
		"tags":[
	        
            "pipedream",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building Table Sorting and Pagination in JavaScript",
		"date":"Mon Mar 14 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1647280800,
		"url":"https://www.raymondcamden.com/2022/03/14/building-table-sorting-and-pagination-in-javascript",
		"content":"As part of my job in managing this blog, I check my stats frequently, and I've noticed that some of my more basic Vue.js articles have had consistently good traffic for quite some time. As I find myself doing more and more with &quot;regular&quot; JavaScript (sometimes referred to as &quot;Vanilla JavaScript&quot;, but I'm not a fan of the term) I thought it would be a good idea to update those old posts for folks who would rather skip using a framework. With that in mind, here is my update to my post from over four years ago, Building Table Sorting and Pagination in Vue.js\nYou don't need to read that old post as I think the title is rather descriptive. How can we use JavaScript to take a set of data - render it in a table - and support both sorting and pagination? Here's how I solved this. Before I begin, a quick note. I'll be loading all of my data and while that works with a &quot;sensible&quot; amount of data, you don't want to be sending hundreds of thousands of rows of data to the client and sorting and paging in the client. That being said, I think an entirely client-side solution is absolutely safe if you understand the size of your data and know it's not going to impact performance.\nSpeaking of data, all of my examples will be fetching an array of cats from this endpoint: https://www.raymondcamden.com/.netlify/functions/get-cats\nHere's a sample of that data:\n\nAlright, let's get started!\nVersion One - Just Rendering\nIn the first version, I'm just going to load the data and render it in a table. I began by creating an HTML table:\n\nNote that the tbody has a loading message. This will render while the remote data is fetched and be replaced with the contents. Now let's look at the JavaScript:\n\nThis boils down to:\n\nWait for the page to load (this fires before images, stylesheets, and so forth are done though, more info on MDN)\nSelect the tbody element\nHit my function and gets the cats\nLoop over each cat and generate HTML\nInject the HTML into the table\n\nNote that while I named my variable table, it's really just the tbody I'm updating. Part of me doesn't like that and part of me thinks I'm being too picky. Guess who won? Here's the complete demo:\n\n  See the Pen \n  JS-Sortable Table by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two - Sorting\nFor the next version, let's add sorting. Sorting will be enabled by clicking on a table header. Clicking once will sort in one direction, clicking again will reverse the sort. First, I modified my table to include data attributes related to the column I'm sorting:\n\nData attributes are a great way to include metadata in your HTML and - as always - you can learn more at MDN.\nTo handle sorting, I did a few things. First, I created variables to represent the current sort column and direction. I placed these with a few other values I'll need up top:\n\nNext, I added a click event handler to my header cells. I needed to do one per cell:\n\nAs I realized I was going to be redrawing my table a lot, I built a new function to handle rendering the table.\n\nFinally, I added the sort function. This handles sorting based on both the column and direction:\n\nI'm not a huge fan of ternary expressions as I find them hard to read at times, but it did make the code a bit simpler above. As I'm not using Vue anymore I have to manually call renderTable again, but that's fine. Here's this version:\n\n  See the Pen \n  JS-Sortable Table (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOne thing I went back and forth on is whether or not I should apply a default sort. I decided not to, but I could definitely seeing doing that.\nVersion Three - Paging\nAlright, for the third and final version, we need to add paging. My dataset isn't terribly large, so I went with a page size of 3 cats per page. Here is a completely unnecessary picture of three cats:\n\n\n\nI started off in HTML by adding two new buttons:\n\nThat's it for the HTML changes. Now let's look at the JavaScript. I began by adding a few extra variables related to paging:\n\nThen I added click handlers for the buttons:\n\nBoth of these functions need to do basic bounds checking and tell the table to re-render:\n\nThat logic in nextPage was a pain in the rear to get right, but luckily I was able to just use the logic from the previous blog post. So where is the paging happening? I do my slicing in the render:\n\nUnlike sort which modifies an array in place, filter returns a new array which means I can use it here to get my page of cats and then render that to HTML. Here's that demo:\n\n  See the Pen \n  JS-Sortable Table (3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThis could be enhanced by adding disabled attributes to the button when at the edge. Feel free to fork my CodePen and show me! As always, I hope this is helpful and if you've got any feedback, just reach out!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Including RSS Content in your Eleventy Site",
		"date":"Tue Mar 08 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1646762400,
		"url":"https://www.raymondcamden.com/2022/03/08/including-rss-content-in-your-eleventy-site",
		"content":"Before I begin, this post is not about generating an RSS page with Eleventy. If you need to do that, check the plugin that makes it (mostly) trivial to do. This post is about consuming RSS for your Eleventy site. I've got a page here (About) where I track my external articles and books. At work, we use Medium to host our blog and I've been publishing there as part of my job. I was curious how I could get that content on my About page as well.\nFirst off, Medium publishes an RSS feed for every profile and publication. You can find details on their documentation, but for simple users like me, it's a simple matter of going from:\nhttps://medium.com/@cfjedimaster\nto\nhttps://medium.com/feed/@cfjedimaster\nThis RSS feed contains articles I wrote in the past, pre-Adobe, as well as my content since joining Adobe. Basically, even though my articles are showing up in the Adobe publication, my RSS feed correctly shows everything I authored.\nAlright, so adding this to my page means I need to parse RSS in Node. I've used rss-parser before and that's what I went with here. I created a new _data file named medium.js with this code:\n\nThe parser returns metadata about the RSS as well as the items, but all I care about is the items. I thought perhaps I'd do some transformation here as well, but I figured I'd add it to a template first and see if anything seemed necessary. Turns out I really didn't see a need.\nSo back in my About page (/about.md), I added the following:\n\nI thought about adding the article date too, but decided to keep it simple. Here's the result rendered out:\n\n\n\nAnd that's all there is to it. As a reminder, you can find the entire source code for this blog up in a public repo if you want to see it in context. If having the most up-to-date version of the feed is important, you've got a few options.\nThe simplest is to just schedule a build roughly along with the schedule that makes sense for the content you are ingesting. So if a feed updates about once a day, you could schedule a build once a day.\nThe other option is to track the RSS feed with Pipedream. One of their sample workflows demonstrates this: New Item in Feed from RSS API\nIf you find this useful, let me know!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with Algolia's Crawler",
		"date":"Fri Mar 04 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1646416800,
		"url":"https://www.raymondcamden.com/2022/03/04/working-with-algolias-crawler",
		"content":"I've been using Algolia on my blog for a while now. It's an excellent search solution for the Jamstack and I absolutely recommend it, especially for sites where the size means Lunr may not be appropriate. While I like Algolia a lot, I haven't really dug terribly deep into it. I went through multiple iterations of my implementation here to help deal with the size of my content and so forth, but outside of that, I kept the actual search experience nice and simple. This week, I gave a talk at the Jamstack_Berlin user group on search options for the Jamstack and that's got me thinking more about both Lunr and Algolia. One of the things I've wanted to play with for quite some time is their Crawler product. While I didn't have time to research it before the presentation, I've had time to check it out and I thought I'd share my findings.\nBefore I begin, note that this feature is not available on the free tier. I think that's totally fair as it's really powerful and flexible (as I hope you'll see below), but I just wanted to be sure my readers were aware. Honestly, it's a bit hard to tell from their pricing page that Crawler isn't available for free, so please keep that in mind. I missed this, but there is a free version of this for open source projects, one already being used by many projects. This is called DocSearch and you find out more here: https://docsearch.algolia.com/\nGetting Started\nAssuming you have an Algolia account, you begin by clicking &quot;Data sources&quot; in the dashboard. If you are not on the free tier, you should see Crawler as a navigation item:\n\n\n\nDoing so will take you - oddly - to this:\n\n\n\nSo... one of the weird things about Crawler is that it's not properly integrated into the main Algolia dashboard. As I said... weird. But honestly it's no big deal. I mainly bring it up so as to prepare you for the odd jump. The UI for Crawler is all easy to use (and really well done), but looks a bit like a different design team worked on it.\nI decided to test Crawler on an old site I had, ColdFusion Cookbook. I converted this site to Jamstack a while back but more recently updated it to use Eleventy. As part of that conversion, I removed the previous search engine (which I think was a Google Programmable Search Engine) and decided I'd use the Algolia Crawler as a replacement.\nOnce logged into the Crawler dashboard, you're presented with a list of your existing implementations.\n\n\n\nThis will be empty at first but you can click to create a new one. I did so and entered a name and starting URL:\n\n\n\nIn the next step, they check for robots.txt and a sitemap URL. Notice how if you don't have one they can find automatically, you can enter one. This is not required though:\n\n\n\nAfter this step, you can start the process. In the screenshot below, note the limit of 100 URLs. I'll explain how we correct this later.\n\n\n\nI've mentioned before how while it was a bit jarring that the dashboard wasn't integrated with the rest of Algolia and the general theme didn't match, I really liked how the Crawler admin was built. Below you can see an example of this. As the crawler starts working, you get live updates and even see sample data as it's ingested.\n\n\n\nWhen done, you get a summary of the total records and any errors. Right away, I noticed a problem. 31 records didn't make sense. The site had 151 content entries, so given that plus miscellaneous pages, I expected a number above that... ignoring for a moment that it said it would cap out at 100.\n\n\n\nOk, so what went wrong? Actually something great. Turns out I had something misconfigured on my site at Netlify. This issue caused content to load just fine, but it was redirecting from the proper URL (/something.html) to an alias (/something). The thing is - the content looked fine to my eyes, but Algolia was seeing the redirects and not indexing them. While maybe that's something you can configure with the crawler, I really wanted to correct it instead. I corrected the problem on the Netlify site and confirmed Algolia could pick up the changes.\nAfter every crawler, Algolia will send you an email report:\n\n\n\nNotice how it suggests setting up a schedule. I'll talk about that in a bit too.\nSo, once the crawl is done, you end up with two new indexes. Remember this will be back in the 'regular' dashboard, but you can then do test searches, look at your data, and so forth.\n\n\n\nHere's an example record from my index. This was all done by the crawler and while I could modify the behavior, I love that it seemed to correctly parse everything perfectly. For example, the content field has UI and stuff stripped away. Algolia knew - for the most part - to avoid non-essential content. I say &quot;most part&quot; as it did pick up a bit of text from the right-hand side.\n\n\n\nAt this point, I was pretty happy and decided to look at fixing the cap of one hundred items. Back in the Crawler UI, you can do so via the &quot;Editor&quot; link, which pops open a JavaScript object:\n\n\n\nWhile on one hand I ",
		"tags":[
	        
            "algolia"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing out the new Pipedream to Get Trance Releases",
		"date":"Tue Feb 22 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1645552800,
		"url":"https://www.raymondcamden.com/2022/02/22/testing-out-the-new-pipedream-to-get-trance-releases",
		"content":"Readers of my blog will know I'm a huge Pipedream fan. I've been blogging about them since I discovered them and find a way to use them for so many things now it's my natural &quot;first stop&quot; when I want to build a new service.\nRecently, Pipedream released a major overhaul to their workflow editor. This is not quite 100% complete yet, and still not the default (you can choose to edit in &quot;v1&quot; or &quot;v2&quot; versions), but it's damn impressive.\nThe updates include\n\nMulti-language support, including.... PYTHON! (Yes, I love Node still but Python makes me incredibly happy.)\nMultiple triggers for a workflow\nNew editing experience\n\nThat last bullet point is a bit vague, but basically, the experience of editing a workflow is incredibly enhanced in v2. My favorite change is that you can now run an individual step by itself, using previous results. What does that mean?\nImagine a workflow with a few steps:\n\nHit an API and get crap\nTake the result and filter it\nTake the new result and generate a nice string\nEmail that crap\n\nYou can now execute the call to the API and work on step two by itself, using the previous results again and again. I'm still learning Python and still make dumb mistakes (like forgetting to use bracket notation for dicts) so this has been super useful for me.\nThat being said, there's still some things you can't do in the new version. For example, there's features missing from Python like the ability to end a workflow early. For me, I got around that by just plopping a Node step between two Python ones. You also can't currently share a v2 workflow. That's kind of a bummer now because I always like to share my workflows in posts like this, but I'm sure it will be around soon.\nFor those of you who did play with Pipedream, you can find a detailed migration guide on their docs. You can also get a better look at the updates below:\n\nOk, so what's this about trance?\nI'm a big fan of Above &amp; Beyond, producers of dance/trance music for over two decades. Specifically, I'm a fan of the &quot;Group Therapy&quot; show. While I like other productions from them (especially their acoustic stuff, yes, acoustic trance is incredibly chill), my main jam is &quot;Group Therapy&quot;. I listen to them on Spotify and wanted a way to know when new music came out.\nSpotify supports alerts for bands if you follow them, but as much as I use Spotify, I don't really use the &quot;Follow&quot; mechanism. Also, I specifically only care about the &quot;Group Therapy&quot; releases. I knew that Spotify had a really good developer API and I figured it would be easy to build something with Pipedream, I also thought it would be a good use-case to kick the tires a bit on their new workflow editor. Here's what I built.\nStep One - The Schedule\nI don't know exactly when they put out their release - it feels like every few weeks - so I decided on a weekly schedule. I figured Monday morning would be a good time to check as it would give me a way to help kick start my week. Here's how CRON triggers look in v2. The important thing is that they're still easy to set up - now need to learn CRON's crazy (and powerful) syntax.\n\n\n\nStep Two - Spotify Search\nFor the second step, I added a generic Spotify step. Remember that Pipedream will handle all the authentication for you. I signed in once, added the step, and modified their code. Spotify's developer platform was incredibly helpful for this. I used their console to test the Search API to craft what I needed. Specifically, search for the artist &quot;Above and Beyond&quot;, the album &quot;Group Therapy&quot;, and the &quot;new&quot; tag, which filters results to the last two weeks. &quot;Group Therapy&quot; releases always start with that name and end with a number.\nIn the code editor, I used the following:\n\nMost of that code came out of the box from the Spotify action, and again, notice the Authorization header. Pipedream did that for me. I tested that step and then inspected the results:\n\n\n\nStep Three - Filter the Results\nThe previous step will return any possible matches of &quot;Group Therapy&quot; from the last two weeks. I then needed a step to filter them down to items in the last week. That may be possible via the Spotify API directly, but I built a new step using Python as the SDK. As I said above, you can't end early year (as you'll see in a comment) so my workflow is a bit brittle here, but I'm ok with that.\n\nBasically, loop, convert the release date into a proper date object, and check the difference. For each result that's not too old, I get out the values I care about. Finally, I export the result.\nSo while this is basic Python, I'm still learning, and I ended up running this step maybe 10 or so times before I got it right, but again, in the new version of the Pipedream editor, this didn't result in more calls to Spotify. It simple reused the existing results. That gave me nice consistent sample data to work with!\nStep Four - A Hack\nNow - while I was ok with t",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Another Early Look - Netlify Graph",
		"date":"Thu Feb 17 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1645120800,
		"url":"https://www.raymondcamden.com/2022/02/17/another-early-look-netlify-graph",
		"content":"Earlier this month I took a look at a new (in beta) Netlify feature (An Early Look at Netlify Scheduled Functions). Netlify has been on a roll this month with new releases and announcements, and on Tuesday they announced another new feature, Netlify Graph.\nI encourage you to read the blog post I linked to above as it will give you a proper introduction to the feature, you can also watch a good (and very short!) introduction here:\n\nBut of course, I think you come here for a) my take on things and b) my cat demos. I've played a bit with this new feature over the past few days and I've got to say... while I feel like they're some rough edges, it's damn nice.\nAt a high level, this feature lets you &quot;design&quot; (more on that in a sec) an API call against a service, with or without authentication. Once you've designed the API (basically, I want X, Y and Z from the API), Netlify can then export a serverless function to your site that provides a wrapper to the call.\nSo for example, I was able to build an authenticated call to the Spotify API to fetch the latest music I played. I then exposed it at https://www.raymondcamden.com/api/latestTracks. I then took that endpoint and whipped up a super simple Vue.jus front end: https://www.raymondcamden.com/tracks.\nCode-wise, I really didn't do much of anything at all! And in fact, I first designed my API, realized I missed something, and was able to use the UI to update the API and add additional fields.\nIt's all kinda... magical honestly. As I said, I definitely hit a few rough spots and I'd probably wait before using this in production for a &quot;real&quot; site, but even right now it's pretty freaking cool.\nAlright, so how does it work?\nAdding a Spotify API\nFirst off, note that the docs for Graph are still on GitHub: https://github.com/netlify/labs/tree/main/features/graph/documentation As this is a beta feature you won't find it on the main Netlify docs yet, so keep that in mind as you play.\nBegin by ensuring your Netlify CLI is up to date. They update pretty often so first run npm install netlify-cli -g to ensure you're running the latest.\nNext, in an existing and connected Netlify site, run: ntl dev --graph. (As an FYI, the netlify CLI has an alias of ntl.) This will do... um... stuff... but also fire up your local server. Notice though that you'll get a new URL as well:\n\n\n\nThis opens up the Graph UI where you can handle connecting APIs as well as working with queries. For connecting APIs, you'll need to go to the various services, create an application, and ensure you setup redirect URLs right. This is detailed here: https://github.com/netlify/labs/blob/main/features/graph/documentation/authentication.md#custom-clients\nI did this for Spotify so that I could make authenticated calls using my credentials. You can then use their GraphQL editor to design a query. Now, don't panic. I'm &quot;aware&quot; of GraphQL, as I assume most developers are, but I really haven't used it a lot. To be honest though, the editor makes it super easy to use. I literally just dug down into the recentlyPlayed part of Spotify's API and picked the things I thought made sense. It then created the query for me.\n\n\n\nI clicked the &quot;Play&quot; button and was able to peruse the result (I trimmed it a bit):\n\nMaking Code\nOk, now comes the freaking magic part. Once you have the query to your liking, you then click &quot;Generate Handler&quot; under the &quot;Actions&quot; menu:\n\n\n\nThis will do two things. First, it creates .functions/netlifyGraph. From what I can tell, this is the core service for the feature and contains your GraphQL queries. You do not touch this. It then generates .functions/ExampleQuery.js. The name comes from the default name of your query. I've renamed mine to latestTracks. Out of the box, this provides an endpoint to hit your GraphQL query. You just hit it and get JSON back... unless you're using authentication. You will see this on top:\n\nAs I am using authentication, I uncommented that last line above. And that was it! Well, I then added a redirect:\n/api/latestTracks /.netlify/functions/latestTracks 200\nI was then able to hit /api/latestTracks and get results! Well, sometimes. Right now there appears to be a bug in ntl dev where my authenticated functions work, randomly. It's either a perfect response, or:\n\nI can't find any reason for why this happens, and as I said, it's random, but in production seems to work perfectly. If you're curious and want to learn more, I filed an issue for it.\nThe process of modifying the query was also pretty straightforward. Like I said, I realized today I was missing a few keys I wanted in the result. I fired up the CLI, opened up the editor, saved it, and then saw that my local .functions/netlifyGraph folder updated with the new query. So I commited to my repo and pushed, Netlify rebuilt the site, and the production API was updated.\nThere's more to this then I'm showing here, but as I said, I really dug how this worked. Outside of the",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Adding QR Codes to Your Jamstack Site",
		"date":"Fri Feb 11 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1644602400,
		"url":"https://www.raymondcamden.com/2022/02/11/adding-qr-codes-to-your-jamstack-site",
		"content":"QR codes have been around for some time now, but I have to admit, when I think about QR codes, I normally think of one thing:\n\nHonestly, I really don't think too much about them, but I've definitely noticed since COVID, I'm seeing an uptick in their usage, especially at restaurants as a way to skip handing out disease-covered menus. (Which is fine by the way, but if your going to do this, stop using a PDF as your menu.)\nA few days ago, Dan Fascia suggested I take a look at it, and honestly, it ended up being so darn simple I was a bit surprised.\nI did a quick search and came across node-qrcode, a simple to use Node library for generating QR codes. It supports CLI usage, browser, and Node usage too which is nice to see. It can output the result as binary, SVG, or even to a data URL.\nI decided to go the data URL route as I figured it would be the simplest. I wouldn't need to figure out where to store the image and ensure it would be available in the production build.\nSo - I began with a simple Eleventy site that displayed a list of cats.\n\n\n\nThis was driven by a static JSON file:\n\nI then built a pagination template for my cats:\n\nThis is pretty boilerplate pagination stuff for Eleventy, but take special note of the comment. As it says, Eleventy provides a URL value for a page, but it's just the path, so for example, /cats/fluffy/. In order to create a &quot;full&quot; URL, I ended up making a new data file called site.json that simply has my host:\n\nYou could probably use a JavaScript file instead and generate the host dynamically, but this was simpler and worked just fine.\nBack to the template, I create a url variable consisting of that host, the current page, and a query string value. I figure marketers will want to know when a page is loaded from a QR code so I added that to the end.\nI then output the image and use a shortcode, qrcode, to get my data. As my .eleventy.js is really small, I'll share the entire thing:\n\nYeah, that's about as simple as you can get. Here's how the page renders:\n\n\n\nYou can see this for yourself here: https://eleventy-qrcodes.netlify.app/ I pointed my camera at one of the cat pages and the camera picked up on the code right away.\nAs I said, this felt ridiculously easy, which is a good thing, right? You can peruse the complete code here, https://github.com/cfjedimaster/eleventy-demos/tree/master/qrcodes. Let me know what you think!\nPhoto by Mitya Ivanov on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Turning a Microsoft ToDo List Into a Public JSON Feed with Pipedream",
		"date":"Wed Feb 09 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1644429600,
		"url":"https://www.raymondcamden.com/2022/02/09/turning-a-microsoft-todo-list-into-a-public-json-feed-with-pipedream",
		"content":"A few months ago I wrote up a blog article on how to use Microsoft's Graph API to check your tasks and congratulate for completing items over the previous week. I'm a huge fan of Microsoft To Do and while I use it for tasks lists, my biggest use case now is a &quot;blog idea&quot; list.\n\n\n\nI've got a horrible memory so whenever a random idea pops in my head, I try to immediately write it down. (Literally during my writing of this blog entry I had an idea for another blog entry and it turns out I already wrote it.) I also try to add a few notes so that I can remember exactly what I was thinking. More than once I wrote down the gist of an idea and later on forgot what the heck I was thinking.\n\n\n\nI randomly thought this morning that it would be kind of cool to take my list and actually put it up on my blog. Based on how easy it was to work with the Graph API and Pipedream I whipped up the following workflow.\nFirst, I have a step to get all my To Dos that are not marked completed:\n\nThe blogList value is the ID of the particular To Do list I care about, my blog ideas. The rootUrl is making use of Microsoft's Graph API end point to get all the items on the list, filtered to those where the status isn't completed.\nI've got a function, getCompletedToDos, that will recursively fetch content until done. Note the Authorization header. Pipedream handles all of that for me. All I had to do was authenticate with Microsoft one time. I know I've said so many times I love working with Pipedream, but it's stuff like this that would normally have taken me the most time. Pipedream just makes it easy.\nI do a bit of normalization on the task to simplify it a bit. If you're curious, here is the documentation for the complete toDo object. I'm only using a small bit of it.\nThe last step of my workflow then just returns the data:\n\nAnd that's all it takes. You can view the result here: https://enyqsjc7dfkrc5s.m.pipedream.net/. I've also shared the workflow here: https://pipedream.com/@raymondcamden/blog-todos-p_yKC3koo. Remember, Pipedream makes it easy to share workflows. You can see all of my code, but you won't have my authentication object.\nThe final bit was to add it to my blog! I whipped up a simple new page:\n\nAnd wrote a quick bit of code to handle the fetch:\n\nYou can see the result here: https://www.raymondcamden.com/queue\nIf you've got any questions about this, let me know!\nPhoto by Levi Jones on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Using the Microsoft Computer Vision API with Python",
		"date":"Tue Feb 08 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1644343200,
		"url":"https://www.raymondcamden.com/2022/02/08/using-the-microsoft-computer-vision-api-with-python",
		"content":"A few weeks ago I blogged about using the Adobe PDF Extract API and Microsoft's Computer Vision API: &quot;Integrating AI Computer Vision with Your PDF Documents&quot;. The idea was to extract images from a PDF and then analyze them to help provide more context about the document. While working on that article, I got to play with Microsoft's image recognition API again and I was impressed by how easy it was to use. As I'm learning Python, I thought it would be a good exercise to convert my JavaScript code to Python and see what results I'd get. Here's what I came up with.\nThe first issue I ran into was needing to find a replacement for the dotenv npm module. This lets you put environment defaults in a .env file that get replaced with &quot;real&quot; values in production. Turns out there's a Python version of this, python-dotenv.\nYou make your .env file and it's then accessible in your code by doing the following:\n\nOf course, I then realized I didn't know how to read environment variables in Python. That's not hard either:\n\nAll together, it looks like so:\n\nFor the Microsoft image API two special values are needed hence the two variables above.\nCool. Next, we need to hit the API. Microsoft ships a Python SDK for the service, but I was worried that, like the Node one, it wouldn't support local images, only those loaded from URL. From what I can see the Python one does support local images, but as the 'just hit the API' version I built in Node was so simple, I figured I'd take the same track using the Python requests library. Here now is the entire script, with the important bits being the scanImage function:\n\nI can then test this at the command line like so:\n\nUsing my avatar in the upper left as a source, here's what I get:\n\nYep, I'm a man with beard and glasses.\nI then modified my script in a new file, checkfor.py. That's a horrible name, but basically, I wanted to have a utility that would scan an image and tell you if there was match to a term. So for example:\n\nWould get the results and then look for the term. While it could be more complex, here's the method I built:\n\nIt will return a dictionary that signifies if a match was found, and if so, where and how. When I ran that, I got:\n\nIf I repeat it with a search for a cat, I (sadly) get:\n\nAll in all, pretty simple and direct, and as always, remember I'm learning Python, so definitely keep in mind that this could probably be done better. Here's the entire final script.\n\nPhoto by Héctor J. Rivas on Unsplash\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An Early Look at Netlify Scheduled Functions",
		"date":"Fri Feb 04 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1643997600,
		"url":"https://www.raymondcamden.com/2022/02/04/an-early-look-at-netlify-scheduled-functions",
		"content":"Earlier this week Netlify announced their acquisition of Quirrel and (more importantly to me), the beta release of scheduled functions. As the name implies, this is the ability to write Netlify serverless functions that run on a particular schedule. It's currently documented over on their Labs repository, and it's certainly possible it will change before it's official release, I thought I'd build a quick example of this to see how it worked.\nI began by creating a new web site. Here it is. All of it.\n\nBut check out that Lighthouse score...\n\n\n\nI then went ahead and created a GitHub repository, added my code to it, and made a new Netlify site that tied to it. Back in my command line, I used the CLI to generate a new function (ntl functions:create rebuild), and copied the code from the docs.\n\nI started my site (ntl dev), and then used the CLI to hit the function (ntl functions:invoke rebuild) and took a look at the event object. (One thing Netlify has not done a great job of is fully documenting the event structure passed to various Netlify functions tied to events.) Here's how it looked:\n\nMost of this probably isn't useful (although I like that user-agent), but the body is really helpful. If we grab the body and parse it...\n\nWe can more clearly see what it's showing:\n\nNice! Your function knows when it will run again. I can see that being useful.\nSo - one of the first things I immediately thought of was using this feature as a way to schedule rebuilds. This is actually listed in the announcement blog about one of the possible use cases.\nTo enable this,  you need to go into your &quot;Build hooks&quot; section of your site's &quot;Build &amp; deploy&quot; settings. There you can add a new hook:\n\n\n\nOnce you add it, you get a secret URL:\n\n\n\nIn order to trigger a build, you need to make a POST request. So I modified my scheduled function like so:\n\nFirst, note that I'm getting my rebuild URL via an environment variable. I simply copied the URL from the build hooks section and added it as a new environment setting.\nNext, I do my POST.\nAnd that's it. I also changed my schedule to @daily so it rebuilds once a day. I tested again via the CLI (ntl functions:invoke rebuild) and had the &quot;Deploys&quot; part of my Netlify site up. I could see it firing!\n\n\n\nAnd that's it. I'm really happy with how easy Netlify made this, especially with them supporting the &quot;simpler&quot; shortcut aliases for CRON. There isn't anything more to the code than what I've shown here, but you can peruse the repo if you would like: https://github.com/cfjedimaster/netlify_scheduled_functions_test. I'd also love to know how you plan on using this, so reach out with your ideas!\nPhoto by Sonja Langford on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack",
            
                "serverless"
            
		]

	},

	{
		"title": "A Google Static Maps Eleventy Plugin",
		"date":"Wed Feb 02 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1643824800,
		"url":"https://www.raymondcamden.com/2022/02/02/a-google-static-maps-eleventy-plugin",
		"content":"I've long been a fan of Google Static Maps (technically the &quot;Maps Static API&quot; which sounds weird) for quite some time, except for when, you know, I worked for a competitor. I've blogged about this product for years now as I love it's simplicity. It's not even really an API, but just a carefully crafted URL. So for example, a map of Lafayette could be done like so:\n\nThe image URL is: https://maps.googleapis.com/maps/api/staticmap?center=Lafayette,LA&amp;zoom=13&amp;size=500x500&amp;maptype=roadmap&amp;key=AIzaSyCA0y7kh6V8poL-faDyVf3TpnLDNf9XtQY\nThe attributes of note are center, zoom, size, and maptype. The API supports precise latitude and longitude, but when you don't have that and just have an address in text, then you can use the center attribute. There's a lot to the API including adding multiple markers and other decorations. Check the docs for examples.\nI thought it would be fun to build support for this in Eleventy via a shortcode. I create a new empty Eleventy site and added a simple function to handle outputting the URL in the right format:\n\nTechnically I probably shouldn't use defaults for height and width, but this lets me build a map with literally just the address. In practice you could use it like so:\n\nMy first version handled outputting the img tag for you, but I thought folks may want to modify the class and other parameters so I figured returning just the URL was best. You can see a test of this in my Eleventy Demos repo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/googlemaps\nOk, so that worked just fine, and was like three lines of code, but I figured, why not make it a proper [Eleventy plugin]? So I created a new directory and moved my logic for the shortcode over there, and added basic validation support for the key:\n\nI threw this up on it's own repo (https://github.com/cfjedimaster/eleventy-plugin-googlestaticmaps) and published it to npm. Now in my Eleventy demo, I can npm install eleventy-plugin-googlestaticmaps and use it via the plugin API:\n\nI used this in a new demo and created a quick data file named stores.json:\n\nI then whipped up an example using pagination:\n\nThat's not terribly exciting, but it works:\n\n\n\nYou can find this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/googlemaps2\nEnjoy, and please feel free to file PRs against the plugin repository if you've got any ideas!\nPhoto by Brett Zeck on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using a Google Photos Album in your Eleventy Site with Pipedream",
		"date":"Fri Jan 28 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1643392800,
		"url":"https://www.raymondcamden.com/2022/01/28/using-a-google-photos-album-in-your-eleventy-site-with-pipedream",
		"content":"Ok, first off, let me apologize if the title seems a bit SEO-spammy. I really wanted to ensure I included all the major &quot;players&quot; involved in this particular demo and the title is - well, a bit busy! That being said, my blog post earlier this week on using Google Photos with Pipedream got me thinking about other ways I could use it, and of course, I started thinking about integration with 11ty. I came up with a little demo I think folks may like.\nImagine an artist who uses Google Photo albums as a way to share examples of their work. They may create an album specifically for items they want to showcase. Or perhaps any site simply wants to include a gallery of images and wants to use Google Photos as the source. By taking what I learned in the previous Pipedream workflow, I was able to setup an Eleventy demo showing this in action.\nThe demo may be found here: https://eleventy-google-photos.netlify.app/. Note that the front end of this is pretty minimal. I used a bit of CSS I found (Pinterest style (Masonry) layout using pure CSS) and the thumbnails simply link to the regular image. Normally I'd have an HTML page per image so I could keep the site layout and such, but as the important bits was the data, I didn't worry too much.\nSo, how does it work?\nThe Serverless Endpoint\nThe first step was to get a list of images for a Google Photos album. I created a Pipedream workflow that uses Google's API to fetch an album passed to the Pipedream workflow in the query string. It's two main steps. The first gets the album based on the query string value.\n\nThe only real fancy part here is that I remembered to lower case the input and the album names so that it was a bit easier to use. The second step gets the photos for the album, and as before, I'm ignoring pagination for now.\n\nNote the return of baseUrl. This is a URL that does not require authentication to work. So all I need now is a final step to return everything:\n\nThe net result is I can take the URL for the workflow and simply do: https://theurl?album=something to get the photos from that album. You can see this workflow (and again, my URL will be private) here: https://pipedream.com/@raymondcamden/getalbumphotos-p_mkCDwpQ\nGetting the Images in Eleventy\nOn the Eleventy side, I could use something like node-fetch to get the image URLs, download them, and copy them in, but guess what? The Eleventy Image plugin does everything. Not only can I tell it to fetch an image at a URL, it can also resize it for me automaticaly! Here's my data file.\n\nSo - I begin by hitting the endpoint and getting the array of images. Google Photos does something kinda cool with their API where you can append values to the URL to change the image returned. This is all nicely documented and you can see where I modify the URLs to require a specific width and height (note that Google will keep the proper aspect ration). For each image, I create an Image instance with the plugin and specify a width of 300 to create a thumbnail. The use of null there means keep the original size as well. In case that didn't make sense, I'm asking for the original size and one set to 300 pixels wide. For an online image gallery I'd probably actually want the 'big' image to also be constrained to something that looks nice in my layout. Obviously you would want to tweak this for your needs.\nThe result of calling the Image plugin is a set of metadata that I 'reshape' into a simpler object of values for the height and width of the original and the thumbnail. I also get the URLs for each.\nAt the end, my data file returns an array of images. I can then use this back in my front end. Here's a portion of the code from the home page:\n\nAs I said, instead of linking right to the image I'd normally link to a page that wrapped the bigger image, but again, I was focusing on the &quot;get the images&quot; part more.\nAnd that's it. Pretty simple with both Pipedream and Eleventy handling the more mundane parts. Let me know if this helps or if you end up using it anywhere. You can find the complete code here: https://github.com/cfjedimaster/eleventy-demos/tree/master/eleventyGoogleAlbum\nOne last note - the biggest issue with this particular demo is that it won't update automatically. I could modify the Pipedream workflow to be hard coded to one particular album and then use client-side JavaScript instead. Or - I could simply schedule a rebuild once a day or such. Also, I could setup a 'secret' URL the artist could bookmark to hit and then rebuild on demand when they need. There's multiple options you could use here to make this more seamless.\nPhoto by Fernando Lavin on Unsplash\n",
		"tags":[
	        
            "pipedream",
            
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Accessing Google Photos with Pipedream",
		"date":"Wed Jan 26 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1643220000,
		"url":"https://www.raymondcamden.com/2022/01/26/accessing-google-photos-with-pipedream",
		"content":"Edit: On May 19, 2022, I discovered an issue with my caching logic. Specifically, the URLs returned by getting a list of photos are only available for one hour. So I edited my cache to be 1 hour, not 6. I've tweaked the text around that area of the blog post as well.\nOur family has had a Google Nest Hub in our kitchen for a year or two now. All of us use it every day for the most part. We'll use it for music, weather forecasts, and basic information queries. When not in use though it's got one of my favorite features - a digital photo album. I set mine up to continuously rotate photos from one of my Google Photos albums. Seeing the pictures always makes me smile and I was curious if I could bring that experience to the web. Obviously I could just open my browser to the Google Photos website, but I really wanted something like the hardware - a random picture. Here's how I ended up building it using one of my favorite workflow services, Pipedream.\nI began by creating a HTTP triggered workflow. I went into this process not knowing exactly how the Google Photos API would work, but I had hoped I could stream the bits back in the request allowing me to do something simple like, &lt;img src=&quot;pipedream url&quot;&gt;.\nNext I needed to make use of the Google Photos API. I did this by searching for &quot;Google Photos&quot; when adding a step. Note that this did not show up in the first page of apps oddly and I had to click &quot;Load More Apps&quot;:\n\n\n\nThis drops in a simple code step with an auth connection dropdown:\n\n\n\nI've shared in the past how Pipedream really makes authentication easy but I've got to mention it again. Nearly every time I've used a Google service in the past, roughly 75% of my time is just getting the damn authentication right. Once I get past that hurdle, their APIs are typically easy to use. Pipedream handles that for me. Once I you add an account, it simply provides the auth info for you so you can focus on actually using the API.\nI will warn you that at the time of me writing this, Google has not yet verified Pipedream's access for this service. You will get a scary-ish warning like so:\n\n\n\nThe approval process is handled by Google and Pipedream started it sometime ago, so right now the delay is Google's fault. That being said, I trust Pipedream so I went ahead and approved it. Hopefully if you're reading this in the future, the warning is gone.\nWith Pipedream handling the authentication, I can focus on process. The first step is to find my Favorites album:\n\nBasically, I went to the Google Photos docs, found the endpoint for albums, and just pasted it in. Super simple and direct! I then simply filter the array down to the album I care about and return the ID.\nNext, I need the photos from that album. I added yet another action using Google Photos, and wrote this code:\n\nNote that I'm using the biggest page they support, one hundred. I've got about 69 photos in the album so I'll need to figure out a solution to paginate later on.\nNext, I added a vanilla Node.js step to get a random picture:\n\nOne note on the above logic. I'm pretty sure the Google Home device doesn't just pick randomly, and instead iterates through the album. Or perhaps it's random, but unique. I could do that. I chose not to. :)\nFor the final step, I added another Google Photos action, and returned the binary data from the workflow:\n\nAnd it worked! I opened my browser to the URL and got a pic. As I reloaded, it randomly selected new ones (here's one of my favorite favorites):\n\n\n\nSo... I was done. And happy with it. But - of course - I decided to tweak it a bit. Specifically I decided to add a cache. Pipedream has an incredibly simple key/value system called $checkpoint. It lets you store data at a per workflow or step level. Going through my workflow, I made the following changes.\nFirst, in my code to figure out the Favorites album, I cached it forever:\n\nNext, in my step to get photos, I added a one hour cache. Honestly I probably only add a photo to this album a few times a month, but one hour is the max allowed by Google for using the URLs.\n\nAnd that was it. Maybe five or so minutes of work, but the difference was amazing. On average, my initial workflow was taking four seconds to process. After this change that time went down to one second.\nWant to try this yourself? Fork my workflow here: https://pipedream.com/@raymondcamden/randomfavoritephoto-2-p_mkCDxrY\nBy the way, you can build a cheap auto reload web page in two seconds with the old meta refresh tag:\n\nEnjoy, and let me know what you think!\nPhoto by Laura Fuhrman on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Searching the New York Times with Python",
		"date":"Sat Jan 22 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1642874400,
		"url":"https://www.raymondcamden.com/2022/01/22/searching-the-new-york-times-with-python",
		"content":"In my effort to keep practicing Python and build fun stuff to help me learn, today I built a few demos of using the New York Times API. I last played with this way back in 2014: &quot;Using the New York Times API to Chart Occurrences in Headlines&quot;. In that demo I let you enter a simple search term and then I report on the number of occurrences of that term over time. I think my favorite image from that post is a search for &quot;Internet&quot;:\n\n\n\nToday the API popped up back in my head and I thought it would be fun to play with it again, this time in Python. I had some success with it today and thought I'd share the results. It's nothing too exciting and is basic Python stuff, but I love getting the practice and I did learn a bit, so that's always a good use of my time, especially on a Saturday, right?\nNote that the New York Times API is free. You can sign up and get a key in seconds. Oddly, they don't link to their FAQ which includes important information about their rate limiting. (Spoiler - I hit it.) Definitely reference that before you go live with any real code.\nMy first demo makes use of the general Archive API. My intent was to return a random article for a day in history. Here's that script.\n\nThe day, month, and year, are all hard coded here. The Archive API lets you filter by year and month, not day, so first I get all the articles for a month and then I filter it down to ones that match the target date. Dates are still pretty hard for me in Python. I used strptime to get the day value out of the resutls from the API and had to build the match by hand. I wonder if Python has a way to make that simpler - like - look at the string and take a guess?\nAnyway, once I've filtered to articles for a day, I then random.choice to pick one. I then print out the headline. I ran this a few times and here's the output:\n\n\n\nFor my second iteration, I simply removed the hard coded year, month, and day. This gave me a bit more practice with Python date support. You can see that in the top portion of the code below.\n\nGetting a random year and month was easy thanks to Python's random library. Figuring out the total number of days in the month was a bit harder, and by that I mean I had to google for it. The calendar module provides a simple API for it. I'll be honest - in the past I've been lazy with code like this and simply used 28 as a max day. Don't tell my employers. After that, it's pretty much the same code as before. Get by year/month, filter to day, and select one.\n\n\n\nFor my final demo, I decided to rebuild the logic I first did nearly ten years ago. Given a term, report on the number of occurrences in headlines over time. Not surprisingly, this is where I first hit the API limits. Thankfully Python has a sleep module to make it easy to 'slow' down my code. I also put a limit of 100 years into the code to make it a bit more reasonable. Here's my little command line utility:\n\nAs you can see, this script is meant to be run from the command like per the usage instructions printed if you don't pass an argument. Instead of a fancy graph (which I know Python can do, I'm not quite there yet), I simply output to the terminal. I also discovered that the requests library can handle URL encoding for you. I do that in the second argument to the get call. Also note I'm filtering the results to one key, web_url, because I don't actually care about the matches, just the count. I ran a test on this on &quot;iraq war&quot; and here's the result. I'm sharing this as text and not a graphic as it's quite big:\nSearching for iraq war from 1922 to 2022\n1922 0\n1923 0\n1924 6\n1925 31\n1926 15\n1927 11\n1928 17\n1929 34\n1930 39\n1931 38\n1932 25\n1933 35\n1934 18\n1935 15\n1936 27\n1937 21\n1938 23\n1939 23\n1940 19\n1941 181\n1942 34\n1943 20\n1944 15\n1945 30\n1946 52\n1947 59\n1948 102\n1949 93\n1950 69\n1951 79\n1952 79\n1953 45\n1954 97\n1955 118\n1956 141\n1957 130\n1958 302\n1959 284\n1960 107\n1961 106\n1962 63\n1963 163\n1964 47\n1965 44\n1966 73\n1967 64\n1968 59\n1969 114\n1970 46\n1971 41\n1972 47\n1973 51\n1974 51\n1975 55\n1976 25\n1977 16\n1978 25\n1979 36\n1980 328\n1981 143\n1982 138\n1983 123\n1984 154\n1985 124\n1986 122\n1987 114\n1988 157\n1989 47\n1990 663\n1991 791\n1992 286\n1993 198\n1994 104\n1995 111\n1996 182\n1997 131\n1998 539\n1999 154\n2000 82\n2001 149\n2002 984\n2003 3597\n2004 2820\n2005 2258\n2006 3524\n2007 3047\n2008 1814\n2009 761\n2010 524\n2011 378\n2012 143\n2013 173\n2014 776\n2015 386\n2016 143\n2017 134\n2018 115\n2019 95\n2020 74\n2021 80\n\nAll in all, fun and fascinating! I've got some ideas on another demo with this but will call it quits for today. Let me know what you think.\nPhoto by AbsolutVision on Unsplash\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Guide to Building a Blog in Eleventy",
		"date":"Wed Jan 19 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1642615200,
		"url":"https://www.raymondcamden.com/2022/01/19/a-guide-to-building-a-blog-in-eleventy",
		"content":"About eight months or so I had the idea to write up a guide for building a blog in Eleventy. It's not difficult of course and others have talked about the process, but I wanted to write something a bit more &quot;formal&quot; than a traditional blog post and something that could be really useful for folks new to the framework. (Framework? Product? You tell me.)\nI started writing, and building, and then, yeah, life happened. This may come as a shock to you, but this isn't the first project I started with lofty goals that ended up forgotten.\n\n\n\nThis past weekend my wife and I had the opportunity to get away for a few days and I started thinking about the guide again. I realized some ways I could do it better, as well as how I could bring it to completion, so I put some work into it and got it to a point where I think it's ready for public consumption!\nYou can find the guide here: https://cfjedimaster.github.io/eleventy-blog-guide/guide.html. It's over five thousand words and nearly twenty-five pages with not a single cat picture to be found.\nAs it explains, you do not need to be an expert in Eleventy for the guide to be useful, but knowing the basic will definitely help. The repository for the guide may be found here: https://github.com/cfjedimaster/eleventy-blog-guide I'd really like to get some feedback on it and would absolutely welcome suggestions, PRs, and more.\nRight now the HTML is built from Markdown and wrapped in some HTML to give it basic styling, but I think it could look a bit nicer as well. I'd definitely welcome some help there.\nAs always, I hope this is useful for folks. If you find it useful, drop me a line and let me know!\nPhoto by Aaron Burden on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Review: Make 7 Apps With Vue 2",
		"date":"Sun Jan 16 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1642356000,
		"url":"https://www.raymondcamden.com/2022/01/16/review-make-7-apps-with-vue-2",
		"content":"A while back I had the pleasure of reading an early edition of this book, and I am incredibly happy it's out to the public now. &quot;Make 7 Apps with Vue 2&quot; is exactly what it says - a look at creating multiple different types of applications with Vue 2. Each application introduces multiple new things though, whether it being something simple (for some) like working with a remote API to a variety of different UI techniques and libraries. I've known Jennifer Bland for a while now and she is an incredibly good educator. Her book is very approachable and would be appropriate for anyone who has learned the basics of Vue and is looking for some good, and practical, examples of what you can build with it.\nThe seven apps you will build are:\n\nA Pinterest-style gallery (I've been meaning to build something with this UI for a while, so this is perfect for me)\nA Movie Search app (ditto!)\nAn Administration dashboard (makes use of Vuetify, one of my favorite Vue UI libraries)\nA Weather app (I've got near ten of these I think on my mobile app, so I love the idea of building one in Vue)\nAn eCommerce web site (with multiple deployment examples)\nA training video app (making use of the YouTube API)\nAn example of internationalization\n\nIn each app, she clearly states what you will learn, making it easy to hop around to what you are most interested in learning next. At nearly 400 pages, you get a very detailed look at each application you build.\nAs I said, I've already read an earlier version of the book and I absolutely love it. I'm really happy to see it for sale now and it's priced very cheaply - 10 dollars and higher (and those of you can who can expense purchases, go ahead and go &quot;and higher&quot;): https://jenniferbland.gumroad.com/l/CMnHu Your purchase gets you the book (duh) and access to the source code as well.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript",
            
                "books"
            
		]

	},

	{
		"title": "Building my First Idle Clicker Game in Vue.js",
		"date":"Thu Jan 13 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1642096800,
		"url":"https://www.raymondcamden.com/2022/01/13/building-my-first-idle-clicker-game-in-vuejs",
		"content":"For a while now I've been enamoured with so-called &quot;idle&quot; or &quot;clicker&quot; games. These are games that are (typically) incredibly simple, sometimes having you just click one button over and over again, and typically let you run them in the background, coming back hours later to check your progress. What makes these games interesting is that while they start simpler, as I said, with one button sometimes, they typically grow in complexity as the game goes on.\nSome of the most wellknown games of this genre are A Dark Room and Universal Paperclips. I'll warn you - before clicking either of those links, be sure you have the willpower to walk away from them. At least for me, both are incredibly addicting. They start off so simple, but then morph into something so incredibly engaging it's hard to let go. I play both maybe 2-3 times a year and typically I get obsessed with it over a three to four day period.\nAs someone who loves games, I've also taken a stab at building my own games as well. I've discovered that, not surprisingly, it's a hell of a lot of work, but I still enjoy doing it from time to time. Most recently, I came up with a simple clicker game in Vue.js - IdleFleet.\nNow if you want - stop reading, open the link above, and just play the game. The rest of the post will talk about how it was built, how it works, and so forth, but if you want to be surprised, now is the time to check it out.\nThe Basics\nIdleFleet is based on a simple premise. You are a commander of a fleet of ships. You order them to go out and do &quot;trade stuff&quot;. The ships of your fleet return after some random amount of time (each ship does it's own thing so they come back one by one) and you earn money. You take that money and buy more ships. Repeat.\nOne of the first fun things you'll see is that I use a npm package called random-word-slug to name ships. This gives you fun little names like &quot;Wet Book&quot;, &quot;Early Printer&quot;, and &quot;Ripe Fountain&quot;. This is done with the following function:\n\nLike most idle clicker games, IdleFleet will slowly add more options as you play. The first option is simple a stat, &quot;Credits Per Second&quot;, and it gets added to your display once you hit 5K credits.\nThen you get &quot;Mercantile Skill&quot;. It opens up once you've earned 10K in credits. This is a skill that slowly improves the credits your ships earn. It can be scaled infinitely, but the price goes up in a linear fashion. I did this with a simple computed value:\n\nThe next item that opens up is Ship Speed. You can start buying that at 100K credits and it will impact how long your ship needs to travel to get credits. I'll talk a bit more about travelling in a minute. This stat uses the same formula as the Mercantile skill.\nThe final unlockable is &quot;auto ship send&quot;, which basically removes the need to click anything at all. It will run at an interval and click &quot;Send Ships&quot;, automatically sending out every idle ship. This opens up at one million credits.\nThe Not So Basics\nAs I said, sending your ships out is a somewhat variable process and one that evolved as I worked on the game. Initially, I designed a simple object for the ship. Here's an early version of the addShip function.\n\nThat's quite a bit of logic, but it worked well, at least at first. Notice how the ship has a function, trip, that handles figuring out ow long the trip will be. It's random based on a min and max range that gets better as you increase your ship speed. I use a setTimeout to handle the ship returning. It marks it as available again and adds money to your account.\nAs I said, this worked great, until one of my players had a few thousand or so ships. The timeouts were making the game drag. So, I pivoted. Instead of a ship having it's own timed function, I created a central &quot;heart beat&quot; for the game. Now ships will simply figure out, &quot;I return at X&quot;, and the heart beat can iterate through them and figure which ones are done.\nHere's that new logic:\n\nAs you can see, I still have a trip function, but now it's just figuring out how long it will be. My heart beat function will handle checking it. I also let the user know when the next ship is returning. Given N ships out on a job, I report on the one that's returning the soonest.\n\nI also have two other timed functions. One is a simple random message generator and one is an &quot;event&quot; system. On startup, I request a JSON file:\n\nThis messages files contains a list of five things - random messages, which have no impact on the game. Then I have a list of events that represent you winning or loosing money as well as winning or loosing ships. The file looks like this (and yes, I see the typos now, going to correct them after I finish the blog post):\n\nI wanted a separate list of strings like this so that it was easy to expand when I was feeling creative. Random messages work like so:\n\nRandom events are a bit more complex. Since they can have a ne",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Front Matter in Python",
		"date":"Thu Jan 06 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1641492000,
		"url":"https://www.raymondcamden.com/2022/01/06/working-with-frontmatter-in-python",
		"content":"Happy day! Yesterday I discovered a bug in the script I built to search my blog content (see my last post for details. My search script uses Python to parse my blog content, try to find matches to a list of input terms, and return the result. I noticed that one result, when clicked, led to a 404. Why?\nMy original script used the filename as a way to determine both the date and URL for a post. My Markdown files actually contain both the date and URL in their front matter, but I figured I didn't need to bother parsing that. Turns out, I was wrong. For reasons that are boring and not terribly important, it's possible the day referenced in a filename may not match the day used in the URL.\nWith that realization I decided to update my search script to support parsing the front matter so I could safely get the URL and date. There's multiple Python libraries for YAML, and I actually started going down that route with the thinking that I'd read in my blog post, split the front matter out, and then parse, when I did a bit more searching and came across python-frontmatter. This is a Python module that will parse a file (or string) of a page that includes front matter and give you both the front matter as a dictionary and the rest of the content as a string. It's perfect! So imagine this source:\n---\ntitle: My Cats\ncategories: [&quot;cats&quot;,&quot;pets&quot;]\ndescription: Why cats are better than dogs.\n---\n\nThis is the rest of the content.\n\nAfter using pip to install python-frontmatter, you can use it like so:\n\nKeys in front matter can be addressed in dictionary-style in the result and .content can be used to reference the text after the front matter. Here's what the above outputs:\nTitle My Cats\nCategories ['cats', 'pets']\nDescription Why cats are better than dogs.\nContent This is the rest of the content.\n\nCool. So I began to modify my search script. First, I modified makeIndex to use the module:\n\nYou can see it's a heck of a lot simpler than the previous version. The next modification was to the results display. My dates now included times which I didn't really need. I also modified how the URL was printed:\n\nHere's sample output:\n\n\n\nCheck out the docs for more information on what the module can do as I only used what was necessary for my script. The full version of my search script may be found here: https://github.com/cfjedimaster/raymondcamden2020/blob/master/search.py\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a File Search Script in Python",
		"date":"Mon Jan 03 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1641232800,
		"url":"https://www.raymondcamden.com/2022/01/03/building-a-file-search-script-in-python",
		"content":"As I've said many times lately, I'm trying to learn Python. I used it for last years Advent of Code and successfully finished ten days of challenges. I'm also on the lookout for other places I can use it, even if just to provide a way to practice the language. Today I looked into making a change to my search interface. It uses Algolia for indexing and searching. Currently it sorts results by the strength of the match, but I was looking for a way to optionally sort by date instead. Algolia supports this via &quot;replicas&quot;, a copy of your index. It supports the idea of a &quot;virtual replica&quot; which is the most optimal way of doing it. Unfortunately, this is not supported on their free tier. To be clear, Algolia provides awesome value at their free tier so I don't blame them for charging for this, but I needed a solution of some sort. Enter Python!\nSince my search page doesn't really get a lot of traffic anyway (mostly me actually), I decided a local script solution would be fine. I decided to build a Python script that would:\n\nBe a runnable script like I've done with Node (and by that I mean something I could run without specifying the python command first)\nTake a number of arguments for search terms\nParse all my local data\nFind the matches\nSort by date\n\nPretty much all of the above was new to me, so even though I knew my &quot;search&quot; logic would not be as good as Algolia's, I figured it was a worthwhile use of my time. First things first, I had to figure out how to make a Python script &quot;runnable&quot; without needing to specify the python executable. Not surprisingly, it followed the typicaly bash style that I've used for Node scripts:\n\nAnd of course, I had to chmod a+x the file too. Cool. Next, I needed a way to check the number of arguments sent to the script. If none were sent, I wanted to print out a usage command and exit out. That was done via the sys module:\n\nsys.argv is a list of arguments where the first item is the file name. Now if I do: ./search.py, I'll get a reminder of how to use it.\nNext I added a main method, and followed what I believe is the standard way of writing Python scripts:\n\nNote how I pass all the args to main except the first one. I have to say, the way you join an array is still really awkward to me. I much prefer the syntax in JS of arr.join(string to use). But I can get over it.\nNow I started to build out my main method. First, I get all of my Markdown files. I defined a variable defining a glob string pointing to my bolog posts:\n\nAnd then used the builtin glob module to get the files:\n\nIt's a bit weird that the glob pattern isn't recursive by default, but I'm sure I won't forget that next time I use it. Surely.\nAlright, next I needed to create an &quot;index&quot;, or an in-memory respresentation of the files. This is not ideal, and not scalable, but in my testing, the performance with my data was fine. The process took a bit less than a second, which is quick enough for me. I began with:\n\nAnd wrote the following method:\n\nThis should start off sensible - I loop over the files and for each, slurp the contents in. The weird part comes into how I parse out the date and path. All of my blog posts follow a file format that looks like this:\n_posts/YEAR/MONTH/DAY/YYYY-MM-DD-slug.md\nSo I can get the date by parsing the file. It's also in the file's front matter, but the path was easier to parse. For the most part. As you can see in the comment, a least one file had a timestamp in the day part. Not sure what happened there to be honest.\nAnyway, I get the date parts and then make a date object. I then need to translate the filename to the URL path, which is the date values again and the slug part of the filename without the md extension. It makes sense. Kinda.\nFinally, I take the three parts, add them to a dictionary, and add them to the result list.\nAlright, next up is the actual search portion. I call it like so:\n\nAnd defined the method like so:\n\nBasically, go over every item in the index, and then loop over every search term (remember I said I wanted it to AND the input). I check to see if every term is there, and as long as they all are, the item is added to a result list. As I write this I noticed I'm lowercasing my search term once per file which is wasteful and I'll fix that later. Surely.\nFinally, I sort my result list. Honestly, I don't quite &quot;grok&quot; that syntax, but I both don't 100% understand it and think it's freaking cool as hell.\nI want to stress that me doing a simple string match is nowhere near as cool as Algolia's search API. But it's good enough for my purposes. Back in the main method, I handle the results like so:\n\nI print out the results with my domain because in both VS Code and Terminal, the links can be clicked. Here's an example:\n\n\n\nHere's the entire script if you wish to see it, and feel free to critique my Python code - I'm sure it could be done a lot better!\n\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "year++",
		"date":"Sun Jan 02 2022 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1641146400,
		"url":"https://www.raymondcamden.com/2022/01/02/year",
		"content":"Once again I'm posting a &quot;yearly roundup&quot; that mainly serves to let me figure out how I did last year and figure out my plans for next year. I figure no one reads these but me, but I honestly feel like it's useful to take stock and recognize my accomplishments. Plus, it lets me get the first post of the year out the door and that's always a good thing. Oh, and I found a small bug in my blog while writing this, so that's even better. Let's get started!\nWhat I Did\nIn March, I (re)joined Adobe (&quot;Hello (Again), Adobe!&quot;) as an evangelist for the Document Services team. The past year has been incredible and I've loved having the opportunity to both learn and share that with others. Honestly, as much as I've changed jobs over my career, I'd probably be a bit leery rejoining a company I left in the past, but it's absolutely been a great decision and I can't wait to get back to work on Tuesday. Don't get me wrong, I've enjoyed this time off, but I'm excited to get this new year started.\nOn the personal, but still professional front, I'm very proud of of the following:\n\nI wrote 67 posts last year. My goal is 4 per month and I blew that away.\nThat's on top of 16 posts I did for the Adobe Medium blog.\nI also wrote 3 articles for other publications (see my About). That's a bit low, but most of my &quot;side writing&quot; time this year was focused on the book I'm writing with Brian Rinaldi (coming out very soon now!)\nSpeaking of books, I was a contributing author to &quot;Front-End Development Projects with Vue.js&quot;. I'm looking at the publication date and that might actually have been 2020, not 2021, but that's fine.\nI gave 14 talks last year. Actually a few more as I gave some internal Adobe presentations as well. With COVID, only a few were in person and I don't see that trend changing much this year. In fact, my first in person talk (not yet up on my speaking page) just recently switched to virtual itself.\n\nAll in all, I'm pretty proud. I'm going to try to maintain the same goals for next year. 4 posts per month. One presentation per month. One book per year (it's actually going to be two I think).\nWhat I Want to Do\nFor years I've said I'm going to learn Python. This past year I actually made some strides in it. It isn't yet a language I'm comfortable in yet (I need to get the docs at hand even for simple stuff), but I'm getting slightly more familiar with it. Oh, and while I have no doubts that it has rough edges like every language, I just keep liking it more and more.\nOn top of that, my rough plans for new things to learn include:\n\nMicrosoft Power Automate - this is a service that lets you build workflows, a lot like Pipedream, but obviously from Microsoft. I've already used this at work (and at my last job) so my plan here is to just get more comfortable with it.\nOracle Cloud Functions - I didn't really plan this, but a very good friend of mine works at Oracle, mentioned this, and I figured I should add it to my queue, especially since I can nag him for help with it.\nAdobe Sign - obviously this relates to my work, but Sign has a really good API and a great development platform in general. I've done a bit with it, but I want to do more.\n\nAnd I think that's it for now. I mean, hopefully I'll learn more, but in terms of planning, I should probably keep it simple. I also have an incredibly bad track record of planning what I'll learn, so the less I predict the less I'll screw up.\nWhat Matters\nI tried my best to be a good father, a good husband, and a good friend. I think I succeeded in that. I am incredibly blessed to have a wonderful, supporting wife and children who constantly surprise and delight me. I have friends that I can rely on for support and keeping me honest. For 2022, if I can support my family and friends, keep them as safe as possible, and help them succeed, than I'll consider it a win.\nAs always, thank you for reading, and have a wonderful new year!\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Python for My Last Post of the Year...",
		"date":"Fri Dec 24 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1640368800,
		"url":"https://www.raymondcamden.com/2021/12/24/python-for-my-last-post-of-the-year",
		"content":"For the last post of the year (probably, I'm off next week with very little on my calendar so I hope to spend a lot of time doing nothing, not blogging) I thought I'd share a quick Python function. This function is inspired by my favorite Christmas movie, &quot;A Christmas Story&quot;. In one of the more pivotal scenes, one character dares another to stick their tongue on a cold pole. You see, one of the characters said their dad stated that sticking their tongue on cold metal would result in the tongue sticking. The other character didn't believe it. As you can imagine, disaster unfolds.\n\n\n\nIn this scene, the character goading the other to stick his tongue on the pole uses progressively more serious &quot;dares&quot;, so for example, first a dare, then a double dare, and so forth.\nAt one point the character goes from &quot;double dog dare&quot; to &quot;triple dog dare&quot;, and the narrator mentions how it's a slight breach of etiquette.\nFor some reason, I thought it would be fun to write a Python checker that lets you take two &quot;dares&quot; and determines if it's a valid &quot;raise&quot;. So for example, &quot;double dare&quot; is a valid raise from &quot;dare&quot;. As is &quot;double dog dare&quot;, even though it skips a few levels. But &quot;dare&quot; would not be a valid raise from &quot;triple dare&quot; as that goes down in - well, &quot;dare-osity&quot;?\nPointless, I know. But I wrote it:\n\nFirst I validate both the initial and new dare. If either are invalid, I return false. Note that you can't use the Python index list method as it returns an error if an element doesn't exist. Then I can simply return a True/False bases on if the first dare's index value is less than the second (equality is not allowed).\nHere's a few simple tests:\n\nAnd the output:\n\nAnd that's it. By the way, I was searching my blog content for Python stuff and discovered that sometime back in 2007 I stated that Python just looked &quot;wrong&quot; to me. Wow. Oh, and a few years after that I casually mentioned trying to learn it. I guess that went nowhere. I've said it before and I'll say it again - Python is the first language I've truly been excited about in a very long time. I love it!\nPhoto by Rodion Kutsaev on Unsplash\n",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Google Analytics 4 for Blog Stats",
		"date":"Fri Dec 17 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1639764000,
		"url":"https://www.raymondcamden.com/2021/12/17/using-google-analytics-4-for-blog-stats",
		"content":"Like a lot of people in tech, I'm a bit of a stats junkie, especially when it comes to my blog. Over the years I've used various tools for stats, primarily Google Analytics. Google Analytics is easy to add to a site, but the reports can sometimes be a bit dense to get into. Because of this, I've actually built my own tools. Here's a few examples:\n\nIntegrating Google Analytics with Eleventy\nGoogle Analytics and RSS Report - Version 2\nGoogle Analytics and RSS Report\nProof of Concept - Dashboard for Google Analytics\nCreating a custom display for Google's Analytics Embed Library\nUsing the Google Analytics Embed API to Build a Dashboard\nQuick example of the Google Analytics Embed API\n\nA few years back I was happy to discover Netlify's own tool, Netlify Analytics. This was perfect. No need for any JavaScript library in my code that a huge amount of people blocked and an incredibly simple report that was quick and easy to read. They didn't have an API, but there was a hidden one I was able to build on. Even though it was probably a bad idea, I built a few demoes with it:\n\nNetlify Analytics and Eleventy\nBuilding a Netlify Stats Viewer in Vue.js\nAnother Netlify Analytics Hack - Stats Per URL\n\nOne of the primary reasons I built hacks with their (unofficial) API is that as cool as Netlify's Anayltics report is, it caps out the results at thirty days max. By using the API I was able to get older stats just fine.\nUntil it wasn't. :) One day the API simply stopped returning older content. Now - keep in mind that Netlify Analytics was first released in July of 2019. One of the first things people asked about was historical data. It took two years for that to finally happen (Announcing Netlify Log Drains for Datadog). While I'm disappointed it took so long to have a way to access historical data, this is a decent solution. The only issue (for me anyway) is that it involves paying for another service, Datadog. I've got nothing against paying for a good service, but right now this blog doesn't generate much income (I removed ads a few months ago) so I'm trying to keep to a free tier.\nAlright, so back in January, I used my unofficial Netlify API hack to try to get a report on my blog over the previous year. This is when I first discovered the API was no longer returning historical data. I was ticked off, but I only had myself to blame. I decided to simply put Google Analytics back in, and keep Netlify Analytics as my &quot;day to day how is the blog doing&quot; report and GA as a way to track historical data. There's a tremendous difference in stats between the two with people blocking tracking apps, but I figured Google Analytics would still be useful as a way to watch trends.\nAs an example of the difference, according to Netlify Analytics (NA from now on, getting tired of typing it), my site had 364K page views in the past thirty days. According to Google Analytics (GA) I got 24K. But hey - that's not the issue. When I added GA back in January, I was prompted to update to the latest version, Google Analytics 4.\nI figured - why not - if I'm adding the product back in I should use the latest version. What I didn't realize is that while previous versions of GA were a &quot;bit&quot; difficult for me to use (hence me building my own tools), GA4 was far away and more opaque and difficult to use. Don't get me wrong - I'm sure people who are power users love it. But honestly - I hate using it. Like, just now when I was writing the previous paragraph, it took me about five minutes just to get that dang number. Nothing in GA4 is &quot;obvious&quot; to me. Not one dang thing. And again, I'll take the blame. Maybe I just need to dedicate a week (month) or two to learn it better, but I just don't like it.\nSo with that mindset, I decided to try my own tools again. And then I discovered that since my web site was using the latest version, the previous code I wrote no longer worked.\n\n\n\nAfter a cry or two, I did some research. I began with the GA 4 API docs. From what I could see, the code was pretty similar to previous versions. You create a somewhat complex query structure, pass it to the API, and then parse the somewhat complex results. I'm not going to attempt to explain it deeply as there's documentation for that, but here's one simple example:\n\nThis runs a report on my page views from November 2nd to today. The result contains an array of records where each record contains dimensionValues and metricValues. dimensionValues refer to the dimensions of my report, which is the date. The metricValues is the data I care about, the page views. These are both arrays with the 0th element being what I care about, hence me dumping that above. The (partial) output looks like so:\nReport result:\n{ value: '20211117', oneValue: 'value' } { value: '1291', oneValue: 'value' }\n{ value: '20211116', oneValue: 'value' } { value: '1272', oneValue: 'value' }\n{ value: '20211102', oneValue: 'value' } { value: '1250', oneValue: 'value' }\n{ value: '20211103', on",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Running Netlify Dev and Eleventy Two or More Times At Once",
		"date":"Fri Dec 10 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1639159200,
		"url":"https://www.raymondcamden.com/2021/12/10/running-netlify-dev-and-eleventy-two-or-more-times",
		"content":"Ok, so this falls into the category of something probably few people will run into, but since I did, I've got to blog it. If only to save myself when I inevitably forget six months from now. Also, all credit for this solution comes from Netlify support engineer [Hrishikesh Kokate](Hrishikesh Kokate). Alright, so what's the issue?\nI use Eleventy for this blog and host on Netlify. Netlify's CLI has a great feature that lets you mimic the Netlify production experience in your local environment. You go into your project and run netlify dev and you're - usually - good to go. Obviously, you sometimes need to configure things a bit. So, for example, this is how I run this blog locally:\nnetlify dev -c &quot;eleventy --serve --quiet&quot;\nThe -c command tells Netlify's CLI what command to use to run my static site generator. This will run Eleventy and then fire up Netlify's local development server as well.\nAs I said, I run my blog on Eleventy, and I also build a bunch of dumb demos with Eleventy, so I write a lot of silly blog posts on it as well. Seriously, I probably need to stop at some point. Anyway, sometimes I need to run my blog locally while I write a post and run an instance of Eleventy for a demo.\nThis past week, I just so happened to try to run my blog and an Eleventy demo that needed to run via netlify dev as well. I was concerned about the ports. The Netlify CLI will automatically recognize when its normal port is taken and simply select a new one. I wasn't sure what Eleventy would do. To be safe, in my second demo, I did this:\nnetlify dev -p 9999 -c &quot;npx eleventy --serve --port 9998&quot;\nBasically, use a unique port for both parts of the second demo. While nothing looked amiss in the terminal output, when my second site opened up, it was showing the content for my blog. I could manually go to port 9998 and see the second Eleventy site, but I needed the &quot;context&quot; of Netlify Dev as I was using its Functions feature.\nSo I posted on the forums and this is where Hrishikesh saved the day. To get it working right, edit the [dev] block of the netlify.toml file:\n[dev]\n  framework = &quot;#custom&quot;\n  targetPort = 9998\n\nHonestly, I'm not 100% sure why this worked, but it did. One quick note - I wanted to stop using -c at the command line in the second instance as I was now using netlify.toml to specify stuff, so I modified it like so:\n[dev]\n  command = &quot;eleventy --serve&quot;\n  framework = &quot;#custom&quot;\n  targetPort = 9_998\n\nAnyway, as I said, this will probably impact two of you, one is me in six months (hi me, you rock, no matter how much weight you lost or gained!), so thanks for reading!\nPhoto by Ronan Furuta on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Fun (Scary?) Webcam Demo",
		"date":"Wed Dec 08 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1638986400,
		"url":"https://www.raymondcamden.com/2021/12/08/fun-scary-webcam-demo",
		"content":"Windy is a fascinating site/app that gives realtime visualizations of wind speed and direction in your area. As a static picture wouldn't do it justice, here's my local area right now.\n\nBack during the last hurricane I took this lovely snapshot. Not terrifying at all...\n\n\n\nAnyway, it's a great little app, and like a few other people I know, I've got a bit of an addiction to weather apps. That being said, a while ago I discovered Windy had an API, and not only that, but a Webcams API. A literal API that returns information about publicly known webcams around the world. This information contains a wealth of information, including screenshots and information about the location. As an example, here's information about a webcam near me:\n\nAnd this is only a subset of the data they can return. For my demo (more on that in a sec) I tried to limit the call to only returning what I needed. All in all it's a cool API, it's got a free tier, and I thought it would be fun to build something with it. I got even more excited when I noticed they had a &quot;nearby&quot; API. This lets you make a request for webcams in a certain circular region:\n/api/webcams/v2/list/nearby={lat},{lng},{radius}\nI thought it would be cool (and honestly, a bit scary), to see how many public webcams are around you. Obviously this wouldn't be all of them, just the ones Windy has in their database, but I was still rather curious as to what it would turn up. I whipped up an incredibly simple Vue.js application. Here's the JavaScript:\n\nAll it really does is get your location via the browser's geolocation API. It then passes to this the Windy API. I display the images in a simple grid:\n\nThe result is pretty cool I think. Here's my area:\n\n\n\nAs you can see they're all traffic based. Also note that Windy's API supports embed and live views, but for my app I thought the pictures alone were ok. You can click for a more dynamic version.\nMicrosoft Edge's devtools makes it easy to fake your location too. Here's Moscow:\n\n\n\nAll in all, this took me like five minutes to code, but it was kinda fun. It's on CodePen, and normally I'd just embed it, but geolocation doesn't work inside CodePen embeds, so if you want to play with it yourself, just head over here: https://codepen.io/cfjedimaster/pen/yLzOpPZ\nPhoto by Scott Webb on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Eleventy 1.0 - The Serverless Plugin",
		"date":"Sat Dec 04 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1638640800,
		"url":"https://www.raymondcamden.com/2021/12/04/eleventy-10-the-serverless-plugin",
		"content":"As Eleventy moves towards a final 1.0 release, I thought I'd try to (finally) wrap my head around one of the biggest new features, the Serverless plugin. I say finally because this new feature has been out in beta form for a while now and I've really struggled with trying to wrap my head around it. This isn't to say that the feature is difficult or the docs are bad, but I just didn't get it. After multiple attempts at trying to figure out how it works, reading blog posts, and building my own demos, I think I've got a handle on it.\nAs with most things I do on this blog, when I struggle with something, I try to write it down and share it with others to help them avoid the troubles I had. I'd take what follows as my &quot;initial impression&quot; of the feature and please know I may not be getting things exactly right. I welcome any feedback about the following so let me know if I've done something wrong or anything isn't quite clear.\nOk, with that out of the way, let's start by describing a situation where Eleventy Serverless could help out!\n\n\n\nWhen it comes to sites that are statically generated, the typical way to add dynamic information is with client-side scripting. The page will load for the user, and sometime soon(ish), hopefully, JavaScript will figure out what it needs to figure out. This may be done entirely by itself, or JavaScript may be used to make a network request to an API.\nAll in all this works fine typically, but there may be multiple reasons why you don't want to use client-side JavaScript. Perhaps you want to ensure that lower-end browser clients don't struggle with performance. Maybe you want to avoid the page displaying with empty blocks of content while the data is being loaded.\nImagine you've built a service to report the current weather for a location. Your initial implementation was a serverless function that took in a location parameter and returned weather information:\n\nThis imaginary API could live at: /api/weather. Your front end code does something like so:\n\nThis works, but as I said above, you may decide to move to a non-client-side JavaScript solution. How would you do that?\nWell remember that APIs do not have to return JSON. They can, if you want, return just a plain string, including HTML. Imagine now we've changed that API to return this:\n\nCool! Now you can simply use a regular HTML link to /api/weather and the browser will render the HTML with no client-side coding. Except... you've now lost your site's layout! You literally only returned that paragraph of text and nothing more. You can, of course, add more HTML to the result. But then you realize, you're using Eleventy (or another generator) and it handles things like layouts, includes, etc. You don't have a simple HTML file you could copy and paste HTML from.\nThis is where Eleventy Serverless comes in. It basically lets you create a mapping to a dynamic resource that when generated, runs Eleventy on the fly. What do I mean? Normaly Eleventy only runs when your site is built. It does all of it's magic of generating pages, incorporating your data files, pagination, and so forth. But once it's done, it's done. That's kind of the point.\nEleventy serverless lets you say, &quot;Hey, on the fly I need you to run a template for me, do all my normaly stuff like layouts and so forth, but just for this request.&quot; You can also use Netlify On Demand Builders such that Eleventy will only run once for a particular request, not every time. Which you use depends on your needs.\nSo how do you do it?\nStep 1: Add the Plugin\nFirst, what follows is mostly from the docs, but simplified a bit. I began by adding the Serverless plugin to my Eleventy site and giving it a name:\n\nThere's a lot of options you can specify, but at minimum here I've given a name to my plugin based on my use case. From what I can tell, the name here does not need to match your intended use case, but rather the type of serverless response your going to use, by that I mean either responding dynamically to each request or using the On Demand run once support. The docs suggest calling it &quot;serverless&quot; if you aren't sure. Basically, even if you have two intended use cases (getting the weather and producing a picture of a cat), you only need one instance of the plugin. That really confused me at first. The docs use &quot;possum&quot; as an example which makes you think you should potentially name it after your use (like &quot;weather&quot;), but that's not what you need.\nStep 2: Handle the Generated Files\nThe previous step will cause Eleventy to generate a serverless function named, well serverless. It's going to put a lot of stuff in there that you should not need to touch. The next step in the docs tells you explicitly to add this to .gitignore:\nnetlify/functions/serverless/**\n!netlify/functions/serverless/index.js\n\nBasically, don't commit it to your repo, except for the index.js file, and let Eleventy manage it.\nStep 3: Add a Template to Handle Requests\nNow for ",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Review: Captain Code: Unleash Your Coding Superpower with Python",
		"date":"Mon Nov 22 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1637604000,
		"url":"https://www.raymondcamden.com/2021/11/22/review-captain-code-unleash-your-coding-superpower-with-python",
		"content":"It's probably a bad idea to blog the week of Thanksgiving and I truly hope my readers (well, my American ones) are spending (healthy) time with family. That being said, holidays tend to be a great time for me to pick up new programming languages and tools. It's amazing how nice it is to learn something when I'm doing it just for fun. I've been learning, off and on, Python now for over a year. I still don't use it a lot, but the more I do, the more I appreciate it. I've come to realize that I think Python would be an amazing language for new developers. It's simple - doesn't require a compilation step - and lets you gradually expand your skillset as your needs and experience grow.\nSpeaking of new developers, this is where the book I'm reviewing will really come in handy. &quot;Captain Code: Unleash Your Coding Superpower with Python&quot;, written by Ben and Shmuel Forta, targets people who have never written a line of code before and gently walks them through the process of becoming a code. It's light hearted, and uses games for many of it's examples, but I'm not sure I'd say it's a kids book. I'd say anyone who wants to give coding a try would be a perfect fit for this book.\nWhile going through the text (disclaimer - at this point I'm approaching halfway done) I was honestly shocked by how many things the authors took time to explain that would have absolutely been a problem for non-traditional developers. As an example, very early on in the text they explain how you have to create a folder for your scripts. They explain getting an editor, saving and naming your files. This is something I figured out a very, very long time ago, but the more I think about it, the more I realize how non-obvious this would be. Think about how most mobile and tablet devices hide away the idea of a file system. While you may have a &quot;Files&quot; app on your device, just the initial &quot;where do I start writing&quot; idea is going to be something a reader has to learn once.\nThis continues throughout the text. Time and time again the authors explain concepts that folks coming from other languages wouldn't necessarily need, but first time coders would. For example, using variables in your programs so you don't have things hard coded in logic. The first time you write a program with hard coded values and realize how hard it can be to update, you figure this out. I love that the book calls this out. As I said, I really feel like Python makes a great first language, and this book makes a great introduction to first time coders.\nI also appreciate how the reader is never talked down to. Like I said, it does look like a book aimed towards kids (although, um, I'm kinda into superheroes too ;) but it's one that never treats their readers as children.\nAnother thing I appreciated were times when the book pointed out concepts in such a way that the &quot;academic&quot; aspect of something could be contrasted with the &quot;practical&quot; nature of something. This is useful for times when we may say, &quot;the precise definition of something is so&quot; but &quot;we almost always just do so and so in real life&quot;.\nAll in all, this feels like the work of people who've done real teaching, and quite a bit of it, and have crystalized it into a book that's easy to understand and quick to pick up.\nThe book is split into three sections. The first section really does a great job of covering the basics - essentially everything you need until you realize you need to start building re-usable blocks of code. By the end you've built a real game and have been given quite a few challenges and ideas for more things to build.\nThe second section really digs deep into packaging up your code, covering functions, and classes and additional libraries.\nThe third section introduces graphics and Pygame. I've only recently discovered it (in the other Python book I'm reading) and it's an incredibly cool games framework.\nAt 400 pages, quite a lot is covered, but there's two things missing I'd like to see added in either a new edition or a follow up book. First, there's no use of HTTP. With so much of a developer's work being API driven, I'd like to see at least a short section on this topic. Heck, using the Random Dad Joke API would be a perfect fit for this book. The second topic not covered is unit testing. Now... I'm not necessarily religious about unit testing. Obviously I believe in it! When learning a new language, I'm not sure I'd teach it in the first book, or first semester, etc. That being said, I remember being really surprised that Python had unit testing built in, and I can't help but think it should be highlighted. The book absolutely talks about testing your code. It does a great job in demonstrating real techniques that will be useful in that regards. But I'd still like to see maybe an appendix on unit testing.\nDo I recommend this book? Absolutely. That being said, if you are an experienced developer learning to pick up Python, keep in mind thi",
		"tags":[
	        
            "python"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Congratulating Yourself with Pipedream and Microsoft To Do",
		"date":"Sat Nov 13 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1636826400,
		"url":"https://www.raymondcamden.com/2021/11/13/congratulating-yourself-with-pipedream-and-microsoft-to-do",
		"content":"I'm very, very excited about this blog post. Not that it's anything super important, but it's something I added to my blog writing queue a long time ago. When I talk about my blog queue, I'm talking about a task list under Microsost To Do. This is a great little application I've been using for a few years now. I tracked things like my tasks for my wedding and shopping lists as well as things I'm doing for work. My primary use case though is a queue of ideas for blog posts.\nHere's how the todo for this blog post looks in the Windows application:\n\n\n\nIn general, I really just dig the application and it's worked well for me. A few years ago (August 2019), Mary Branscombe shared this tweet:\nThis would be a lovely &quot;achievement view&quot; for @MicrosoftToDo @marcusash   https://t.co/zqkJ00iTFm&mdash; Mary Branscombe (@marypcbuk) August 19, 2019 \nShe was retweeting Julia Duimovich who herself was talking about how @alicegoldfuss had created a process that looked for tasks she completed and emailed her a list of the things she did. You can, and should, read her post here: Automating My Todo with GitHub and Twilio.\nWhen I saw that Tweet, I thought it would be cool to do something similar in Microsoft To Do. At the time, there wasn't an API that let you read your To Do later. Fast forward to about a year ago, they added it:\nFYI - https://t.co/RC0p9uh1ui&mdash; Raymond Camden (@raymondcamden) November 3, 2020 \nCool! We had an API and I was ready to go. Microsoft has great documentation as well as a cool online Graph Explorer. Unfortunately, when I started working with the API, I ran into errors. It was beta initially, but I couldn't get anything to work.\nI reached out on their forums, and got responses, but after three plus months of back and forth, I never got a resolution. While I appreciated Microsoft folks trying to help, in the end though I was stuck with an API that didn't work for me. (And that's an important detail, it seemed to be tied to an issue with my account, but we never got to the end of it. If you want you can peruse the details at the thread.)\nOn a whim, and wanting to do something productive and fun on a Saturday morning, I randomly hopped over to the Graph Explorer and tried it again... and it worked!\n\n\n\nOk - with access to my To Dos, I began to dig in. First, to get a list of all your task lists (to dos are grouped within lists), you make an authenticated call to https://graph.microsoft.com/v1.0/me/todo/lists. This returns a result like so (I cut some out):\n\nTo get the to dos for one list, you take the ID and go to https://graph.microsoft.com/v1.0/me/todo/lists/${TASK ID}/tasks. This returns data like so (and again I slimmed it down a bit):\n\nCool. So now I needed to think about my logic a bit. The idea I was trying to emulate was a scheduled task that would congratulate me for completing tasks. (And forgive me if I go back and forth between saying 'task' and 'todo'. As you can see in the APIs, Microsoft also uses tasks.) The response includes a @odata.nextLink property which means I could build code to get every 'page' of data and then filter to completed items. If you look at the completed example above (the first item in the list), you'll also see that the completed time is marked. So I could filter by completed as well as items completed in the past seven days. I was worried about performance though so I did some digigng.\nLooking at &quot;Use query parameters to customize responses&quot; I discovered I could use $filter. I played around a bit and came up with this:\nhttps://graph.microsoft.com/v1.0/me/todo/lists/AQMkADAwATMzAGZmAS04MDU4LWQ4ZjctMDACLTAwCgAuAAAD2b-xt4VpMU28CRdh70oBigEAzwpFFkTJnUqSIr7l4olnFgACofznJAAAAA==/tasks?$filter=status eq 'completed' and completedDateTime/dateTime ge '2021-11-01T08:00'\nThat's a rather long URL but the important bit is the filter: $filter=status eq 'completed' and completedDateTime/dateTime ge '2021-11-01T08:00' Status should make sense as is, but the date filter is dynamic (you'll see this code in a bit).\nThis worked great. I then tried to use the $select filter, but it didn't work for me. I kept getting errors even when using the autocomplete in their web-based tool. I fiddled around trying different things and eventually I got an error saying select wasn't supported, so I imagine in general it's not something you can do with this particular resource. I could definitely be wrong so keep that in mind.\nAlright, so at this point, I've got an endpoint that returns completed To Dos filtered to a date range. Now I just need to get some code up! I decided to make use of Pipedream. I knew I could both easily handle the schedule (once a week) as well as the authentication. Here's how I built my workflow.\nI began with a scheduled based trigger. Pipedream lets you pick from some defaults or enter a cron expression. What's really freaking cool is that as you enter your expression, it updates text that reflects the expression itself. Cron has always been hard for me",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Eleventy 1.0 - Global Data via Plugins Example",
		"date":"Sun Nov 07 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1636308000,
		"url":"https://www.raymondcamden.com/2021/11/07/eleventy-10-global-data-via-plugins-example",
		"content":"A few days ago I blogged about a new Eleventy 1.0 feature that gives a new way to use global data. This new feature adds a method call, addGlobalData, that can be used in your eleventy.js config file. I said I thought this was cool, but that I'd probably not use it. I'd rather have my global data living in my data folder where it's easier to see, easier to keep track of, and so forth.\nWhen I tweeted, Zach responded that this featured was added by Mike Riethmuller as a way for plugins to add to a site's global data. Immediately that lit a few light bulbs in my head and the feature made a lot more sense to me. I thought I'd whip up a demo or two to see this in action and here's what I came up with.\nBefore I begin, let me say that the two plugins I describe below were just built for this demo and aren't meant for real usage. If you like em, cool, copy the copy from the repo link below. Just keep in mind though that they're a bit rough. Alright, so for my first demo, I built a &quot;weather&quot; plugin. Now, the idea of using weather data in a static site may or may not make sense. (See &quot;How to Enable your Jamstack Site to have a Rain Day&quot; for an example.) But I've got a thing for weather data so I figured why not.\nI used the free weather API from OpenWeatherMap which has a simple endpoint that looks like so:\nhttps://api.openweathermap.org/data/2.5/weather?zip=YOURZIP&amp;appid=YOURKEY&amp;units=imperial\nI built an Eleventy plugin that lets you pass in a zip code and your key:\n\nI could transform the data but I kept it as is. Note the option for name. This lets you have control over how the plugin writes to the global data scope. Back in my &quot;main&quot; Eleventy site, I loaded the plugin via a relative path as it's not in npm:\n\nAnd then loaded it:\n\nI then used it in one of my templates:\n\nTo demonstrate giving the data a unique name, you can specify it in the options:\n\nNow to use it I simply pass the new variable name:\n\nNot necessarily rocket science, but kinda cool I think. To kick it up a notch, I built something along the lines of what Mike had in mind - integration with CMS APIs. I fired up a local WordPress server (using the excelllent Local app) and built an incredibly barebones WordPress Eleventy plugin:\n\nThis plugin hits a configured WordPress host and fetches the lastest posts. I do a minor bit of remapping of the data but the end result is that WordPress posts are now available as global data. Back in my site I loaded it:\n\nAnd then configured the plugin:\n\nAnd literally - that's it! I could then add posts to my site using the pagination feature:\n\nThis could all be a lot more polished, but like I said, these uses cases really help me understand how addGlobalData will be useful to Eleventy users. If you want to play with my demos, you can find them here: https://github.com/cfjedimaster/eleventy-demos/tree/master/plugindatademo\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Eleventy 1.0 - New Option for Global Data",
		"date":"Tue Nov 02 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1635876000,
		"url":"https://www.raymondcamden.com/2021/11/02/eleventy-10-new-option-for-global-data",
		"content":"Like my last post, this is going to be another minor example, but it's yet another cool Eleventy 1.0 I'm happy to see added. (Even if I won't use it much - more on that towards the end.) Today's post is all about a new way to add global data to Eleventy, the new addGlobalData function.\nBefore Eleventy 1.0, you could define global data for your templates by using the _data folder (the exact name being configurable as well). In that folder you could drop in either a JSON or JavaScript file. JSON was good for setting hard coded values. Here's one I use here called site.json:\n\nJavaScript files lets you create data more dynamically. So for example, if I wanted to include Star Wars ship data in my templates, I could created ships.js:\n\nNow in Eleventy 1.0, there's an API you can use in yuour configuration file to specify the same kind of data. So for example:\n\nI used a simple string and object above, but any valid data works here. You can also use functions:\n\nAs well as async functions:\n\nYou can return promises as well if async/await isn't your thing.\nThis all works as expected so obviously I tried to break it a bit. One thing I was curious about was whether or not you could access all your global data. So I tried this:\n\nAnd it works. The test object was a list of the keys to all my data. But - and this is an important but - it only contains the key created before this particular line runs. If for some reason you add stuff afterwards, it won't reflect those changes. On a whim, I tried this:\n\nAnd while I can confirm the log message above showed test2, my templates did not have access to it. I've got an open question about this on the Eleventy discussion board.\nSo... I like this! Will I use it? Probably not! Why? Right now I feel like my Eleventy config file is a bit complex, especially for this site. Having my global data in _data makes me feel like things are a bit more organized. I also like having it there from a source control perspective. If I change one of the values in site.json, I'd like that history to keep to itself if that makes sense. However, I cannot stress how much I appreciate that Eleventy provides multiple different ways of solving problems. One of the reasons I've ditched other static site generators is due to overly perscriptive functionality. I think it's great we've got multiple options now for this feature.\nDon't forget that Eleventy has a &quot;order of priority&quot; when it comes to data. You can see this list at the &quot;Sources of Data&quot; documentation. Let me know what you think.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Welcome to RaymondCamden.com 2022",
		"date":"Thu Oct 21 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1634839200,
		"url":"https://www.raymondcamden.com/2021/10/21/welcome-to-raymondcamdencom-2022",
		"content":"Ok, so no, it isn't 2022 yet, but with how crazy things have been the past few years, I'm just going to pretend we'll actually make it to 2022 relatively unscathed. In my mind, the process of moving to a new theme was going to slowly play out over the holidays, but things kinda progressed quickly and while I'm sure there's still a few things left to tweak, if you're reading this then you're experiencing the new site now.\nMy new theme is called Royce and developed by Just Good Themes. My previous theme was also by them, but it looks like they no longer sell it which is a shame, I really did like it. My reasons for changing were - well I just like to try a new look ever few years, and I also wanted a slightly different home page. For years, my home page has just been &quot;the last ten posts&quot;, but in this theme it's slightly different.\nOk it's still the last ten posts, but the most recent three are highlighted nicer I think and called out more. I wanted something even more different, but this was a good compromise. I may tweak it a bit more later and perhaps only show three items and spend a bit more time there talking about what I do at a high level. Or maybe just add pictures of cats. I'll figure it out.\nThe other big change is that I've completely gotten rid of advertising. I've had advertising of some form or another since the beginning almost. While the income is small, it did help pay for my video game and toy habit. On the flip side, I wasn't necessarily happy with how it looked on the site. My expenses here are pretty small. Netlify graciously hosts my site for free, and my S3 charges are probably between one and two dollars a month.\nI've decided to instead call out more the ways that readers can support me, and the content produced here. As you can see at the bottom of this post, I've linked to my new Patreon page which has a grand total of zero patrons so far. (Hey, you could be the first!)\nI've also linked to my Amazon wishlist. Back when I first started blogging, that was a prime source of donations from the public, but it's really rarely used now. I'm fine with that as my wishlist still works as a way for me to log things I want but don't need. ;)\nFinally, I've linked to my Buy Me a Coffee page, which is a fun way to donate. To be clear, it isn't buying me coffee, but just using it as a metaphor for small donations. I've had 29 people use this so far (notice the button on the left, it shows the current number) and it always makes me smile to see when folks use it.\nAnyway, that's a lot of me talking about yall giving me stuff, but to be clear, I launched this blog because I wanted to give yall stuff. As I like to say, I've been writing buggy software since the day I started. I'm not the smartest person out there, and when I struggle with something, I like to share how I got past it. Or heck, when something just makes me happy (yes, code makes me happy sometimes), I like to share that excitement as well. So thank you to everyone who has been a regular reader over the years, and thank you to everyone who is reading a post on this blog for the first time.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Eleventy 1.0 - Dynamic Ignores",
		"date":"Fri Oct 15 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1634320800,
		"url":"https://www.raymondcamden.com/2021/10/15/eleventy-10-dynamic-ignores",
		"content":"So today's Eleventy 1.0 feature is pretty minor, but since I asked for it, it's the most important feature ever! (Ok, no, not really, but I still like it.) Eleventy 1.0 nows supports the ability to dynamically ignore files. What does that mean?\n\nEleventy can be told to ignore files by using a file named .eleventyignore. This matches the same format as .gitignore. I use this feature on my blog as a way to make local development much quicker. Here's how mine looks:\n/_posts/200*/**\n/_posts/201*/**\n/_posts/2020/**\n/_posts/2021/01/**\n/_posts/2021/02/**\n/_posts/2021/03/**\n/_posts/2021/04/**\n/_posts/2021/05/**\n/node_modules/\n\nBasically, when working locally I have Eleventy ignore 90% of my previous blog posts. This makes builds go much quicker when I'm writing blog posts or testing other things locally. Note, as the docs state, you do not have to include things already in your .gitignore so the /node_modules there could be removed.\nBecause of how I'm using the ignores feature, I'm sure to not include it in my repository as it would nuke most of my content in production. That means everytime I tweak it one place (like my desktop), I have to tweak it in another (like my laptop, and honestly, only if I really care to).\nIn Eleventy 1.0, you can now both add, and remove, from the ignored files setting via configuration settings. So for example:\n\nYou could remove too, via delete:\n\nAll of which means that if you have a case like mine where the ignore files are different based on environment, you could check process.env.NODE_ENV (or some other value) and dynamically ignore based on one environment versus another.\nEven better, this is additive. While for me I had no ignores in production, if you had stuff you wanted to ignore everywhere and optionally ignore more in one environment, you can still use .eleventyignore for the global items and the API for the dynamic one.\nSo again, simple feature, but I'm psyched to see it in Eleventy 1.0!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding PDF Output Support to Eleventy",
		"date":"Wed Oct 13 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1634148000,
		"url":"https://www.raymondcamden.com/2021/10/13/adding-pdf-output-supports-to-eleventy",
		"content":"I've blogged a few times now about integrating Adobe PDF Services with Eleventy, but so far my examples have either been for supporting existing PDFs or converting documents into PDFs for a consistent viewing expirement. Today's test is yet another example of something that may not be a good idea, but it worked, and it's cool, so I'm sharing it!\nI was thinking about how one could take a regular Eleventy template and have it output PDF instead of HTML. Eleventy already lets you output whatever you want. So for example, you could use your favorite template language to create dynamic JSON and tell Eleventy to save it with a JSON file extension. This works for anything really, so for example, my RSS feed. This wouldn't really work for binary style formats though.\nOn a whim, I took a look at Eleventy's transforms feature. This feature lets you take the output of any template (after it's been parsed) and transform it. The minification example from their docs works great and I use it here:\n\nLooking at this, I wondered what would happen if I tried to make a PDF inside a transform like this. I began with a simple Liquid template:\n\nWhile most of the content is just regular old stuff, notice how in the permalink I tell Eleventy to save it as a PDF file, not HTML. I used page.fileSlug so I didn't have to type in the real filename.\nWith this in mind, I then added a basic transform:\n\nSo what's going on here? First, I look at the output path and see if it's PDF. If not, I return the content as is. But otherwise, I create a PDF. This is done by taking the contents and saving it as an HTML file. Our HTML to PDF API requires either a file or a URL so I need to save it temporarily. I'm using nanoid() to ensure the filename is unique. I pass this to a utility function I wrote, createPDF which handles wrapping the calls to our service. When it's done, I save the file, read in the binary data, and return it. (I also clean up the temporary files.) A few important things to note here.\nOur API supports giving you a stream access to the data so I didn't have to save the PDF to the filesystem, but I'm really unsure how to use streams. I took the easy way out.\nThis line in particular took me a while to figure out:\n\nInitially I just returned contents, but the PDF was corrupt. While testing, I temporarily changed my return statement to return null, and when I did, I got this error:\n[11ty] TypeError [ERR_INVALID_ARG_TYPE]: The &quot;data&quot; argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received null\n\nI noticed the or Buffer part and that's when I swiched to using Buffer.from and that's when it started working well. Despite the error message from Eleventy, I'm not entirely sure I'm supposed to be doing that. I filed this issue to ask the docs to be clarified on it or the feature to be removed. (Not transforms, but being able to return non-string results.) Here's an example of the output:\n\n\n\nlet clientId = (location.host.indexOf('raymondcamden.com')>0)?'33f07f2305444579a56b088b8ac1929e':'9861538238544ff39d37c6841344b78d';\nif(window.AdobeDC) displayPDF();\nelse document.addEventListener(\"adobe_dc_view_sdk.ready\", () => displayPDF());\nfunction displayPDF() {\n    let adobeDCView = new AdobeDC.View({clientId: clientId, divId: \"adobe-dc-view\"});\n    adobeDCView.previewFile({\n      content:{ location: { url: \"https://static.raymondcamden.com/enclosures/test.pdf\"}},\n      metaData:{fileName: \"test.pdf\"}\n    }, { embedMode: \"SIZED_CONTAINER\" });\n};\n\nAs I said, the createPDF call just wraps our SDK. But here it is:\n\nThe complete source code for this demo may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/pdftest5\nAs always, let me know what you think!\nPhoto by Iqram-O-dowla Shawon on Unsplash\n",
		"tags":[
	        
            "eleventy",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Eleventy Hack/Tip/Possibly Bad Idea - Dynamic Theme Testing",
		"date":"Mon Oct 11 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1633975200,
		"url":"https://www.raymondcamden.com/2021/10/11/eleventy-hacktippossibly-bad-idea-dynamic-theme-testing",
		"content":"As I've done every few years, I'm in the process of working on a new look for this blog. I figure it's going to take a while to get it set up properly. In the past I've simply made a copy of my site and worked there when I had free time, but today another idea occurred to me. As the title says, this may be a bad idea, but hey, it worked on my machine so surely I should share with everyone, right?\n\n\n\nThe idea I had was to see if I could operate my site with two layouts. By default, Eleventy will use the _includes folder for included documents and layouts. You can specify a new folder for includes and for layouts if you wish. What I thought would be interesting was if I could use a completely new folder for my new theme and simply tell Eleventy to use it when I wanted to work on it.\nThe first step is to return a configuration object in your .eleventy.js file. I already had one where I did various things, but I wasn't returning an object. I added it like so:\n\nBy the way, in the sample above I'm using the new(ish) JavaScript shorthand where if you are returning an object key and variable with the same name, you can skip the colon. So instead of { includes: includes } you can just do { includes }. I love you JavaScript, warts and all.\nTo handle changing the value of includes, I used an environment variable and just checked for it before my return. Here's what I had initially:\n\nI'm using WSL (Ubuntu), so I was able to test this like so:\n\nThe value I used was completely arbitrary as I just check for the existence. This worked, but I noticed errors being thrown when Eleventy tried to parse _includes like &quot;regular&quot; files. Because I'm on the 1.0 beta, I was able to dynamically add to the ignores setting:\n\nBasically I flip what I'm ignoring. If you aren't on 1.0, you could manually add to your .eleventyignore file and manually remove it once done.\nThis worked well (so far) and let me do some initial work on my new theme. I did run into some issues with ensuring I copied the themes assets so it's not quite as simple as described above, but for now I've got a way to keep blogging while also work on getting the new theme ready. As always, I'd love feedback/opinions/arguments on this so let me know what you think.\nPhoto by John Noonan on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Eleventy 1.0 - Upgrading Experience",
		"date":"Fri Oct 08 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1633716000,
		"url":"https://www.raymondcamden.com/2021/10/08/eleventy-10-upgrading-experience",
		"content":"With Eleventy 1.0 coming soon, I thought I'd take a look at the experience of upgrading an existing implementation to the latest version. As I've warned, Eleventy 1.0 is still in beta so the details may change, but I figured it was safe to give it a try on my own site (the very place you're reading this post). Eleventy is shipping a tool to help with that process, and I cover that a bit later, but me being who I am I just went ahead and Leroy Jenkins the process.\n\nAfter upgrading Eleventy to the 1.0 beta (npm i @11ty/eleventy@beta), I fired up my local copy of this site and... bam! It crashed. Luckily though the error was nice and clear:\n\n\n\nThis error makes me very, very happy. What is it? The LiquidJS template language (the primary one I use for this site, but not exclusively) is a great project, but for some reason has a default behavior that I find insane. When using a filter that doesn't exist, the default behaviour is for Liquid to ignore it. So your coworker tells you to use the cat filter in certain blocks, like so:\n\nBut it turns out you misheard them and they meant to say kitten filter. By default, Liquid sees the undefined filter and just does nothing. I am sure there is a valid reason for this, and it absolutely makes sense to make being strict about this an option, but I cannot fathom why it would default to not being strict. Turns out, I had about 5-6 cases of a filter that wasn't defined that was just silently ignored.\nSo in Eleventy 1.0, they simply set the default Liquid option to enforce strict filters. I really like this change. It took me maybe five minutes to fix the various filters in my site that weren't working (I literally just got rid of em, I didn't need them).\nWoot! Ok, but then I had another problem:\n\n\n\nThe line in question looked like so:\n\nThis issue is covered in the Eleventy Liquid docs here:\n\n\n\nThe solution was to just add single quotes:\n\nI had maybe ten of these around my site, but it was also a five minute fix or so.\nOk, so at this point, I was able to get things running locally just fine. As an FYI, and I don't think it would have mattered but it's nice to know, I run Eleventy locally via the Netlify CLI and Netlify Dev, and nothing in that process seemed to intefere with Eleventy 1.0. Again, I didn't expect there would be an issue, but it's nice to know. At this point, I decided to look at the helper plugin.\nFirst off, I was a bit surprised the 'helper' was a plugin, as I would have imagined some kind of local script you run that looks at your code, but running in context with your site itself as a plugin makes sense too. As a regular Eleventy plugin, you simply install it and then add support to your .eleventy.js. While I don't think it matters if I keep it around forever, I added some helpful comments like so:\n\nI guarantee you I'm going to forget and look into removing it sometime around Eleventy 2.0. The plugin fdocuses on a few particular items. While you can get details at the docs, it checks for:\n\nUse of the slug filter that should now be slugify.\nWarnings about deep data merge not being turned on.\nLiquid changes (would have helped me with the stuff above!)\nAnd a change to non-root .gitignore stuff (doesn't apply to me)\n\nWhen I ran Eleventy, I saw this (switching from screenshots to a text copy so it's hopefully easy to read):\n\nNice, simple, and direct. You can tweak what the plugin checks if you want, but I'd probably leave it at the default.\nFor now, I'm not running the 1.0 changes in production, just locally. As this site is a blog that's pretty easy to do - I only commit my new posts. With me assuming we'll have a formal 1.0 release on the 11th, I'll wait till then, test again, and commit. Enjoy!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Eleventy 1.0 - New Output Options",
		"date":"Thu Oct 07 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1633629600,
		"url":"https://www.raymondcamden.com/2021/10/07/eleventy-10-new-output-options",
		"content":"Ok, today's Eleventy 1.0 post (remember, it's still in beta and this may change before release) is a short one. I was looking over the post on 1.0 features and wanted to learn more about this:\n\nSupport for CLI arguments to do JSON and NDJSON output (instead of writing to the file system). Use `--to=json` and `--to=ndjson`.\n\nWhen I read this, my assumption was that it meant the output of the CLI itself, specifically, the messages about what it did. So for example, here's the output of a typical Eleventy operation on a small site:\n\n\n\nGiven that, my expectation was that by using the to argument, I could ask for a JSON version of the results instead of plain text. I've encountered other CLIs that will do this as well. This can then be useful when executing a CLI from within another Node script as it gives you programatic access to the result of the call.\nBut I was wrong - this new argument literally means the output of the site itself! Here's an example on a very small site:\n\n\n\nSo to be clear, when runnig eleventy --to=json, nothing is written to _site. (You may miss this as I did since I don't normally remove the previous output.) Instead, the JSON is the complete output of the build. You get an array of files that would have been written, their input and output, the URL for it, and the content itself. In the screen shot above, the content comes from a Liquid page that included the current date and time.\nIf your curious, the output also includes files that aren't written. So given this input:\n\nYou get this result:\n\n\n\nBy the way, if you're curious, &quot;ndjson&quot; refers to newline delimited JSON which is a JSON format more appropriate for streaming a record at a time. In my testing, the array of objects was instead output as just a list of objects - but I did not have a newline delimiter between each object. I filed a bug on it but I could be misreading the output.\nAnother thing to watch out for is in saving the output. I did this on my blog's project folder:\neleventy --to=json &gt; result.json\n\nAnd while it correctly piped to the file, any console.log message was in there as well. That makes sense, but I kinda wish there was a way to pipe just the JSON output. I filed an enhancement request for the idea of being able to pass filename.json instead of json and have the result be automatically written to that file instead of the terminal.\nAll in all, this seems like a pretty cool new feature. Being able to access the output programatically could be useful for QA and testing in general, or more. I'd love to hear more about how folks plan on making use of this.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Eleventy 1.0 Beta!",
		"date":"Wed Oct 06 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1633543200,
		"url":"https://www.raymondcamden.com/2021/10/06/eleventy-10-beta",
		"content":"In the past, I used to do quite a few &quot;announcement&quot; blog posts (for a shock, take a look at my stats for how many blog posts I did in 2007). With the introduction of Twitter I don't do that nearly as much, but I know there are quite a few of you out there who don't use Twitter and avoid social media in general. Good for you - I honestly wish I could walk away from that sometimes.\nThat being said, I'm making an exception in this case as it's something I'm really excited about. Folks know I've been a fan of the Eleventy project for some time now. (If you click the &quot;Eleventy&quot; tag at the bottom of my post, you can see my previous articles on it.) Today the team announced the introduction of the 1.0 beta. You can find the details at here: The Very First Eleventy 1.0 Beta Release\nIn general I shy away from betas as I tend to focus on things that are generally available and in production use. However I get the impression 1.0 is really close (if I had to guess, October 11th ;) so I figure it's a good time to dig into it.\nFirst, be sure to follow that link above as it gives details on how to test the beta and the big changes. The biggest is Eleventy Serverless which I'm still wrapping my head around myself. I'm also very happy to see the template languages being updated as I've hit issues with the older LiquidJS implementation more than once. You can also peruse the list of issues tied to the 1.0 release for details. I've done that and I plan on building a few demos (and posts) for things I think are particularly interesting.\nAnyway, that's it! As I said, I plan on digging into this like - well now - and I've got my own ideas for what I want to play with, but if you have a particular question or feature you want to know more about, reach out and I'll make time for it!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building the PlacePlaceHolder Service with Pipedream",
		"date":"Tue Sep 28 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1632852000,
		"url":"https://www.raymondcamden.com/2021/09/28/building-the-placeplaceholder-service-with-pipedream",
		"content":"Before I begin, a few quick notes. Yes, that title is intentional and not a typo. Honest. Secondly, like most of my dumb ideas, I think there's some nuggets of interesting info in here, so I'll do my best to highlight those important bits while minimizing the dumb idea. (Which I enjoyed building no matter how dumb it was and that's the important part. ;) Lastly, this post will include some images that are randomized. I am fairly confident that no image will be inappropriate. If you see something bad though please let me know.\nYesterday a friend on Facebook shared that he had recently discovered Fill Murray, a Bill Murray placeholder service. So for example, the URL https://www.fillmurray.com/200/300 creates:\n\n\n\nThis is one of many silly image placeholder services out there, with my favorite being, of course, placekitten:\n\n\n\nCommenting on the friends FB post along with others, we were sharing different placeholder services we like, when it occured to me - what if I build a placeholder service that dynamically returned another placeholder service?\nThe idea was simple - all of these services support, at minimum, a height and width. So give a request to my service for a sized images, I could dynamically pick a service and craft the proper URL. I'd then simply re-direct.\nI fired up a new Pipedream workflow with a HTTP trigger. Next, I created a step that would handle getting query parameters from the URL for height and width. I could have used path parameters instead but this was a quick hack.\n\nI allow people to pass width or w and height or h. If any dimension isn't passed, it defaults to 350. By the way, the conditional on top soon won't be necessary as Pipedream has a new HTTP trigger coming that can auto block favicon requests.\nThe next step simply defines my services. My thinking was to create an array of objects where each object contains the name of the service (not really needed, so mostly just for debug purposes) and a function that would map height and width to the URL for the service. Initially I had something like this:\n\nLook at those fat arrow functions. That's slick, right? I'd totally get hired by Google if I wrote that during my last interview with them. I didn't. Oh well. However, in my testing, something odd happened.\nI'd select a random item from the array and I'd get an error saying map wasn't a function. I thought the problem at first was due to the fact that map is a method of arrays. I quickly tried renaming it (mapF, yes, I'm creative), but it didn't work. I confirmed that I was getting a random item by outputting name, but map wasn't there.\nI then discovered this nuggest in the docs:\n\nYou can export any JSON-serializable data from a step by setting properties of this:\n\nThe important bit there is &quot;JSON-serializable&quot;, which my functions were not.\nBut luckily I figured out a workaround, although it's the kinda thing that I think would not get me hired by Google. I defined my services in a step like so:\n\nStill an array, but notice the URL pattern is juist a basic string. Now comes the fun part. Here's my final step:\n\nI've been writing JavaScript since it came out in a Netscape beta and I don't think I ever used the Function constructor. Basically I use the 'format' string inside a function that makes a new function and turns it into a template string. I then call that function: let url = makeTS(service.map)(steps.getargs.width, steps.getargs.height);\nI don't know about you but that feels like some proper black magic vodoo shit right there. So given my root URL of https://placeplace.m.pipedream.net I can then do stuff like https://placeplace.m.pipedream.net?width=300&amp;height=500:\n\n\n\nAnd here's a few more examples of differing sizes:\n\n\n\n\n\n\n\n\n\nIf for some unknown reason you want to see the whole workflow, you can check it out here: https://pipedream.com/@raymondcamden/placeplace-p_q6CzbDg\nPhoto by Kelly Sikkema on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Creating a (Manual) Related Posts Feature in Eleventy",
		"date":"Fri Sep 24 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1632506400,
		"url":"https://www.raymondcamden.com/2021/09/24/creating-a-manual-related-posts-feature-in-eleventy",
		"content":"Something I miss from my old blog-ware was the ability to define blog post relationships. While editing a post, I could easily select posts I wanted to create a connection with. When the blog post was rendered, these related entries were shown at the end in a consistent manner. These relationships were bidirectional so if post A linked to C, when rendering C I'd render a link to A as a related post. I was curious how this could be done with Eleventy.\nTo be clear, there's already posts out there showing how to automate this process. So for example, given a blog post written in the category of cats, you can select other posts from that category, perhaps randomized, and link to them. I can see that being a great option to automatically entire readers to check out other content on your blog.\nBut for this demo, I wanted a way to manually define this. I specifically want this post X to link to post Y. To make this work, I knew I needed to define the relationship in front matter. The question was - how? In my older blog, everything was database driven. Every blog post had a unique ID and then I had a join table for relationships. You don't get that for Eleventy.\nWhat you do have are page properties. One of those properties is the filePathStem. Given a file like /posts/alpha.md, this will be /posts/alpha. This should be a unique value that is - probably - an easy thing for an Eleventy author to refer to.\nSo for example, if I wrote a post last year about my top ten cats, I know it's at /posts/top-ten-cats.md and I'd know the filePathStem should be /posts/top-ten-cats. Based on this, I could reference it in my front matter:\n\nI've created a related property in my front matter as an array so I could related to multiple items, but certainly you can link to just one too.\nOnce that was done, I then edited my post layout like so:\n\nThe top portion is the basic layout and the if at the bottom is where the magic happens. If a post has related items, I need to get the 'real' objects so I can link to them. I do that via a filter called getRelated. Eleventy filters don't have access to collections so I need to pass it as a second argument. Remember that in a filter, the item before the filter name is the first argument. The result of this filter is a list of regular page objects so I can link out as usual.\nHere's the filter in my .eleventy.js file:\n\nOne thing to note about this solution - its unidirectional. In theory you could make this work both ways. I'd remove the condition from the template (since the page you link to may have no related items defined) and then use code that looks at the current page's filePathStem to see if anyone links to it. I was ok with it being unidirectional but that's a modification that could be done. Here's how it looks in the awesomely designed demo I built:\n\n\n\nIf you want to play with this, you can grab the source here: https://github.com/cfjedimaster/eleventy-demos/tree/master/relatedentries\nPhoto by JJ Ying on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Supporting Multiple Authors in an Eleventy Blog - Follow-Up",
		"date":"Sun Sep 19 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1632074400,
		"url":"https://www.raymondcamden.com/2021/09/19/supporting-multiple-authors-in-an-eleventy-blog-follow-up",
		"content":"About a year ago I wrote up a blog post demonstrating how to set up an Eleventy blog that supports multiple authors (Supporting Multiple Authors in an Eleventy Blog). While you should definitely read that post first, the gist of it goes like this:\n\nDefine authors in a simple JSON data file and ensuring each one has a unique key.\nInclude an authors field in front matter so you can specify the author\nDefine a filter that let you get the author's name for the post\nDefine an author template that shows all the posts for the author\n\nWhile this worked fine, a few days ago I got an email asking if this supported multiple authors per post. Unfortunately, it didn't. Despite me using authors as a front matter key name, if you actually did pass multiple authors it didn't recognize them as a list. I whipped up a quick modification to make this work.\nFirst off, I decided on a simple comma-separated list for multiple authors. Here's an example:\n---\ntitle: Epsilon Post\nlayout: post\ndate: 2020-08-02\ntags: post\nauthor: brinaldi,rcamden\n---\n\nThis is Epsilon\n\nNote that YAML does support specifying an array, or list, but honestly, I find that format not terribly easy to use. (Ok, I'd have to Google it, so it's not that hard.)\nAfter creating a post like this, I had to make two changes. First, consider the previous version of post.md:\n\nIt makes use of the getAuthor filter to convert the author primary identifier to a real name. To support multiple authors. I first change the getAuthor filter to getAuthors:\n\nAll I do here is take the label, split it on commas, an filter to any author in the new array. Then, back in the post.md, I change it to this:\n\nYou can see now that I loop over a list of authors and output a comma if the loop has more than one item. This made individual blog posts work fine. Here's a post with two authors:\n\n\n\nThe second change I had to make was to the authors template. The template was fine, but the getPostsByAuthor filter had to be updated:\n\nAnd that's basically it. I did not edit the previous demo in my GitHub repository as I wanted a nice way to compare both, so instead I saved this into a new directory: https://github.com/cfjedimaster/eleventy-demos/tree/master/multiauthor2\nAs always, if you've got any questions about this, just let me know!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building an App with the StackOverflow API",
		"date":"Thu Sep 16 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1631815200,
		"url":"https://www.raymondcamden.com/2021/09/16/building-an-app-with-the-stackoverflow-api",
		"content":"One of the first things I did when I joined my new team at Adobe was to look at how, if at all, we were making use of StackOverflow. We've got a very active set of forums but I know that most developers tend to use StackOverflow for all their support needs. At the time I joined, we didn't really have tags that were being used consistently, so myself and others figured out what tag names were going to use, updated some older questions to use the right tags, and even seeded a few questions ourselves. (This is OK by that way.)\nOne of the things that my teammates wanted to know was how well these tags were working, how many questions were being asked, how many were answered, and so forth. StackOverflow provides really good metrics for an individual tag. But I was curious if where was a way to aggregate that over multiple tags. Also, our marketing people don't use StackOverflow and wouldn't know where to look for stats.\nAnd plus - if I have the opportunity to try to learn a new API and build something, I'm going to leap at the opportunity.\n\n\n\nI did what any good developer would do and did a quick google for &quot;stackoverflow api&quot; and ended up at the Stack Exchange API. As I expected, there's an API for pretty much every aspect of the site. Even better, you can do things without authentication or an API key. That being said, I absolutely recommend registering an application just to get a key. I worked on my demo over a few days and didn't hit the anonymous quota towards the end, but signing up was quick and painless (and free), and the key worked right away.\nFor my initial report, I wanted to see how many questions had been asked, and how many were unanswered. I wanted a total number as well as a value for the past seven and thirty days. The API has multiple methods related to questions, but they did not let you pass a list of tags that could be used as an aggregate. By that I mean if you checked tags A and B, it would be an AND search where only questions tagged with both would work. I then found the search API. In that method, the list of tags are considered an OR search.\nThe URL ended up like so:\nhttps://api.stackexchange.com/2.3/search?order=desc&amp;sort=activity&amp;site=stackoverflow&amp;key=${KEY}&amp;tagged=${tags}&amp;filter=total\n\nWhere KEY is my application key and tags are my list of tags. The filter=total at the end is a feature that lets you get just the total for your query.\nThe response is super short and simple:\n\nTo handle date filters, I setup my function to allow for an optional age in days. If passed, I add this to the URL:\n\nNow for the fun part. To handle unanswered questions, I needed to switch to the advanced search API. It was very similar with the main change being me using accepted=false. That doesn't mean that the question wasn't answered, just that an accepted answer isn't there. Unfortunately, I ran into a bug here. While the docs describe the tagged attribute the same as the search API, it treated the query like an AND. I raised this on stackapps: Call to search/advanced is treating tagged as an AND search, not OR. It turns out this is an eight year old bug and from what I can tell, the API isn't getting a lot of attention from the Corporate folks over at StackOverflow. That's a shame. So to handle this, I took the list of tags, split them, and ran multiple async fetch calls to grab them all.\nThe net result of the function is a total for the number of questions and a total for unanswered ones as well. Here's that function:\n\nThis is probably yet another example of why I can't pass the Google interview test, but it works, so I'm happy with it.\nWhen I shared this data with my coworkers, they had two asks. One was to simply see the questions. I decided to write a quick function to simply return the last ten. The search API already returns questions, I was just turning that off with the filter. If I removed that filter I'd get the question data. But I looked more into filters and saw that they provide a cool system where you can define, on the fly, a subset of data you want. So if I just needed a few bits of the question, I could create, via the API itself, a filter specifying that. It's a bit wonky to use, but on the search API page itself, I used their Try It tool, designed a filter, and copied the filter value out. Right now I just need the question's title, posted date, views, and links:\n\nThe filter is a random ID value so I simply stored it up top (I ended up changing it once or twice, so that helped). I also made use of Intl to format the dates outside of this function.\nAll of this was wrapped in a super simple Vue.js application. I did get a bit fancy and made use of my post where I describe making use of URL paramters in a Vue app so I could simply give my teammates a URL with the tags already in it.\nSo one final bit. I mentioned that there were two requests of me. I already described how I handled the first. The second one was a way to get a complete view count for the ta",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Making Monsters Fight for Fun and Profit (minus the profit)",
		"date":"Mon Sep 06 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1630951200,
		"url":"https://www.raymondcamden.com/2021/09/06/making-monsters-fight-for-fun-and-profit-minus-the-profit",
		"content":"My frequent readers (do I have those?) will know I've got a thing for building random Twitter bots. I just like randomness in general. A few days ago I was thinking about an API I had run across, the Dungeons and Dragon's API. This is a free, simple to use API that returns information related to D &amp; D. Pretty much every aspect ofthe ruleset is available via the API. Part of the API is deep information about monsters.\nThis got me thinking about what I could build with that information. I thought it would be kind of fun to pit these creatures against each other. Not in the &quot;Godzilla vs Kong&quot; fashion, but something simpler and - of course - sillier.\nWith that - I built @monsterconflict, a bot that shares a conflict between two different creatures. Here's a few examples:\nA lawful neutral Satyr and a chaotic neutral Deep Gnome (Svirfneblin) are having a misunderstanding over a kitchen.They resolve their issue by discussing the merits of cats instead.&mdash; monsterconflict (@monsterconflict) September 6, 2021 \nA chaotic evil Kobold and a lawful neutral Lion are having a misunderstanding over a mouse.They resolve their issue with an epic dance off.&mdash; monsterconflict (@monsterconflict) September 6, 2021 \nBuilding this was fun because I ran into some interesting issues with the language of the conflict. Resolving those issues introduced me to some cool npm modules as well. Everything is built on Pipedream and you can see a complete copy (and fork it) here: https://pipedream.com/@raymondcamden/peaceful-d-d-bot-p_mkCGly6\nThe first step the bot takes is to get a list of all the monsters. This is done by making a call to https://www.dnd5eapi.co/api/monsters. That API returns an index of monsters that looks like so:\n\nI figured this data doesn't change too often, so I made use of Pipedream's $checkpoint feature for some simple caching. Here's the entire workflow step:\n\nDylan Sather of Pipedream shared this workflow as another example of using $checkpoint to cache network calls. Be sure to check his example for a much nicer version of what I did above.\nSo - at this point we have a list of all the monsters. Selecting two by random is trivial. Initially I then made calls to the API to fetch more information about the creatures. But I realized I was only using one piece of information from that detail - the alignment. While I like the idea of my creature having it's &quot;real&quot; (according to the rules) alignment, I figured that having a random one instead would both save me two network calls and make things a bit more random. The next step handles that.\n\nBasically I'm just getting random values from arrays - either my list of monsters or the list of alignment types. D&amp;D supports the idea of &quot;true neutral&quot; which I just return as &quot;neutral&quot;. Also, the monster names sometimes had things after a comma that I just drop.\nAlright, now comes the interesting bit. I've got my two monsters - it's time to put them in conflict. I went with a generic form that looked like this:\n\nMonster A and Monster B are TYPEOFCONFLICT over NOUN. They resolve it RESOLUTION.\n\nFor &quot;TYPEOFCONFLICT&quot;, I just made an array of types of conflicts. Some serious, most silly. The NOUN part was interesting. I used the npm package random-word-slugs to generate a noun. This is typically used to create random strings based on real words. I use it to simply select a noun. This worked well into I noticed a problem. I began to see results like this: so and so are fighting over a umbrella&quot;. The &quot;a&quot; in that sentence shuold be &quot;an&quot;. At first I thought I'd just write a utility function to check the noun and see if it starts with a vowell, but then I remembered there were exceptions, like unicorn. Turns out there's yet another npm package for this, indefinite. Give it a string and it will return &quot;a&quot; or &quot;an&quot;. This worked well, if a bit complex in code. Here's the entire step:\n\nLike I said - that last line there is a bit hard to read in my opinion, but it works so I'm not touching it. And that's it. The last step just posts the text to Twitter and that's the entirety of the bot. Don't forget you can see the entire thing here: https://pipedream.com/@raymondcamden/peaceful-d-d-bot-p_mkCGly6.\nPhoto by Anne Nygård on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using PDFs with the Jamstack - Building a Document Viewer",
		"date":"Mon Aug 30 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1630346400,
		"url":"https://www.raymondcamden.com/2021/08/30/using-pdfs-with-the-jamstack-building-a-document-viewer",
		"content":"I've been blogging quite a bit about how to integrate Adobe's PDF Services with the Jamstack (11ty specifically but applicable to any generator) and today's I think is pretty cool. One of the features of our PDF API is the ability to convert documents into PDF. I thought it would be interesting to use that as a way to provide a consistent document viewing experience using PDF and the free PDF Embed API. Here's what I came up, and as always, comments and suggestions are welcome!\nThe Setup\nMy intent was to create something that would be simple to use and not require any technical knowledge of the person who owns the final site. To enable that, there's one folder (documentLibrary) that will contain all the files they will want visible on the site.\nUnder this folder will be one subdirectory, pdfversions, which contains generated PDF versions of files. What files get converted?\nIf you check our docs, we support the following:\n\nMicrosoft Word (DOC, DOCX)\nMicrosoft PowerPoint (PPT, PPTX)\nMicrosoft Excel (XLS, XLSX)\nText (TXT, RTF)\nImage (BMP, JPEG, GIF, TIFF, PNG)\n\nHowever it doesn't make sense to convert images to PDFs since the browser can render that just fine. (Technically it can render many of these, but we want to provide a consistent experience in our site UI.)\nOn startup, our site will scan the document library folder and find files that can be converted. But it will first see if they have been previously converted and if so, will not bother.\nAt the end, it will return Eleventy data consisting of an array of documents that we can then render out.\nPart One - Setting up the Data\nFirst, let me share how I created the data values that will be used later on in the Eleventy templates. This file (_data/documents.js) is pretty important. It scans the library, figures out what it needs to convert to PDF, and is responsible for outputting the result in such a way that the templates can use it later on.\nI went through a couple of different iterations on this, but here's the final version:\n\nI tried to document the code as much as possible, but here's how it breaks down. I begin by scanning my directory for documents. For each one, we see if we can and need to convert. If so, that process is fired off. Note that it does take time for our APIs to convert your code. In my test files (which are in the repository), it took almost two minutes, but two of the files were rather large so this isn't surprising, and it's also a one time hit. Once a PDF version exists, there's no need to create it again.\nI really went back and forth with exactly how to output the data. I ended up changing this a few times when I built my templates. I figure it's better to do more work in the data area if it helps keep the templates a bit more simple.\nThe final bit of code, convertToPDF, simply wraps our documented example in a nice function that's easy to call.\nPart Two - The Templates\nMy &quot;site&quot; is relatively simple - two templates. (There's also layout files, CSS, etc, all available in the repository.) The two templates cover the home page and then one 'view' page per document. Let's look at the home page first:\n\nSo nothing really too exciting here - a bulleted list that iterates over my document data. The only kinda weird part may be this:\n{{ file.slug | slug }}\n\nSo I wanted to link to a URL based on the original file name, but not with the extension. So back in my data file, I took something like /documentLibrary/cat.docx and removed everything but the file name without the extension. With the previous example, that would be cat. However, I still wanted something URL safe, and given that a document could be named cats are better than dogs.docx, I'd use the slug filter to turn that into cats-are-better-than-dogs. I'm not happy with slug/slug there, but, it works. Here's how this renders using my lovely Boostrap layout:\n\n\n\nThe template that handles documents is a bit more complex. It needs to handle using the PDF Embed... when it can... and then either rendering an image or just plain giving up (mostly). Here's that template.\n\nAlright, so how in the heck is this working? If you remember back up in the data file, I use pdfpath to represent the path to either the original document, if it's a PDF, or to the converted path. This then lets me use the Embed API for any of those documents.\nThen we either show the image as is, or a message saying we can't render it.\nWhile the Embed API has save functionality built in, I always include a link at the bottom that lets you download the image. Thank you handly download attribute, I love you.\nHere's an example where the Embed API is rendering a PDF version of a Powerpoint:\n\n\n\nIf you would like to see the complete repository, you can check it out here: https://github.com/cfjedimaster/eleventy-demos/tree/master/pdftest4 Enjoy and ask for help if you need it!\nPhoto by Mr Cup / Fabien Barral on Unsplash\n",
		"tags":[
	        
            "eleventy",
            
            "pdf services",
            
            "adobe"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Using Liquid Blocks in Eleventy Layouts",
		"date":"Thu Aug 19 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1629396000,
		"url":"https://www.raymondcamden.com/2021/08/19/using-liquid-blocks-in-eleventy-layouts",
		"content":"Today's post is based on an interesting question I ran into on StackOverflow: How do I use LiquidJS Layout Blocks in Eleventy? The person asking the question was trying to accomplish the following:\nEleventy makes it super easy to use templates with your work. You create a file, add front matter, and specify a layout file:\n---\ntitle: Hello World\nlayout: main\n---\n\nYou can then create a file, _includes/main.liqdui, and then render your content like so:\n\nLayouts work great when your content lands in the middle of some block of HTML. But consider a layout that has a block that needs to be dynamic and that's not between a pair of tags?\n\nIn the example above, we've added a footer element that right now is hard coded, but we'd like to have our page templates pass in content. So for example:\n\nSo, one quick solution is to just use front matter!\n---\ntitle: Hello World\nlayout: main\nfooter: This is the footer!\n---\n\nThat works perfectly fine, but really only works for short blocks of static content. If I wanted something more dynamic, I'd be out of luck. Computed Data could be a solution, but the StackOverflow user was looking to use a Liquid feature, Blocks. Idealy, this is what they would like to do:\n---\ntitle: Hello World\nlayout: main\n---\n\n## Hello World!\n\nThis is me testing.\n\n{% block footer %}\nThis will be used in the footer.\n{% endblock %}\n\n\nThe result is not what you would expect:\n\n\n\nOk, so what now? I did some Googling and found this Eleventy issue: Using Nunjucks blocks from within Markdown. In the issue, they described that in order to do this with Nunjucks, you can't use Eleventy layouts, but must specify the layout with Nunjucks itself.\nAlright, so I tried that in test1.md:\n\nAnd... it kinda worked. But I noticed a few issues. First, I lost all my output. It looks like when Liquid executed in Markdown, it ended up folowing one of the Markdown rules where code that's tabbed over is meant to be used as displayed source code, so it escaped it. I fixed that by removing the tabs in my layout. Annoying, but I can deal. Then, to display my footer, I had to change from:\n\nTo:\n\nI'm ok with that change as it lets me specify a default value for footer as well. Now I've got this:\n\n\n\nAnd if you think about it, the missing &quot;main&quot; content makes sense. I'm no longer using Eleventy to do my layout, so in layout.liquid, {{ content }} doesn't exist. (Well the code is there of course, I mean the value of content isn't set.)\nSo how do we fix this now? Use blocks again. Here's layout.liquid now:\n\nAnd then back in test1.md:\n\nWoot! Now it's perfect! Except my other site pages don't work!\n\n\n\nOn a page using Eleventy's built in layout processing, my content isn't working. Dangit. Luckily there's an easy, if somewhat hackish, solution:\n\nOn pages not using the footer block, the content variable will be declared. On pages using it, the content block will exist.\nWhew.\nSo.... this works, and honestly it isn't too terribly ugly. But then I remembered something. A few weeks ago I blogged about creating &quot;additive&quot; capture blocks in Eleventy. Basically, I wanted to wrap content two or more times and have it append to one variable. I'd then display that variable.\nIn that blog post, I created two shortcodes, one called mycapture and one called displaycapture. My code worked by storing the values so that I could add to it and then display it. Today I discovered a bug in that implementation (fixed in this post and will be fixed in the old post by the time you read this) but was able to quickly correct it. So here's my .eleventy.js with my shortcodes:\n\nSo while we don't need additive shortcodes, we can now do this (in test2.md):\n\nYou'll notice I'm using main2.liquid for the layout. That's closer to my original version, but uses my shortcode for the footer:\n\nI like this solution as it removes some of the complexity around the blocks and lets me keep using &quot;normal&quot; Liquid layouts. Anyway, as always, I'd love to hear what you think. You can find this solution here: https://github.com/cfjedimaster/eleventy-demos/tree/master/blockquestion\nPhoto by Michael Fousert on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "More Work on Algolia and My Blog",
		"date":"Wed Aug 11 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1628704800,
		"url":"https://www.raymondcamden.com/2021/08/11/more-work-on-algolia-and-my-blog",
		"content":"Over a year ago I wrote up my experience on adding Algolia to my blog (&quot;Adding Algolia Search to Eleventy and Netlify - Part Two&quot;. This process was different then my initial post on the topic (&quot;Adding Algolia Search to Eleventy and Netlify&quot;) as I had to set up things a bit differently to handle the large size of my site.\nWell, things worked ok for a while, but I later discovered a bug in my implementation (I updated the post to share those issues). I thought I had things fixed, but I kept having issues with my Algolia index being blanked out. Heck, it got so annoying I wrote up a Pipedream workflow just to help me monitor it. While the workflow worked great as a warning, I still wasn't exactly sure what was wrong. On top of that, Netlify's function logs seem to not be working properly for me when trying to debug issues in my deploy-succeeded function. (I've raised this on their forums if you want to track.)\nThis week I decided to see if I could finally come up with a solution. What I had running before (and described in that first blog post linked to above) was this process:\n\nWhen Netlify does a build...\nClear my entire Algolia index...\nDownload a JSON copy of my blog (6000+ blog entries)\nDo a batch update\n\nWhy do I nuke the entire thing? In case I delete a blog post. Why do I upload every blog post? In case I edit.\nAll of that makes sense, but the size and time to run of the process was ultimately causing the issues I believe. I realized that I've probably deleted one or two blog entries in the 18 years I've run this blog. I do make edits to blog posts, but on average, 5 or so per year.\nTherefore I decided to change my process. Now what I do is:\n\nWhen Netlify does a build...\nDownload a JSON copy of the last 5 blog entries\nDo a batch update of the 3 most recent entries\n\nAll in all, this is a much quicker operation. First off, while my code could quickly download the large JSON packet of six thousand plus blog entries, the parsing took quite some time (thank you Node.js timeLoad and I wish I knew you existed years ago). And obviously, doing a batch update of three blog entries is much quicker than multiple thousands.\nYou're probably wondering - why 5 and 3? Honestly, I don't have a good logical reason for that. I guess I just wanted &quot;a couple&quot; of recent entries and not just the most recent because... I don't know. I just felt it was a bit safer. I'll probably edit both operations to only work with one item. At the same time, I want to wait a bit and see how these changes go.\nAlso, I will need a script to handle blog edits, but I can just take the code I had before, drop it into a Node script, and run it locally when I need to do that.\nI'm not sharing this to imply Netlify or Algolia have done anything wrong here. But I do think there's a whole side to the Jamstack that impacts large sites that I'd love to see more discussion around. (I think I even CFPed on the topic before but it wasn't picked up.) As it stands, I hope this helps others who may be dealing with large static sites, and as a reminder, you can always dig into how I've built this site at the GitHub repo: https://github.com/cfjedimaster/raymondcamden2020\nPhoto by Ben Allan on Unsplash\n",
		"tags":[
	        
            "eleventy",
            
            "algolia"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Uploading Multiple Files with Fetch",
		"date":"Sun Aug 08 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1628445600,
		"url":"https://www.raymondcamden.com/2021/08/08/uploading-multiple-files-with-fetch",
		"content":"This afternoon I was going through my &quot;blog ideas&quot; list and cleaning up entries I've changed my mind on. I came across something I added many months ago - using the Fetch API to upload multiple files at once. The reason it's stuck in my &quot;todo&quot; pile for so long is that I wasn't aware of a good service I could use to post my files against. I've done it before in Node.js and I know it's something I could do locally in an hour, but honestly I just didn't want to. That probably sounds a bit lazy but it's honest. Today though I came across httpbin.org, an online service that lets you hit it with various types of HTTP methods and even supports file uploads. (Obviously it doesn't make those files available, it just reports back on the upload.) Even better, it supports CORS which means I could use CodePen. So with no more excuses at my disposal, today I finally built a simple demo.\nFirst off, I created a simple form:\n\nI've got a file field, a button, and an empty div. Notice the file field uses the multiple attribute. This lets the end user select one or more files. For my first iteration, I used the following JavaScript:\n\nFrom top to bottom - I begin by using querySelector to cache access to my file field and empty div. Then I add a click handler to the button.\nThe click handler first checks to see if any files were selected. If none were then we print out a message and leave. Otherwise, we then iterate over the files array and call an async function, uploadFile. In my demo, uploadFile does a POST to httpbin and returns the result. Right now I'm ignoring the result but in a real application you would probably need something from there. At the end of each upload I update my div with a status.\nFinally I report that everything is complete and reset the file field. Here's a CodePen for you to try it out yourself:\n\n  See the Pen \n  fetch multi sequential by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThis works well, but uploads the files one after the other. It would be nicer if they were all uploaded at once, right? Here's an updated version that does that:\n\nThe main difference is tht now I don't await the call to uploadFile and use the implied Promise returned instead. I can then use Promise.all on the array of uploads to notice when they are all done. One thing I don't have is the nice &quot;X of Y&quot; message, and that's possibly something I could do too, but for now the improved speed should be nice. If you want to test this version, it's below.\n\n  See the Pen \n  fetch multi sequential by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEnjoy, let me know what you think!\nPhoto by Mia Anderson on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Check out Begin",
		"date":"Fri Aug 06 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1628272800,
		"url":"https://www.raymondcamden.com/2021/08/06/check-out-begin",
		"content":"In general, when I learn something new I like to share it here on my blog, and typically I wait till I have something of a decent handle on the topic. I have to be honest here and say that that isn't quite the case here. I'm still very early on my exploration of Begin and I wanted to share some initial thoughts.\nSo what is Begin? While you can (and should) check out the home page, I'd like to give you my impression of it. Begin is a serverless platform that reminds me a bit of Express. Back when I first learning Node, it was Express that really &quot;clicked&quot; for me mentally. It made the act of building a web application with Node much simpler by handling boilerplace code and letting me focus on what I was actually building.\nBegin feels like it took Express and made it even simpler, completely removing the need for a core JavaScript file handling routes and crap and instead using convention for much of the same things.\nI realize that's a bit vague so let me try to describe a real world example (taken from their quick start). A simple Begin application can consist of:\n\nA public folder that acts just like a static Express folder. Just put your CSS, images, etc in there.\nA src folder for server-side code. So you need to hit a remote API with a private key, manipulate the results, and return data? Just write a quick serverless function.\n\nAnd... that's it. So in Express, I'd have one app.js file, I'd set up the static directory, I'd set up the route for my function. With Begin, I don't worry about any of that.\nHere's a real example of how this could look. First, in public/index.html, I could do:\n\nAnd then in public/index.js, some simple code to update the div:\n\nTo set up my serverless function, I'd define the path in my package.json. Look at the arc section:\n\nAnd then - by convention - I'd make the following file: src/http/get-foodata:\n\nLocally I can run npm run start and start testing right away.\nOk, so how is this better than Netlify or Vercel? To be honest I'm not 100% sure it is. But you do get more fine grained control over setting up your routing. So for example, if you want to expose POST /addcat and only expose it that way, you can. In Netlify I'd have to write code in my function to check the request method and block non-POST code.\nLike Netlify, you can deploy static sites generators like Eleventy and single page applications like Vue.js. (Here's a guide specific to Vue.) And with both you can then add your serverless code to support the application.\nSo at this point, you may not be convinced yet to give Begin a try. As I said, it feels like a simpler, easier to use version of Express, with more power to configure your applications behavior. But Begin has two really killer features that I think are incredibly useful.\nFirst is built in support for scheduled functions. In a Node application I'd add this via a CRON library and it wouldn't be too hard to do, but Begin makes it trivial. Netlify and Vercel (as far as I know) don't support this at all. You would need to use a third party service to schedule calls to serverless functions.\nThe other really cool part is Begin Data, which gives you easy access key-value database system. This is super useful for cases where you want a database and don't necessarily want to work with another system, like Fauna. To be clear, I like Fauna, and you can absolutely use it or any other database system via your functions, but having a built-in solution is hella useful.\nOk... so that's the good. There are some pretty rough aspects as well. (Before I continue, just note that everything I'm about to complain about I've shared directly with the folks at Begin as well.) The docs now are a bit hard to follow. The quickstart is good, but I quickly got confused after that.\nNext, Begin is built on (or with?) an open source project called Architect. I would find references in the Begin docs to things from Architect that just didn't make sense to me. The Begin docs feel like they fail to handle the case of a new developer coming in with zero knowledge of that part of the project. Keep in mind that as a developer relations person, I'm always looking at projects with an eye to what other developers will think, what they will struggle with, and so on, and I think that Begin needs to work real hard on the post-Quickstart experience and more clearly directing folks on when they need to look at Architect.\nOn the flip side of that, I had stellar support from their Slack channel. After getting support there I was able to play around a lot more and get things working. One more quick tip - your free Begin account only supports five apps. That feels a bit low, but if you want to look at more examples without provisioning and taking up one of those slots, you can find all of their demo projects here: https://github.com/begin-examples.\nSo I hope this was helpful. As I said, I'm very new to this, but I'm finding it really interesting and will keep digging. I've got an application I'm working ",
		"tags":[
	        
            "javascript",
            
            "begin"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "An Adobe PDF Embed Plugin for Eleventy",
		"date":"Mon Aug 02 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1627927200,
		"url":"https://www.raymondcamden.com/2021/08/02/an-adobe-pdf-embed-plugin-for-eleventy",
		"content":"I've covered in the past how to use the Adobe PDF Embed API with Eleventy (&quot;Using PDFs with the Jamstack&quot;). While the Embed API is relatively simple to add to a page, I thought it would be kind of fun to build an Eleventy plugin to make it simpler. Last week, I released it: https://www.npmjs.com/package/eleventy-plugin-pdfembed.\nTo use it, first add the plugin via npm to your Eleventy project:\nnpm i eleventy-plugin-pdfembed\n\nThen, in your .eleventy.js file, require it and add it:\n\nTo use it you'll need to get your credentials first, which are free. Also keep in mind that your credentials are host based, so most likely you'll want to use an environment variable for it.\nOnce done, you can then use the shortcode. It takes an argument for the URL at minimum, but you can also specify the viewing mode. Here's a simple example:\n{% pdfembed 'https://documentcloud.adobe.com/view-sdk-demo/PDFs/Bodea Brochure.pdf' %}\n\nThe plugin takes a second argument for viewing mode which is best demonstrated at our online demo. Finally, a third argument lets you rename the default ID value used for the div that renders the PDF. This defaults to adobe-pdf-view if you want to tweak the size via CSS.\nAnyway, let me know if this helps!\n",
		"tags":[
	        
            "eleventy",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Page Level URL Fetching with Eleventy",
		"date":"Fri Jul 30 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1627668000,
		"url":"https://www.raymondcamden.com/2021/07/30/page-level-url-fetching-with-eleventy",
		"content":"Let me begin by being very clear. This is not a very good idea. I just got back from a much delayed honeymoon with my wife (apparently right before the mask mandates all come crashing back) and haven't written code for a while so perhaps I was a bit desperate to create something useless. That being said, working on this did let me kick the tires a bit on a few Eleventy things and that's always a good idea.\nSo - the background for this was a recently released article on Astro over on css-tricks: &quot;A Look at Building with Astro\n&quot;. It was an interesting article and I'm hoping to get some time to play with Astro more later in the year, but one aspect in particular stood out to me.\nAstro supports loading remote data via front matter. Here's an example from the article:\n---\nimport Card from '../components/Card.astro';\nimport Header from '../components/Header';\n\nconst remoteData = await fetch('https://css-tricks.com/wp-json/wp/v2/posts?per_page=12&amp;_embed').then(response =&gt; response.json());\n---\n\nThis then lets you use remoteData as page level data represented by the network call used. Now... I see that and it's like I have two immediate responses... &quot;that's cool&quot; and &quot;I'm not sure I like that&quot;. That's a fairly typical response I think. Not every feature that looks good on a first impression is actually a sensible idea. But seeing that got me thinking about how something like that could be done in Eleventy.\nRight now, you could easily fetch data and use it in your pages using either global or page level data files. So if I didn't want to add to the &quot;global&quot; data variable space in Eleventy, I could do something like so:\n\nIf I name this foo.11tydata.js and place it in the same folder as foo.liquid (or any other template), then my page would have access to a ships value.\nThis is what I'd do.\nBut again - I wanted to see if I could get it working just on the page itself.\nFor my first attempt, I tried to use JavaScript front matter, this let's you define functions in your front matter that your template can use. Here's the example from the doc I just linked to:\n\nBut, as the docs point out, you can't use {{ currentDate() }} in Liquid. However, you can use an IIFE if you want:\n\nWhich is fine if you want it executed one time only when the page is being built. However, you can't do things like const fetch = require('node-fetch'); in there - I tried.\nBut then I tried another tact... shortcodes. I wrote a filter that let's you pass a URL and a variable. The filter will call the URL and return the results in the variable you created. Here's an example:\n\nAnd here's the filter:\n\nAll it does it take the URL you sent, request it, and return it. Shortcodes have access to page level data so I use the second argument as a way to name the place to store the value. Finally, a lot of APIs will return top level meta or page data and then results, so I included a filter argument as a quick way to get just what you want:\n\nSo um... yeah. That works... I just don't think I'd ever actually do that. ;) I can say the idea of a shortcode creating data you can use again is interesting so I'd love to hear if folks have more... sensible ways to make use of this. Let me know!\nPhoto by Benjamin Davies on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Integrating Eleventy with GitHub Flat Data",
		"date":"Wed Jul 14 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1626285600,
		"url":"https://www.raymondcamden.com/2021/07/14/integrating-eleventy-with-github-flat-data",
		"content":"This post was inspired by a follower on Twitter who saw this announcement by the GitHub folks:\nReleasing our first public project to the world! From our team within GitHub that explores the future of development, we&#39;re excited to share:✨✨ Flat Data ✨✨We asked ourselves:How easy can we make it to work with live-updating data?Check it out!https://t.co/njLvo3CxNj pic.twitter.com/jBDnnUD4y3&mdash; GitHub OCTO (@githubOCTO) May 18, 2021 \nThey were curious how well it would work with Eleventy and specifically asked me if I could take a look. I spent some time playing with it and I have to say, I'm rather intrigued by it. Before I get to the Eleventy bits, let me back up a bit and explain what this whole Flat Data thing is about.\nThe Flat Data project is based on a simple idea. Many projects need to incorporate data from APIs in their projects, but don't necessarily need the most up to date version of that data. Flat Data lets your GitHub repository download data from an API and store it in your repository on a scheduled basis.\nSo imagine your project needs weather information from an API. Typically you would fetch that data using either server-side code or client-side code. Both of these may be problematic. The API may not support CORS which means client-side use will be off the table unless you create a serverless proxy of some sort. The API may have usage restrictions where you don't want to fetch the data too often.\nFlat Data basically lets you take the result of the remote API and save it to your repository as if you had done it yourself. So for example, I can go to remote API in my browser, copy and paste the JSON and save it to a file, like data.json, and then check it in to my repository. Flat Data automates this.\nIt does this via GitHub Actions, a powerful feature added recently that lets you do CI/CD related things to your repository. The Flat Data action lets you specify an API to hit and even lets you execute a script to change that data.\nIt also makes use of a Visual Studio Code extension. I've never used GitHub Actions before but the extension made it brain-dead easy for me.\nAnd that's basically it. The web site has pretty good docs and demos so check it out, but let me show how I used it in Eleventy.\nFor my demo, I used a free Weather API that doesn't require any kind of authentication. Here's the endpoint for weather for my hometown:\nhttps://goweather.herokuapp.com/weather/Lafayette,LA\nThe result is pretty minimal:\n\nThirty-three degrees? That's chilly! All kidding aside, note that it's in Celsius. We'll get to that in a moment.\nSo, the first thing I did was use the Visual Studio Code extension. It prompts you for a schedule (supporting both simple schedules and CRON):\n\n\n\nI selected every day as I figured that was appropriate for weather data. Next, you need to select your data source. This can be either HTTP or SQL. You'll be prompted for what URL to hit, where to save the data, and what post processing you need, if any.\n\n\n\nIn the screen shot above, you can see I've specified my API endpoint. I tell the action to store the result in Eleventy's _data folder. This means I can then access it in my templates. Finally, since I'm a crazy American and won't convert to metric until the end of time, I specified a post processing template.\nThis part was a bit weird as it has to use Deno, a new server-side JavaScript platform like Node, but it's not terribly different. I used some of the Flat Data's own sample code and modified it. Here's my post processor - it basically just rewrites the temperatures in Fahrenheit.\n\nNotice that I write the output back to the input file. In the examples I saw they wrote out to another file but my code worked fine. I suppose do what makes sense here.\nAnyway, everything the extension does is written out to a file in .github/workflows called flat.yml. You can absolutely edit it by hand. Here's how mine looks:\nname: data\non:\n  schedule:\n    - cron: 0 0 * * *\n  workflow_dispatch: {}\n  push:\n    paths:\n      - .github/workflows/flat.yml\n      - fixweather.js\njobs:\n  scheduled:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup deno\n        uses: denoland/setup-deno@main\n        with:\n          deno-version: v1.x\n      - name: Check out repo\n        uses: actions/checkout@v2\n      - name: Fetch data\n        uses: githubocto/flat@v3\n        with:\n          http_url: https://goweather.herokuapp.com/weather/Lafayette,LA\n          downloaded_filename: _data/weather.json\n          postprocess: fixweather.js\n\nI'm not a fan of YAML myself so I greatly appreciate having the visual editor instead.\nOk... so here comes the magic part. I say magic as I really haven't made much use of GitHub Actions yet. (I have a bit with Azure Static Web Apps, but a lot of that's hidden from you.) Once I committed my code... it just worked. (Ok, I lie, I ran into some issues, but they weren't things I think readers will run into so I'm not going to cover them here.) By just committing this file, my ",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Creating an Additive Capture Shortcode in Eleventy",
		"date":"Mon Jul 12 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1626112800,
		"url":"https://www.raymondcamden.com/2021/07/12/creating-an-additive-capture-shortcode-in-eleventy",
		"content":"Edit on August 19, 2021: I found an issue with my code where a shortcode for 'foo' on page 1 would be shared with the same name on other pages. I corrected it by using the current page scope. Fixes are inline.\nOk, so let me start off by saying that a) I'm not sure this is a good idea and b) it may already exist and I just don't know about it. This all came about from me doing some research on an Eleventy tagged question on StackOverflow. If you aren't aware, Liquid has a tag built in called capture. It looks like so:\n\nThis then lets you output my_variable. Having paired shortcodes like this makes it easier to capture dynamic output and save it to a variable. So for example:\n\nOne interesting aspect of the capture shortcode though is that it always sets the value to what you capture. If you had something in that variable already, it gets overwritten. I think that's expected and not bad, but here's an example of that as well:\n\nIf you output my_variable, you will only get MORE captured. Again, I think this is expeted. But it got me thinking - what if we built a shortcode that appended, rather then replaced, content? This is what I came up with:\n\nThis .eleventy.js file defines two shortcodes - mycapture and displaycapture. I define a global variable (I'll explain beforeBuild in a sec) named _CAPTURES that stores key value pairs. In order to keep a key, foo, local to one page, I use the current page's inputPath value. (This is something I edited after the initial blog post.)  When using mycapture, the text inside the shortcode get passed to the content variable and when I actually write the shortcode, I include the name argument. Here's an example:\n\nHere I've captured &quot;foo&quot; twice. And then to output it, I do:\n\nAnd that's it. Using the sample above you get:\n\nSo one thing weird I noticed is that the content began to duplicate itself. So instead of two paragraphs, I'd had four. From what I could gather, Eleventy was not rerunning .eleventy.js on me editing a page, so it didn't clear the variable. I initially had:\n\nI kept getting inconsistent results that would go away if I killed the Eleventy CLI and ran from scratch. I finally figured out what happened and that's when I added the beforeBuild event. In theory it's not needed in production as you aren't refrefshing there, but it doesn't hurt being there as is I think.\nIf you want a copy of this, you can find it here: https://github.com/cfjedimaster/eleventy-demos/tree/master/additive\nPhoto by Jakob Owens on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Graphing Movie Rating Distribution For No Good Reason",
		"date":"Fri Jul 09 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1625853600,
		"url":"https://www.raymondcamden.com/2021/07/09/graphing-movie-rating-distribution-for-no-good-reason",
		"content":"I'm on vacation this week (one of the benefits of working for Adobe are two shutdowns during the year) and haven't really had a chance to write a lick of code (been busy grinding levels in Black Ops multiplayer), but yesterday I found myself working on a small demo for no real important reason outside of just wanting to see what would happen. For folks outside of America, the MPA (formally the MPAA) is the Motion Picture Association. They're responsible for assigning ratings to movies released in America. There's a lot to be said about these ratings, and if you haven't seen it, I highly recommend &quot;This Film is Not Yet Rated&quot; as it details some of the hypocritical ratings giving out by the association.\nI found myself on the web site for the ratings system, https://www.filmratings.com/, and discovered they had a search engine that let you find films for a particular year and rating. For example, here's the R movies released when I was born: https://www.filmratings.com/Search?filmYear=1973&amp;filmRating=R&amp;x=20&amp;y=18\nBeing the kind of person I am, I opened up devtools in my browser and discovered the web site was hitting a SOAP based web service to get its data: https://www.filmratings.com/Filmratings_CARA/WebCaraSearch/Service.asmx\nI love SOAP web services, and by love, I mean truly, truly hate. But I've dealt with them before (&quot;Working with SOAP in a Node App&quot;) and I thought it would be interesting to see how the distribution of ratings varied over the years. I noticed that the web service returned a value representing the total number of films for a year and rating along with a page of data. The total would be enough for me. By playing with the web site, I discovered that the earliest data I could get was for 1968, and with that I whipped up a script that would gather totals for ratings from 1968 to 2020.\nNote that the ratings themselves have changed over the years. For example, PG-13 was added in 1984. Some, like &quot;GP&quot;, were removed. &quot;X&quot; was changed to &quot;NC-17&quot;. For my script I decided to focus on the &quot;common&quot; ratings most people recognize.\nI began with a simple script to get one year, one rating:\n\nNotice I'm using soap and xml2js packages. The soap package handles talking to the web sevvice and xml2js helps me parse the final result. Remember I'm only interested in the total, not the names of the movies. With this done, I then made the script a bit more generic:\n\nNotice I write out the results to a file. My thinking was that I'd do the &quot;scrape&quot; of the data once only. I didn't want to 'abuse' the API and hit it while I played with the results. Here's a subset of how the results look:\n\nCool. So at this point, I had all my data, I just needed to chart it. Since I can't seem to use the same client-side charting solution more than once in a row, I went with ApexCharts since it was free and came up when I searched for a stacked bar chart. I'll shate the code, but honestly I mostly just cut and pasted from their docs. Here's the HTML, which is just an empty div:\n\nAnd here's the code. Most of the work is in translating the data I made into a form that ApexCharts wants.\n\nAnd here's the result:\n\n\n\nI realize that's too small to read. I put the web app itself up here: https://static.raymondcamden.com/demos/mpa/mpa.html If you've got any questions about this, hit me up!\nPhoto by Felix Mooneeram on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Pipedream to Proxy Other APIs",
		"date":"Wed Jun 30 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1625076000,
		"url":"https://www.raymondcamden.com/2021/06/30/using-pipedream-to-proxy-other-apis",
		"content":"One of the first things that intrigued me about serverless, and honestly it's not really that novel, is the ability to build proxies to other APIs. So for example, imagine a cool API that requires authentication of some sort to use, like an API key. If you use this in client-side JavaScript, anyone can look at your code and get your key. Better services let you lock a key to a domain, but if you don't have that option, then a simple use of serverless is to simply give you an endpoint that makes the call to the API with your key.\nPeople looking at your code see a URL like so, mydomain.com/api/getweather, and your code simply makes a call to protectedservice.com/api/getweather?key=secretkey. Of course, building a proxy like this provides other benefits as well. Many years ago I had need of an API that returned a rather large set of data. In that large set of data, I literally needed one value. So my serverless proxy hit the remote API (with credentials) and transformed the result to a much smaller packet.\nWhile Pipedream is really freaking powerful for building workflows, you can use it for simple purposes like this as well. Let's walk through the process. My assumption is that you have a Pipedream account, but if not, it's quick and easy to sign up for one before you continue reading. (But please come back of course!)\nTo begin, create a new Workflow and select the HTTP API trigger. This is how you create a Pipedream workflow that can be executed via a URL call:\n\n\n\nNext, you need to add a new action, Run Node.js code, this gives you a step where you can write any code you want. At this point, the exact code depends on your remote API. It may require authentication via a header. It may require complex parameters and the such. Also, Node provides multiple different ways of making HTTP calls. Even Pipedream itself has a baked in action for hitting a remote URL. To keep things simple (and, being biased to my preference as I'm writing this), I'll use node-fetch.\n\nThe endpoint in this example, a get-cats function on my blog, doesn't actually require authentication, but it's a quick and simple API to hit. The key here is hard coded to a simple value - we'll return to that in a moment. Here's how the complete workflow looks in Pipedream:\n\n\n\nYou can hit this now at: https://enm7s7e1ezjcufu.m.pipedream.net/\nOk, so far so good, but let's do a quick change. While our Pipedream workflows are private by default, we may want to share it with others. Having the key hard coded in there isn't a good idea. Also, we may build multiple different workflows using that remote API. The quickest way to correct this is to add a Pipedream environment variable. If you click the &quot;Settings&quot; link in the left hand column of the Pipedream fashboard, you'll land on a page where you can select &quot;Environment Variables&quot;. On that page you can add, edit, and delete any number of environment variables. And heck, the help text here describes the exact thing we're doing!\nFor example, if you need to fetch data from an API that requires an API key, you can create an environment variable named API_KEY and reference its value in a code cell like so:\n\nClick on &quot;New Environment Variable&quot; and give it a name and value. Since this is account-wide, you normally want to use a specific name of some sort. Since we're hitting my fake little cat API, I'll name it &quot;FAKE_CAT_API&quot;:\n\n\n\nWhat's cool is as soon as you save it, it's available in intellisense in the editor:\n\n\n\nWhile we're here, let's go ahead and make one change to demonstrate transforming the response. My fake API returns an array of cat objects that contain name, age, breed, and gender. What if we know we don't need breed or gender? We can map that out before we return it like so:\n\nNow you have an API proxy that hides the key from your front-end code and also reduces the size of the data to exactly what you need. You basically &quot;recreated&quot; the other API to better fit your needs, which is cool.\nFinally - your API may have parameters that you want your front end code to support. You can make use of those in a variety of ways depending on how you want to call your Pipedream wortkflow. So for example, if you want to use the query string, you can access those values via steps.trigger.event.query. If ?name=foo is passed, then steps.trigger.event.query.name would be foo. You could then pass this on to the remote API.\nOr - and here's the cool part - if the remote API doesn't support that kind of logic - yours can! So my cat API does not suport filtering by name. That's ok - I used Pipedream to fix it:\n\nNow I can hit https://enm7s7e1ezjcufu.m.pipedream.net/?name=p and return cats with p in the name. At this point I've now used Pipedream to:\n\nhide the key\nmake the response smaller\nadd a filter feature the original API didn't have\n\nIf you want, you see the last version of my workflow here: https://pipedream.com/@raymondcamden/hiding-api-keys-in-pipedream-for-blog-p_V9C9",
		"tags":[
	        
            "pipedream",
            
            "javascript"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Dynamic Short URLs with Eleventy",
		"date":"Tue Jun 22 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1624384800,
		"url":"https://www.raymondcamden.com/2021/06/22/dynamic-short-urls-with-eleventy",
		"content":"One feature that some CMS systems have is the ability to handle short URLs that map to pages on the site. I'm not talking about services like TinyURL, but internal systems specific to a site. So for example, Adobe.com supports &quot;go&quot; URLs where you can go to this url: https://adobe.com/go/coldfusion and it maps to https://www.adobe.com/products/coldfusion-family.html. I decided to see if I could implement this with Eleventy. My demo is using Netlify but in theory could work anywhere that lets you specify redirects via a file.\nSo, first off, if you want a simple hard coded system for handling redirects like this, you can simply edit your _redirects file and specify your aliases. So for example:\n/go/cats\t/documentation/animals/cats\n\nWhile this format is pretty simple and a non-technical person could probably handle that just fine, what I wanted to create was a system where the page itself could define it's redirect. So for example, I've got a page located at /docs/gettingstarted.md. Here's the contents:\n\nIn this page, I defined a front matter variable, go, that defines the alias for this particular page. So how did I make this work?\nBefore I show how, let me quickly thank Zach Leatherman for this solution. My initial version worked but his idea made my code much simpler. I begin by first creating a custom collection that contains every page with a go value:\n\nThis new collection, goPages, can then be used in my redirects file. Netlify requires it to be named _redirects, but remember that Elevently lets you output to anything, so I created _redirects.liquid:\n---\npermalink: /_redirects\n---\n\n#old home page\n/home / 301\n\n{% for page in collections.goPages %}\n/go/{{ page.data.go }}\t{{ page.url }}\n{% endfor %}\n\n\nNotice I've got &quot;regular&quot; redirects on top and then my custom ones output beneath. The important bits are the permalink setting which writes to the right place for Netlify and then the loop over goPages. All I do is map the alias provides in the front matter to the 'real' URL.\nI saved this demo in my Eleventy demos repo here (https://github.com/cfjedimaster/eleventy-demos/tree/master/gourls) and deployed it to Netlify here: https://gourltest.netlify.app/. You can test the alias by going here: https://gourltest.netlify.app/go/gs. To be fair, it isn't that much shorter than the real URL, but for a larger site with more nested subdirectories, it could be a handy shorthand. Personally, I love how I can set this up from the content page itself.\nThis could be nicer. For example, I could support passing a list of creating one redirect for each value. Also, I could see building a shortcode such that when run, it either returns the alias version if it exists or just the regular URL. Anyway, let me know if this is helpful!\nPhoto by Javier Allegue Barros on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using PDFs with the Jamstack - Adding Search with Text Extraction",
		"date":"Fri Jun 18 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1624039200,
		"url":"https://www.raymondcamden.com/2021/06/18/using-pdfs-with-the-jamstack-adding-search-with-text-extraction",
		"content":"A few weeks ago I shared a couple of blog posts describing how to add PDFs to your Jamstack site. The first talked about the using the Adobe PDF Embed API to give you more control over viewing PDFs in an Eleventy site. The second example took it a bit further by using Adobe's PDF Services API to generate thumbnails of the PDFs when the Eleventy site was generated. Since I wrote those posts, we (oh, I did tell you I work for Adobe, right?) released a new service, the PDF Extraction API.\nThis API extracts information from your PDF, including:\n\nText, of course, but deep information about the text, things like it's font, it's position, and so forth.\nTables as Excel, CSV, or plain images\nImages\n\nThere's a huge amount of detail available in the output, but that's of interest to me is the text itself. If our Jamstack site is making use of PDFs, it would be helpful to provide a way for users to search for text contained inside those PDFs. Let's look at an update to the last demo that adds this feature.\nFirst, I worked on the data file I use to get PDF information for the site in general. This file (I'll link to the repo at the end) is found in _data\\pdfs.js. In the last post the code did two things - find the PDFs and add them to an array and generate a thumbnail for each one (if it didn't exist). My first modification is going to be getting and saving the text content of the PDF. As with the thumbnails, we only do this one time.\n\nIn the code above, pdf is one of the PDF file names and will be something.pdf. We want to cache the output so I use the same name with a different extension. The hard work is done in getPDFText. Our docs go into detail about how this works, but the general flow is:\n\nTell the API what you want (text, tables, images)\nPass that and the PDF to the API\nGet a zip result back\nExtract the zip and do whatever\n\nFor the most part it feels pretty simple, but the hard part will be dealing with the zip. Not really hard per se, but you need to find a good Zip library for your platform that's easy to use. I used node-sream-zip which seemed to work fine. Here's the entirety of the function:\n\nIt starts off creating a unique filename for the result zip so that multiple operations don't try to overwrite the same file. The code cleans up after itself but I figured better safe than sorry. Next is setup code for PDF Services, basically just loading the credentials. We then build an operation object, set the parameters (addElementsToExtract) and then execute.\nAt the of this part we'll have a zip file with our data. In that zip is a JSON file that contains every single bit of text data possible from the call. This is a huge JSON file and you can grab a schema file to help make sense of it. Here's one snippet of some sample output:\n\nTo me, the important part is just Text, and I use that in my filter and reduce calls to generate one blob of text.\nThe end result of all this is an array of PDF data that includes where the PDF is (I use this so I can render it with the Embed API), a path to the thumbnail (used in the home page for links), and the text content.\nAlright, so at this point, I can build a search engine. I've covered various types of Jamstack friendly search engines here before, but the simplest one would be Lunr. The quick and dirty solution for building the search engine would be to expose my PDF data for indexing by Lunr and then adding a page that uses JavaScript and the Lunr library to search it. First, here is my index:\n---\npermalink: /searchdata.json\n---\n\n[\n{% for pdf in pdfs %}\n\t{\n\t\t&quot;name&quot;: {{pdf.name | jsonify}},\n\t\t&quot;url&quot;:&quot;/pdf/{{ pdf.name }}&quot;,\n\t\t&quot;text&quot;:{{ pdf.text | jsonify }}\n\n\t}{% unless forloop.last %},{% endunless %}\n{% endfor %}\n]\n\nThe jsonify filter is one I wrote myself and may be found in .eleventy.js:\n\nI forgot to use an arrow function for that I feel so un-hipster. Sorry. The end result of this is a JSON file containing each PDF, it's url, and the text. In case your spidey-sense is tingling, yes, this could end up being a very large JSON file. I discussed how to use Lunr in a serverless fashion earlier this month: Using Lunr with Eleventy via Netlify Serverless Functions - Part Two Just keep that in mind as a possible alteration to what I've done here. Now for the front end:\n\nI'm just using some vanilla JS here to load in the data, pass it to Lunr, and set up the form field and button to handle doing the search. If you want to give this a spin, head over to https://pdftest3.vercel.app/ and click the Search link on top. A good search term is &quot;launch&quot;. To make it even fancier (I'll all about the fancy), I made it such that when you go through to the embedded view, I pass along the search term and use the Embed API to highlight it:\n\n\n\nYou can find the complete source code for this demo here: https://github.com/cfjedimaster/eleventy-demos/tree/master/pdftest3\n",
		"tags":[
	        
            "eleventy",
            
            "pdf services",
            
            "adobe"
            
		],
		"categories":[
            
                "javascript",
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Quick Tip - Using Pipedream to Monitor my Algolia Index",
		"date":"Wed Jun 16 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1623866400,
		"url":"https://www.raymondcamden.com/2021/06/16/quick-tip-using-pipedream-to-monitor-my-algolia-index",
		"content":"Last year (sometimes that surprises me, time has been so weird with COVID) I wrote about using Algolia with Eleventy, and specifically how I added it here (Adding Algolia Search to Eleventy and Netlify - Part Two). A few weeks ago I discovered an issue with my implementation that caused the index on Algolia's side to be empty. I fixed that issue and updated my blog post with a detailed description on top. Everything was perfect.\n\n\n\nExcept when I tried searching for something yesterday and nothing worked. I hopped on over to the Algolia dashboard and saw my index was empty again. I went over to the Netlify dashboard, fired off another build, and couldn't recreate the issue. Last time it was a race condition so one test obviously wasn't enough, but something is up.\nUnfortunately, a full Netlify build is ten plus minutes so rerunning that a bunch of times wasn't an option. It was also the end of the day and frankly I was just done. I decided to try something else. I went over to Pipedream and built a &quot;monitor&quot; script. My workflow runs once a day and then uses a bit of Node:\n\nAlgolia doesn't provide a utility to find the size of an index, but if you use the listIndices API you can find the value there. The find filter is filtering to my index name. I built the above as one step, then added another custom step:\n\nAll this does is a quick sanity check on my index size. I've got over six thousand posts so as long as it's some number above that, I can end the workflow early. By that way, that line of code could obviously have been in the previous step. Pipedream doesn't force you to to do one thing per step. I just prefer to build my workflows like that. Makes me feel like a real programmer. Honest.\nThe final step of my worklow is an email step:\n\n\n\nAnd that's it. If you want to see the entire workflow yourself, here's the link: https://pipedream.com/@raymondcamden/algolia-test-p_yKCaROg I'll let folks know if I figure out the issue!\nPhoto by Louis Smith on Unsplash\n",
		"tags":[
	        
            "pipedream",
            
            "algolia"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using Lunr with Eleventy via Netlify Serverless Functions - Part Two",
		"date":"Sun Jun 06 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1623002400,
		"url":"https://www.raymondcamden.com/2021/06/06/using-lunr-with-eleventy-via-netlify-serverless-functions-part-two",
		"content":"A few days ago I posted (&quot;Using Lunr with Eleventy via Netlify Serverless Functions&quot;) about how you could use Lunr via serverless functions. The thinking was that it would help make Lunr more useful for large content sites. In a &quot;typical&quot; Lunr example, you load your data and client all in the browser, which means indexing a lot of content wouldn't be very effecient. That post demonstrated that you absolutely could run Lunr in a serverless function, but it bothered me that the index was being recreated on every search.\nThe index is how Lunr is able to return matches based on input. It takes your raw data, applies some magic to it based on your text, and makes it so it can return more appropriate results for what your users are searching for. As the index process is pretty important, it's also something that takes time to build. (To be clear, it's incredibly fast in my testing, but obviously as your usage increases, it's going to take more time to build the index.)\nLunr supports pre-built indexing to make this process go quicker and I covered how to use them in Eleventy earlier this year: &quot;Using Pre-Built Lunr Indexes with Eleventy&quot;\nFor today's post, I took my previous demo and modified it to use a pre-built index. The change was pretty simple, I think. (But as always, reach out if you have questions.) I decided to use Eleventy's afterBuild event to create my index:\n\nThe function, createIndex, is the same from the previous blog post, but here it is again:\n\nLunr indexes can be serialized to JSON so I simply store the result in a file next to my data file. Modifying the search function was even quicker. Instead of building an index, I read in the file and pass it to a Lunr utility function:\n\nAnd that's it. You can find the code here (https://github.com/cfjedimaster/eleventy-demos/tree/master/lunr5) and a demo here (https://eleventy-lunrtest2.netlify.app/search/). Use &quot;pdf&quot; as a good search term to see results. I also made the search code a bit nicer to let you know when it's working. As always, let me know what you think!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using Lunr with Eleventy via Netlify Serverless Functions",
		"date":"Wed Jun 02 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1622656800,
		"url":"https://www.raymondcamden.com/2021/06/02/using-lunr-with-eleventy-via-netlify-serverless-functions",
		"content":"Lunr is a great client-side search engine that works really well with the Jamstack. (Be sure to see my earlier posts on the topic: Adding Search to your Eleventy Static Site with Lunr, Integrating Navigation Search with Lunr and Eleventy, and Using Pre-Built Lunr Indexes with Eleventy) While I like the simplicity of Lunr, it requires that your searched data be loaded on the client-side. This makes it a bad fit for large sites. For example, my blog has over six thousand posts so I went Algolia instead. However, I was doing some thinking recently and realized that Lunr can also be used on the server as well. I decided to take a stab at trying this out and here's what I came up with.\nWhat I had in mind was this:\n\nUse Eleventy to generate a JSON file that represents my site content, something appropriate for Lunr to index.\nMake that JSON available to a serverless function\nIn the serverless function, accept input (what to search for) and pass it to the index\n\nFirst off, I wasn't exactly sure if I could use Eleventy to generate content that my serverless function uses. So for example, my site here uses a JSON file that is just stored under web root. As part of my deploy process serverless function, I do a HTTP call to my own site to get the content and then pass it to Algolia.\nInstead, I wanted Eleventy to give the file directly to the serverless function itself. But I wasn't sure if Netlify would deploy the serverless function before Eleventy ran. I did some testing and it turns out that Eleventy runs before your functions are deployed so that seemed to be a safe thing to do. I also got confirmation from a support person at Netlify that it was indeed supposed to work that way.\nHere's how I built the index in Eleventy:\n\nThe crutical bit is the front matter. Notice I'm writing directly to my Netlify functions directory and I've told Eleventy to not use the output directory. You need both of these to avoid writing to _site. The actual content of the JSON isn't terribly important for this demo. I decided to use the title, date, url, and complete content of a post. I could have also included categories, tags, even an author field. This is where you would customise it to match the shape of your site and what you need to search.\nNow let's look at the search serverless function:\n\nI begin by looking at the query string for a search term. I load in my JSON file (that's what is generated by the Liquid template above) and then tell Lunr to create an index from it. Pay special attention to these lines in the loop:\n\nRemember that Lunr has that weird behavior where search results do not contain the original data of the matched item. I need a way to associate a particular search result back with it's original data. My original data is an array, so when I loop over it, I store an id value that matches the loop index.\nYou can see this being used here:\n\nThe ref value in a search match is that loop index which means I can get the original information from the data array. Note I also truncate the content a bit as that's not needed.\nAnd honestly that's it. My front end search form just hits the end point. Here's my pretty basic vanilla JS page that does this:\n\nYou can test this yourself here: https://eleventy-lunrtest.netlify.app/search/ The content I used for my test comes from a subset of my blog content, so try searching for pdf to see some results. Note that I didn't use any kind of &quot;loading&quot; indictator as the search is performed. It takes about two seconds (more on that in a second) so be patient when searching. The source code for the demo may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/lunr4\nSo... one thing I'll note is that I'm recreating the index on every search. That's wasteful. One thing I could do is generate both my data JSON file and a pre-built index. Both could be copied to the search serverless function directory. I'd still need to load both into memory when doing a search, but I wouldn't have to build the index. I've got an idea of how to do that and will give it a shot next week.\nI hope this helps! Reach out if you've got any questions!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Recreating Breaking Bad Credits with JavaScript (and a bit of CSS)",
		"date":"Mon May 31 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1622484000,
		"url":"https://www.raymondcamden.com/2021/05/31/recreating-breaking-bad-credits-with-javascript-and-a-bit-of-css",
		"content":"I try to exercise every week day (although with today being a holiday I gave myself a pass). In order to make the exercise a bit more palatable, I'll watch a good show while I'm exercising. The best shows are those that are an hour long on TV since they are typically right at forty-five minutes with commercials removed. Forty-five minutes is my target workout length so that works out great. Currently I'm rewatching &quot;Breaking Bad&quot;, an incredibly good show I've watched before but am enjoying all over again.\nOne of the signature things of the show is their credits. I don't mean the short opening sequence, but rather the credits shown at the bottom in the beginning of the show. For each person's name, they attempt to replace part of the name with a matching element's symbol. They keep the case of the symbol and apply a green color to it. Here's an example:\n\n\n\nBecause I was bored, and because it wasn't necessarily useful, I took a stab at seeing if I could build this in JavaScript (with a bit of CSS of course).\nI began by googling for &quot;periodic table json&quot; and found a GitHub project with the elements in JSON format: https://github.com/Bowserinator/Periodic-Table-JSON/blob/master/PeriodicTableJSON.json This JSON file had a lot of data I didn't need, so I copied it to my RunJS application. If you haven't tried RunJS, it's a great &quot;scratch pad&quot; for JavaScript. It even supports npm modules.\nAnyway, I used RunJS to do a few things. First, I knew I only needed the symbols, nothing more. So I wrote code to iterate over the array of elements and return a new array of just the symbols. Next, I figured that the effect would be better when it could replace a two character symbol versus a one character symbol. (At the time, I wasn't aware of the three character symbol for Ununennium.) I used a quick array sort to order the array of symbols longest to shortest. This then gave me an array of just symbols sorted in a more preferable manner.\nThen I wrote up the function. All it does is take an input name, the list of elements, and the name of a CSS class to apply to matches. Here's how I wrote it:\n\nMost likely this could be written in fewer lines and with more &quot;I can pass the Google interview test&quot; coolness, but it worked. I then used the Random User Generator to spit out a hundred users, copied that into RunJS again and used it to return just an array of names. For fun, I then added mine on top. (I also removed a few names that used non-Roman letters to keep things simpler.) Here's how it looks:\n\n\n\nAnd that's it. Here's a CodePen if you want to play with yourself. Enjoy!\n\n  See the Pen \n  Breaking Bad CSS by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Quick Netlify Tip for Redirects",
		"date":"Mon May 24 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1621879200,
		"url":"https://www.raymondcamden.com/2021/05/24/quick-netlify-tip-for-redirects",
		"content":"Since this just bit me in the butt - for the second time - I figured I'd do a real quick blog post. This isn't to help me remember, but to ensure it comes up next time I google for it.\nI was working on a local 11ty site with the Netlify CLI dev command. For folks who don't know, this lets you simulate the Netlify environment locally. While a great tool, you sometimes run into issues where things behave differently locally compared to production.\nFor me, I ran into just such an issue Saturday morning. I had set up a simple _redirects file to support giving my serverless functions a nicer, and simpler, path:\n/api/*\t/.netlify/functions/:splat\t200\n\nThis worked locally just fine, but in production, I kept getting a 404. Also, when I looked at my deploy log, I saw a message stating that no redirect rules were processed. On a whim, I duplicated my redirect in my netlify.toml file, deployed, and it woked fine.\nI posted on the Netlify forums, and after some back and forth, a Netlify support person asked if my _redirects file was in the published directory. I'm using Eleventy and for non-supported files, you need to tell it to explicitly copy the file to output. This is done with one simple command (I'm sharing a few just to give you more examples):\n\nAs soon as I did that, and deployed, it worked perfectly. I do find it odd that a netlify.toml file in the root of my project works fine, even though it's not copied to output, but _redirects has to be copied for it to work. I think it should be consistent.\nAnyway - as I said - this hit me twice now so hopefully I won't forget.\n\n\n\nPhoto by Jamie Templeton on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Choose Your Own Adventure site with Eleventy",
		"date":"Sun May 16 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1621188000,
		"url":"https://www.raymondcamden.com/2021/05/16/building-a-choose-your-own-adventure-site-with-eleventy",
		"content":"Growing up (a long, long time ago), I was a huge fan of the &quot;Choose Your Own Advenure&quot; line of books. These books all followed a basic idea. You would begin reading and quickly be given a choice. If you pick one option, you turn to a page, if you pick another you go there. You could typically do an entire read through quickly an then just back in and make different choices. There were a huge amount of these published and they were known for having some pretty bizzare subjects. One even included a path that was impossible to reach unless you cheated. (And yes, I remember doing exactly that.) These books were pretty popular years ago and in fact led to folks building maps for the story lines and the various paths.\nBeing that it was the weekend and I felt like coding something completely pointless, I thought it would be fun to build a Choose Your Own Adventue (CYOA) style Eleventy site. Now to be clear, there's nothing special in this at all. Any HTML page can link to any other, so I could simply build a bunch of pages and ensure I handle the links correctly. But I was curious if I could simplify the writing process a bit to make it easier.\nI've been a gamer all my life and one thing I've done throughout my programming career is work on systems that make it easier to build games. I'm not talking about UI systems per se but more shorthands that let you focus on the creative aspect of the game. So you get an idea for your story, game, and you can quickly add it with little to no &quot;programming&quot; involved. (In fact, if you want to read about the code that I'm most proud of, you can take a look at this post from a decade ago: Share Your (Code) Pride)\nBefore I show the code, you can take a look at the demo here: https://cyoa.vercel.app/. The repo for the code is here: https://github.com/cfjedimaster/eleventy-demos/tree/master/cyoa. Honestly the demo is pretty shallow so it won't take you long to explore all the 'branches' of the story.\nHere's an example page from the demo:\n\n\n\nIn the screen shot above, the choices presented to the user are all driven by front matter. Here's that particular page:\n\nAnd here's another example:\n\nThe basic idea is that you create a new Markdown file (in the pages directory) and define an array of choice options in your front matter. I'm not a big fan of YAML, but it is simple once you learn the syntax. In this case I had to search how to define an array of objects and you can see the basic syntax above.\nWith this front matter, the display is handled by a layout file. I used a directory file (pages.json) to save myself from having to type it in each page.\n\nFinally, here's the page.liquid layout file.\n\nNotice how the path value from the front matter is assumed to be the URL/path of another path. Again, my thinking here was to require less typing for the writer. (I don't think the slugify call is necessary there but it doesn't hurt.) If no choices are provided then it's the end of the story.\nAnd that's it. Probably not worthwhile to anyone but fun to build. Also, it got me thinking more about offloading work to Eleventy layouts and that could (hopefully) be useful in the future!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Updating (and Supporting) URL Parameters with Vue.js",
		"date":"Sat May 08 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1620496800,
		"url":"https://www.raymondcamden.com/2021/05/08/updating-and-supporting-url-parameters-with-vuejs",
		"content":"Today's article is something that's been kicking around in my head for a few months now, and seeing a recent article (Update URL query parameters as you type in the input using JavaScript) encouraged me to finally get around to writing it. The basic idea is to make it easier for a person to share or bookmark the current state of an application. Let's start with a basic example.\n\n\n\nThere's a list of items that consists of people, cats, and a dog. Each item has a name and type. On top there are filters for the name and type. If you enter any text, the items that match the name (ignoring case) will be shown. If you select one or more of the types, only those matching will be shown.\n\n\n\nLet's look at the code. First the HTML:\n\nAnd here's the JavaScript.\n\nAs you can see, the items referenced in HTML comes from the 'raw' data, allItems, and is filtered in a computed property. Here's a CodePen if you want to see it in action.\n\n  See the Pen \n  Vue Blog Post about URL Params by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAlright, so that's the application in it's initial state. Now imagine you've filtered the data, like the results, and want to bookmark it, or share it? To do that we need to do two things:\n\nWhen you filter, in any way, change the URL in a way that doesn't reload the page\nWhen you request the page, check the URL for query params and default our filters\n\nLet's tackle the second one first:\n\nI make use of the created event to look at the current URL query parameters. If I have a value for filter, I can simply pass it use it as is in this.filter. For typeFilter, it will be an array of values which in a query string will be comma delimited. So if it exists, I turn it into an array using split. I could test this by manually changing the URL, htting enter, and seeing the page load with the right values.\nNow we need to handle updating the URL when you filter. While Vue supports a watchers feature, it only lets you associate a handler with one variable at a time, which means I'd need a watcher for both filter an typeFilter. Vue 3 fixes this. (See more on this here.)\nAs my application was using a computed value that already executed when either of my filters updated, I added a call to a new function there:\n\nAnd here is updateURL:\n\nI create new, blank URL params and build it up based on the values of my filter. I then use history.replaceState to update the URL without actually reloading the page. Unforunately I can't show this on CodePen as it doesn't let you change the URL, but I have the complete code up on this pen. I put a demo here if you want to kick the tires a bit:\nhttps://cfjedimaster.github.io/vue-demos/urlthing/vue_url.html?\nAnd here's an example with some filters:\nhttps://cfjedimaster.github.io/vue-demos/urlthing/vue_url.html?filter=a&amp;typeFilter=cat\nPhoto by Stephen Kraakmo on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding an Email Subscription to Your Jamstack Site",
		"date":"Sat May 01 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1619892000,
		"url":"https://www.raymondcamden.com/2021/05/01/adding-an-email-subscription-to-your-jamstack-site",
		"content":"Before I begin, a quick note. While what I've built seems to be working ok, I'm still tweaking things a bit so please forgive me if anything doesn't work exactly right. Be sure to reach out and let me know if you run into any issues. Alright, so the topic of this post is how you can incorporate an email subscription service into your Jamstack site, specifically a blog. While most blogs, including mine, offer an RSS feed, I've not been a big user of RSS readers myself. I like the idea, it just hasn't worked well for me as I forget to run them, fall behind and then feel like it's a chore to go through my various subscriptions. Instead, I prefer signing up for an email when a new post is released. Not every blog has that service and in cases like that I've used services like IFTTT to create my own subscription. I decided to take a look into what it would take to add such a service to my own blog.\nI was spurred on by the recent news that Google was removing email subscriptions from FeedBurner. I used FeedBurner in the past and it was a pretty cool service, giving you stats on your RSS subscribers and also providing a free email subscription service. But with the email service going away, that's no longer an option.\nTo build a replacement, I decided to use MailChimp. Brian and I used MailChimp for our music newsletter (which sadly is a victem of COVID and not having enough time) and I knew they had a good developer API. My initial plan (which quickly changed) was this:\n\nSet up my account.\nUse their developer API and a serverless function to let people subscribe from my blog. I did this for our music newsletter and described it in detail here: Using the MailChimp API with Netlify Serverless Functions\nCreate a service that would run on a schedule to check my RSS feed, find items released in the past 24 hours, and if any, create an email and use the MailChimp API to send that email.\n\nIt turns out step three ended up being somewhat of an issue and I had to pivot, but let's wait to tackle that. I signed up at MailChimp for their free tier which seems pretty reasonable. According to their pricing page, the free tier includes two thousand contacts and one audience. (In my understanding, you can think of an audience as a mail list.) If I hit two thousand subscribes I'll do a happy dance. The next tier is ten bucks a month and supports fifty thousand contacts.\nNow - a quick note. I was curious about the specifics of the plans this morning and checked the prices from my admin dashboard. While the prices were the same there, the numbers of contacts supported were quite different. In fact, it seemed to imply that going from free to paid results in 75% less contacts! I'm guessing that I'm either misreading things or they have old UI there. You can check my tweets to them to see if they responded. (Quick note: I did some more digging and I believe the issue is this. The first paid tier lets you have up to 50K contacts and you pay a sliding scale with the number of contacts starting at 500 for ten bucks a month. I don't get why the first paid tier would reduce your total contacts, that seems... weird.)\nAlright, so I set up my account on the free tier and turned towards building a serverless function to let people add themselves to the account. I won't go into much detail there as I documented it in my earlier blog post, I'll just point out that finding your list ID can be a bit of a pain. This article from MailChimp helps: Find Your Audience ID.\nOnce I had that, I created my serverless function using the Netlify CLI and called it newsletter-signup because I am so creative at naming things. Here's my code:\n\nThere really isn't much there. I'll point out that you can add an existing email to a list and the API won't complain. That made testing easy as I just kept adding my own email address. I also used merge_fields to tag the user as coming from my blog, but I'm not actually doing anything with that, at least not yet. Note that right now, on the free tier, you've got one audience, or list, so I'm basically saying my own personal MailChimp account is only being used for my blog which is fine, but if you plan on doing more, you'll need to update and ensure you use new list IDs for your future work.\nOnce done and tested, I then thought about how to add this to the site. I want to thank Brian Rinaldi for this help here as he had some great suggestions. I ended up adding it in two places. First, in the top nav. It points to a subscribe form that uses a simple Vue.js app to handle posting the email address. As it uses Vue and I needed to escape it's tokens to not intefere with Liquid and Eleventy, showing the code here would be a bit awkward. So instead I'll use a Gist. As a reminder, the code can be found up on my GitHub repo for the site.\n\nAlong with that, I added a new form to the end of every post (just scroll down and be sure to scroll back ;) with an inline form. For that I used vanilla JS because it's my blog and I can mix thin",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack",
            
                "serverless"
            
		]

	},

	{
		"title": "Crickets and Other Things",
		"date":"Wed Apr 28 2021 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1619632800,
		"url":"https://www.raymondcamden.com/2021/04/28/crickets-and-other-things",
		"content":"When I don't blog on a regular cadence (once a week), I start to whig out a bit, and despite the absolute huge amount of content here (notice I didn't say good content), I begin to stress out when I don't meet that self-imposed schedule. On a whim, I decided to work on something that's been in my queue for a while but realized that in order for that post to be done, I needed some recent content on the blog.\nWith that in mind, I'm writing this post just to add some filler content to my blog. I feel bad about that, as I want folks to always get value from my site, but at the same time, since it is my site I'm allowed. ;)\nSo as to make this more worthwhile, here's some things for you to consider:\nRead Adam Cameron's Blog\nAdam Cameron is an old friend of mine from the ColdFusion community and is an altogether smart and straightforward guy. He doesn't pull punches with his opinions, but he's scary smart and worth a read. You can find his blog at https://blog.adamcameron.me and follow him at @adam_cameron.\nCheckout my work for Adobe\nIf you don't follow me on Twitter (I understand), then you may be missing my blog posts over on our Medium site: https://medium.com/adobetech. Not just my content, which is a small part, but a huge amount of great technical content from Adobe. Returning here has been eye opening in terms of how much developer stuff is going on with Adobe. Also, I was able to get the jedimaster email address at adobe.com (jedimaster@adobe.com), so I'm never leaving this job.\n\n\n\nLearn Python\nFinally, late last year I started to learn Python. Unfortunately, with the job search and the new job keeping me busy, my time to continue my learning has come to a stop. My plan is to try real hard to get back on track in May. That being said, I really like the book that I ended up with. If you pick it up via the link below, I'll earn a few pennies, so consider picking it up if you've been meaning to learn. As I said, I really like it and it's working great for me (well, when I had time for it). (If your ad blockers removes the link below, try this one.)\n\n    \nPhoto by Trollinho on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Database Driven Eleventy Site",
		"date":"Thu Apr 15 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1618444800,
		"url":"https://www.raymondcamden.com/2021/04/15/building-a-database-driven-eleventy-site",
		"content":"For a large portion of my development career, I've been a databaser user of some sort or another. I spent many years working with app servers (ColdFusion) and almost always they were tied to a SQL database of some sort. In the beginning this was Microsoft Access (it was really easy to use), then SQL Server and eventually MySQL. Most recently I've made more use of NoSQL databases, like Mongo and Fauna. While I definitely prefer NoSQL databases, after years of using SQL, I still have a bit of fondness for it. It's with this in mind that I decided to look into integrating MySQL with Eleventy. Over the weekend I built a quick demo and was planning on blogging sometime this week, but was inspired to get this out sooner when I saw this cool Tweet:\nDid you know you can query a MYSQL database right in @eleven_ty? With JavaScript data files, it&#39;s easy!I’ve built a new, ever-growing database to catalog a music collection I’m building. You can see it here: https://t.co/KQZWPnN8P5Context for why: https://t.co/7jwdB3JNHo pic.twitter.com/KXXksbloOq&mdash; Andy Bell (@piccalilli_) April 14, 2021 \nFor my demo (and I'll link to the code at the end) I decided to build a simple blog. I created a MySQL database containing three tables:\n\nposts - has columns for id (integer, primary key, autonumber), title, body, and published\ncategories - has columns for id (integer, primary key, autonumber), name\nposts_categories - a table that lets you associate a blog post with multiple categories - has a column pointing to the primary key of posts and the primary key of categories\n\nOnce I created the tables, I used the MySQL Workbench to input some basic data. Once I had data, I then created a blank Eleventy site, added a _data folder, and Googled for &quot;nodejs mysql&quot;. When I teach about Eleventy, I tell people it's Node-based, but that you do not need to know NodeJS in order to use it. That's true, but having some familiarity with Node, even just the basics, will help you in the long run.\nMy search turned up the mysql npm package. It looked easy enough to use, but I quickly ran into a connection problem. Hitting up Google again, I discovered that another package, mysql2 fixed my issue and seemed to be the best library to use. (You can read more about the 'why' of this package here).\nHere's an example of this in use. I set up my connection properties (host, username and password, and database name) in an .env file and then created a file to grab my blog posts:\n\nPay special attention to this line, const mysql = require('mysql2/promise');, you will absolutely want to use the promisified version of the library (assumng you are comfortable with async/await and promises, and if you aren't, just ask me fo rhelp!). The logic to generate post data for my site is slightly complex as I have to get posts and then for each one, get a list of associated categories. This is exactly the kind of thing a NoSQL database makes easier, but honestly it isn't too much work here.\nThe end result is an array of post objects that contain the id, title, body, published properties and an array of categories (id and name). Here's how I used it in my blog's home page:\n\nI then used Eleventy's awesome pagination from data feature to create one page per post:\n\nI think most of the above is standard Eleventy usage, but I'll point out the very last line. Notice I take the post body string and pass it to a markdown filter. I defined this in .eleventy.js:\n\nI did this so that blog posts could be written simpler. So for example, a post body could look like so:\n\nAnd the space between each line above would become one paragraph.\nBack in the post template, you may have noticed I linked each category to a page. Let's look at how I handled that. First, I created categories.js in my _data folder:\n\nI've got a bit of repitition here connecting to the database and I could probably optimize that so that both posts.js and categories.js share some common connection code. I'm going to be honest here. One of the reasons I like the Jamstack is that I can write, um, &quot;not the best code&quot;, and know it's only going to be run once during the build process. If it's a bit slow, I'm ok with that.\nAlright, with that done, I then build the category pages like so:\n\nThe only thing really interesting here is the filter to get posts by category. Here's how I defined that in .eleventy.js:\n\nNotice how in Eleventy filters, the first argument is the object you pass to the filter and the second argument was the argument I passed after naming the filter. When I first started building these kind of things, it was a bit confusing to me.\nSo the end result is a home page with a list of posts, pages for each post, and category lists. You can see this in action here: https://mysqleleventy.vercel.app/. The source code may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/mysql_blog. Obviously the big missing piece here is administration. As I said, I &quot;wrote&quot; my blog p",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Proof of Concept - Dynamically Filtering a Large Select",
		"date":"Mon Apr 12 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1618185600,
		"url":"https://www.raymondcamden.com/2021/04/12/proof-of-concept-dynamically-filtering-a-large-select",
		"content":"A while back a friend wrote me with an interesting problem. He has a form where one of the fields can have near a thousand or so entries. It didn't impact load time that much for his users, but it did create a dropdown control that was difficult to use. He was curious to see if there was a way to let the user filter the dropdown to make it a bit more easier to read. Here's what I came up.\nFirst, I did not go down the datalist route. While that provides similar behavior, it only lets you pick a string value. A select field lets you display a string value while binding it to a value in the option. So for example, the text displayed to the user could be American and the value some primary key value used in a database.\nInstead of using a datalist, I went with a simple text field next to the dropdown:\n\nMy JavaScript code then listened for changes to the filter and applied them to a filter on the data that populated the dropdown. Here's the complete code.\n\nSo first off, getOptions is meant to represent the API call or some other 'real' process. In my case I'm just generating dummy data.\nThe function setOptions handles setting the options available to the dropdown. It expects an array of values passed to it. By default this is the full result of getOptions, but when you type into the filter, it filters the values returned. Here's a demo:\n\n  See the Pen \n  Select Filter by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nI shared this on Twitter and got some good responses. Markus Oberlehner responded with a fork of the CodePen where he does something fascinating. Clicking in the filter field activates the multiple property of the dropdown, providing a bit more visual feedback of the filter being performed. Here's his version.\n\n  See the Pen \n  Select Filter by Markus Oberlehner (@maoberlehner)\n  on CodePen.\n\n\nLet me know what you think - remember you can fork my CodePen (or Markus) to work on your own version!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Load a PDF Embed when Visible",
		"date":"Fri Apr 09 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1617926400,
		"url":"https://www.raymondcamden.com/2021/04/09/load-a-pdf-embed-when-visible",
		"content":"A quick tip before I turn my brain off for the weekend (that's not entirely true, tonight I plan on building LEGO). I've blogged before about the PDF Embed API, it's one of the tools my new job involves. If you didn't see my first post on it, definitely give it a quick read: Using the PDF Embed API with Vue.js Today's tip is a bit simpler - how can we use the PDF Embed API to only load a PDF once it's actually visible in the DOM?\nTurns out it's rather simple. Modern browsers support the Intersection Observer API. When I say &quot;modern browsers&quot;, I mean all but Safari, but they're working on it. You can find more details at CanIUse: https://caniuse.com/intersectionobserver.\nI thought I'd do a quick demo of using the PDF Embed API and Intersection Observer together. Turns out it was incredibly simple:\n\nBasically, if the browser supports the API, I set up an observer to monitor part of the DOM (see the earlier querySelector. When it detects that it's visible, I run loadPDF. If the API is not supported, I just run loadPDF immediately.\nAnd that's it. I freaking love how simple that was. If you want to see a demo with some lovely Cat Ipsum, take a gander at the CodePen below.\n\n  See the Pen \n  PDF when Visible Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Michael Dziedzic on Unsplash\n",
		"tags":[
	        
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "javascript",
            
                "development"
            
		]

	},

	{
		"title": "Building a Simple Image Gallery with Eleventy",
		"date":"Wed Apr 07 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1617753600,
		"url":"https://www.raymondcamden.com/2021/04/07/building-a-simple-image-gallery-with-eleventy",
		"content":"For a while now I've been meaning to take a look at the Image plugin for Eleventy and this week I finally got around to building a simple demo. I'm not sure I used the plugin exactly as intended (I'm great for using tool the wrong way!), but once I wrapped my head around the plugin, it was fairly simple to get it working. My idea was this:\n\nStart with a folder of &quot;raw&quot; images. The idea being I could just dump in photos right from my phone or elsewhere.\nUse Eleventy (and the Image plugin) to create a standard size version of each image\nUse Eleventy (and the Image plugin) to create a thumbnail of each image\nIn my site, display the thumbnails with a chance to view the original (and by original I still mean the nicer version created from the raw copy)\n\nI got my demo up and running here (https://imagegallery-eta.vercel.app/) and the source is available as well (https://github.com/cfjedimaster/eleventy-demos/tree/master/imagegallery).\nSo how did I build it? I began by just playing with the plugin. I wrote this in .eleventy.js:\n\nI use a glob library to get all the images from my rawphotos folder. For each, I call the Image plugin with options for width (250 and 650), formats (just JPG), and I customized the filename to keep the original name (minus the original extension) and add thumb- in front of the thumbnail versions.\nWhen I ran this, it properly added the files to my img folder:\n\n\n\nCool - so while that worked, I then had an interesting problem. I needed to integrate this into a &quot;real&quot; Eleventy site with an .eleventy.js that did other things as well. Here was my first attempt (spoiler, it didn't work):\n\nI basically moved my logic into a function, generateImages, and used the beforeBuild Eleventy event. However, you can't use await in this function. I mean you can, but it won't work properly. This is a known bug that is already fixed... for the not yet released 1.0 version. I'm betting it will be soon.\nFor now, I simply took the code to generate the images and moved it into a script, doImage.js:\n\nThen I wrote code in .eleventy.js to read these images and make them available to templates. I go back and forth between using data files and collections, but decided on a collection today.\n\nBasically, scan the img folder for files, ignore the thumbnails, and return an array of paths that also includes the thumb path.\nTo make this work with the script, my build command would need to look something like: node doImages &amp;&amp; eleventy.\nTo use this, I spent five minutes Googling for &quot;javascript image litebox libraries&quot; and settled on CSSBox, which is a simple CSS only solution. After adding the CSS script to my layout, all I had to do was output my images and use the styles that the library wanted. I had to do a bit of logic to handle the previous and next arrows.\n\nFor the most part simple, but I struggled with Liquid's syntac for addition. I kept trying to do {{ x + 1 }} which doesn't work.\nThat's it. As I said, the Image plugin is pretty easy to use and I kinda wish I had taken a look at it before. My use of it (resizing and renaming) is just one example. You can also have it generate HTML for you which is pretty powerful. Let me know what you think!\nPhoto by Soragrit Wongsa on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Quick Tips for Eleventy and Vercel",
		"date":"Sat Mar 27 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1616803200,
		"url":"https://www.raymondcamden.com/2021/03/27/quick-tips-for-eleventy-and-vercel",
		"content":"I primarily use Netlify for my Jamstack hosting service, but I also make use of Vercel quite a bit as well. Vercel's CLI is quite nice and tends to be a bit more intelligent about figuring out your site's requirements with little to no configuration. Other things, like their serverless functions, are a bit easier to use as well. That being said, I've recently run into a small issue with Eleventy and Vercel that I thought I'd share in case others hit as well. It isn't a bug, but a combination of a few things together that may trip you up.\nTo start, I create a two file Eleventy site. It's got a home page:\n\nAll I'm doing here is iterating over an array of cats. That data comes from _data/cats.json:\n\nJust to confirm it works, I ran eleventy --serve and hit the page in my browser.\n\n\n\nAwesome, right? Ok, so if I want to run this with Vercel and use it's local dev server, I'd probably try: vercel dev. However, doing so will result in this:\n\n\n\nNotice how it doesn't recognize the framework? That's because, at least for me, I use my globally installed Eleventy CLI and do not install it locally. I may be in the minority for that, but that's typically how I role. Luckily it's easy enough to fix. First I'll do an npm init -f to create a blank package.json. Next I'll do a npm i --save @11ty/eleventy to set Eleventy as a dependency. Now if I run vercel dev, it recognizes that I'm using Eleventy.\n\n\n\nCool! Except when it starts, I get this:\n\n\n\nIt may be a bit hard to read in the screen shot, but here's some of the relevant bits:\n\n`TemplateContentRenderError` was thrown\n&gt; Having trouble compiling template ./node_modules/liquidjs/README.md\n\n\n\nNotice how the error is being thrown in a file in node_modules? Why?\nBy default, Eleventy ignores the node_modules folder, which is a good thing. However, if you have a .gitignore file, this feature isn't enabled (unless it's empty). This is documented of course. So what happened? The Vercel CLI creates a .gitignore file if you don't have one. It does this to tell Git to ignore the .vercel folder it creates.\nSo now you have a .gitignore file and Eleventy won't ignore node_modules anymore. The fix, of course, is to just add it:\n.vercel\nnode_modules\n\nThis will also speed up your development server as it's ignoring the ten billion or so files under node_modules.\nAs I said, none of this is a bug, but it's tripped me up a few times now so I thought I'd share!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "How I Write Content Here...",
		"date":"Wed Mar 24 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1616544000,
		"url":"https://www.raymondcamden.com/2021/03/24/how-i-write-content-here",
		"content":"A few days ago someone (sorry, I forgot to Like the tweet so I'd remember) asked me how I create content for my blog, specifically the tech stack and process. About a year ago I wrote about my tech stack in general (My Tech Stack (So Far) in 2020) but I didn't go deep into the &quot;process&quot; of writing here. It's not too terribly complex and it works well for me. Since I'm the only user, I'm the only one I have to keep happy so keep that in mind if what I describe below seems weird or crazy.\nFirst off - a bit of history. I launched my blog in February of 2003 (you can still read that first post if you want) on custom blogware written in Adobe ColdFusion. The software eventually morphed into an open source project called BlogCFC that went through numerous updates and editions and had a pretty large following in the ColdFusion community. The authoring experience was a simple web-based administrator with a simple form to write content. I did not use a rich text editor but did do things likes automatically insert paragraph tags and line breaks where appropriate.\nI kept it on ColdFusion for over ten years before moving to WordPress in 2015 or so. I had about five thousand or so blog posts under my belt by then. (To be clear, before I got into Twitter, I used my blog many times for short announcements, links to other posts, and things that I primarily do on Twitter now.) I loved WordPress, especially the authoring environment. I hated how fragile WordPress was and trying to keep my server running. I wanted my blog to &quot;just work&quot; and I was disappointed that was so hard with WordPress. I'm totally fine with that being my fault, but at the end of the day I didn't care, I just wanted something that worked.\nI kept it on WordPress for about a year before moving to the Jamstack in January of 2016 (Welcome to RaymondCamden.com 2016). First with Hugo and Surge. I eventually moved to Jekyll as I found Hugo to be a hard to use. I also migrated hosting to Netlify. Finally, I moved to Eleventy in February of last year.\nSo that's the history, but it doesn't answer the initial question - how do I create content?\nFirst off, every blog post is a Markdown file. I don't create this by hand. I use a Node script called genpos.js (I'll be linking to a repository of everything at the end) that does a few things.\n\nFirst, it creates a folder for the blog post based on YEAR/MONTH/DAY. It intelligently creates YEAR and MONTH when it needs to. Ditto for DAY of course if I somehow post twice in a day.\nThen it creates a base Markdown file to save me some typing. This Markdown file uses front matter that Eleventy recognizes and makes it easier for me to start writing.\nThe script makes me provide a title which it then uses in the front matter and as part of the filename.\n\nSo for me, I start like so:\n\nNext, I run a script that uses the Netlify CLI to start a local dev server. This runs Netlify's local dev environment (which lets you test redirects and serverless functions) and runs my Eleventy install. What you won't find in my Git repo is an .eleventyignore file which looks like so:\n/_posts/200*/**\n/_posts/201*/**\n/node_modules/\n\nThis tells Eleventy to ignore the first twenty years of my blog and makes it run a heck of a lot quicker locally. Netlify's CLI will pop up a tab in my browser and since my initial Node script made a file, I can actually see it immediately. It's just a title but I can click to go into it and start writing. Eleventy has hot reload so as I write and save, I can see how it looks.\nImages are another matter. When I switched to the Jamstack I had a huge amount of old images. I didn't want them in my Git repo for... I don't know. It just felt wrong. I also had a large number of attachments (zips for blog posts) as well. So I decided to use Amazon S3 for that. I set up a bucket and made it resolve to https://static.raymondcamden.com.\nWhen I have an image for a blog post, first I resize it to a max of 650 wide. I normally do this via a Windows Explorer plugin (Image Resizer Utility. I then copy it to an S3 folder with a path of the form: /images/YEAR/MONTH. I don't make a folder per day as I don't usually have more than 10-20 images per month.\nTo make it quicker to use in my editor, Visual Studio Code, I built a shortcut that outputs the relevant HTML. It's dynamic as well:\n\nI also use a &quot;lazyload&quot; library from Google to - wait for it - lazily load images as they scroll into view.\nImages are probably the slowest part of my process, but I've got the muscle memory for it now that such that it hardly seems like an issue.\nCode samples are done using regular Markdown-isms (three single quotes before and after) with Prism used to render them. The only issue I have with code samples is that I use Liquid for my template engine and if I want to actually talk about Liquid, I have to escape the tags. I created a Visual Studio Code keyboard shortcut to make that easier for me. Vue.js uses similar tokens so I have t",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using PDFs with the Jamstack - Now with Thumbnails",
		"date":"Tue Mar 16 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1615852800,
		"url":"https://www.raymondcamden.com/2021/03/16/using-pdfs-with-the-jamstack-now-with-thumbnails",
		"content":"A few weeks ago I posted a tutorial on using PDFs with Eleventy. In that post I described how to use a data file to scan a directory of PDFs and make them available to a Liquid template. I then followed up that post with another, where I described using Adobe's PDF Tools API to generate thumbnail images from PDFs. I thought it would be nice to combine the two so I could have my Eleventy site both list the PDFs as well as generate thumbnails. Here's how that looks with me spending about five seconds on layout:\n\n\n\nSo how did I do it? Keep in mind I described most of the process in my earlier post (&quot;Using the Adobe PDF Tools API to Generate Thumbnails&quot;). The process boils down to:\n\nUse Adobe's PDF Tools API to generate a zip of images for each page of the PDF\nExtract the first file from the zip\nResize\n\nI took that logic and combined it with the code from the first demo (&quot;Using PDFs with the Jamstack&quot;). That process was:\n\nUse a glob pattern to get PDFs\nCreate an array of those PDFs with names and such to make them easier to use in Liquid\nUse Eleventy pagination to generate an HTML page per PDF\nUse the Adobe PDF Embed API to render the PDF in the HTML layout\n\nHere's the updated data file (named pdfs.js):\n\nThat's a bit long, but let me point out the highlights. First off, I modified my use of Adobe's Node SDK to use variables instead of files. This let me store everything in a .env file that would be regular environment variables in production. That makes the initial setup a few more lines of code, but the code is safer to check into source control now:\n\nI still use a glob to get my PDFs, but now I look for a corresponding filename with the .jpg extension. If it doesn't exist, I generate the thumbnail. This makes it quite a bit more performant. In my initial version I simply regenerated it everytime, but while the API was pretty fast, that's still a lot of work I don't need to do more than once.\nThe other change was to include the thumb filename in the result data:\n\nAnd really, that's it. As I said, I did modify the homepage to show the thumbnails and used a bit of CSS, so if you're curious, you can peruse the entire codebase here: https://github.com/cfjedimaster/eleventy-demos/tree/master/pdftest2\n",
		"tags":[
	        
            "adobe",
            
            "pdf services",
            
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Hello (Again), Adobe!",
		"date":"Mon Mar 15 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1615766400,
		"url":"https://www.raymondcamden.com/2021/03/15/hello-again-adobe",
		"content":"A few days ago I shared that I was leaving HERE Technologies and starting a new role. Today I'm doing something new - returning to a company I've worked at before. Today I'm starting a new role as a Senior Developer Evangelist for Adobe, focusing on the document services APIs that you may have noticed me blogging about the last few weeks.\nThis new opportunity really came out of nowhere. I didn't even know they were hiring. I caught up with an old friend, discovered what she was doing, and when I saw what they were working on (and that they were hiring), I thought it would be a great fit.\nI first got introduced to the power of PDFs when I worked with ColdFusion. Maybe ten or so years ago, the ColdFusion team added in basic support for PDF manipulation and inspection by integrating a version of LiveCycle. Honestly back then PDFs didn't really interest me, but I typically tried to test every aspect of a new version of ColdFusion. I started playing with the integration, and it just really struck a chord with me. I also started paying more attention to the PDF space in general, the capabilities, and more.\nWhen I saw what the document services teams we're working on (the client-side library, the APIs, and more), and started playing with them myself, I got pretty excited, and I'm thrilled to be able to join this team and help introduce the tools to everyone.\nI'll still be blogging about Jamstack, Vue, JavaScript, development, and of course, cats, so please wish me good luck as I start my (second) first day at Adobe!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding Filtering to my Vue.js Table Sorting and Pagination Demo",
		"date":"Thu Mar 11 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1615420800,
		"url":"https://www.raymondcamden.com/2021/03/11/adding-filtering-to-my-vuejs-table-sorting-and-pagination-demo",
		"content":"A few years back I wrote about adding table sorting and paging with Vue.js (&quot;Building Table Sorting and Pagination in Vue.js&quot;). While this post is a bit old, it's still helpful and I know this as a reader reached out to me both thank me for the demo and ask if I could demonstrate filtering. I'm not going to go over everything I did in the previous post so be sure to give it a quick read.\nAlright, so I'm assuming you've read that post written in the Way Before Pre-COVID times. If so, you saw me load an array of cats that contain names, ages, breeds, and gender. Here's an example of a few:\n\nAnd here's how the old demo rendered:\n\n  See the Pen \n  Vue - Sortable Table (3) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIt's not terribly pretty, but it gets the job done. So given this as a start, how do we add filtering?\nI began by adding an input field for my filter:\n\nI used type=&quot;search&quot; as it provides a quick way of clearing out values. I added filter to my Vue data with a default value of an empty string.\nNow comes the fun part. We currently render the table using sortedCats. This was a computed property based on the &quot;raw&quot; cats array that handled sorting the data and &quot;filtering&quot; to a particular page.\nTo support filtering based on a search term, I used a new computed property, filteredCats. This new property handles filtering the cats based on user input:\n\nNotice that I lowercase both the original value and the user input. Also notice I only filter based on the name. I could absolutely see filtering on name or breed as well. The important thing is the lowercase. This will make it much easier on the end user.\nWith this computed property, I then updated sortedCats to base it's value on filteredCats:\n\nThe end result is a Vue computed property based on a Vue computed property, which I knew was possible, but I don't think I've actually used it before.\nEdit on 3/12/21: After releasing this blog post yesterday, the reader who originally reached out to me discovered a bug. If you go to page 2 and filter to a value that only has one page, you see an empty page. To fix this, I added a watcher such that when you change the filter value, we reset to page one:\n\nHere's the completed CodePen for you to play with:\n\n  See the Pen \n  Vue - Sortable / Searchable Table by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Saying Goodbye to HERE",
		"date":"Fri Mar 05 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1614902400,
		"url":"https://www.raymondcamden.com/2021/03/05/saying-goodbye-to-here",
		"content":"I just shared this on Twitter, but I figured it would make sense to share a quick post here as well. After nearly two years (I know, that sounds like so long right?), I've decided to move on from HERE. I had an incredibly good time working at HERE and was lucky enough to work with people who were all better than me. I learned a lot and feel like I'm taking valuable knowledge with me into my next role.\nSpeaking of that next role - I'll be starting my new job on the 15th. I'll say then where it is but anyone who reads the blog could make a good guess.\nWish me luck!\nPhoto by Jan Tinneberg on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using the Adobe PDF Tools API to Generate Thumbnails",
		"date":"Tue Mar 02 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1614643200,
		"url":"https://www.raymondcamden.com/2021/03/02/using-the-adobe-pdf-tools-api-to-generate-thumbnails",
		"content":"As folks have noticed, I've been blogging lately about the tools Adobe has for working with PDFs. Broadly speaking these fall under the umbrella of Adobe Document Services. I've focused so far on the Embed API but today I want to share an example of the Tools API.\nUnlike the previous examples where I used client-side code to display PDFs in a browser, the Tools API are all HTTP based APIs built to let you work with PDF files. You should check the docs for a full set of features, but it allows for things like:\n\nCreating PDFs from HTML and Office formats.\nExporting PDFs to Office or image formats.\nOCRing a PDF to let you use search.\nProtecting, or removing protection, from a PDF.\nSplitting, combining, re-ordering, PDFs as well as adding or removing pages.\nAnd more.\n\nThe feature that interested me the most (and will be used in my next blog post) is the ability to convert a PDF into images. My specific use case was to take a PDF, convert it into images, grab the first page, and resize it into a thumbnail.\nBefore I get started sharing my solution, note that unlike the Embed API, Tooling is not free. However, you get a free trial of 1000 API calls over six months. (By that way, to all tech companies that do timed trials. Please consider using a length of time like Adobe has done here. I can't tell you how many times I've signed up for a trial of something and then gotten too busy to use it!) One, very, very cool part of the API is how credential creation is handled.\nIf you create new credentials from the Getting Started page, you have the opportunity of downloading example code (in a few languages, including Node) that includes your authentication details in the zip itself. After struggling with Google's APIs and their authentication, this was really neat to see. I feel like Adobe's API authentication requirements are a bit complex, but having working samples with my own credentials made testing so much easier. I highly recommend using that option when you sign up, even if you don't plan on looking at the examples for a while.\nAlright, so once you have your credentials, you can start using the API. Adobe provides an NPM package you can use like so:\n\nNext, take a look at the example for exporting a PDF to images. It works by taking a source PDF file, generating an image for each page, and saving it to a zip file.\nHere's the example from their pages (and again, if you download the samples you can run it yourself):\n\nThis boils down to:\n\nPoint to the authentication\nPoint to a local PDF\nExport to a zip\n\nThe end result is a zip of every image. Here's an example:\n\n\n\nSo given that we've got a way to create a zip of images, what I need to do is take this, extract out the first file (which should represent the first page of the PDF), and then resize it so it's appropriate for a thumbnail.\nHere's my script, bit by bit, and I'll share the entire script at the end. First, I generate the zip.\n\nMy generateImageZip function is just a more dynamic version of the code above:\n\nNote the use of nanoid in there. This is a npm package for generating a unique string appropriate for a file name.\nNext I need to get the first file from the zip file. I used the npm package node-stream-zip. Here's how it's called:\n\nAnd here's the function:\n\nI'm very unsure about this part: let first = Object.values(entries)[0]; Everything I know about objects tells me that there is no order to the keys (or values), but this seemed to work well. I'd feel better getting all the file names, do a custom sort to find _1, and then returning that, but again this seemed to work. Just know I've got reservations. At the end of this, we've got a file name for the extracted image.\nTo handle resizing it, I used jimp. In the main portion of my script I call my function like so:\n\nAnd here's the actual logic:\n\nI'm resizing it and setting a quality. Normally I'd probably save it to a new file, but I just overwrite the original. Here's the entire script:\n\nAnd here's an example I got from a lovely IRS form.\n\n\n\nIn the next post, I'm going to show how to take this and employ it with Eleventy!\nPhoto by Annie Spratt on Unsplash\n",
		"tags":[
	        
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using PDFs with the Jamstack",
		"date":"Thu Feb 25 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1614211200,
		"url":"https://www.raymondcamden.com/2021/02/25/using-pdfs-with-the-jamstack",
		"content":"Earlier this week I spent some time working on a demo that combined the Jamstack (with Eleventy of course) and the ability to work with PDFs. I recently blogged about using Adobe's free PDF Embed API with Vue.js and I thoughbt it would be interesting to tie this in with a Jamstack example. Here's what I came up with.\nFirst, I stoleborrowed a bunch of PDFs from the IRS. I figure they own me a few PDFs, right? I grabbed around ten or so and put them into two subdirectories based on whether they were a form or instructions for a form (and to be clear, I didn't really check, I just kinda threw some around):\n\n\n\nWith my source material available, the first thing I had to do was ensure the PDFs ended up being available on the static site. By default, Eleventy is going to ignore the PDFs as they aren't recognized as supported files (much like it ignores JavaScript, CSS, and images). This is easy enough to fix with passthrough copy. I added the following to my .eleventy.js:\n\nThis will recursively grab my pdfs folder and the files underneath it. That part was relatively simple. (Although I think this particular aspect - the &quot;dont copy what I don't recognize&quot; is the single most common thing I screwed up when learning Eleventy!)\nNext, I need to make Eleventy &quot;aware&quot; of the PDF data. I can't use Collections feature as it only works with files Eleventy recognizes. Instead I can use the Data feature which lets you add pretty much anything you want. Inside of _data, I created pdfs.js:\n\nBasically - get all the files under my pdfs folder and create an array that contains the path as well as a 'name' field, which for me was just the filename minus the extension.\nOnce this was done, I could then the pdfs array in my Liquid templates, so for example, here is my home page:\n\nYou'll notice that I'm linking to pdf, not pdfs. Why? I could link directly to the where I copied the PDF file, and modern browsers will render it full screen. However, the PDF Embed API will give us much more control over the experience and let us present it inside our site user interface as well.\nTo support this, I used Eleventy's &quot;pages from data&quot; feature to create new HTML pages to render my PDF documents. Here's how I did it:\n\nFrom the top, I use the Pagination feature to iterate over my pdfs array. I specify a permalink under the pdf folder (quick side note - my source directory uses two subdirectories - it's possible that I could have two or more PDFs of the same name and this would cause a problem here - a fix would be to replicate the same subdirectory strucutre as the source - let me know if you want to see that) and for each one, I output the name of the PDF and then use the simple JavaScript embed code.\nThis code is pretty much boilerplate from the embed docs with a few things to note.\nFirst, I needed a key. To do this, I created a new project on Adobe's dashboard. I already had one for my localhost system, but right now your keys are limited to one domain at a time. I knew I was going to deploy this to Vercel so I went ahead and created a new project and key just for that. You'll notice I'm using site.pdfkey. I'll explain this in a bit.\nNext, I need to specify a full URL for the PDF. For this, I use site.url. Both of the site values come from another data file, site.js:\n\nFor the URL I switch to my Vercel site if I detect I'm in production. Ditto for the key value. That hard coded value is the one I use for localhost so it will run locally only.\nAnd that's really it. When the Eleventy site is generated, I end up with HTML files under pdf/ and the raw PDFs under /pdfs. That's not terribly good naming but it works well enough I think. Here's an example of one of the pages.\n\n\n\nYou can test this yourself here: https://pdftest.vercel.app. As a reminder, I used the most basic embed possible. Check the docs for more examples of how you can configure it. You can find the source here: https://github.com/cfjedimaster/eleventy-demos/tree/master/pdftest\n",
		"tags":[
	        
            "eleventy",
            
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Keeping Count of User Visits",
		"date":"Tue Feb 23 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1614038400,
		"url":"https://www.raymondcamden.com/2021/02/23/keeping-count-of-user-visits",
		"content":"Yesterday I was complaining about something on Twitter because, as far as I can tell, that's the main use case:\nIf I wrote a simple blog post showing how to wait until a user&#39;s 3rd or 5th visit to your site before you prompt for goddamn notifications, will any of you stop doing it on my first visit?No? Didn&#39;t think so.#sigh&mdash; Raymond Camden 🥑 (@raymondcamden) February 22, 2021 \nIn case it isn't obvious, I'm talking about the incredibly annoying behavior of sites prompting you to accept notifications (or join a mailing list) on your first visit to a site. At least for me, 99.99% of the time these notifications block what I'm trying to do - actually read something on your site.\nThat being said, I do see how it could make sense to ask this of a &quot;return&quot; visitor, someone who has demonstrated an active interest in your site by returning more than once. After my (admitidly) snarky tweet above, I followed it up with:\nActually - that leads to an interesting idea. If you want to know the Nth &quot;visit&quot;, you need to (well could) use a combination of Local and Session storage. That way you don&#39;t prompt on the 3rd *page view* of the 1st visit.Blog post!&mdash; Raymond Camden 🥑 (@raymondcamden) February 22, 2021 \nOk, so what exactly am I talking about here? LocalStorage is an incredibly easy way to store data on the client. Most people talk about the persistant version, but there's a session based version as well with the exact same API. This is &quot;persistent&quot; as anything else on the client-side, but is trivial enough to use as long as you're ok with the knowledge there's no 100% gaurantee.\nWhile easy to use, it brings up an interesting problem. It would be simple to track every page visit. Here's an example:\n\nIf you've never worked with LocalStorage before, I bet you can still understand that example. The only aspect that may confuse you is the parseInt. All values in LocalStorage (and SessionStorage) are strings, so you want to be sure to convert it to a number before doing any math on it.\nThis &quot;works&quot; but isn't really tracking a visit to a site but rather a page view. What we really want is to know the number of times you had a &quot;session&quot; with the site itself. In order to do that, we can use a combination of local and session storage together.\nBasically:\n\nIf I don't see a value in session (temporary) storage...\nIt's a new site visit! Increment a local storage (persistent) value\n\nHere it is in code:\n\nThis is basically what I just described in text above. If a session value doesn't exist, it means our session has just started and we can update our persistent value keeping track of the number of times we've had a session with the site. And yes, I was lazy and didn't do the fancy thing where if numberOfSessions is 1 I drop the &quot;s&quot; at the end of the output.\nThis is not fullproof. Someone can block or edit the LocalStorage values, but if you use this as a way to not prompt someone with an annoying prompt and you end up never annoying them, that's a win, right? Anyway, here's the code in a CodePen. Note that I'm using console.log to print a message that won't be visible in the embed. If you click the link to open the code in a new tab and see the console there.\n\n  See the Pen \n  Session Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPhoto by Crissy Jarvis on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Integrating Navigation Search with Lunr and Eleventy",
		"date":"Mon Feb 22 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1613952000,
		"url":"https://www.raymondcamden.com/2021/02/22/integrating-navigation-search-with-lunr-and-eleventy",
		"content":"Forgive me for what may be a slightly confusing title. I've previously talked about integrating Lunr and Eleventy (&quot;Adding Search to your Eleventy Static Site with Lunr&quot; and the more recent &quot;Using Pre-Built Lunr Indexes with Eleventy&quot;). In both of those blog posts I had a simple home page with a search for embedded directly on it:\n\n\n\nFor my simple demo, this was sufficient, but I wanted something that was a bit more realistic. In many sites, the navigation itself has a small form field where a user can enter a term, hit a button (or Enter), and then takes them to a search page with results. So for example, imagine this as your top navigation bar:\n\n\n\nThe expectation is that I can enter a term there, hit the button, and on the search page, it should already be performing a query for my input. As you can probably guess, with Lunr this involves noticing the search term in the query string and automatically performing the search. Here's how I did that.\nFirst, I'm not going to go over how the site was built, I did that in my first post on the topic. If you didn't read it and don't have time, the basic procedure was:\n\nI told Eleventy to take the data from one collection (a set of GI Joe characters) and generated a JSON version of it.\nMy search code reads the JSON and builds a Lunr index from it.\nI used Vue to build a simple search interface that interacted with the index.\n\nI used that demo as my source and then modified it quite a bit. First, I added Bootstrap to the UI. Look how pretty it is now:\n\n\n\nI also removed the search application from the home page and instead made a dedicated page for it (search.liqud):\n\n\n\nHere's how I enabled the search in the navigation to correctly default the search. First, I made sure my search form was using GET, this will include the term in the query string. Here's the relevant code from my layout:\n\nI then made a slight modification to my existing Vue code (the complete code is both in the previous blog entry and the GitHub repo I'll share at the end):\n\nThe changes are at the beginning and end of the created method. I start off by looking at the query string and checking for the q parameter (matching the name of the form field). At the end, if I have a value, I fire off a request to search. This means you land on the page and after it loads the JSON file and makes the index it will then perform the search. Of course, you can change the search term after and perform new searches.\nYou can demo this here: https://lunr3.vercel.app/. Try &quot;cobra&quot; as a search term. Or simply go here: https://lunr3.vercel.app/search?q=cobra This small change lets you link people directly to searches as well.\nThe full source may be found here: https://github.com/cfjedimaster/eleventy-demos/tree/master/lunr3\nFor the heck of it, and since everyone isn't a Vue user, I also built a &quot;vanilla&quot; JavaScript version available at /search-vanilla (and search-vanilla.liquid on the repo above). Here's that template.\n\nIt's pretty similar to the Vue version except I've got to build the HTML in JavaScript, which I don't care for but template strings make a hell of a lot better. Anyway, I hope this helps!\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using the PDF Embed API with Vue.js",
		"date":"Wed Feb 17 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1613520000,
		"url":"https://www.raymondcamden.com/2021/02/17/using-the-pdf-embed-api-with-vuejs",
		"content":"I've recently become acquainted with Adobe's PDF Embed API. As you can probably guess by the name, it's a library for embedded PDFs on a web page. Not just a simple viewer, it has APIs for interacting with the PDF as well really good mobile support. This is a part of the Document Cloud service which provides other PDF tools as well (extraction, conversion, and so forth). I've been playing with the viewer a bit and wanted to see what Vue.js integration would look like. Here's my solution, but note that I'm still learning about the product so it could probably be done better.\nFirst off, to use the API you need a key. Clicking the link from the webpage will walk you through the process of generating a key. One important note on this though. You have to lock down your key to a domain and that domain can not be changed either. Also, you can only specify one domain. So if you want your domain and localhost, create two projects, generate two keys, and set them as environment variables for your development and production environment. I did my testing on CodePen and had to use this domain: cdpn.io\nOnce you have a key, you can copy the code from the Getting Started to quickly test. Here it is in its entirety as it's pretty short:\n\nBreaking this down, you listen for an event signifying that the library is loaded and then create a new &quot;view&quot; based on a div in your HTML. (In the example above, adobe-dc-view.) Once that's done you can use the previewFile method to add it the PDF viewer to the page. Here's a screen shot of this particular example:\n\n\n\nI realize that screen shot is a bit small, but in case you can't see it, the viewer includes the tools you would normally expect in Acrobat - navigation, search, as well as annotation tools. You can even save directly from the viewer and include your annotations. Here is my attempt at making life insurance documents more fun.\n\n\n\nCool. So as I said, it's a pretty powerful embedded viewer, and I want to play with it more later, but I first wanted to take a stab at adding it to a simple Vue.js application. Here's how I did it.\nFirst off, notice in the code listing above that we listen for an event on the document object, adobe_dc_view_sdk.ready. For my code to work in Vue I needed something a bit more robust. An Adobian on the support forum noted that you can check for window.AdobeDC to see if the library is ready. I wrote my code such that the created method of my Vue app can check that and still handle the library being loaded library. Broadly I did it by using a variable, pdfAPIReady. My created method does this:\n\nI then add a watcher for that variable:\n\nAnd the final bit is a listener outside my Vue application. Remember that you can access the data variable using the Vue instance. This is how I handled that:\n\nNow, in theory, my Vue app can make use of the library. The Adobe docs describe how to use local file content driven by an HTML input tag. Basically you can pass a FileReader promise to the embed and it will handle knowing when the local file is read and then render it.\nHere's the HTML I used for my demo:\n\nNotice the pdfSelected conditional. This is going to toggle after the user has selected a file. I originally had this in a div around the h3 and the div (pdf-view), but the embed viewer didn't like its div being hidden by Vue. (I could probably change how I hide the div, but for now I'm leaving it.) Now for the JavaScript:\n\nFor the most part, all I did was use Adobe's example of reading a file and moved it inside a Vue method. The end result lets you select a local PDF and have it rendered on my Vue app:\n\n\n\nAs I said, this is a rather simple integration, but hopefully useful to folks wanting to use it with Vue. I've got some more examples coming! You can find the complete source code below.\n\n  See the Pen \n  PDF Embed Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "adobe",
            
            "pdf services"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Exporting Disqus Comments and Adding Them to Eleventy",
		"date":"Thu Feb 11 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1613001600,
		"url":"https://www.raymondcamden.com/2021/02/11/exporting-disqus-comments-and-adding-them-to-eleventy",
		"content":"Apologies for what may be a long winded, kinda haphazzard post. The beginning of what I'm sharing here would be useful to anyone using Disqus while the remainder will only be of use to Eleventy folks. I'll try to be clear about when that transition occurs so you can stop reading when it makes sense. Or you can just read everything, I won't mind!\nAlright, so what's the point of all this? I've noticed for some time now the comment traffic on my blog has decreased to near nothing. In fact, here's a chart that shows in pretty stark detail how much it's dropped:\n\n\n\nFor folks curious, that chart comes from the Disqus reporting tool I built a few years ago called Vader. You can read more about it here: Working with the Disqus API - Deeper Stats (2) Disqus is a simple way to add commenting to a site, and I totally get why strong analytics requires a paid update, but I will never understand why Disqus simply can't tell you the total number of comments a site has. That's incredibly silly, if you ask me.\nThat being said, I decided it was time for a change. I decided I would export my Disqus data, create a static version of it, and start working with Webmention. The first part is done and is covered in this post.\nLet's begin by talking about the data. Disqus does have an export feature, which they warn you may not be available for all sites (tip to every developer resource ever - don't have vague limits in your docs). I tried it on mine and was surprised when I got an email five minutes later with a link to my export. Unfortunately it ended up being a corrupt gzip file so it was useless. As I had previous familiarity with their API, I decided to give that a shot.\nDisqus considers comments to be in threads, where each thread, typically, relates to one blog post. (You can use Disqus for non-blog sites of course.) Their API to retrieve comments lets you get related thread data for each comment, so my first strategy was to get all the comments for my blog. Here's the script for that (I'll share links to GitHub sources at the end):\n\nFor the most part this is simply a recursive call to retrieve a 'page' of comments one a time. That's the highest value possible. For my sixty thousand plus comments this took roughly a minute I think. I don't do any data transformation at this part as I figure it would be the slowest. I wanted the data local so that I could then slice and dice it.\nYou'll notice a &quot;temp&quot; block in there. I was worried the API would return comments I had deleted (for spam obviously) but I never saw that happen. I'm still not 100% sure that's safe so I'm leaving the block in for now.\nThe result is a very, very large JSON file (well for my site anyway) consisting of an array of comments with embedded thread information. Here's one comment:\n\nBy the way, it totally freaks me out when people call me mister. I mean I get that I'm older (more experienced!) but it still surprises me.\nOk, so that gives up comments, but what I really want is an object where the top level array is threads and the posts are underneath it. For that, I wrote the second script.\n\nThe logic here is to create new threads when encountered, add the comment to an array inside the thread, and remove the embedded thread. The end result is essentially the same data, but now centered on an array of threads.\nWoot! OK, if all you want to do is export your Disqus comments you can stop reading now.\nAlright, so the next part was tricky. My goal was to create a set of files such that blog post X could import comment file X (when it existed) and display them instead of the Disqus embed. For that, I wrote a script that read in my thread data and wrote out one file per thread. It also applied basic HTML layout to the thread. To help with this, I used a CodePen (this one if your curious) to design something I thought was decent.\nHere's that script:\n\nA few things to note. The function generateFileName uses the link value from the comment to create a file name based on the URL of the blog post. I originally used a .html extension because they're HTML files, but I discovered that some of my comments had code in them that broke Liquid rendering in Eleventy. By simply renaming it I avoided the issue.\nThe end result of this was a bunch of folders and files that mimicked my blog:\n\n\n\nThe next thing I want to point out is the use of parentText. Disqus supports deep comment threading. In order to keep my sanity, I decided I'd simply present them in a single list of comments, but to flag (and link) to parent posts. Let me be clear, this is not the best solution by a long run, but it felt like a reasonable compromise.\nI copied this into my repo inside the _includes folder... and surprisingly, this is where things got dicey. You see, I needed to import comments into each blog post but a) the import was dynamic and b) would only be done when the file actually existed, since not every post had comments.\nI was not able to get dynamic includes working so I worked around i",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Hack for Reveal.js Presentations",
		"date":"Fri Jan 29 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1611878400,
		"url":"https://www.raymondcamden.com/2021/01/29/quick-hack-for-revealjs-presentations",
		"content":"I go back and forth between creating presentations in PowerPoint and Reveal.js. Both have features I really like a lot, but as I'm primarily talking about web development, I tend to prefer Reveal.js as it isn't quite as jarring to go from slide to code/demonstration as it is when PowerPoint is displaying.\nLike PowerPoint, Reveal.js has a &quot;notes&quot; feature that lets you add notes to individual slides. I use this a lot as I tend to write less text on my slide and rely on the fact that - hello - I'm talking and my slide should only support my talk, not be a replacement for the awesomeness that is my speaking ability. (I'm kidding by the way.)\nIn Reveal.js, slide notes are written in an aside tag that is hidden from view in the presentation. Here's a real example from the talk I just gave.\n\nNote that in the notes above, I used line breaks to seperate each &quot;part&quot; of my note. Mentally I read that as a timeline to go along with the current slide.\nWhen giving a Reveal.js presentation, you can open up the speaker view in another window by just hitting the S key. Here's how that slide looks.\n\n\n\nIt may be a bit hard to see in the screen shot above, so here's another one focused on the lower right side panel:\n\n\n\nNotice what happened? My notes are all on one line. If you think about it, that makes sense. Reveal.js is HTML based and while I treated the notes area like I would have in PowerPoint, it's still HTML, which means a line break is meaningless outside a pre tag.\nI could easily fix that by adding some br tags, but honestly, when I'm in the &quot;flow&quot; of working on a good presentation, I don't want to have to worry about that. That's one thing PowerPoint does really well - as a slide authoring environment it's incredible.\n\n\nReal picture of Raymond working on a presentation.\n\nSince I knew I couldn't rely on me remembering to include proper HTML, I turned to the solution every developer turns to when they want to break&quot;enhance&quot; HTML - JavaScript! I added this quick snippet right before I initialize Reveal.js:\n\nThis could be done in one line but I'm not currently doing a technical code test so why bother pretending. The result is a slighly better view:\n\n\n\nThere's probably a nicer way of doing this, but it works for me!\nPhoto by Alex Litvin on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Highly Analytics - A Review",
		"date":"Thu Jan 28 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1611792000,
		"url":"https://www.raymondcamden.com/2021/01/28/highly-analytics-a-review",
		"content":"Before I begin - a quick note. The service I'm reviewing today is something I've had on my blog for about six weeks. I'm literally removing it tomorrow not because the service is bad, but that I didn't get a chance to write up my review until the day before my trial period ended. I'm going to include some screenshots of how the service looked on my site but I just wanted to be sure people weren't confused with me talking about something I've tested here and then not be able to actually see it. Sorry, life happens and this post just kept getting pushed down my &quot;editorial calendar.&quot; :)\nAlright, so that was a long preamble for what I really wanted to cover, Highly Analytics. Highly Analtycs (HA) is a service that does a couple of really interesting things.\nFirst, it provides a &quot;Medium-like&quot; service where users can highlight and share snippets from your site. Here's an example of that (and again, see the note above) from one of my posts:\n\n\n\nIn case it isn't obvious in the screen shot above, you have options to save the highlight, multiple sharing options, and a way to add a reaction.\nThis by itself I thought was pretty cool. I was a big Medium user (poster and reader) a while ago, but it seems like most folks have left the platform and switched to places like DEV Community. Because I haven't been there recently, I had forgotten how nice the highlighting aspect of Medium was. Now to be clear, HA is not currently sharing highlights and reactions, it's just for your own use, but I can see it being incredibly useful for especially deep blog posts. Being able to add my own highlights and reactions to a post for later rereads and checks is incredibly useful.\nSo speaking of that, here's how a highlight looks when you return to a post:\n\n\n\nThe front end right now is somewhat simple (but as I said, I think really engaging) but the back is pretty deep. The analytics aspect is pretty deep. You can demo this yourself at https://www.highlyanalytics.com/demo which will let you play with some sample data. You get analytics of what you expect - what content is getting highlights, shares, and reactions. You also get some of the same analytics you would from traditional sources, like Google Analytics, so things like page views and user metrics.\nAs I said, I've been running this here for about six weeks. I never told anyone about it nor did I encourage folks to check it out, but if I remember right, I started getting usage like the hour after I added the code. Here's some live stats from the HA dashboard for this blog. First, the Top Articles:\n\n\n\nI wish it showed more than three at a time, but I can say the list of top articles meshes with what my other analytics is reporting as well. Going down the dashboard, next we've got information on how folks are reading on my site.\n\n\n\nThe final chart on the main screen of the dashboard are all visitor type things. This is all data that Google Analytics provides, but even though I like GA (I'm back to them after being solely focused on Netlify Analytics), HA makes it a heck of a lot easier to consuime in my opinion.\n\n\n\nThese are the general stats, you can then drill down into article stats. This provides many different stats. You get a table of pages sorted by views, number of highlights, or reactions:\n\n\n\nYou also get top highlighted keywords, top reactions, and more. If you select one individual page, you can see how folks have &quot;marked&quot; up your page (and again, this is only visible to them):\n\n\n\nAll in all - a pretty fascinating tool. Comment usage on my blog has plummetted over the past few years, but the amount of people adding reactions and highlights was honestly shocking. I knew I was still getting good traffic, but it felt like I've not gotten much feedback about my content. HA shows that while people may not be commenting much on my site, they're definitely actively reading and noting stuff.\nAs I said, this is a commercial service. If you scroll to the bottom of https://www.highlyanalytics.com/, you'll see their current pricing tiers. My site, at around 350K page views per month, would be at the 99 dollars a month level. While I do really like HA, it's definitely more than I want to spend right now. The price drops pretty quickly for less traffic but I do think, overall, the price is a bit high. I can say though that I've been told that price is changing soon (and in a good way), so I'd definitely check it out to see if it makes sense for you.\nOverall, I think this is a pretty fascinating service and one worth checking out. You can give them a follow on Twitter at @highlyanalytics and reach out to them if you have further questions.\nPhoto by Nick Fewings on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Pre-Built Lunr Indexes with Eleventy",
		"date":"Fri Jan 22 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1611273600,
		"url":"https://www.raymondcamden.com/2021/01/22/using-pre-built-lunr-indexes-with-eleventy",
		"content":"Way back in 2019 I wrote a blog post on integrating Lunr with Eleventy. Lunr is a pretty nifty light-weight search engine. One of the features it has is the ability to use a pre-built index. This saves the client from having to build the index on the fly. I took a look at this earlier and built up a demo I'd like to share.\nFirst off, you should definitely read my earlier post. I'm not going to cover all of that again as the older post still works well today. In essence it boils down the following steps:\n\nDetermine what you want to search.\nDetermine how you're going to build your index. This is a bit different from the first step as while you may decide you want to search blog entries, you need to figure out exactly what the index will contain. So perhaps the first three paragraphs of the blog entry and the tags used for the post. You have to weigh what you want to search against the size of your index. You don't want user's having to download a 500K file just to search.\nIn your client side code, load your data and build the index from it.\nThen whip up your search code.\n\nThat's a bit high level, but as I said, the previous post goes into more detail. According to the Lunr docs, pre-building indexes can be beneficial:\n\n\nFor large numbers of documents, it can take time for Lunr to build an index. The time taken to build the index can lead a browser to block; making your site seem unresponsive.\n\n\nA better way is to pre-build the index, and serve a serialised index that Lunr can load on the client side much quicker.\n\n\nCreating a pre-built index is simple. You take the same code you used to build your index and just JSON.stringify it. If we're doing this before the client tries to search then it needs to be server-side, and luckily Eleventy makes this easy with the afterBuild event. I first used this in my last post (&quot;Accessing Eleventy Data on the Client Side&quot;) and it works pretty much as you would expect.\nHere's what I added to my .eleventy.js file:\n\nFirst, you'll note I hard code the output directory. It's a known issue that the Eleventy config file doesn't have access to these settings and hopefully that will be corrected in the future. I begin by reading in a file named raw.json. In the previous post on working with Eleventy and Lunr, I used Liquid to create my raw data file and named it index.json. That was a bad name as it really wasn't the index, but rather the source of what I use to build my index.\nI read in the file and create my index in literally the same fashion I did in the earlier post. Once done, I write the file back out again and use the name index.json. So now my site has both the raw data of my index and the actual built index.\nIn order to use this, I needed to change my search code. I know I said to read the previous post, but just in case you didn't, here's how I loaded my data and built my index:\n\nRemember that initially I named my raw data index.json - sorry if that's confusing. So how do we use the pre-built index? Here's the new version:\n\nTwo big changes. First, I load the index and pass it to lunr.Index.load to have it ready for searching. I then do a second call to get my raw data again. One of the weird things about Lunr is that search results do not contain the actual record you search but a reference to it. Well that's not weird per se, it's probably effecient, but in order to display my results properly, I need those original docs too.\nIn my testing, my pre-built index was bigger than the raw data. That makes some sense I guess. But the end result of this change is that I'm doing two network loads, one potentially big, in order to save the indexing time. You would have to hope that this change is, overall, worthwhile performance wise and that the network &quot;penalty&quot; is comparatively less. Hopefully. :)\nAnyway, this was an interesting experiment. If you want to see the source, you can find it here: [https://github.com/cfjedimaster/eleventy-demos/tree/master/lunr2]. As always, if you've tried this yourself I'd love to hear about it. Leave me a comment below.\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Accessing Eleventy Data on the Client Side",
		"date":"Mon Jan 18 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1610928000,
		"url":"https://www.raymondcamden.com/2021/01/18/accessing-eleventy-data-on-the-client-side",
		"content":"This is something I've demonstrated before but haven't had a chance yet to write about it by itself. As you know, one of the best features of Eleventy is support for data files. This feature lets you define JSON or JavaScript logic to return data your templates can use. But along with using data in your templates, you may want to use the same information in your client-side JavaScript code. So how can you do that?\nLet's start off with some sample data. (I'll be sharing a link to the repository with all this code at the end.) For my demo I'll use two samples, first a static JSON file named site.json:\n\nAnd then a more dynamic JavaScript file named starWarsFilms.js:\n\nJust to test, I create an index.liquid file that dumps the values out:\n\nOk, so the first way we can get our _data files out is to create a new template. Here's how I output the site data:\n\nAnd that's it. I use the permalink front matter to tell Eleventy where to save the file and then just dump out site. There's nothing more required. Do note that normally, like in the test file above, I include a space between the front matter and the content of the template. For my JSON file though I removed that to keep the white space down.\nThat's one example, here's another, this time using EJS:\n\nIn this example, I use a bit of logic to remove something from the site data that I don't want exposed in my JSON. Obviously every case is different, but you have a choice between just showing the complete value from the original data or modifying it a bit before using it on the client-side site.\nAnother example of that can be demonstrated in how the Star Wars data is exposed:\n\nIn this example I only output two keys from the film data. This makes the client-side data quite a bit smaller and gives better performance on the client-side.\nWith these in place, you can then use them as you would any other data. Here's an example (in the repo it's test1.liquid):\n\nNice and easy, right? Let's consider another example. Recently Eleventy released support for events that your code can tie into. One of them is afterBuild, which as you can guess runs after every build is complete. We can use this event hook to copy our data to our output directory. Unfortunately, at the time I wrote this you do not have programatic access to the output directory setting. This GitHub issue comment shows a way of handling that and in general it's a known issue. I'd also like access to the values from _data directory. If our templates can use the value, I think the event should have access to it as well.\nThat being said though we can hard code some values, make note of it, and handle it simply:\n\nAll I do is copy the JSON file from it's source directory to the output directory. I could do some transformations here as well. Honestly I don't know if this option is any better than the earlier versions, but it gives you another option.\nYou can find the complete source for this here: https://github.com/cfjedimaster/eleventy-demos/tree/master/data_to_client. Let me know if you've used these techniques below, and especially if you've done it differently!\nPhoto by NASA on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "A Vue Component for Handling Loading State",
		"date":"Fri Jan 15 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1610668800,
		"url":"https://www.raymondcamden.com/2021/01/15/a-vue-component-for-handling-loading-state",
		"content":"I've been doing some Vue consulting recently with a client and he came up with an interesting scenario. He asked about adding a &quot;loading&quot; state to his UI such that when\na user clicked a button, it was obvious &quot;something&quot; was going on. This is actually something I've covered before in my Vue Quick Shots series:\nVue Quick Shot - Disabling a Submit Button While Waiting for an Ajax Call\nVue Quick Shot - Using a Loading Message\nIt's fairly simple and basically comes down to the following pseudo-code:\n\nWhile that's fairly simple, my client wanted to see if he could turn it into a component. At first I told him this felt like overkill, but he was concerned, rightly so, that if he had multiple buttons on a page doing the same thing, that he didn't want multiple different flags for each one.\nTogether we built up a simple demo of this and it's interesting, but I'm also unsure of one aspect of it and I'd love to get some feedback. I'll share a link to all the code at the end of this post, but let's start with the component.\nFirst, I named it AnotherClickWait. That's a pretty bad name but it will make sense in a minte I think. First let's look at the HTML and style of the component:\n\nAs you can see it's pretty simple. It's got a click event and a disabled property. The only reason I bothered styling it is that I noticed that the disabled state didn't look very disabled. That could be a CodeSandbox issue (where I have the demo), but I just wanted something more in your face. The slot tag lets you pass in the text for the button.\nNow for the code:\n\nOk, so there's a few things to note here. First, I use a flag, loading, that will handle, internally, the state of, well, loading. In order for the parent using the component to know the click event happened, I used this.$emit, and here's where things get interesting.\nHow does the parent let the button know that it's done doing whatever it's doing? This is where my client came up with the idea. The event passes a function as an argument that the caller can use to tell the button it's done doing whatever logic it's supposed to be doing.\nThis part in particular was fascinating to me and also a bit worriesome. I don't know why, it's a good solution, but it's the main reason I'm blogging this as I'd like to get some feedback.\nUsing the component looks like so:\n\nAnd then here's the event handler:\n\nAs you can see, I expect to be called with a function that I can run when I'm done. I think what bothers me is that this feels like a bad dependancy, but on the other hand, the client (in this case the code using the component), has to be responsible for knowing when things are done.\nMake sense?\nSo yeah, about that name. The client is using Vuetify for his UI library and his component actually wrapped v-btn instead. This created an odd issue where the props in the parent component, named ClickWait, did not propagate down to vue-btn.\nI asked about this on Twitter and Alex was happy to help me out:\nSo i have noticed this behavior as well, and i tend to always add a v-bind and v-on so that i am explicitly passing things through. I don&#39;t know why it gets weird about it sometimes.&mdash; Alex Riviere (@fimion) January 14, 2021 \nFrom what we can see, something is going wrong, and we have to manually bind the arguments in. Here's the entirety of that component:\n\nIt's mostly the same but we disable prop inheritance and manually bind it. Again, this is not what we thought we would have to do, but it worked. Want to see it in action? Try out the CodeSandbox below:\n<iframe src=\"https://codesandbox.io/embed/vibrant-mirzakhani-xwrgv?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"vibrant-mirzakhani-xwrgv\"\n     allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n     sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n   >\nAgain, I'd love to hear what people think of this approach. Leave me a comment below.\nPhoto by Dorothea OLDANI on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Remembering (and Restoring) a Route with Vue Router",
		"date":"Tue Jan 12 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1610409600,
		"url":"https://www.raymondcamden.com/2021/01/12/remembering-and-restoring-a-route-with-vue-router",
		"content":"While playing Dragon Quest X1 Sunday morning, a random idea popped in my head and it was interesting enough to convince me\nto put down the controller and take out the laptop and work on a quick demo. What idea was it? In most (well built) sites, if you request a resource that requires a login, you will be returned to that resource after successfully logging in. I was curious how I'd do that with a Vue single page application.\nI began by creating an incredibly simple Vue application. It consisted of these views:\n\nA home page.\nA products page with a list of static products.\nA detail page for each product.\nA users age with a list of static users.\nA detail page for each user.\nA login page with a button you can click to login.\n\nNothing in the above was hitting an API so it was pretty quick to build. If you are reading this I'm assuming you're already familiar with Vue and Vue Router. I'm also using Vuex for state management.\nThe only thing really interesting in the initial demo would be how I handle login checking and redirecting. This is from the end of my router/index.js file:\n\nPretty straightforward. If I'm not logged in and I'm not actually loading the login page, redirect. As I said, everything is static data, so this is how the login routine works on Login.vue:\n\nBasically pass on to Vuex the current login state and then go home.\nYou can demo this yourself live here: https://v1.raymondcamden.vercel.app/. Click the button to login, click around a bit, and reload. Note that you will be logged out. After logging in again, you return to the home page. The source code for this initial version may be found here: https://github.com/cfjedimaster/vue-demos/tree/master/rememberroute/v1\nOk, so that's V1. To support what I wanted, here's the changes I made in V2. First, in my router, I told Vuex to remember my route:\n\nI store the value of fullPath as it contained everything possible in the URL, including the path as well as query string parameters. To test this, I modified a page to manually include one, like so:\n\nMy next change was to my login routing. Here's how it was modified:\n\nAs before, I store my login state, but now I look to see if a lastPath value was stored in Vuex. In theory, it always should be. If so, I clear the old value and redirect there, otherwise I just go home. Again, in theory, lastPath should always be there, but it just felt safer using the if statement to be sure.\nYou can demo this version here, and note I'm linking to a subpage, not the home page: https://v2.raymondcamden.vercel.app/users. And you can see the full source here: https://github.com/cfjedimaster/vue-demos/tree/master/rememberroute/v2\nDefinitely not rocket science Vue.js stuff, but I wanted to see it in action myself so I figured I'd share the results. I'd love to hear how others are doing it and if you would like, share a comment below with your implementations.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Identifying Pictures via SMS with Pipedream, Twilio, and Microsoft Cognitive Services",
		"date":"Thu Jan 07 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1609977600,
		"url":"https://www.raymondcamden.com/2021/01/07/identifying-pictures-via-sms-with-pipedream-twilio-and-microsoft-cognitive-services",
		"content":"Nearly three and a half years ago I wrote a blog post on using IBM OpenWhisk (sigh, I wish I could still recommend it), Twilio and IBM Watson: Handling SMS with OpenWhisk, IBM Watson, and Twilio. In the past, I described how I built a serverless function in OpenWhisk that was used as a webhook for Twilio. I'd send a text to a number and Twilio would fire off the request to my function. I looked for an attached picture, uploaded it to IBM Watson's image recognition service, and reported back to the user what was identified in the picture.\nFor some reason, that particular blog post (and the followup) has been getting an incredible amount of traffic lately on my blog. (If you're curious, they have gotten over thirty thousand pageviews in the past moth.) Looking at those stats, I thought it might be time to revisit the topic and see if I could build it quicker in Pipedream. Turns out the answer was yes. In fact, I'd say I had the entire thing rebuil in about thirty minutes of work, and ten of that was me trying to fix a bug that was completely stupid on my part (using GET instead of POST) for my request. So, how did I build it?\nFirst, I ensured that I had a phone number set up with Twilio. Twilio has an excellent free tier and as part of that, you can create phone numbers that can be used for testing. I'd suggest doing that first (well, if you want to replicate what I did). I already had an account with them, but numbers I had used in the past were deprovisioned at some point. It took all of about two seconds to create a new one.\nWith that in place, on Pipedream I created a new Twilio event source. They have one specifically for incoming SMS:\n\n\n\nIn this process you will enter your Twilio keys, and as soon as it's authenticated, the Pipedream UI will be able to fetch your available phone numbers and let you select it. Honestly I was really impressed with how easy that was. Once created, I could test it immediately by sending a text to the number:\n\n\n\nOk, so with that source working well, I created a new workflow. The workflow would fire every time a SMS message was received. I then needed to do the following:\n\nSee if a picture was attached.\nIf so, send it to my identification service.\nSend the results to the user.\nOr if they didn't send a picture, tell them that.\n\nFor my identification service, I decided to use Microsoft's Computer Vision API. Unlike the times I've used it in the past, it looks like they have a proper free tier now. In the past it was a time limited free preview, but now it's actually free free, which is cool. After I got my keys for that service, I then wrote my first action step in my workflow:\n\nLet's break this down. The first thing I do is look for the presense of an attached image. How did I know it was event.MediaUrl0? I looked at the event source events which lets you dig in the data. You can also do this in the workflow itself by using the test feature. It lets you pick from multiple previous events.\n\n\n\nOk, back to the code. THe value I look for (MediaUrl0), is a URL pointing to the image. You can open that in your browser to test. I noticed that when I used Microsoft's online tester for the API, it didn't like the fact that Twilio's URLs didn't end in &quot;.jpg&quot; or some other image file type. But the API itself was totally fine with it.\nIn the result data (and note, I could do better error handling here, like, any error handling), I take both the descriptive text of the image as well as the list of tags. I specify this as the text value that will be exported from the step.\nThe last part of the code handles telling the user they need to send an image if they want to use the service.\nSo with that done, I need to send the text back. Guess what? Pipedream has a Twilio SMS sending action built. I literally added it and specified values for To, From, and Body. I didn't write one more line of code:\n\n\n\nAnd that's literally it! I know I'm a complete fan boy when it comes to Pipedream, but I was blown away how much simpler this version was. I feel confident saying that 90% of my time was gathering keys, fighting my darn GET vs POST issue, and basically non-Pipedream stuff. So does it work? Yes! Here's some fun examples.\n\n\n\nAnd another...\n\n\n\nAnd another...\n\n\n\nWho else had wine last night? And finally, my son being my son:\n\n\n\nWant to try this out yourself? View and copy the workflow URL: https://pipedream.com/@raymondcamden/identify-picture-p_mkCkL5o\nPhoto by Annie Spratt on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building Generic Workflows in Pipedream",
		"date":"Sun Jan 03 2021 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1609632000,
		"url":"https://www.raymondcamden.com/2021/01/03/building-generic-workflows-in-pipedream",
		"content":"I've been a huge fan of Pipedream since I first started using it. It's ease of use, flexibility, and just overall approach to rapidly creating workflows has really resonated with me as a developer. One of the more interesting\naspects of Pipedream is that when someone shares a workflow with you, you can copy that workflow to your own account and then modify it for your own use. I was thinking of this over the holiday break and wanted to take a look at how I could build a workflow specifically for sharing with others, i.e. something that doesn't really do anything by itself but would be a good starting point for others. I had some fun with this project and thought I'd share what I discovered.\nLet me begin by describing what I created. I began with a simple idea - fire off a process when the temperature gets too cold. So for example, &quot;if the temperature is below 32 degrees, message me so I know to cover my plants.&quot; I was originally going to just build that as a proof of concept, but I began thinking about how to make this more generic. What if the workflow was defined more abstractly:\n&quot;When the temperature or weather (i.e., storms, rain, etc) meet some condition, then do something.&quot;\nSo basically we have two things we can check, the raw numeric temperature or the type of weather. Conditions would be simple. Either we are below, at, or above a target temperature (temperature below 45, or temperature above 90), or there is a match to the weather (the current conditions are rain).\nTo create this, I started building an event source. HERE has a weather API with a free tier and Pipedream supports it natively as source:\n\n\n\nAfter selecting this, you then get prompted to connect it to a HERE developer account. Doing so requires a key (free!) and giving it a name.\n\n\n\nA note about this process. One cool thing about authentication values for stuff like this is that once you've &quot;connected&quot; once to a service, you can reuse that authentication again. This is where using a nice name will help you out.\nAfter you've connected, you then need to enter some values for the event. The weather will be retrieved via zip code and will run on a schedule. The default is somewhat overkill for weather so I changed it to once an hour. This impacts how often the event source will run and &quot;emit&quot; the current weather for that zip.\n\n\n\nOnce created, you can immediately start testing. This will ensure that you defined a proper key and it lets you look at the results. Here's how it looks in the event sources UI:\n\n\n\nNotice the existing event on the left hand side. Clicking this lets you look at the data.\n\n\n\nAlright. So now that we have a configured event source, we can create a workflow from it. The event source UI provides a button for that (see the screen shot above, the one before the event result) and if you click it, you'll be dropped into the workflow editing UI.\nI began by adding a Node.js step and named it checkweather. This step needs to use parameters that define what kind of check it will be doing (temperature or conditions), the type of condition (less than, greater than, or equal), and the target value.\nThis is where things get interesting. In order for my code to use parameters, I can simply write code that expects it. What I mean is, as soon as I type: let check = params.type, Pipedream recognizes that my code is expecting a parameter named type and it adds it to the UI:\n\n\n\nParams in Pipedream workflows can get pretty complex. I clicked on &quot;edit params schema&quot; and started to work on defining my three parameters. The first one, type, needed to be either temperature or weather, and what's cool is that Pipedream let me define that as enum:\n\n\n\nOnce I did that, the step changed the type input into a drop down, making it even easier to use. My second parameter followed the same format:\n\n\n\nMy last parameter, value, was just a text field. After setting things up, I then realized I could also add descriptions to my parameters to make them even more clear:\n\n\n\nOverall, that entire process was really well done, and I should point out that while I did all of this with the intent of making it nicer for others, I can see this being super useful for my own workflows as well, or workflows that a team of developers are working on. I guess this falls into the same category as documentation - it can always be useful!\nOnce I had my parameters done, it was then time to write the code:\n\nWhat's going on here? My step falls into one of two conditions - check the temperature or weather condition. For each, I look at the result from the HERE API and execute the relevant logic. For temperature this requires three possible checks and for condtions just one. Pay special attention to how the logic works. If a condtion fails, I use a built-in Pipedream API, $end, to stop the workflow from continuing.\nAnd that's literally it. As I said, this workflow was meant to be a base for others, so it only consists of the event s",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "2020 Can Kiss My...",
		"date":"Sat Dec 26 2020 12:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1608984000,
		"url":"https://www.raymondcamden.com/2020/12/26/2020-can-kiss-my",
		"content":"Sigh, I can still remember how excited I was when I wrote my year end post last year. I also remember being on the treadmill, during my morning exercise and watching CNN. Covid was the topic and it was a &quot;potential&quot; concern. I can remember thinking - news agencies love to be dramatic and this will be off their radar in a week or so.\nOops.\nAll things considered though - there's a lot for me to be happy about. In March, I married a wonderful woman. She makes me happy, supports me, and is just plain incredible. It doesn't hurt that she's also a huge nerd. I'm happy to say our wedding song was the Imperial March. We've not been able to go on a honeymoon yet but we're making plans.\nI've also managed - somehow - to keep all ten of us (my wife has a son with her ex, so with the seven I brought in, we're eight kids and two adults) healthy over the past year. We've been homeschooling four of our kids and while that's been tough, it's at least keeping them safe. Everything else below is - to use a Cajun phrase - lagniappe.\nSo as I do every year, here's a quick review of what I accomplished. This isn't meant to brag, but is my way of taking stock of what I got done.\nThe Blog\nIn February of this year, I migrated my site to Eleventy and never looked back. I absolutely love Eleventy (see my posts on it) and don't see switching. Of course I've gone through a couple of &quot;favorite&quot; Jamstack tools, but something tells me this one will stick. Looking at my stats, I managed to write 84 posts this year, a far cry from the hundreds per year I used to do in the past, but with most of my short form stuff being shared on Twitter and with me writing more for external sources (I list these on my About) page), I think I had a great number. All I really want is one per week and I absolutely hit that. If you're curious about maintaining a &quot;per week&quot; flow, you can read the post I wrote about tracking that with Eleventy.\nI think I'm still doing good traffic wise. I removed Google Analytics earlier this year to rely on Netlify Analytics. I'm happy I did that, but I do wish Netlify would improve the available toolset a bit. I can't remember the last time it changed - I think it was a date range selector that lets you pick one of three ranges. I'd like to see more done to it, but it's definitely worth it now so I'd still suggest Netlify customers consider it.\nFor folks curious, my average traffic rate this year as been over three hundred thousand page views per month. This is pretty much on par with what I've gotten for quite some time now so I'm happy enough with it. Not that I need the site to earn money (Netlify graciously serves my site for free) but I started using Carbon Ads a few months back and it's... ok. It earns more than AdSense does for me, but still a bit a low I think for what my traffic and readership brings in. I may try to see what I can to improve that next year. Typically though I make the most money writing for other publications, and I like doing that as it exposes me to other audiences.\nProfessional Stuff\nI work too, sometimes anyway. I've now been at HERE for about sixteen months. I've really enjoyed getting to know the mapping space more and I work with some great folks. I do think there's going to be a change for me and my job in 2021, but time will tell.\nDespite Corona, I somehow managed to do 24 events! I'm super proud of that. Virtual presenting is a completely different beast compared to in-person events, and to help, I wrote up some tips on giving remote presentations. Give it a read if you think you'd like to try.\nIn terms of my skills, as I said I've definitely learned more about mapping. I feel like my JavaScript skills got a bit sharper this year. Still not quite &quot;Pass the Google tech skill&quot; level, but better. I'll take that. I also started playing with Python a bit. I used this year's Advent of Code as a way to practice. I only got eight days done but I'm really digging Python. I want to make it a focus for next year, along with GraphQL as well.\nLastly, I released a book on Vue.js (to be clear, my part was just three chapters) and I'm working on another book on the Jamstack with my buddy Brian.\nMedia\nIn the past I used to do a separate post on my favorite movies, music, and so forth. I just don't think I've got the energy. This year my wife introduced me to musicals so I've been catching up on that genre. I think my favorite was the\nLes Misérables film with Sacha Cohen. I never quite understood George Constanza non-stop singing &quot;Master of the House&quot; until I heard it myself.\nIf I could make one suggestion for a movie to watch from this year, I'd recommend Antebellum. I didn't really know about this movie before I started watching it and that just made it more powerful. Don't look it up, just give it a watch.\nTV wise I can only recommend an older show, Gotham. My wife and I are watching it and are almost done with season four. It's incredibly fun to watch and ",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Vue Quick Shot - Downloading Data as a File",
		"date":"Tue Dec 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1607990400,
		"url":"https://www.raymondcamden.com/2020/12/15/vue-quick-shot-downloading-data-as-a-file",
		"content":"This isn't necessarily a new trick (and it's one I've covered sometime in the past), but I thought a quick example of how to do it in Vue.js would be helpful. For folks who may not know, HTML has included, for a few years now, a way to force a link to act as a download. This is done via the download attribute of the anchor tag. So for example:\n&lt;a href=&quot;cats.csv&quot; download&gt;Cats in CSV&lt;/a&gt;\n\nYou can supply a filename to the download attribute but left blank like that it will use the filename specified in the link. While you can do this with &quot;physical&quot; files, you can also use it with JavaScript data on the client-side. There's multiple web pages explaining how to do this, but the general technique involves:\n\nCreating an anchor element in JavaScript\nStyling it to be invisible\nSetting the link to a data URI that includes an encoded version of your data\nAdding the element to the DOM\nFiring the click event.\nAnd then removing the element.\n\nI found a nice example of this here: Making JavaScript download files without the server. Let's take this and add it to a simple Vue application.\nFirst, let's begin with an app that just displays a table of cats. Not a cat on a table...\n\n\n\nbut an HTML table of cats. Here's the code with some static data:\n\nAnd here's the layout:\n\nNow let's add a button to let the user download the information:\n\nAnd then a method to handle it:\n\nFor the most part this follows the blog post I linked to above with a few small changes. I modified the MIME type to be appropriate for JSON and switched a few var statements to let, because I'm hip like that. This works great and you can test it below:\n\n  See the Pen \n  Vue Download by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nCool. While this is nice, JSON is only really familiar to us nerds. It's a table of data, and tables just scream Excel, right? I've been enjoying the heck out of PapaParse lately for parsing CSV, but it also generates CSV as well. I added the library to my CodePen, and then spent about 30 seconds rewriting the code to support CSV:\n\nThe changes were me just making use of the unparse method and then updating the filename and MIME type. That's literally it. Now if you download, and you have Excel or another such program installed, you can open up the file right away.\n\n\n\nYou can play with this version here:\n\n  See the Pen \n  Vue Download 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nEnjoy, and let me know if you've used this technique in your own code by leaving me a comment below!\nPhoto by Valentin Vlasov on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Google Calendar to Your Jamstack - with Pipedream",
		"date":"Tue Dec 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1607385600,
		"url":"https://www.raymondcamden.com/2020/12/08/adding-google-calendar-to-your-jamstack-with-pipedream",
		"content":"Late last year (remember last year - sigh) I wrote up a post demonstrating how to integrate Google Calendar into your static web site: &quot;Adding Google Calendar to your JAMStack&quot;. In that article, I describe how I used Google's Node libraries to read my event data. While it was mostly painless, authentication was a bit difficult to figure out. Also, I ended the post with this warning:\n\nSo, this solution isn't perfect. If you add, edit, or delete an event, it won't be reflected on the site. However, you could simply do daily builds of your site via a CRON job let it be updated at that point. Or do a manual update if you want.\n\nA few days I was thinking about this usecase and realized I could probably do it a lot easier making use of Pipedream. How so? Don't forget that Nelify lets you create a build hook. This is a unique URL that when hit with a POST request will trigger a new build. In theory, all I have to do is create a Pipedream workflow that's fired on new events. How is that done?\nAfter logging into Pipedream and going to my &quot;Source&quot; panel, I created a new source and selected Google Calendar. Pipedream lets you select from a few different types of events (to be clear, I mean programatic events, not calendar events, sorry for the confusion!) including one named, &quot;New Or Updated Event (Instant)&quot;. As the name says, this will run instantly based on any particular edit done to a calendar. I authenticated with Google via Pipedream, selected my calendar (a custom one I made for this blog post), and then took the defaults for the rest. I did specify a name for the source though so it would be easier to find later. (I do a lot of testing!)\n\n\n\nOnce this was created, I then made a few edits to my calendar. As I did, I was able to see, instantly, new events. Note that in the screenshot below, the event source code is smart enough to use the calendar event title as a label. That's cool!\n\n\n\nNow that I have an event source that fires when I edit a calendar, the next step was to use it in a workflow. I created a new Pipedream workflow, used my event source as the source. Do not forget that there is a boolean trigger for event sources that &quot;enables&quot; it. Your event source is running, but in the context of the workflow, you must enable it there. This is different from the &quot;active&quot; toggle of the workflow itself and every time I use an event source like this, I forgot to hit the toggle:\n\n\n\nThen I had add a second step. Remember that the idea is to trigger a build. While this is pretty simple code in Node, Pipedream actually has a built in action for performing HTTP requests. I added it, totally did not forget to change them ethod to POST, and pasted in the URL I got from the Netlify site settings so I can trigger a build.\n\n\n\nThis just left testing. I created an event in Google Calendar, which automatically triggered my event source, which automatically triggered my Netlify build, which then triggers happiness!\n\n\n\nWoot! So in theory I could stop there, but then I thought, if Pipedream makes Google Calendar so easy to use, could I use it in my Eleventy site instead of the Google Node library I had? Turns out - I certainly could.\nI built a new Pipedream workflow with a HTTP trigger. I then added the action, &quot;Google Calendar - Get All Future Events&quot;. Note that there is also a &quot;Get Events&quot; action and that even the &quot;future events&quot; action lets you configure a minimum time, effectively allowing you to get past events too.\nThis action requires you to paste in the ID of your calendar which you will need to get from your calendar settings. (The event source has a nice dropdown of your available calendars and I've already aksed if the action can be updated to suppor this as well.) Also, there is a default setting, &quot;Single Events&quot;, which takes recurrring events and &quot;collapses&quot; them into one. For my use case, I did not want this behavior so I set it to false.\nThe final step was to simply return the data so I could use this workflow as an API. I added a Node step and wrote this code:\n\nAnd that was it, literally. In two minutes, I had an end point I could hit to return Google Calendar events in structured JSON. All the auth was handled by Pipedream.\nI went to my Eleventy site and created a new file in my _data folder to use this API:\n\nA few things to note here. I could obscure my Pipedream URL by using an evironment variable set by Netlify. I also did some basic data wrangling on the event structure to make it easier to use in my templates. As an example, here's the event's page:\n\nI could absolutely display my evevents nicer. Heck, I don't even display the end time for events that have it, but you get the basic idea. You can see this yourself here: https://calendar-eleventy-pd.netlify.app/events/. You can see the source code for this Eleventy site here: https://github.com/cfjedimaster/eleventy-demos/tree/master/gcal2. If you've got any quest",
		"tags":[
	        
            "eleventy",
            
            "pipedream"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Web App Powered by Google Forms and Sheets",
		"date":"Fri Nov 20 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1605830400,
		"url":"https://www.raymondcamden.com/2020/11/20/building-a-web-app-powered-by-google-forms-and-sheets",
		"content":"This past week I've been working on a project that had an interesting requirement. We needed a way for the public to submit information and then we needed to display that information, once curated, in a web application. This is a fairly typical web application and something I've been doing for nearly twenty years now. But what we were curious about was whether or not this could be done without using any kind of server-side processing. We ended up going a different way for the project, but not before I had built out a fully working process using Google Forms and Sheets. How did it work?\nI began by building a Google Form. I've used their form builder before and I knew it &quot;just worked&quot;, but I got to say I was blown away by how incredibly easy it was to use. It's got many different options for how to build your form, but what impressed me the most was how it intelligently parsed your questions. For an example, if you asked a question that seemed like it would need a yes or no answer, Google would default the type to a multiple choice and suggest both Yes and No and answers. If you picked Yes to add it as an option, it would then present No as the next suggestion. Time and time again it would guess right based on my questions which made working with it easy.\nGoogle lets you direct form answers to a Google Sheet. And here's where things got cool. Your form answers can be put in a sheet that is modified to have additional columns. This is how we did our curation. Since I knew I was going to be driving a web application with the data, I didn't want spammers to attack the form. By adding a simple &quot;Approved&quot; column to the sheet, I was able to write my code such that only approved rows of data would be rendered. It would still be possible for a person to discover the original data and see spam, but only if they went looking for it.\nLet's check out a demo of this in action. First, I built a form:\n\n\n\nMy form has three questions, all required. Obviously you can have more, have optional questions, and so forth. You can view this form here: https://docs.google.com/forms/d/e/1FAIpQLSfYL868eNC-iWLVI50EvsPHtIVwfCIReMrBkbkZGiL_xd81sA/viewform\nI set up the form to save to a Google Sheet (that isn't the default), and then once in the sheet, I added a new column, Approved. I set it to be a checkbox so it was quick to use:\n\n\n\nNotice that Google has also added a timestamp. I don't need that but it's handy information. Now for the fun part. My sheet is only editable by me, but the public has the ability to store data in it via the form. But only I can edit the additional columns.\nI then published my sheet to the web. As I said above, this is where spam can come into play, but as I said, it's only going to be visible by curious web developers who look at my code and open up the sheet directly. You can see this yourself here: https://docs.google.com/spreadsheets/d/e/2PACX-1vQLVvd7h1zohI2GTCI4GPEVNEn_9t9qqtW-YJK4FKgv7p98d1PkLuMyAawF_uoLYulyzcqmJ301BDlF/pubhtml.\nSo how do I get this into a web app? You can modify the URL to output both CSV and JSON. I'm going to use CSV since there is a great library for it, Papa Pare. To get the CSV output, you change the URL to this: https://docs.google.com/spreadsheets/d/e/2PACX-1vQLVvd7h1zohI2GTCI4GPEVNEn_9t9qqtW-YJK4FKgv7p98d1PkLuMyAawF_uoLYulyzcqmJ301BDlF/pub?output=csv If you open that URL your browser should download the raw CSV.\nEdit: Note that enabling remote access takes one step. When you publish to the web, it defaults to web. If you switch to CSV, you actually get the link shared above, but it does not work for anonymous requests, the user has to sign in. I also needed to get a shareable link, viewer only. The link did not need to be used and the URL itself isn't important. I shared the document that way too, opened it at least once, and then went back to to test my CSV link. At that point it worked. Long story short, you need to do both, and confirm your link works in an incognito browser before you try to use it in JavaScript.\nSo how do I use it in a web app?\nI began by building a simple Vue.js application. I didn't have to use Vue but I had to use Vue, know what I mean? I started off with a simple layout:\n\nI've got a table to iterate over my awesome cats. Beneath that, I either show a loading message, or provide a link for folks to add their own cats. Now the JavaScript:\n\nIf you don't use Vue.js, don't worry, lets just focus on the Papa.parse section. I pass it the following options:\n\nFirst, the URL of my Google Sheet.\nNext I use the download flag to tell Papa Parse to retrieve the contents.\nThe header option means to treat the first row as a header.\nWhen you use greedy, Papa Parse tries to remove lines from the sheet that are just empty strings. This actually didn't work for me because my &quot;Approved&quot; column set defaults to False all the way through the entire sheet. I fix that later.\nBy default, Papa Parse will create an array of objects for each row of dat",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Preventing Multiple Form Submissions",
		"date":"Tue Nov 17 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1605571200,
		"url":"https://www.raymondcamden.com/2020/11/17/vue-quick-shot-preventing-multiple-form-submissions",
		"content":"Good morning! Before I begin, a quick note. I was about to write this post yesterday when I discovered that it was actually very close to another one I wrote a few months back, which was my very first Vue Quick Shot - &quot;Vue Quick Shot - Disabling a Submit Button While Waiting for an Ajax Call&quot;.\nIn that post, I describe how to modify a form that performs a network call to an API such that you can't submit the request until the first request is done. Today's post is very similar. I was inspired by a post earlier this week on the topic, &quot;HTML Forms: How (and Why) to Prevent Double Form Submissions&quot;.\nIn today's post, the difference is rather slight. Instead of a form used to collect data before making an Ajax call, it's going to be a &quot;regular&quot; form that just posts to an action, leaving the current page completely. The solution is incredibly similar, but as it's my blog I figure I'm allowed to do that. ;)\nI also want to point out that if this is the only thing you're doing on a page, Vue's going to be overkill. Just use vanilla JS instead (and see my note at the end). But if you're already using Vue,perhaps for some complex client-side validation, then the following tip will help.\nI started off building a Pipedream workflow that merely outputs HTML after a four second wait. This You can test this yourself if you view it yourself: https://enz7ceue7sb4c7j.m.pipedream.net. I'm not doing any form validation or handling, I'm just waiting four seconds and responding.\nI then built a simple form:\n\nAnd then I submitting the form, clicking rapidly to send multiple requests. Pipedream records executions of workflows, and I could see multiple firing at once:\n\n\n\nSo let's fix it! Again, I want to stress that if this is the only thing you're doing on a page, Vue is going to be overkill. First, I modified the HTML a bit:\n\nI've got a submit handler on the form and my submit button has a disabled property tied to a variable. And then here's the simple Vue.js code:\n\nYeah, pretty trivial. But effective. Test it out here:\n\n  See the Pen \n  Block Multiple Submission by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "The Jamstack Book - Early Access Release",
		"date":"Thu Nov 12 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1605139200,
		"url":"https://www.raymondcamden.com/2020/11/12/the-jamstack-book-early-access-release",
		"content":"I've already mentioned this on Twitter and LinkedIn, but this week the Manning Early Access Program (MEAP) version of the new book I'm writing with Brian Rinaldi has gone live - The Jamstack Book.\n\n\n\nThis book is a much updated version of our earlier book on static site generators. It covers everything from why you would consider Jamstack to when you would not. It shows you multiple different examples of static site generators, from simple blogs to ecommerce sites, and goes into how you move to production and integrate APIs and serverless.\nRight now it's in &quot;early access&quot; which means you can buy it now and get updates as the book is updated. There are ten planned chapters with four released now, and I know we've got two more coming soon. Here's the table of contents, subject to change of course:\n\nChapter 1 - Why JamStack?\nChapter 2 - Building a Basic Jamstack Site\nChapter 3 - Building a Blog\nChapter 4 - Building a Documentation Site\nChapter 5 - Building an Ecommerce Site with Gatsby\nChapter 6 - Deployment\nChapter 7 - Adding Dynamic Elements\nChapter 8 - Working with Serverless\nChapter 9 - Adding a CMS\nChapter 10 - Migrating to the Jamstack\n\nYou can order the book right now at https://www.manning.com/books/the-jamstack-book\nBut wait! If you use the super-secret hidden code below, you get it for 50% off the current price.\nNot yet - keep going down...\n\n\n\nNope, more to go...\n\n\n\nAlmost there, honest!\n\n\n\nHere it is: mlcamden2\nEnjoy, and please let me know what you think!\nPhoto Credits: Photo by Jeanie de Klerk on Unsplash / Photo by Alvan Nee on Unsplash / Photo by Nadi Whatisdelirium on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding a Warning for Old Posts to Your Jamstack Site",
		"date":"Mon Nov 09 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1604880000,
		"url":"https://www.raymondcamden.com/2020/11/09/adding-a-warning-for-old-posts-to-your-jamstack-site",
		"content":"While doing some important research this past weekend (yes, it was research), I ran into something interesting on a Forbes article:\n\n\n\nLook at that warning on the bottom. This is an incredibly useful warning for readers to let them know the content may be out of date. And while the topic here was a video game, you can imagine this being even more useful on a technical blog. I thought this was such a good idea I went ahead and implemented it here:\n\n\n\nSo how did I do it? I'm using Eleventy for my Jamstack framework and Liquid for my template language, but this could be implemented anywhere.\nFirst, I needed to figure out what would be considered &quot;old&quot;. I decided to follow Forbes' lead here and use two years as a delineation. In theory then my code would be something along the lines of this pseudo-code:\nif post's date more than 365*2 days ago\n   add warning\nend if\n\nLiquid doesn't have a &quot;age in days&quot; function built in. This StackOverflow post demonstrates how to do it in &quot;in the template&quot;, but I thought a filter would be a bit simpler. Here's the template code I used:\n\nI begin by getting the &quot;age&quot; of the post. This is done by passing in the date value which is driven by the post. The result is how many days old the post is. I then create a variable for the number of days in two years. Yes, I could have simply used 730, but I like having it spelled out like that. Finally, I do a simple condition.\nHere's my filter:\n\nThis is pretty typical JavaScript date math. I didn't bother with a library like Moment.js nor do I check to see if a post is in the future. I know my content and I know I don't do that (i.e., write posts for future publication). If you implement this in your site though you may want to take that into consideration. Also, I use Math.floor and I could see people using round instead. Since I know I'm doing a check for two years, I wasn't too concerned.\nAnd that's it. Now, there is one more issue to consider. This condition is only executed when the site is built. That means if I don't run a site build every day, it's possible some articles will &quot;age out&quot; and not be properly marked. This is a common thing to consider in the Jamstack. Given that content is only as fresh as it's build time, you may need to consider automating your build process to a certain schedule. As with most things in development, &quot;it depends.&quot; For me, I know I try to post once or twice a week, so I know the number of items not properly marked will get taken care of fairly shortly.\nPhoto by AussieActive on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Your Netlify Build Status to Your Site",
		"date":"Wed Nov 04 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1604448000,
		"url":"https://www.raymondcamden.com/2020/11/04/adding-your-netlify-build-status-to-your-site",
		"content":"One of the cool little features Netlify has is the ability to add a &quot;build status&quot; graphic to your site. You can find this by logging on to your Netlify admin, going to your site settings, and scrolling to status badges:\n\n\n\nAnd here's a live example, right in my blog post:\n\nThis works well, but I was curious what it would take to have a more controlled way of displaying the build status. There's an API for returning build information: listSiteDeploys. You can use this API with either a site ID or site domain, which means I can get a list of my deploys at this location:\nhttps://api.netlify.com/api/v1/sites/www.raymondcamden.com/deploys\nThis returns an array of deploy results with the most recent being first. Here's an example of how that result looks (taken from their docs):\n\nSo to get the current deploy status, you need logic to get the deploys and simply return the first item in the array. I build a serverless function called deploy-status that does this:\n\nAs you can see, I hit the endpoint and pass in my security token (you can get this from your Netlify profile page, I've set it up as an environment variable). I get the latest deploy, and then I made some decisions about what to return. I felt like there was some sensitive information in the status result as well as stuff I just didn't think I needed. Therefore I create a new variable of the &quot;important&quot; stuff from the status and return that. You can see this right now on my site if you go here:\nhttps://www.raymondcamden.com/.netlify/functions/deploy-status\nHere's an example result:\n\nCool! So at this point you can do just about anything. For me, I decided to add information about my build status to my stats page. I added two new fields to the data I display. First is the build status. If the result of the call is ready, then it means published. If my build is published, then I render the published_at result. You can get the complete source of my stats page (along with the rest of the site) on my GitHub repo for the site, but here's the simple Vue.js code I'm using in my create method:\n\nAs I said, you can see this on my stats page if you're curious. I hope this little example is useful. Most of my Netlify API posts relate to analytics which are not officially supported, but this time everything I've shown is safe to use. Enjoy!\n",
		"tags":[
	        
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Selecting Random Posts in Eleventy",
		"date":"Mon Oct 26 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1603670400,
		"url":"https://www.raymondcamden.com/2020/10/26/selecting-random-posts-in-eleventy",
		"content":"This is something that's been kicking around in my head for a few weeks and I finally found some time to build a demo of what I was thinking about. When working with a blog in Eleventy, what if you could direct folks to other blog posts once they've finished reading one? In this post, I'll demonstrate three different ways to link to random(ish) other posts in an Eleventy blog.\nAlright, so let's look at the site before adding in the randomness. My Eleventy blog consists of a subdirectory of posts. This directory contains each blog post. Here's a sample one, alpha.md:\n\nI've got four posts total with each post using similar content. My home page renders the posts, newest to oldest:\n\nNot that it's relevant, but my dtFormat filter makes use of Intl support in Node.js. Here's the relevant function in my .eleventy.js file:\n\nThat could be a lot fancier, but I love how it nice and simple. Now, let's look at the post layout:\n\nThe top half of the post just renders the title, date, and content of the post. The bottom is where I include a link to another random post. Let's look at that filter:\n\nNice and simple. It could be even shorter, I don't need to have two lines, but I kept it like that as I knew I was going to build better versions. Here's an example of how this renders:\n\n\n\nCool! Except that it's entirely possible that the random post selected would be the same as the current one. Especially while the blog is just starting and only has a few entries. So I then created a new filter, getRandom2. This one expects the current page as an argument. Here's how I called it in the template:\n\nAnd here's the filter:\n\nFirst, I ensure that I have at least two or more items. If I have one, or zero (which doesn't make sense as I'm calling it from a blog post, but whatever), then I return nothing. To get my random post that isn't my current post, I select a random one and loop until it's url does not match the current page's url. A while loop may not be best here. I could have made a new array with the current item filtered out and then selected randomly from there. As always - multiple ways to skin the cat.\n\n\n\nI was going to wrap it up here, but then I thought of yet another version. While random selections are fine, it would be cool to perhaps select a random post in the same category. My sample post above didn't have categories, so for each post I added one:\n\nMy sample blog has four posts. For three of them I used a value of sports and the last one used music. Now I want logic that follows this logic:\n\nIf my category has 2 or more items, select one from that list and still bypass the current post.\nIf I'm a category of one, select a random from all the rest, again ignoring the current post.\n\nI added yet another link to my template (which you would't do on a real site, you would just use one):\n\nNote I'm now passing two arguments to a filter - both the current page as well as the current categories value. A quick note - I'm using the plural &quot;categories&quot;, but for this demo I'm assuming only one category per post. So here's the filter:\n\nThe filter begins by creating a new list of posts that filters out posts that don't match the category or have the same URL. If that list is blank, we create another list of posts that don't have the same URL. We do a final check to ensure we've got something left, and if so, return a random item.\nIf you find this interesting, you can check out the repository here: https://github.com/cfjedimaster/eleventy-demos/tree/master/randompost. I also created a very simple &quot;blog&quot; project that I used as a seed for this demo. I'm not sure if that's going to be useful but you can find that here: https://github.com/cfjedimaster/eleventy-demos/tree/master/basicblog\nFinally, if you want to see an example of the output - you can do so here: https://eleventyrandom.vercel.app/ Obviously it isn't random in the built version, but every time a new post is added and the site is rebuilt, the links would change completely. Let me know what you think by leaving me a comment below!\nPhoto by Brett Jordan on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Review: Learning Progressive Web Apps by John Wargo",
		"date":"Wed Oct 21 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1603238400,
		"url":"https://www.raymondcamden.com/2020/10/21/review-learning-progressive-web-apps-by-john-wargo",
		"content":" \n\nNormally a book review goes like this. I get the book. I read it. I write up a summary of my thoughts, grab the Amazon Affiliate link (I've made almost four dollars this year, folks!), and then post it. Typically all within a month or two. Again, that's the *normal* process. This year has been... somewhat far removed from normal which I'm going to use for my excuse when I try to explain why a book I read near the end of last year and was released in March is just now getting up on my blog in late October. \n\nToday I'd like to introduce you both to &quot;Learning Progressive Web Apps&quot; and it's author, John Wargo. I don't remember exactly when I first met John, but it was sometime during the heyday of PhoneGap and Cordova. I absolutely loved being a part of that project and met some really incredibly, really smart, people while building hybrid mobile apps.\nBut as was predicted in the very beginning with PhoneGap, the need for solutions like PhoneGap has mostly come to an end. (Mostly, I can spend a lot of time talking about iOS and it's shameful impact on the web.) With the lightspeed improvement in web standards (even on iOS) and the rise of &quot;PWA&quot;, the need for hybrid mobile apps has (again, mostly) passed.\nThe web community has been talking about PWAs for about five years now, which means it's mostly spread out from the nerds like us who get paid to explore and build fun demos and hit the developers who actually have to get things done. Right now is a really good time to start looking at PWAs and learning about the technology. While things are still in flux (they always are), PWAs have great support and browsers have really developer tool support as well.\nWargo's &quot;Learning Progressive Web Apps&quot; does an incredibly good job of introducing you to PWAs and going deep into the technologies, everything from service workers and offline support to background sync, a topic I don't see a lot of people talking about yet.\nHe also spends time talking about related but critical tools like Lighthouse. One thing I always worried about when talking about PWAs is that it's important to remember that nearly everything that's found in a typical PWA would be useful in a &quot;non-app&quot; web site. Even if you're just using progressive enhancement to improve a small part of your site, you can find a lot of what's covered in this book very useful as well.\nThe book clocks in at 272 pages. Here's the table of contents:\n\nIntroducing Progressive Web Apps\nWeb App Manifest Files\nService Workers\nResource Caching\nGoing the Rest of the Way Offline with Background Sync\nPush Notifications\nPassing Data between Service Workers and Web Applications\nAssessment, Automation, and Deployment (an especially awesome chapter covering Lighthouse and PWABuilder)\nAutomating Service Workers with Google Workbox (absolutely recommended, Workbox makes things incredibly easier)\n\nI definitely recommend picking it up (and ordering via the link above will net me a few more cents in my Amazon fund) and if you do, please drop me a line below in the comments to let me know what you think.\nPhoto by timJ on Unsplash\n",
		"tags":[
	        
            "pwa"
            
		],
		"categories":[
            
                "development",
            
                "books"
            
		]

	},

	{
		"title": "Vue Quick Shot - Warn Before Leaving a Form",
		"date":"Thu Oct 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1602720000,
		"url":"https://www.raymondcamden.com/2020/10/15/vue-quick-shot-warn-before-leaving-a-form",
		"content":"Welcome to another Vue Quick Shot - my series of posts of quick (kinda) solutions for common web development issues with Vue.js. Today's Quick Shot is an interesting one. First off, I'm not entirely sure what I'm sharing makes sense. I always encourage folks to leave comments with their suggestions but even more so for today's post - if what I'm sharing is problematic, I really want to know.\nThe tip today is how to warn a user before they leave a form that hasn't been submitted. So imagine a form where a user has entered some data, but then they get distracted.\n\n\nReal picture of Raymond being distracted.\n\nInstead of submitting the form, they instead hit some other link on your site, leaving the form without realizing that they forgot to finish what they had started. So how can we handle this?\nModern browsers support an onbeforeunload event, which as you can imagine is fired before the current page is unloaded, either via navigation or reload. According to MDN, your handler should look as follows:\n\nAs you can see, you prevent the default behavior as well as set a returnValue property for just Chrome. In theory, returnValue lets you specify a message to show to the user, but since this can be abused badly, many browsers will ignore the value set here and just use a default. But the existence of the value is enough to trigger what you want - prompting before the user leaves. Just to be clear, you can't stop the user (which is a good thing), but you get to throw up a prompt to at least let them know.\nWhile this seems simple enough (kinda), now we have to figure out another problem. When do we use this? We need a way to determine when a form is &quot;dirty&quot; and has edited values. Here's what I figured out.\nFirst, I began with a form:\n\nThe form consists of two fields, a submit button, and then I include a link as a way for a user to skip out of submitting the form. Now let's look at the code:\n\nIn my created function, I use $watch instead of watchers because I want to define my own function for recognizing a change. In my case, I create a string version of all the form fields and append them together. If you change anything in any field, it will fire off the watcher value.\nInside the watcher handler, I then have to handle two cases:\n\nOur form did not have content, and now it does.\nOur form had content, and now it doesn't.\n\nThe first part of the if block handlers going from no content to some content. I create the handler in the this scope so I can use it later. Later is the else block where I remove the handler.\nNow technically, a form could default to having values and you may edit it to be blank. In that case you could determine an initial value on load and compare the new values against that.\nThere's one last aspect we have to handle. When you submit the form, the beforeunload event is going to fire. To handle that, can we listen for the submit button click event. First, we'll add a handler to the HTML:\n\nAnd then our code:\n\nBasically - if we setup the handler, remove it. Now, in theory, if you edit the form and try to leave via the link, you'll get a prompt:\n\n\n\nThis is certainly not a perfect solutio, but it may help prevent a user from accidentally losing changes. If you've done this better (most likely!), please share your solution below! Here's a CodePen with the entire solution.\n\n  See the Pen \n  Warn on Page Leave by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Another Netlify Analytics Hack - Stats Per URL",
		"date":"Thu Oct 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1602115200,
		"url":"https://www.raymondcamden.com/2020/10/08/another-netlify-analytics-hack-stats-per-url",
		"content":"I've blogged a few times on the Netlify Analytics API (&quot;Building a Netlify Stats Viewer in Vue.js&quot;) and &quot;Integrating Netlify Analytics and Eleventy&quot;) and today I've got some more code to share. Now - every time I do this - I remind folks that there is not a published doc for the Netlify Analytics API. That is still the case. Today I'll also add that what I'm sharing is very rough. It worked for me and it's up on GitHub (repo), but just keep in mind that I wrote this as a tool for myself. If it can help you too, great!\nFor a while now I've wished I had a way to get analytics about a particular blog post. Netlify's analytics for page views are all date-based. I can easily tell what pages were viewed over a time period. But what I really wanted was the ability to see page views for a post over all time.\nI decided to take a crack at it with some Node.js scripts. Why Note and not a client-side application? In order to get my stats, I'd need access to all of my data. Technically not everything for a recent blog post, but if I wanted to search more generically, like /2020 to see page views for my content this year, I'd need analytics from the beginning of the year. Therefore I took this approach:\n\nFirst, I wrote a script that gets data for one specific data. It stores this in a folder.\nI wrote a script that takes the saved data from a cache and creates one array of URL and view counts.\nI built a Vue app that sucks in the resulting JSON and let's me do a quick filter.\n\nLet's take a look at these - and again - remember this is rough code. Let's call it - organic, farm fed, all-natural code. First up is get.js, which, as you can guess, sucks in data.\n\nFrom the top, I begin by using two env variables. The token is a personal access token as I described in my last post. The site ID represents my blog, where you are right now. This part:\n\nComes from the fact that the analytics API returns data about how long it's been generating logs:\n\nI converted ingestion_start to a date to get what you see in earliest. You could pick any date really. The getForDay function handles actually hitting the API and that's actually the simplest part of this whole script.\nIf you scroll down into the main block, you'll first see an odd loop, from 0 to 1. Once I got my code working, I was sucking down 25 days at a time. I felt like that was safe and not abusive to Netlify's API. However, I never wrote code to &quot;stop&quot; at the current day. So as I got close to October 6th (the last time I played with this I believe), I simply reduced the counter by hand. Hack.\nNext, getLastCacheDate looks at my cache folder to figure out when I last ran the code. My cache files are named YYYY-MM-DD to make it easier to work with dates, but I still screwed this up a few times. I'm actually running this in the loop which is a bit wasteful, but I'm ok with that.\nFinally, I take the resulting data and just store it. The results look like this:\n\nMy next script, read.js, handles combining these files:\n\nI read them all, create one big array, sort it such that the highest viewed pages are on top, and then output it. Here's another snippet:\n\nTo be honest, I was really surprised to see NativeScript as my number one post. It's also incredibly depressing to see how many pages get just around a hundred views of so. Of course, my blog was up for a roughly 16 years before I enabled Netlify Analytics so I'm going to worry most about my most recent content.\nI ran read.js and saved the output to output.json. I then built this horribly simple Vue app:\n\nWhich basically has a form field on top and a table of results:\n\n\n\n64K page views on my vue content is pretty nice. I can also search by year since my pages follow a date based path system:\n\n\n\nNot bad for a year that is totally insane and hellish, right?\nAnd that's it. Again, you can grab the code at the repo if you wish and if folks have some ideas for improvements, I'm all ears. For now my plan is to update my cache every now and then, take a look, and carry on writing blog posts a few people read. :)\nPhoto by Joshua Hoehne on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Tracking Posts by Week in Eleventy",
		"date":"Wed Sep 30 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1601424000,
		"url":"https://www.raymondcamden.com/2020/09/30/tracking-posts-by-week-in-eleventy",
		"content":"I've been running a &quot;successful&quot; blog for about seventeen years now. I say &quot;successful&quot; as the measure of success has certainly changed over the years. My posts used to get near five hundred page views each, and that's even with me blogging quite a bit more than I do now. Of course, I started blogging before Twitter existed and a lot of my posts were merely short informational notices for my readers. Nowadays the traffic isn't nearly as good and comments are way down. Some days I even want to quit. But I enjoy building things and writing about them so I'm not going to stop yet.\nOne of the ways I try to make my blog successful is by having a consistent schedule. I used to blog every week day. That's not really doable these days so instead I try to do at least one blog post a week. I wanted an easy way to see how well I was doing with that schedule so I decided to hack up a quick addition to my stats page.\n\n\n\nThis new information looks at the last eight weeks and reports on how many total posts I wrote. As of the day I'm writing this, it looks like I've got two weeks where I &quot;failed&quot; but honestly I'm fine with that. So, how did I build it?\nLet me start off by saying that I built my stats page based on what I thought was important. With that being said, it's an incredibly messy bit of EJS-driven JSON (you can see the raw code and the output) and an HTML page that uses Vue.js to render the stats.\nTo build this new stat, I used the following logic. First, create an array of 8 &quot;weeks&quot; which are objects containing the Sunday and Saturday of the week:\n\nAs you can see, I store the two date values along with a hits variable set to 0.\nThe next part is where it gets messy. My EJS stats file creates an array of dates for every single blog post I've created. For my blog this is a rather large array but when I tested the size in the resulting JSON file, it seemed acceptable. (And again, my stats page is mainly for me!) So given that I have an array of dates, this is what I did.\n\nEssentially - loop from the end of the date array and continue down until we start seeing dates before the earliest of my week ranges. For each date, I see if it falls inside one of the week ranges and if so, I increment hits.\nThe last part is simply outputting the array in my DOM and the only thing I do special there is format it with Intl.DateTimeFormat.\nAs always, my entire blog is up on GitHub (https://github.com/cfjedimaster/raymondcamden2020) and folks are free to take from it what they need!\nPhoto by Andrew Neel on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Vue Quick Shot - Uploading a File",
		"date":"Sun Sep 20 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1600560000,
		"url":"https://www.raymondcamden.com/2020/09/20/vue-quick-shot-uploading-a-file",
		"content":"Welcome to the last Vue Quick Shot, and when I say last, I mean the last one I've got in my queue of blog ideas. Today's tip demonstrates how a Vue application can upload a file via a form post operation. I started off with an incredibly simple form - one text field and one file field.\n\nHow can we convert this to let Vue take over and do the post for us?\nFirst, I did some modifications to the HTML:\n\nFirst, I specified a submit action as well as ensure it prevented the default behavior. I then changed the text field to use v-model. I did not do the same for the file type because file types are a bit special when it comes to Vue. Instead of using v-model, I used the ref attribute so I could read the data manually later. (Basically, you can't use v-model because Vue, or JavaScript in general, can't write to a file field type for security reasons.) The last change was to add a disabled attribute so I can prevent multiple submissions of the form while data is uploading.\nNow let's look at the Vue side. Here's the entire script:\n\nFor data, I've got one data value for the text field, a boolean to flag when uploading, and a result value. The upload method makes use of Fetch API and FormData. Fetch makes it super easy to different types of network calls and FormData makes it easy to build a form post request. The only weird thing possibly is how I address the file field: this.$refs.fileToUpload.files[0]. The this.$refs.fileToUplaod part simply connects to the file field in the DOM. The files[0] aspect handles references selected files in the field. It's an array because you can add multiple to a file field and then the user can select multiple files.\nI post to a local Node server I had running via ngrok, a super-useful tool that lets you expose servers running on your local development machine. Because this is a temporary tunnel, my code will not actually work for you, so please keep that in mind when playing with my CodePen below.\nMy Node server simply echoes data back that I render as is in the template. Here's an example of how that looks:\n\nIn a real application you wouldn't do that of course. In the end though, Fetch and FormData do all the heavy work for us! Here's the complete application below, and please remember that you won't be able to actually submit.\n\n  See the Pen \n  Vue File Upload Test by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Hooking Up FaunaDB to Eleventy",
		"date":"Tue Sep 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1600128000,
		"url":"https://www.raymondcamden.com/2020/09/15/hooking-up-faunadb-to-eleventy",
		"content":"As a very new user of FaunaDB, I'm pretty impressed by how easy it was to set up and start using, both from the server as well as the browser. I decided to take a quick look at how FaunaDB could be integrated with my favorite static site generator, Eleventy. Eleventy (aka 11ty) is one of the many options developers have for working with the Jamstack (JavaScript, APIs, and Markup) and is known both for it's speed as well as it's flexibility. I've been using Eleventy pretty exclusively now and it's easily become my preferred way to build static sites. While I won't be giving an introduction to Eleventy here, be sure to read the docs for more information and if you would like an introduction, check out this great one by Gift Egwuenu, &quot;Getting Started with Eleventy&quot;.\nSo given that a Jamstack site is static by it's very nature, how can we incorporate FaunaDB data into it? For my experiment, I began by creating a database of products.\n\n\n\nEach product had a pretty simple structure:\n\nName\nPrice\nDescription\nshippingTimeInDays\n\nI made a few random products using the dashboard and employed all of my creativity for the names and descriptions.\n\n\n\nAlright - so I have data. Not a lot of it, but enough for a demo. How to get it into Eleventy? One of the more interesting features of Eleventy are global data files. These are files that provide data to the rest of the site. So for example, you could build a hard coded like of products and name it products.json:\n\nOnce saved in a special folder (_data), Eleventy templates can make use of it. Another strength of Eleventy is the large amount of different template engines it supports. My personal favorite is Liquid so I'll be using that, but note that Eleventy also supports Handlebars, Jade, and more. Here's a simple Liquid template that makes use of this data:\n\nWhen Eleventy runs, it reads in the hard coded JSON file, makes it available as a products variable (this is because of the filename, if you had used something like prods.json, the variable would be prods), and then Liquid can loop over the product and output values.\nStatic data can be useful, but obviously we want to be able to use our FaunaDB data. While plain JSON files work as global data in Eleventy, you can also use JavaScript files. These files will be executed when Eleventy creates a static build of your site and can perform any logic necessary, including integrating with FaunaDB! Here's an example where I fetch my data from FaunaDB:\n\nI'm using the faunadb npm library and ask for my products. For each object I really only want the data so after fetching the information from FaunaDB I return an array of product values. The console.log message will get displayed locally while I build and helps me see that things are working.\nNext, I built a home page for my site that loops over the products. Here's that template:\n\nThe portion you see on top is front matter, a common way in Jamstack programs to set metadata for web pages. In this case I'm specifying a layout file for the page as well as a title. Layout files simply take the content of the current page and insert them inside some markup. (See the Eleventy layout docs for more information.)\nThe code loops over each product and creates a link to a product detail page I'll share in a moment. This portion, {{product.name | slug}}, demonstrates a filter. It takes input, like &quot;Raymond Camden&quot;, and creates a filename safe version of it: &quot;raymond-camden&quot;. The end result is a set of links and names based on my data in FaunaDB:\n\n\n\nNotice that the products are sorted based on how the FaunaDB code returned it. I could have sorted it there, or I could sort it in Eleventy. In my case I'm happy with the default sort. Now lets look at the product pages.\nEleventy supports the ability to paginate data. It will take a large list of data and let you create pages of them dynamically. It also supports taking a list of data and creating one page each. This is perfect for our needs here. This is how I defined a product template.\n\nThe frontmatter on top is a bit more complex here, but hopefully understandable. I've defined a pagination of 1, basically one page product. I've specified a permalink for each product (where to save the file) that matches how I linked to them from the index page. The last part, eleventyComputed, is a workaround for specifying custom values in front matter based on pagination. Basically it just ensures the title value is based on the current product being generated.\nAfter front matter I simply display the product. After saving this, Eleventy generates one page per product:\n\n\n\nAnd if we return to the index page, we can click to load one of the products:\n\n\n\nAnd voila, we're done! (You can demo this version here: https://faunadbv1.vercel.app/) But while this may make you incredibly happy, you probably realize an important issue. Eleventy will only load the products from FaunaDB when the site is built. What if your products change?\nLuckily w",
		"tags":[
	        
            "eleventy",
            
            "faunadb"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Vue Quick Shot - Form Field Character Counters",
		"date":"Mon Sep 14 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1600041600,
		"url":"https://www.raymondcamden.com/2020/09/14/vue-quick-shot-form-field-character-counters",
		"content":"A common user interface feature you'll see on sites is a counter by form fields to let you know how much you've typed. Typically this is when a certain minimum or maximum number of characters are required. Instead of simply displaying an error (&quot;You haven't typed enough, dangit!&quot;), this feature will give you a &quot;live&quot; update as you type of how many characters have been typed so far. Here's a quick example of how to do this in Vue.js.\nFirst, let's consider a use case where we require a certain number of characters. You can start with a simple HTML field:\n\nEven though I'm not going to do a traditional form submission, I wanted to use minlength anyway as it's a well-supported feature of HTML forms. I've bound the field to a Vue data value named description. Now let's show the field with the rest of the layout.\n\nI've added a bit of descriptive text to clearly tell the user what they need to do but I've also added a character counter after the field itself. Now let's look at the JavaScript:\n\nAs you can see description is just simple data, but currentLength is a computed property based on the field itself.\nAnd that's it. Simple. But let's make it a bit more fancy.\n\n\n\nHere I've modified the character count to add a span with some classes applied:\n\nNotice that the bad class is only applied when isBad is true. Here's the CSS I used:\n\nAnd my new computed property:\n\nNow when the user has less than ten characters, the bad class is applied and clearly signifies that the data isn't ready yet. You can play with the completed version below:\n\n  See the Pen \n  Char Count (minimum) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nNow let's flip it and write count that flags a maximum number of characters. First, the HTML:\n\nNote the help text and the use of maxlength. Unlike minlength which won't have any impact unless the user submits the form, this time the user will immediately be stopped typing when they hit the max. So while most of the code is the same, I modified isBad like so:\n\nNow it returns true when the length is greater than 90. Instead of being a flag of &quot;you have incorrect data&quot;, it's more of a warning that you're about to hit your max. Here's a demo of that version:\n\n  See the Pen \n  Char Count (maximum) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Traffic-Based Workflow in Pipedream",
		"date":"Sun Sep 06 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1599350400,
		"url":"https://www.raymondcamden.com/2020/09/06/building-a-traffic-based-workflow-in-pipedream",
		"content":"A few months ago I wrote about working with event sources in Pipedream. The folks at Pipedream have continued to work on the feature and have been making it easier to build them with a new command line tool. At the time I'm writing this, unfortunately, Windows support is not ready yet. While typically a show stopper for me, I was given a temporary build of the command line tool for Windows to help test. (You can follow this issue for more information.) Normally I don't like to blog about stuff that isn't generally available to all, but as it will be available sometime soon, I decided to go ahead anyway. And I built something really cool I want to share so that's another reason to talk about this now!\nTo begin, take a look at the Quickstart guide for using the CLI. You can use the CLI to deploy and update code for event sources which lets you use your preferred editor for development. At a high level, an event source kinda looks like this:\n\nEssentially metadata and your logic in one file. It gets a bit more complex than that depending on what you're doing, but after writing a few samples it began to make sense to me. The CLI process is a bit clunky now in terms of what you use to deploy versus update versus other items and I've passed this feedback on to the team, but it's still Alpha so you can expect this to change.\nI thought I'd take a stab at building something real. My local city has a website, http://lafayette911.org/, that publishes &quot;live&quot; traffic incidents.\n\n\n\nIt's an old site, one I've built demos on in the past past, but it works. Using devtools, I was able to see that it's making a POST request to https://apps.lafayettela.gov/L911/Service2.svc/getTrafficIncidents. The result is:\n\nBasically an object of one key, d, that is HTML which is just then dropped on the page. I knew that I could use Cheerio to parse this HTML so I began by working on a test script to see how well it could be done. Cheerio is, for all intents and purposes, jQuery for Node.js. It's really powerful, but I had a bit of trouble as I don't really use jQuery anymore! Here is that initial script:\n\nTo let it run quicker and be more consistent, I used one hard coded result of the API and just got to work parsing it. I knew the shape of the table an what each column represented, so from this I was able to get an array of objects representing the traffic incidents being reported by the API.\nOk, so with that working, I then began working on the Pipedream version. It needed to be rewritten in the right &quot;form&quot; for Pipedream and obviously switch to using a network call instead of hard coded data. Here's that event stream:\n\nWhile most of this is the same, there's some crucial differences. First, note the use of the timer on top. That sets up how often the event source will run and should be set whatever makes sense for your data. While the website refreshes every fifteen seconds, I didn't think it was necessary to run this code that quickly. Ten minutes seemed sensible so I went with that.\nThe next important change is how the code reports data. So in my test script, I just output an array of events. For Pipedream, you need to use this.$emit instead. Now for the truly cool part. How do we know a new traffic incident versus an existing one? Pipedream has built in for support with this using 2 settings.\nFirst, I added dedupe: &quot;unique&quot;,. This tells Pipedream to filter out any output from the code and ensure it's unique. How does it determine uniqueness? Via the id value. You can see me emit that towards the end of the file. I generate an id by using the traffic incident address and time. It's absolutely possible to have multiple accidents at the same location, but probably very rarely will they be at the same time. I could make this a better perhaps by looking at the what and who values but that felt like overkill.\nI deployed this and then started testing. On the Pipedream website, I can see my events as well as a graph over time:\n\n\n\nWhat may not be terribly obvious is that you can see a &quot;spike&quot; in the incidents. I'm writing this at around 10 in the morning so it makes sense that more reports would come in with the morning traffic.\nWhat's truly cool though - and I feel like a bit of a broken record when it comes to Pipedream - is that all of this complexity (and honestly it wasn't too complex) is completely hidden from anyone who wants to use it. So for example, want to get an email everytime an accident happens?\nMake a workflow - select this event source - and then add the email step:\n\n\n\nAs you can see, this one's already disabled as it got too noisy too quickly, but with zero code, I've got a working notifier about accidents in my city.\nDon't want email? I can send an SMS instead using a variety of options:\n\n\n\nIt really doesn't matter. If you just want to do X when there's a new accident, the support is there. What you actually do with the data is up to you. And this is why I love Pipedream. By making i",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Vue Quick Shot - Fullscreen API",
		"date":"Fri Sep 04 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1599177600,
		"url":"https://www.raymondcamden.com/2020/09/04/vue-quick-shot-fullscreen-api",
		"content":"After yesterday's quick shot, today's should be simpler - using the Fullscreen API. This is one of the simplest and most useful API's out there. If a browser supports it (currently at near 100%) than all you need to enable fullscreen on your web page is the requestFullscreen DOM method.\nFor example:\n\nThe API supports more options (events and exiting fullscreen mode via code), but let's look at a simple example with Vue.js.\nLet's begin with our HTML. I'm going to include an image and a button to enable fullscreen access. The button will only show up if the Fullscreen API is enabled. Note the use of ref on the image so I can grab it easier via Vue later.\n\nNow let's look at the JavaScript:\n\nSo my data just includes the boolean for whether or not the button will show up. My created method checks if the feature exists and if so will set the value to true.\nFinally, the button's click event uses the API to open the image in fullscreen mode. And that's it! Here's a full demo in CodePen, and yes the button works in the embed.\n\n  See the Pen \n  Vue + Fullscreen by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs always, let me know if this helps you!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Using Page Visibility",
		"date":"Thu Sep 03 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1599091200,
		"url":"https://www.raymondcamden.com/2020/09/03/vue-quick-shot-using-page-visibility",
		"content":"Back in March I did a week of quick Vue tips (you can see them all here). I really enjoyed that set of blog posts as it let me show some quick and simple &quot;X with Vue.js&quot; examples. To be honest, I haven't really thought about them for a while, but earlier this week a reader posted a comment on one of them and for some reason, that got the creative juices flowing again. With that in mind - I'm happy to share another Vue Quick Shot - using the Page Visibility API.\nThe Page Visibility API is a way to determine when a page becomes hidden based on user interaction. You can then use as a way to tell your code to stop any logic that may be particularly intensive or battery draining. Or heck, even if it's not intensive, if there's no need for it run while the user isn't looking, you should probably pause it anyway. Browser support is really good with near 100% coverage, and of course, this is yet another thing that you can add to your site without impacting any browser that doesn't support it. (And yes, even Safari supports it - thank you New IE6!)\nWhile the MDN Docs on the API go really deep, at a simple level you can start using it by listening for the visibilitychange event:\n\nInside your event, you can check for document.hidden, which will be true if - wait for it - the content is hidden.\nBefore we continue - a very important note. This API will notice when you minimize your browser or change tabs. It will not notice when you take another application and &quot;cover&quot; the web page. It would be cool if it did, but there's probably good reasons for it not supporting that. Ok, so how can we use it in Vue? First, you can add a listener when the Vue application starts:\n\nIn this case, I'm running a method named visibilityChange:\n\nYou can see a somewhat boring example of this here:\n\n  See the Pen \n  Vue Visibility Change 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nTo test, switch to another tab and then come back, and you'll see a text message in the output above. Two actually - one when you hid the tab and one when you came back. (Please come back.) So how about a slightly more realistic example?\nI built a Vue application that makes use of an audio tag and an MP3 file. I added a button to control it myself:\n\nTo control playback, I made it such that when you click play, it starts the audio. Notice it uses the loop attribute, that will make it last forever. To pause, you click the button again. Here's that logic:\n\nIf I wanted to, I could switch the text on the button to make it more obvious, but as it stands it's workable. If you click play, the MP3 will start and keep playing. (And I apologize, it's kind of an annoying sound.) Now let's add logic to notice when the page is hidden. First, a listener in created:\n\nThen the method:\n\nNotice I'm only pausing, not playing. I could start it up again, I'd need another variable to remember that the audio was playing, but I kinda figure the user can decide if they want the music to return hen they tab back in. (But if folks want to see that, let me know!) Here's a complete CodePen:\n\n  See the Pen \n  Vue Visibility Change 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nI hope you found this useful. If you did, or have any questions, leave me a comment below!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Experiment with Vue Components",
		"date":"Fri Aug 28 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1598572800,
		"url":"https://www.raymondcamden.com/2020/08/28/an-experiment-with-vue-components",
		"content":"I really enjoy components in Vue.js as they feel very nicely done in terms of functionality and usage. Like most of Vue, you can use them simply to abstract away some UI logic or get very complex. Part of why I love Vue is that it feels like it excels at working at multiple levels - from the &quot;I'm just playing around&quot; to &quot;I'm building the Next Big Thing.&quot; Recently I started thinking about a particular component use case. Imagine the following:\n\nWhat I've described above is an imaginary slide show. It's built with a parent &lt;slides&gt; tag and each individual &lt;slide&gt; component represents one particular slide. When displayed in the browser, it should render some basic slide show controls and render one slide at a time. Seems simple enough, right?\nTurns out it's a bit difficult. Let me break down how I solved this (and what I learned). When I'm done, I'll show you how some friends on Twitter did it much better than me so be sure to read the entire post.\nAlright, so let's start off by focusing on the core feature which is to display one slide at a time. We can start off by simply hiding slides. So &lt;slide&gt; could look a bit like so:\n\nThat immediately hides all the slides. But how will the slide know when it's visible? The parent, &lt;slides&gt;, can keep track of a currentSlide value which will change whenever a person advances the slide show. But how would the child tag know about the change? Normally a parent can pass a value to a child like so:\n\nBut I wanted to keep my slide show simple. Notice how easy it was to type each slide? I wanted to keep that simplicity if I could. Turns out, child components can reach out to parents by using this.$parent. You can read more about that here and be sure to note the warning that basically boils down to &quot;This is usually a bad idea.&quot; In my case I was ok with it. It does mean my slide show will break if some other component wraps &lt;slide&gt;, but I'm ok with that. Here's the updated code:\n\nNotice the updated currentSlide computed property. We're still not done yet. Instead of checking for === 1, what I really need is: &quot;If the current active slide number equal to my number.&quot; What number? The number of the slide based on where it comes in play. This was a tough nut to crack. One &quot;quick fix&quot; would have been to simply hard code it:\n\nBut again, I was trying to keep things as simple as possible for the person building the slides. Plus, as every experience presenter knows, you often end up moving slides around and those numbers would quickly get hard to maintain. So how would slide N know that it is slide N?\nTurns out that along with with this.$parent, there's a this.$children as well. So in my &lt;slides&gt; tag, I added this:\n\nBasically, iterate over the children, ensure they are a Slide, and then manually set data upon them to assign them a yourIndex value. Basically slide N will be told it is in position N. I also keep track of the total number of slides so I can use than in navigation later.\nBack to Slide, I now have this:\n\nI'm using a 1-based index for the currentSlide so I need to add 1 to yourIndex, but hopefully it makes sense. And really that was it. Here's the final &lt;Slides&gt; component with navigation tools built in:\n\nIt could be a lot fancier of course. You can demo this yourself below:\n<iframe src=\"https://codesandbox.io/embed/component-test-gi61v?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"component test\"\n     allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n     sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n   >\nAll in all... I kinda dig this. It does feel a bit brittle with the $parent and $children connections, but I dig it. Of course, I've got smart friends who did it better. First up is Alex Riviere who made use of provide and inject. This is an advanced component feature that aims to make it easier to work with parent/child tags. Specifically, it lets a top level parent &quot;provide&quot; data that any child, no matter how deep it is, can &quot;receive&quot; by using &quot;inject&quot;. Sorry for all the quotes - this is still kind of new to me. ;)\nIn this version, the parent tag provides access to values related to what the current slide is as well as a method that lets the child slide &quot;register&quot; itself and gets its position. Here's his &lt;slides&gt; component:\n\nAnd then his updated &lt;slide&gt;:\n\nThis feels a bit &quot;safer&quot; compared to my version and definitely would better handle &lt;slide&gt; tag that are grandchildren, not children. Here's his version:\n<iframe src=\"https://codesandbox.io/embed/component-test-forked-p1vef?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-rad",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Supporting Multiple Authors in an Eleventy Blog",
		"date":"Mon Aug 24 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1598227200,
		"url":"https://www.raymondcamden.com/2020/08/24/supporting-multiple-authors-in-an-eleventy-blog",
		"content":"Here's a quick tip on how you could build an Eleventy blog supporting multiple authors. By that I mean have a blog where every post\nis assigned an author with links to a unique profile page. If you just want to see the final result, the repository is here (https://github.com/cfjedimaster/eleventy-demos/tree/master/multiauthor) and the live demo is here - https://admiring-fermi-e83d2d.netlify.app/.\nAlright, so let's start off with a simple Eleventy blog. It's got a home page that lists items from a post collection:\n\nAnd is driven by four blog posts that look basically like this:\n\nI'll skip sharing the layout as it's a basic Bootstrap template. But this is enough to get started. I can hit the home page and click to visit any of the four posts. Now let's start talking about author support.\nI began by creating a new file in my _data folder named authors.json. For my demo, I decided each author would have:\n\nA name (self-explanatory)\nA bio (text about the author)\nA link to their website\nA link to their photo\nTheir Twitter username\n\nCertainly more could be done, like including a GitHub profile or LinkedIn. I also realized I was going to need a way to link posts to individual authors. While most likely the name values would be unique, I wanted to make it simpler. So for each author I defined a key value that was simply a unique code based on their name. Here's the data I'm using for my demo.\n\nCool, so this gives us access to author data in our Eleventy pages. I want to include the author on blog posts as well as create unique author pages. Let's first get the blog posts updated. To keep it simple, I built a new layout file for my blog posts and switched all of them to use this in their front matter:\nlayout: post\n\nThe post layout looks like so:\n\nNotice that I don't assume every post has an author, and it's feasible that on a site with many authors some posts may be more 'housekeeping' in nature and not really attributed to a specific person. If I do have an author value, I need to get information about the author. To do so, I created a filter, getAuthor, that accepts two parameters - all of the authors and the key. Why do I have to pass authors in? Because Eleventy custom filters don't have access either global data or collection values. Here's how the filter is defined in .eleventy.js:\n\nAs this returns the entire author object, I can use it in the text of my post layout. I use the key as a URL safe destination for the profile page and then display their name. This creates the link you see here:\n\n\n\nFor the author pages, I used the incredibly awesome Eleventy feature that lets you create pages from data. I defined my author page like so:\n\nFrom the top, you'll see the pagination aspect handles making one page per author. Next, I specify the permalink to match what I was using in the post layout. The next part may be a weird looking, but in order to get Eleventy to set page data that's dynamic based on the pagination, I have to use eleventyComputed. To be honest that feature still kinda confuses me but I only ever run into needing it in cases like this.\nAfter the front matter I simply display the author (using all of my fine design skills) and then list out their posts. To get them, I use another filter, getPostsByAuthor. You can see it here:\n\nHere's a sample author page:\n\n\n\nAnd that's it. You could certainly do more. Don't forget that front matter supports setting multiple values for a particular key, so you could even support posts written by more than one author. You could also build out individual RSS feeds for unique authors if you choose. Again, the repository for this demo is at https://github.com/cfjedimaster/eleventy-demos/tree/master/multiauthor and the live version may be visited at https://admiring-fermi-e83d2d.netlify.app/. Let me know if you have any questions, or suggestions, about this approach!\nPhoto by Hudson Hintze on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Designing Random Encounters for my Vue RPG",
		"date":"Wed Aug 19 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1597795200,
		"url":"https://www.raymondcamden.com/2020/08/19/designing-random-encounters-for-my-vue-rpg",
		"content":"So I'm still piddling away at my RPG in Vue.js. To be clear, I'm never going to finish it. But you can read about it here (Testing Vue.js Application Files That Aren't Components) and here (Making Monsters with JavaScript). Over the past few months I've been slowly reading an incredible book for RPG lovers, The CRPG Book. It's a review of over 400 games over the past 40 years of computer role-playing.\nWhile reading the book, I'm discovering some cool features that older games had and that I missed while growing up. (Or possibly just don't remember.) A few games have mentioned using encounters with simple &quot;Choose Your Own Adventure&quot; logic. Basically, you are presented with something and given a choice of options. No combat, just simple choices.\nThe more I thought about this the more I thought it could be an interesting part of my game. If you imagine that there's a random chance of combat as you walk around (part of the core gameplay I want to have), then there could be a smaller chance of a simple encounter. I'd imagine these happening maybe once or twice per gameplay so somewhat rare, but they would be a break from the typical combat encounter.\nI designed my system with the following features:\n\nEncounters are in an array, randomly chosen.\nEach encounter has hard coded text and is static.\nEach enouncter has hard coded options.\nHowever, each option has random results.\nResults can impact player stats, for example, draining health or giving your gold.\nFinally, each encounter has an optional &quot;prereq&quot;. This is a 'safety' check to make things a bit easier for results. So if a result drains 10 gold, I don't want to have to worry about negative balances. Ditto for death. While I'm fine with an encounter harming you, I didn't want it to kill you. I can say this point is one I'm reconsidering and may roll back. For gold, I could simply let it take all your gold and leave you at 0, and for harmful encounters, it may be kinda fun if some could actually kill you.\n\nMy data structure than looks like so:\n\nprereq: If passed, a string that is evaluated against player data, like \"hp>10\". If false, this encounter can't happen.\ntext: The text of the encounter.\noptions: An array of options where:\n\ntext: The text of the option\nresults: An array of results based on this option where one is randomly selected. Each result has:\n\ntext: The text describing the result.\neffect: An effect, if any, on the player, also a string that is evaluated, like gold+=10.\n\n\n\n\nHere's an example:\n\nThe JavaScript utility has two main methods. The first returns a random encounter that's filtered by prereqs. A player object is passed in (I'm not using TypeScript so what I really mean is a &quot;simple object representation&quot; of the player). The next method takes a player object, an encounter, and a selected option. It figures out the random result and applies the effect. Here's the entire utility.\n\nThe two methods I described above are defined as select and resolve. Notice that I wrote a function, fixEvalString, that can be used by my prereqs and effects to modify the player. This feels like bad code. I mean, eval is bad in general. Given that I know the &quot;shape&quot; of my player data I could switch to another way of doing this, but I'll worry about that when I finish the game, which is, you know, never.\nI did build a utility to help test this, and here's what it looks like:\n\nAs you can see, I've got a few select calls and a few resolve ones. The output looks like so:\nbasic player\n{\n  prereq: 'hp&gt;0',\n  text: 'You hear a growl from behind you.',\n  options: [\n    { text: 'Put on a brave face.', results: [Array] },\n    { text: 'Run away', results: [Array] }\n  ]\n}\npoor player\n{\n  prereq: 'hp&gt;0',\n  text: 'You hear a growl from behind you.',\n  options: [\n    { text: 'Put on a brave face.', results: [Array] },\n    { text: 'Run away', results: [Array] }\n  ]\n}\ndead player\nnull\n---------------------------------\nbasic player resolve\nchosen enc {\n  prereq: 'gold&gt;0 &amp;&amp; hp&gt;0',\n  text: 'You meet a beggar who asks for help. He looks desperate.',\n  options: [\n    { text: 'Give a gold coin to him.', results: [Array] },\n    { text: 'Walk away.', results: [Array] }\n  ]\n}\nresult for 0 { text: 'The beggar thanks you!', effect: 'gold--' }\n{ gold: 10, hp: 10, exp: 200 }\n{ gold: 9, hp: 10, exp: 200 }\nPlayer at end { gold: 9, hp: 10, exp: 200 }\nresult for 1 { text: 'The beggar spits at you!', effect: '' }\nPlayer at end2 { gold: 9, hp: 10, exp: 200 }\n\nYou can find the complete repo at https://github.com/cfjedimaster/vue-demos/tree/master/grpg. I think next I'm going to take a stab and creating a map. I've been hashing around some ideas for a few weeks now and I think I'm ready to put pen to paper so to speak.\nPhoto by Tommy Tang on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Migrating from Node and Express to the Jamstack - Part 3",
		"date":"Sun Aug 16 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1597536000,
		"url":"https://www.raymondcamden.com/2020/08/16/migrating-from-node-and-express-to-the-jamstack-part-3",
		"content":"Ok, so I know I just posted part two yesterday (and don't forget to check out part one) but I was feeling inspired this morning so I made a bit more progress. Also, I forgot something I wanted to cover in yesterday's post and I didn't want more time to pass without me talking about. Let's get started!\nRecognizing Login State\nIn yesterday's post I described how to add the login and logut functionality to the Jamstack. What I forgot to mention was how I'd recognize your current login state on page load. Inside the same method I wrote that fires on DOMContentLoaded and initializes netlifyIndentity, I have this block:\n\nBasically, if there's a current user, hide the login button and reveal the logout button. What does user look like?\n\nNotice the _fromStorage bit? You can see this information stored in LocalStorage if you open up your devtools.\nThis works really well, but you may notice a &quot;flicker&quot; in the UI of the login button switching to the logout one. I think it would be better to hide both buttons and only enable the proper one. My demo site definitely has some less than optimal design choices but as it's not really my focus for this series, I'm ok with it. Just keep in mind that the fault is mine, not Netlify's.\nSecured Serverless Functions\nThe first new feature in this series is the addition of a serverless function to post comments. Netlify does a good job of documenting this here: Functions and Identity. I designed a serverless function that would accept two paremeters - the ID of the film being commented on and the comment text. I didn't pass the user information as Netlify provides that for me.\n\nI pretty much just used the sample code they provided and then added the Mongo code to record a new comment. If you remember in the last post I had some concern about how I was going to &quot;connect&quot; users to comments. I took an easy route out. I have access to the email and name of the user and just stored it in the comment. In theory, a user associated with an email address may change their name, but I figure that's unlikely. I could handle that in a &quot;user profile system&quot; if I wanted to build one and handle updating related content then.\nThe function to get comments doesn't require security and is much simpler:\n\nThis is the back end work - the front end work is mainly a bunch of messy JavaScript. I didn't use Vue.js for this project as I wanted to keep things simple with so many moving parts already. Each film page now renders comments and includes a form for adding a new one.\n\n\n\nInstead of sharing my ugly code, I'll just say that I added a form to the films page and if you are logged in, you can submit it. I've got some UI manipulation I'll skip for now, but here's how I call my serverless function in a secure manner:\n\nBasically I just use an access_token value from the user in my header. You can see the complete front end (and all the source code) over on the repo: https://github.com/cfjedimaster/eleventy-auth0-serverless-mongo. Again though keep in mind that the JavaScript isn't the most optimized, clean version.\nYou can, if you wish, actually test this. I'm probably going to regret it, but it's live up on https://hardcore-curie-802f8f.netlify.app/. Hit the site, login, and post a comment. Please don't curse or spam. I can clean them up with my MongoDB client but I'd rather not have to. ;)\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Migrating from Node and Express to the Jamstack - Part 2",
		"date":"Sat Aug 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1597449600,
		"url":"https://www.raymondcamden.com/2020/08/15/migrating-from-node-and-express-to-the-jamstack-part-2",
		"content":"Welcome to the second part of my series where I look at migrating an &quot;older&quot; style Node/Express web application to the Jamstack and serverless. If you haven't done so yet, please be sure to read part one as it goes into detail the kind of site I'm building and the design decisions I've made. This part was somewhat difficult to get to and I didn't progress as far as I wanted, but there's a lot of stuff swirling around in my head and if I don't get down on paper, well electronic paper, than I'm afraid I'll forget.\nThe focus of this particular sprint in development was on user authentication. The site I'm building lets anyone view the content, but you have to be logged in to &quot;purchase&quot; films and leave comments.\nMy original plan was to use Auth0 for user authentication and authorization. My former coworker and all-around smart friend Bobby Johnson built me some excellent sample code that demonstrated Auth0 integration in a &quot;simple&quot; Jamstack application. I say &quot;simple&quot; because many demos seem to assume a SPA application and that's not what I'm building.\nHowever, I decided to take another look at Netlify Identity. This is a feature that I've been meaning to dig into for a while now. Every time I had taken a look before it had been a bit overwhelming and not something I could pick up a few minutes. That's not a complaint per se, and the fact that security isn't incredibly simple should be obvious.\nI wanted to give Netlify Identity a fair chance because I'm already committed to using Netlify for the site and because of how it integrated automatically into serverless functions as well. I knew that if I needed to build an end point and require a user be logged in, it would be trivial. I also knew it had various client-side libraries to support the login flow. Auth0 does all of this too, but again, the idea of keeping it all &quot;in house&quot; for a Netlify project was compelling.\nOk, so that's a lot of preamble. I did get things working. I struggled with the docs towards the end. But I got it working. I still have questions, but I think I'm headed in the right direction. Let's start by talking about the high level aspects of whats in the site now.\n\nEvery page needed a way to login, signup, or logout. In the nav obviously.\nI wanted to support &quot;regular&quot; and social login.\nI wanted to know who my users were. Why? Well when a comment is posted, it needs to know who posted it, ditto for film purchases.\nEventually (and this isn't done yet) - support posting of comments, support getting comments for a film and show who wrote what, and let people buy films (the ecommerce part)\n\nLet me break down I accomplished the first three parts (maybe two and a half to be honest). First, Identity is a feature you have to enable to use first. This is done in your site settings:\n\n\n\nThat's the easy part. Next, you need to provide a way to let users login, signup, and logout. The docs suggest either the Identity widget or a custom solution with gotrue-js. My initial assumption was that the widget would be 'cute' but probably not customizable for my needs. I decided to try it anyway and I'm glad I did as it worked just fine.\nI began by adding two buttons to my site. I'm using Bootstrap so the classes you see come from that:\n\nThe d-none there is a way for Bootstrap to hide the button. Basically I'm assuming the user is not logged in on hitting the site. Alright, now lets look at the JavaScript.\nFirst, you initialize the widget:\n\nThe container field links back to the login button. To enable the button to fire the UI, I then used this:\n\nClicking the button opens up this dialog:\n\n\n\nNotice the social login provider there - Google. Unfortunately, this is the only &quot;regular&quot; social login provider that is supported. By regular I mean I'm ignoring developer-centric ones like GitHub. About two weeks ago I posted a request on the Netlify forums asking for more support, specifically Twitter and Facebook. I got a response that said such support would have to come from the gotrue project. So I went to the project and discovered that a pull request from almost two years ago added Facebook support. The PR says that there's a ToDo for Netlify Identity to work with the provider which implies the impetus is on Netlify to add it. Unfortunately I haven't gotten a response yet on the forum thread.\nThe UI nicely handles logging in and signup, with email confirmation built in. In the code I can respond to login like so:\n\nLogout works pretty much the same, here's the click event and handler:\n\nAnd that's pretty much it for the login/logout functionality on the client-side. Here's where things got a bit more tricky.\nIn the original Node/Express application, whenever you login I check to see if you are a 'known' user in my Mongo collection and if not, add you. I started to investigate how that would work here. I mean, the actual code itself to work with Mongo woul be easy, but specifically the &quot;recognize t",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Migrating from Filters in Vue 3",
		"date":"Thu Aug 13 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1597276800,
		"url":"https://www.raymondcamden.com/2020/08/13/migrating-from-filters-in-vue-3",
		"content":"I've been holding off on learning (and playing) with Vue 3 until it's gotten closer to release, but with Vue 3 in RC it feels like an appropriate time to start digging into it. The docs are in a pretty good state and there is a really well done migration guide that clearly defines the changes and how to update your code.\nOne of the changes that I'm not a fan of is the removal of filters. Filters provide a way to format text in your display and were one of my favorite Vue.js features. (To be fair, I've got more than one &quot;favorite&quot; Vue feature. ;) In case you haven't seen this feature yet, let's look at a quick example.\nFirst, I set up an application with data:\n\nIn my DOM, I want to display the bio, but I want to limit the size of it. I can add this to my Vue application:\n\nWhich then allows this syntax in my DOM:\n\nNow maybe it's my experience with HTML template languages, but that pipe syntax looks completely natural to me. Here's a complete CodePen of this in play:\n\n  See the Pen \n  Filter 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFilters also support optional arguments allowing for syntax like so:\n\nThe filter has to be updated of course:\n\nHere's another CodePen for you to play with:\n\n  See the Pen \n  Filter 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo as I said - I dig this! But it's being removed from Vue 3. Why? The migration guide says this:\n\nWhile this seems like a convenience, it requires a custom syntax that breaks the assumption of expressions inside of curly braces being \"just JavaScript,\" which has both learning and implementation costs.\n\nThis makes sense. I don't like filters going away as a feature, but I can see the logic in this decision. To keep using this logic in a Vue 3 application, you can simply move the filter to a method. Here's how it looks in a Vue 3 application:\n\nYou have to change the DOM of course:\n\nAnd here's an example with a custom length:\n\nThe end result is the same of course. You can see it here:\n\n  See the Pen \n  non filter filter by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWhat happens if you forget and use the pipe operator? Vue 3 treats it as a bitwise OR operator leading to unexpected results.\nThis isn't the only way to move from filters of course. If you knew for a fact that you were always displaying the bio value in a trimmed fashion, you could trim it when the value is retrieved. In my example the value is hard coded, but typically it would be dynamic. Of course, having the original value lets you do things like displaying it in a trimmed fashion and letting the user click to expand.\nBe sure to check the Vue 3 migration guide on Filters for more examples and I'll be blogging more about Vue 3 as I start playing with it more.\nPhoto by Nathan Dumlao on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Hiding Future Content with Eleventy",
		"date":"Fri Aug 07 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1596758400,
		"url":"https://www.raymondcamden.com/2020/08/07/hiding-future-content-with-eleventy",
		"content":"Here's a quick tip for something that's been on my mind lately with Eleventy - hiding content so that it's published but not linked. What do I mean by that? Eleventy doesn't support the idea of &quot;drafts&quot; such that you can have content locally but not in production. You can use permalink: false (documented here) to stop a page from being output, but it will still exist in collections. I thought a more useful case may be the ability to publish content for the future such that they are not listed in collections until their publish date has come to pass. This has the benefit of letting you see the content if you know the URL. This lets you write a post for next week, publish, and share the URL with a reviewer. In order for this to be effective though you need to have builds scheduled on a regular basis. It would be simple to schedule a daily, or even hourly, build on Netlify and other platforms (let me know if you want to see an example of that!). Let's consider a simple example.\nFirst, I build an Eleventy site that contained one index page:\n\nI then added a posts subdirectory with three blog posts. Each blog post had front matter like so:\n\nNotice the date. For my three posts I picked two dates in the past and one in the future.\nMy first attempt at hiding the future post was via universal filter:\n\nBasically I take the current time (which remember will be build time, hence my warning above about having a scheduled build process) and compare it to the post time. To use this in my index page, I just did this:\n\nAnd that worked like a charm. But in order for it to be effective I'd have to ensure I used the filter everywhere I get content. Another option would be to use a custom collection. I've used this before but only via the glob option. Being able to build a collection on another collection is sweet:\n\nNow I can change my home page to iterate over the new collection:\n\nIt's a small change I suppose but feels a bit safer. Both solutions also leave us open to changing the logic at some later time. For example, maybe I want to use another piece of front matter, like hide: true, instead.\nAgain, this does not stop Eleventy from publishing the URL, but with nothing linking too it it's going to be (mostly) safe. Certainly safe enough for the process of having someone take a look at a post for review, or heck, even just to have it published later. Anyway, I hope this helps, and you can get the source for this here: https://github.com/cfjedimaster/eleventy-demos/tree/master/hide_tests\nPhoto by Michael Browning on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Migrating from Node and Express to the Jamstack - Part 1",
		"date":"Thu Aug 06 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1596672000,
		"url":"https://www.raymondcamden.com/2020/08/06/migrating-from-node-and-express-to-the-jamstack-part-1",
		"content":"Welcome to the first of a series of some unknown length. For the past year or so (seriously), I've been looking at an old Node.js project I have and thinking about how I could update to it. I have to be a bit vague because it's a secret project I'm doing with some friends but it involved a number of features:\n\nMongoDB for storage\nAuth0 for identity\nStripe for ecommerce\n\nI used Express.js for the framework. The front end used a bit of JavaScript, but not much. Auth0 was used for login and registration with Passport.js being used as well. Ecommerce was done via Stripe and a simple checkout modal. This was followed by a POST handler in Node to record the order.\nThe site itself was fairly small. A few pages that were just text and a set of dynamic pages representing the main content. As I have to be a bit vague, let's pretend for now it is a movie site with the ability to load information about a movie via a path like so: /movie/urlslug, so for example: /movie/the-force-awakens.\nWhile the site worked, the service it was on was moving past Node.js hosting and while I could find another, I thought it might be time to look into a Jamstack solution. As I said though, this has been on my mind for about a year now. While I feel really comfortable with the Jamstack, I just struggled with how to convert this existing site over, especially with the Mongo, login, and ecommerce aspects. I knew there were solutions for all of that, but again, I just struggled with the particulars.\nFinally last weekend I decided to take a stab at it. I made some progress and after talking with some friends, I think I know how to proceed. While I can't show a &quot;before&quot; and &quot;after&quot; demo, I am working on a new demo that mimics some of the existing site. I'm not necessarily saying this is the best conversion, but I had to start somewhere. As always, I'd love your feedback in the comments below. With that out of the way, let me begin by covering what the features of this demo site are and the technology stack.\n\n\nThe site in question will be a film site. You'll hit the home page, see a list of films, and can click for details. You can optionally login to post comments and there will be a page that lists every comment you wrote.\n\n\nI had to decide between a Single Page Application written in Vue and a Jamstack site written in Eleventy. Since the site is so simple, I decided to go with Eleventy. I'm still using Vue a bit on the front end, but I wanted static files backed by serverless functions as my core architecture.\n\n\nI'm using Mongo for data storage. It's what I used for the Node site and I see no reason to change that. Previously I used Mongoose as a wrapper for Mongo but I'll be dropping that for now. I haven't used Mongo seriously in a while, but I was really impressed with how much it's improved and how quick it was to setup. Im also now using their Compass application for local editing.\n\n\nI'll be using Netlify for the site, because of couse I am.\n\n\nAuth0 will be used for identity. I wanted to use Netlify Identity, but they only support Google for social login (and a few others that none of our users will recognize). I need Twitter and Facebook support as well. I'm really surprised this hasn't been added to Netlify Identity yet. I raised it on the forums as a request for now.\n\n\nMy &quot;dynamic&quot; content will be split between &quot;kinda&quot; dynamic and really dynamic. This is an important point. I wanted a real file for every film. For that I used Eleventy's pagination support. That means when a new film is added, a site build has to happen. Since this can be automated and is quick, I was fine with that. Also, in the context of this demo, films are added only so often. At the same time, every film has data that does change often, namely comments. So when you hit the film page, a serverless function will &quot;enhance&quot; the page by fetching that additional data. I'm also tracking the total number of film puchases so that will be fetched as well. (See bullet point below.)\n\n\nTo post comments, you have to login. The site knows you are logged in as you go from page to page. This has to work even though I'm using static pages and not a SPA. This was a big deal because nearly every demo I saw of this assumed a SPA. I've got a good friend who works at Auth0 and he helped me out. I'm going to wait to the next post though before I show that.\n\n\nFinally, you can buy a film. Ok, that doesn't necessarily make sense, but I need to have ecommerce in the demo. Stripe will process the payment and serverless functions will be used to record the order. It has to know who did it (via Auth0) and what film was purchased.\n\n\nSo that's nearly a thousand words, and I still don't feel like I've quite nailed it down precisely, but my entire reason for building this blog was to work through things that confused me (and excited me) and share them. I've got the first phase done so let me share what I did.\nI began with an existing MongoDB datab",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Sharing Your Movies with Pipedream and Letterboxd",
		"date":"Tue Aug 04 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1596499200,
		"url":"https://www.raymondcamden.com/2020/08/04/sharing-your-movies-with-pipedream-and-letterboxd",
		"content":"I recently discovered Letterboxd via a cool example of integrating it's data with Eleventy (&quot;Show Off Your Letterboxd Film Diary with Eleventy&quot;). Letterboxd is a site (and app) that lets you track the movies you've watched and give reviews and ratings. I'm a huge fan of GoodReads for keeping track of the books I've read and I'm going to give Letterboxd a try as well. My wife and I are both huge movie nerds so I thought it would be kind of cool to have a list of movies I've watched when the year finally ends.\nWhile there isn't a proper API yet (apparently it's in beta now), every account has an RSS feed setup. Here's mine: https://letterboxd.com/raymondcamden/rss/\nWhile we all know what RSS looks like (ok, maybe it's just me), Letterboxd has quite a few extensions to the specification that provide additional data about your films. Here's one entry (which is all I've got for now - I've told the site about a bunch of old movies I've watched but have only &quot;logged&quot; one review so far):\n\nEverything with the letterboxd: prefix is a namespaced set of data that they've added to provide more information to the feed. Looking at this, and the blog entry I shared earlier, it occurred to me that it would be easy to build an integration between this and Pipedream. I got this working and you can see it below.\nI just watched John Mulaney: Kid Gorgeous at Radio City and rated it a 4.0. See my review at https://t.co/4sPdaLTFeJ.&mdash; moonpicbot (@moonpicbot) August 4, 2020 \nNotice - when I &quot;play&quot; like this, I use one of my bot accounts, not my main account. If I continue to use Letterboxd I'll update my workflow to post to my main account. Alright, so how was this built?\nThe first step of my workflow was an RSS event source. I first wrote about Pipedream's event sources back in May. It's a powerful way to build workflows built on custom events. One of the events built in is an RSS feed parser that runs every fifteen minutes and on a new RSS entry will emit an event. With this as the source of my workflow I've got a serverless function that will execute automatically whenever I do a new movie review. (Well, within fifteen minutes.)\nThe next step was a custom Node step. I did two things in here. First, I wanted to get the URL of the image for the movie. In that blog entry I shared earlier, they used a npm package called letterboxd. This is a cool little package that abstracts away the complete logic of reading and parsing the RSS feed. But for me, the RSS feed was already parsed, I just needed the &quot;find the image logic&quot;.\nI went to the GitHub repo, opened up index.js, and found the getImage function. I took the logic from there and incorporated it into my Node step:\n\nThe second thing I did was to simply write up the text I wanted to tweet. I used the custom values from the RSS feed to get the title and rating.\nBy the way, make note of the use of cheerio package. This is an awesome implementation of jQuery on the server and works really darn well for cases where you need to parse HTML as a string.\nSo at this point, I've got images (multiple to pick from) and text. My plan was to tweet with the image so to do that you first need to upload the image. I picked the pre-built upload_media_to_twitter step where all I did was plugin my URL param: steps.parseEntry.$return_value.imgdata.medium.\nLastly, I used the post_tweet step with two params: status was steps.parseEntry.$return_value.text and media ids was steps.upload_media_to_twitter.$return_value.\nAnd that's it. You can see the complete workflow here: https://pipedream.com/@raymondcamden/letterboxd-to-twitter-p_V9CVvK/ As I mention every time, don't forget you can copy this workflow to your own Pipedream account and use it as you will. Enjoy!\nPhoto by Felix Mooneeram on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Twitter Scheduling System with Pipedream and Google Sheets",
		"date":"Tue Jul 28 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1595894400,
		"url":"https://www.raymondcamden.com/2020/07/28/building-a-twitter-scheduling-system-with-pipedream-and-google-sheets",
		"content":"A few months ago, I blogged about how I used Pipedream and Google Sheets to create a Twitter bot. The idea was simple - read a sheet - select a random row - and use that as the source of a new Tweet. I was thinking about this recently and how useful Google Sheets can be as a &quot;light weight CMS&quot; and figured out another interesting use case - Twitter scheduling.\nSo let me be clear that I know that Twitter already lets you schedule tweets. So does Tweetdeck, my preferred way of using Twitter. But I wanted to investigate how a different workflow could be used. Google Sheets provide a simple Excel-like editing experience that may be more friendly to non-developers. Also, maybe the user wants to work on a Tweet for next week, but edit it before then to make changes. To be honest, I'm not even sure if this makes sense, but I gave it a shot and I can share the results below.\nFirst off though - I can say I spent much more time thinking about the process than I did in implementation. The final workflow is a grand total of six steps. I wrote a little over thirty lines of code total and if we ignore some of the dumb coding mistakes I made, my total development time was probably around ten minutes. That's really bad ass. I did - though - spend a lot of time thinking about how it would work and specifically made choices to simplify the process a bit. My final workflow isn't perfect, but it works.\nAlright, let's start by looking at the Google Sheet:\n\n\n\nI've got a simple header and two columns, one for the text and one for the date. For the text, I found a cool StackOverflow post that described how to limit the size of text in a cell. I used this to prevent the user from typing too much in the text cell.\n\n\n\nWhat you can't see in the screenshot above is that it actually edited my text after I entered it to reduce the total number of characters. I didn't even know about the &quot;note&quot; feature of cells, but that worked pretty well!\nFor the date column I applied date validation. Nice and simple.\n\n\n\nAll in all, I've made the sheet such that the writer should be guided to enter appropriate data. It isn't a web form with fancy hipster JavaScript, but it works.\nNow for the Pipedream part. Here is how my workflow works.\n\nFirst, get the entire sheet.\nFilter to tweets in the past, and remember the oldest one.\nTweet that one.\nDelete that one.\n\nSo pay attention to step 2. I may have multiple tweets in the past, but I only tweet the oldest one. My thinking was that the user would be scheduling, at most, a few per day, and typically not ones very close to others. Also, I can set up the CRON schedule of the workflow to check more often if I'm worried about having things be late. My assumption is that if the user schedules for 3PM and I'm checking every 10 minutes, that it's ok to be a few minutes late. Obviously that may be a problem and you could increase the the frequency if you wanted.\nAnother reason I like this is that I could - if I choose, pause the workflow and enable it later, knowing that it will &quot;catch up&quot; on Tweets it missed. Now for the some details.\nThe first step in the workflow is the CRON trigger. As all of this is just a test mine is still turned off, but it would be simple to pick a good schedule:\n\n\n\nFor my second step, I use a trick I learned from Pipedreamer (that's not really a word) Dylan Sathar - a Node step that sets constants for use later in the workflow. My code is just this:\n\nBecause my workflow needs to read and write to the sheet in multiple steps, I wanted to abstract out the ID of my sheet.\nMy next sheet reads the data. Since my first row is a header, I skip that in my range:\n\n\n\nTo be clear, that was zero code on my part.\nMy next step is the &quot;find earliest&quot; part. I wrote this code perfectly the first time and absolutely didn't make a bunch of stupid logical issues that would be clear to anyone with average intelligence.\n\nNotice the two $end calls here to possibly end the workflow early. Outside of that it's just a loop over the values. Also note that I remember the row I selected. I need to know this to delete it later. Also note that since my selection of cells began on row 2 (1 on the API side), I need to add one to my value otherwise it will be too low.\nThe next step posts the tweet. No code, took two seconds to type in the paramater:\n\n\n\nThe next step removes the row. Again, no code, took four seconds to get it working, much longer than the previous step, because I didn't notice I needed to pass a sheet ID along with the spreadhseet ID.\n\n\n\nAnd that's it! You can see the entire workflow yourself here: https://pipedream.com/@raymondcamden/scheduled-tweet-manager-p_jmCyaa/. Don't forget you can fork this and play with it yourself if you want. Let me know if you've got any questions or suggestions by leaving me a comment below!\nPhoto by Harald Arlander on Unsplash\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Making Monsters with JavaScript",
		"date":"Sun Jul 19 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1595116800,
		"url":"https://www.raymondcamden.com/2020/07/19/making-monsters-with-javascript",
		"content":"A few days ago I blogged about how I had started on a Vue.js RPG game a while ago and never got around to putting more work into it. This may be hard to believe, but building games is pretty hard! I realized though that the parts I most looked forward too, the more creative parts, were things that I could work on and just stop worrying about actually finishing the game.\nThat realization was incredibly freeing. It also immediately started the creative juices flowing. While walking my dog a few days ago (another activity that really improves my creativity) I formulated a good plan to build random monsters. One of the staples of many RPGs are random encounters. Your character, or party, is wondering the wilderness and all of a sudden they find themselves under attack.\n\n\n\nThe idea I had took a base set of monsters and then applied various modifiers and randomness to them. The idea being that you could have a small of monsters that would &quot;scale up&quot; to many more unique ones. Now, by &quot;unique&quot; I mean something like comparing a blue gremlin to a red gremlin. Many old games would take a base monster, apply a color shift to it, and call it done. Here's the basic flow of what I built.\nFirst - define an array of monsters. For now, I just have five:\n\nEach monster has a name, three attributes related to how well they fight (my game only has strength, dexterity, and intelligence) and their hit points. For each stat I assign dice rolls applicable for their respective strength as a creature. This was kinda arbitrary of course. I gave rats higher (possible) dexterity because I figured they were quick. I gave gremlins higher intelligence because, well, gremlins.\n\n\n\nSo the first step is to select one and then run the dice rolls for each stat. Next, there is a chance that a monster has a boon. A boon is a positive or negative change to one part of their stats. The chance for this change is based on a constant:\n\nThis is out of a 100, but I also make it a bit higher if the monster is scaled higher. I haven't mentioned scales yet but I will in a second. Here's the function that determines if the monster has a boon:\n\nIf true, I then flip a coin to see if it's a good or bad one:\n\nNow I figure out what stat is changed by just picking a number from 1 to 3 (ok technically 0 to 2):\n\nNow I have an if statement and based on boonType, either change STR, DEX, or INT. The boon does two things. It adds, or subtracts, a 1D6 value (roll a six sided die one time). For example:\n\nNotice I also ensure the value doesn't go below 1. Next, I wanted a way to let the player know that there's something special about this creature. I created a list of &quot;titles&quot; for each stat and each type of boon, as well as whether they were positive or negative.\n\nAt this point, we've got a random monster, with random stats, although stats that make sense for how strong they are in general, and a potential boon that impacts their name, so for example, if face a clumsy pig, you may know this means their dexterity is lower than normal.\nAlright, the final part comes in the scale I previously mentioned. In most RPGs, the monsters closer to you when you start out or relatively easy to take on. The farther you move away from the starting point, the stronger they get. My utility takes a scale argument. This scale can be any number. For example, a scale of 1.2 means a monster bit higher than normal. The scale does two things.\nFirst, it improves every stat:\n\nAnd remember, this is done after a boon. So a monster that got a bonus to strength will be incredibly strong after the scaling. Next, I created a set of titles that helped reflect the higher scale.\n\nScale titles are only used when the scale is above 2. A scale of 2.0 to 2.9 will use a random title from the first index of scaledTitles, and so forth. If you pass a scale of 5 or 6, it uses the highest tier.\n\nSo just to recap - while I only have 5 monsters now, the total number of variations is really high. And even better, to improve the set of possibilities, I can add a new base monster, add new boon titles, new scaled titles, as they come to me. Every single addition is a multiplicative change. I'll be honest, the actual is probably so so in terms of quality. I don't care. What excites me is that as soon as I get a creative idea, it's an incredible simple change!\nFollowing the tip I previously posted about, I wrote a quick test script:\n\nAnd here's some results:\n\n\n\nYou can find this repo at https://github.com/cfjedimaster/vue-demos/tree/master/grpg. Feel free to make PRs to add new monsters and titles.\nPhoto by Anne Nygård on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Vue.js Application Files That Aren't Components",
		"date":"Fri Jul 17 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1594944000,
		"url":"https://www.raymondcamden.com/2020/07/17/testing-vuejs-application-files-that-arent-components",
		"content":"Ok, before I begin, a huge disclaimer. My confidence on this particular tip is hovering around 5% or so. Alright, so some context. I'm working on a game in Vue.js. Surprise surprise. It probably won't ever finish, but I'm having some fun building small parts of it here and there. The game is an RPG and one of the first things I built was a basic dice rolling utility.\nIn my Vue application, I created a utils folder and made a file dice.js. I used this setup because I wasn't building a component, but rather a utility that my Vue components could load and use. My dice utility takes strings like this - 2d6 - which translate to &quot;roll a six sided die 2 times&quot;. It even supports 2d6+2 which means to &quot;roll a six sided die 2 times and 2 to the final result&quot;. It's rather simple string parsing, but here's the entirety of it:\n\nIn one of my Vue components, I use it like so:\n\nI import the dice code and then can make calls to it for my UI. Nothing too crazy here, but I ran into an interesting issue today. My initial version of dice.js didn't support the &quot;+X&quot; syntax. I wanted to add it, but also wanted a quick way to test it.\nSo I could have simply gone into my Vue component and add some random tests to the created block, something like:\n\nAnd that would work, but as I developed, I'd have to wait for Vue to recompile and reload my page. In general that's pretty speedy, but what I really wanted to do was write a quick Node script and run some tests at the CLI. To be clear, not unit tests, just literally a bunch of console logs and such. That may be lame, but I thought it might be quick and simple.\nHowever... it wasn't. If you look back at the source of dice.js, you'll see it's not using module.exports but just a regular export.  This was my test:\n\nAnd this was the result:\n\n\n\nOk, so an admission. I'm still a bit hazy on the whole module thing in Node, and JavaScript in general. I've used require, imports, exports, but I wouldn't pass a technical interview question on them. I hope you don't think less of me. Honestly.\nThat being said, the error kinda made sense, but I didn't want to use the .mjs extension because I didn't know if that would break what the Vue CLI does.\nI was about to give up and was actually considering adding a route to my Vue application just for debugging.\nThankfully, StackOverflow came to the rescue. I found this solution which simply required me adding esm and then running my code like so: node -r esm testDice.js.  It worked perfectly! And because my memory is crap, I added this to the top of the file:\n\nYes, I write notes to myself in comments. You do too, right?\nAnyway, I hope this helps others, and I'm more than willing to be &quot;schooled&quot; about how this could be done better. Just leave me a comment below!\nPhoto by Nancy Yang on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript",
            
                "serverless"
            
		]

	},

	{
		"title": "Review: The Bard's Tale IV",
		"date":"Sun Jul 12 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1594512000,
		"url":"https://www.raymondcamden.com/2020/07/12/review-the-bards-tale-iv",
		"content":"\n\nIt's been sometime since I've posted a video game review. (My last one was a review of my Arcade1Up machine and I still love it!) As I'm spending a lot more times indoors lately (thank you Corona!) I've had a chance to play quite a bit more and I figured it couldn't hurt to start up the review process again. I first ran across &quot;Bards Table IV&quot; (BT4 from now on) while perusing the list of games available on XBox Game Pass. This is their &quot;Netflix-style&quot; service that offers a library of games you can play for a monthly fee. At first, I thought it was a different game.\nYou see, first there was BT1. I cut my teeth on that game as a kid, drawing maps out on hex paper and actually learning hex so I could edit my save games and cheat. I played it, and BT2 and 3, on an Apple 2+ in glorious green monochrome colors. A few months back, I discovered there was a remastered collection available on XBox. I gave it a try and really enjoyed it. The graphics are updated, the UX remodelled for a controller in a really sensible manner, and, thank god, automapping. I played all of BT1 and a bit of BT2.\nAnyway, when I saw the entry on Game Pass, I actually mistook it for &quot;The Bards Tale&quot;, which was made a decade ago and has nothing to do with the original trilogy. Rather, this game is a proper entry in the earlier trilogy. It's a bit old now (Game Pass generally has older games, but sometimes will have new releases) but as I had recently enjoyed playing the updated older trilogy, I figured I'd give it a shot.\nI'm now roughly 50 hours or so in. Normally I try to wait till I finish a game before I review it, but I'm not sure if I will or rage-quit. Because here's the thing... the game is really interesting. It's got some really cool mechanics. But for every aspect I think is neat, it's got a corresponding aspect that bugs the hell out of me. I almost quit today and I'm glad I didn't, but overall it feels like a game that is real close to greatness but just has too many issues to really be great.\nLet's start with that's good. Character creation, and setup, is uniquely different compared to BT1-3. You pick a base character type, like bard, fighter, practioner (magic class), and then use skills to customize your character. The skill tree is varied (and beautifully designed too) and lets you have very different characters with the same archetype. This is especially true of the magic users as you can focus on the style of magic you think is best. For example, I ignored the summoning skills. Similarly, for my rogue I avoided poisons and traps and focused more on theft and arrows.\nCombat is also updated. While still turn based, it takes place on a 8x8 grid (2 rows of 4 on each side) where position is much more important than it was in earlier games. To be honest this really threw me at first but once I got the hang of it, I rather enjoyed it. You also get hints before you engage an enemy. These hints let you know if you're ready to take them on of if you should haul ass and turn around. You can also also wait for enemies to turn around to attack them and get initiative. While not an action RPG by any means, this does give a small taste of one.\nAnd speaking of combat, it's a minor thing, but actually seeing my spells, spells I first used thirty plus years ago playing BT1, was pure joy.\nThere's a crafting aspect that's really well done. You heal, mainly, by eating, and you can craft different types of food with different healing qualities while traveling. You can also craft potions and equipment. I made my bard focus on that aspect. Speaking of bards, I absolutely love mine in game. She has the best skill I've ever seen in a game - mean drunk. Every time she drinks (bards drink for spells), she'll throw her tankard at the enemy for a small amount of damage. It never takes out an enemy but it makes me laugh every time I use it.\nThere's other nice things too. Interesting &quot;puzzle weapons&quot;, a really good soundtrack at times, and more. But... now for the bad.\nFirst, the game is incredibly stingy. So remember that skill tree I mentioned? You've got something like 12+ or more skills related to armor. That means that even if your fighter picks up a piece or armor, there's a strong change she won't have the skill for it. And to be clear, I'm not talking skills in light, medium, and heavy armor, but as I said, 12+ (ok, maybe not that many, but it's close). This wouldn't be so bad if it was easier to buy equipment, but not only are shops slow to become available, you will rarely have the money to actually buy anything. And when you do have the money, they won't have what you need.\nTime and time I'd come across a treasure chest, open it up, and find... wood. Yes, wood. This is used in the crafting aspect so it's not a waste, but seriously, wood in a treasure chest? The first twenty plus hours of the game felt like I was being rewarded with absolute craft. Now that I'm 40 hours in, I finally feel like I'm slowly",
		"tags":[
	        
		],
		"categories":[
            
                "video games"
            
		]

	},

	{
		"title": "Building a Vue Application with Serverless on Netlify",
		"date":"Tue Jul 07 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1594080000,
		"url":"https://www.raymondcamden.com/2020/07/07/building-a-vue-application-with-serverless-on-netlify",
		"content":"This is something that is - surely - documented in a thousand other blog posts, but it's not something I've tried yet\nwith Netlify so I thought I'd give it a go. It was this or watch another episode of &quot;Unsolved Mysteries&quot; on Netflix and obviously I chose this instead. I'm glad I did because it worked surprisingly well with no real hiccups. I just needed to\n&quot;see&quot; it myself at least once to be sure it worked.\nAlright, so as the title says, how would you deploy a Vue.js application to Netlify while also making use of their serverless platform? Here's how I did it.\nFirst, I made the Vue application:\nvue create vue-netlify-demo -b\n\nIf you've not seen the -b option before, it means &quot;bare&quot; and generates a much smaller Vue application with less boilerplate text. (In my opinion it could be even more bare, but I'll take what I can get.) I didn't change any options because I wasn't worried about testing Vuex or the router.\nI then fired up the application with npm run serve and confirmed it worked.\n\n\n\nNext, I started using the netlify dev command. This enables you to test local applications as if they were running on the Netlify platform. I noticed that netlify dev ran npm run serve, but I don't remember that being documented. It was the first script defined in my package.json file and so maybe that's why it fired, but I went with a more specific command: netlify dev -c &quot;npm run serve&quot;. The -c flag specifies the command for the CLI to run.\nI noticed that the output that was much more verbose when running via netlify dev. When you use npm run serve, there's a lot of webpack related messages that gets output to one line, constantly being overwritten. It's a lot of noise so I'm fine with that. But when running via netlify dev, they all get output to the screen. This is fine, but you may miss the message stating that the server is up and running:\n\n\n\nOnce I saw this message and opened my browser to localhost:8888 I didn't worry about it again. Cool, now let's go serverless.\nFirst, I added a netlify.toml file to my project to specify my functions folder:\n[build]\n\tfunctions = &quot;.functions&quot;\n\nI then used the CLI to scaffold a hello-world function: netlify functions:create. This laid down this file in .functions/hello-world/hello-world.js:\n\nThe Netlify Dev environment supports testing serverless functions locally so I modified my little one page Vue application like so:\n\nAll I've done here is fire off a call to the function (the .netlify/functions path is how you &quot;address&quot; serverless functions on Netlify) and displayed the result. Here's how it looks:\n\n\n\nBeautiful, right? Now to get it live. First, I made a new repo for it: https://github.com/cfjedimaster/vue-netlify-demo. Then I made a Netlify site tied to the GitHub repo. I specified npm run build to generate the Vue production version of the app and entered dest for the folder to use as the site source.\nAnd that was it. You can see it running here: https://vue-netlify-demo.netlify.app/. And don't forget I linked to the repo right above.\nSo all in all - no surprises - it just plain worked - which is exactly what I want!\nPhoto by Johannes Plenio on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript",
            
                "serverless"
            
		]

	},

	{
		"title": "How to Enable your Jamstack Site to have a \"Rain Day\"",
		"date":"Mon Jul 06 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1593993600,
		"url":"https://www.raymondcamden.com/2020/07/06/how-to-enable-your-jamstack-site-to-have-a-rain-day",
		"content":"So this is perhaps a bit of an edge case, but I was thinking about it this weekend and decided to build a quick demo of it just to see if it would actually work. Imagine a simple Jamstack site for a farmer's market. Now imagine that this particular market is closed when there is bad weather. What if we could build a Jamstack site that checked the weather in the morning and added a warning to the site that they may be closed due to rain? Here's how I implemented this idea.\nFirst, I began with an incredibly simple one page site built with Eleventy. You can see the site in all it's glory at https://weather-demo.netlify.app/. Here's how it renders normally:\n\n\n\nNow here is what it does when it thinks it may close due to weather:\n\n\n\nThe giant red arrow is there just to point out the change. My CSS isn't quite good enough to do something like that. ;) So how did I build this?\nFirst, I found a weather API. It just so happens, my employer HERE has a Weather API. Obviously anyone would do, but I like ours so I used it. The API basically supports two formats - an observation (what's going on now) and a forecast. The observation also reports on future weather so I went with that. A basic API request could look like so:\nhttps://weather.ls.hereapi.com/weather/1.0/report.json?apiKey=${HERE_KEY}&amp;product=observation&amp;name=YOUR+LOCATION&amp;metric=false\n\nWhere the key needs to be supplied and a location of some sort. You can also use a latitude and longitude pair or zip code if you like. Also, the last bit disables that crazy metric thing that probably won't go anywhere. Hitting this returns data for the location and possible alternatives, but if we focus on one report, the first one returned, we see the following detailed weather data:\n\nThat's a lot of data, and sadly 82 degrees is nice compared to what it would normally be without all the cloud cover. You can check the API reference for detailed information about each part, but for my use I thought the precipitation12H would be useful. I figured if I checked this in the morning, it would be a good way to see if the market may need to close that day. I built the following in _data/weather.js. For folks who don't know Eleventy, this will create data my pages can use at build time. In this case, the data will be available as a variable named weather.\n\nYou can see where I make the fetch to the API as well as where I grab the first location and observation. Finally, I add a &quot;simplification&quot; of my &quot;will be possibly close&quot; logic in the rainWarning value. What's cool is that if I switch to another provide for my weather data, I can just preserve this logic in the data file and my template won't need to worry. Speaking of the template, this is how I handled it:\n\nJust a basic IF check and nothing more. I could go more complex, but for a simple site like this, it's enough of a warning. And speaking of that warning, that brings up an interesting issue. How do we add this when our site is static?\nWell first off, we could simply use JavaScript in the browser and hit the API when the client visits the site. This particular API does not support CORS but does support JSONP. However, that means every hit to the page will hit the API. HERE has an incredibly generous free tier, but I'd still like to avoid that. In this particular case, a market that opens at 6AM, I could simply check the weather at 5AM. How? I'm hosting the site on Netlify, and they support a unique build URL that you can hit to generate a new build. You can find this in your &quot;Build hooks&quot; setting for your site:\n\n\n\nNext I needed a way to run this on a schedule. For this I decided to use Pipedream. It may be overkill, but I created a quick Workflow that used a CRON source and a &quot;send http request&quot; step.\n\n\n\nBy the way, I totally suck at CRON so I used crontab.guru to help me write the expression.\nSo just to recap:\n\nMy Jamstack site, on build, will check the weather report and see if rain is coming in the next 12 hours.\nMy home page looks for this boolean and adds a warning when it's true.\nPipedream will call the Netlify Build webhook daily at 5AM to refresh the site.\n\nAnd that's it. Obviously not perfect, but also automated so hopefully less work for the poor soul running a market at 6AM!\nPhoto by William Felker on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Algolia Search to Eleventy and Netlify - Part Two",
		"date":"Wed Jul 01 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1593561600,
		"url":"https://www.raymondcamden.com/2020/07/01/adding-algolia-search-to-eleventy-and-netlify-part-two",
		"content":"\nImportant Update: May 18, 2021\n\nRecently I noticed that my search feature wasn't working. When I checked my index, it was empty. I wasn't able to replicate\nit consistently so I reached out to Algolia's support. Turns out, the `clear` API call I was doing was *not* synchronous. I don't mean the HTTP aspect. That was obvious and I was doing an await on it. But the actual operation itself wasn't complete when the HTTP call was done. This meant that my next operation, where I added 6k objects, would fail as it put me over the max for my free tier. \n\n\nYou'll notice that Haroen in the comments below suggested using the SDK. So did support. And that helped right away. I'm not sure why I was opposed to using the SDK, maybe I just didn't want another dependency, but I wish I had just used it as it makes the code quite a bit simpler. So for example, I now do: let clearResult = await index.clearObjects().wait(); to handle the clear and wait for it to finish. Much easier. \n\n\nYou can see the code in my deploy-succeeded serverless function here: github.com/cfjedimaster/raymondcamden2020/blob/master/.functions/deploy-succeeded/deploy-succeeded.js Sorry for the mistake folks!\n\n\nThis will be a quick update as I'm on vacation and should be busy playing XBox, but I've got an update to my\npost on using Algolia with Eleventy. Please read it first as this post won't make much sense without it. In that post, I described a way to use Algolia with Eleventy. The process was basically:\n\nGenerate a JSON index of your content.\nPush the index to Algolia\nUse their JavaScript library to add a search\n\nThe second bullet point was the crucial one. I made use of algolia-indexing to handle updating my index. This utility created a copy of your index, did atomic updates focusing only on what changed, and then copied back your index. This utility was super cool, but it wouldn't work for me. Algolia has a max of 10k records per index on their free tier, which is a very generous amount, but my blog has a bit over six thousand posts.\nI was prepared to &quot;give up&quot; when Algolia, just yesterday morning, announced friendlier pricing. Follow that link for details, but the part that was most important to me was that indexing operations (adding, updating, and removing records) was now free. That means I could adapt a &quot;delete everything and re-upload data&quot; approach for my site.\nNow to be clear, this means that for a few seconds, my search index is blank. I'm ok with that. Heck, as far as I can tell I'm the only one who actually uses my search page. But you do want to keep that in mind before you consider using the approach I describe below. If you're below 5K records, I'd definitely suggest using algolia-indexing instead.\nOk, so with that in mind, here's what I'm using on the blog.\n\nAs I described in my last post, I generate a JSON file representing all my content. In my Netlify `deploy-succeeded' function, I fetch that file so I can use it for my updates.\nThe first thing I do is use the clear endpoint on my index, which does what you would assume, empty the index. Then I use the batch endpoint to add all my data. Notice I have to 'reshape' the JSON into an array of commands (addObject). I kinda wish Algolia had a &quot;addAll&quot; type endpoint that just took an array of objects. That would make the POST a bit smaller too. But it wasn't a big deal to create that data for the batch call.\nSpeaking of time - Netlify has a limit of 10 seconds for serverless function executions. While trying to get Algolia working on my site last month I asked for an extension and got the max, 20 seconds. In my testing locally, I saw the duration typically take around 8 seconds, but sometimes it took 11. Most of the time spent was in network calls, so I don't believe it's going to be an issue when running on Netlify's CDNs, but it's something to keep in mind while testing on your local machine. The netlify dev command will enforce a 10 second limit so you may get errors there that you would not get in production.\nI think you could also argue that it doesn't make sense to re-index your data in every build, especially since you may update your site in a way that doesn't impact content (like changing a header graphic for example), but I'm ok with this for now.\nOf course, I also had to update my search page as well. In my last post I explained how I used the JavaScript wrapper to build a simple search interface with Vue.js. For my site, I made it a bit nicer. Here's the method I use for my search:\n\nA few things. First, notice I'm asking for date in the results. Showing the date for my results is important so I can see which results are more recent. Next, I ask for a snippet of the content property, this lets me display part of the content in the result. Finally, I make use of the Intl API to format the dates returned in my results. I'd normally use a Vue filter for that, but as they are deprecated in Vue 3, I'm trying to wean myself off of them. Here's a screen",
		"tags":[
	        
            "eleventy",
            
            "algolia"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Algolia Search to Eleventy and Netlify",
		"date":"Wed Jun 24 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1592956800,
		"url":"https://www.raymondcamden.com/2020/06/24/adding-algolia-search-to-eleventy-and-netlify",
		"content":"Before I begin, a quick warning. I got things working, but I honestly cannot say this is the best way to do it. I'm still learning and my goal is to add Algolia to my site here, but I failed in that attempt. (I'll explain why after the main tutorial.) I first wrote about search and the Jamstack earlier this year, and one of the first comments I got was about why I had not covered Algolia. At the time, I didn't have a good answer outside of &quot;I just haven't tried it yet.&quot; Over the past week I've played with it off and on and I have to say I'm incredibly impressed even after a brief foray into it. As I said, what follows is rough, and just my first attempt, but I hope it helps others.\nI also want to note that my approach is pretty specific to Eleventy and Netlify. You'll see why Netlify was important but any Jamstack host that has support for &quot;run this on a build&quot; (for example, via a webhook) would be able to achieve similar results.\nBefore I begin, note that Algolia is a commercial service with multiple pricing tiers. The free tier allows for 50K operations and 10K records. That's incredibly generous but did cause a problem with my blog. (Again though, I'm holding off until the end.)\nAt a high level, Algolia is very similar to Verity and Lucene. I don't hear people talk about either of these anymore, but I spent many years using them with ColdFusion. You start off by defining your bucket of data that will be searched. Algolia refers to this as an index. Indexes have data as well as many options, for example, specifying how some properties may be more important than others. You can even do cool things like custom synonyms relevant to your data.\nThe index is a mirror of your data which is an important thing to keep in mind. Every time you add, edit, or delete data from your site you need to update the index to reflect that change. In an app server type site, that's trivial. On the Jamstack, it's a bit more complex. Here's how I solved it in a simple Eleventy application.\nI began by creating a boring blog in Eleventy. One home page, a posts folder and a couple of markdown files. Nothing else really.\n\n\n\nI'm not going to show the code for this part as I'm assuming you already have a basic understanding of Eleventy, but everything's in my GitHub repo I'll share at the end.\nFor my search, I decided to index the posts. These are all markdown files available to Eleventy as collections.posts. Algolia supports seeding your index with a JSON file, so I built the following:\n\nThe output of this will be a JSON collection of all my posts. I include the title, date, url, and content. (I'll explain the filters in a second.) I could also include post categories and other information. Algolia lets you index anything and you've got control over what's searched, which means you can include data you want to return in searches but not actually search against. You can see this live here: https://eleventyalgolia.netlify.app/algolia.json By the way, the reverse sort was just a way for me to see if it was updating properly with a large amount of data.\nSo there's two filters for my content, algExcerpt and jsonify. jsonfify just does a JSON stringify on the data. algExcerpt is a bit more complex:\n\nAs the comment says, I wanted to remove code blocks, and the text inside them, and HTML. Also, Algolia has a max record size limit on the free tier (10k). I figured 5k was enough to get the point across. I also decided it made sense to get rid of code as if I blog about code feature foo I'm probably going to mention foo more than once in the regular text of the post. Obviously these are a bit arbitrary. Also, I don't think Algolia has a problem with HTML, but I figured it would make the size a bit smaller. (On reflection, this may be even be a mistake. Algolia may recognize text in a h1 as being more important than a p tag.)\nWith that file generated, I manually uploaded it to Algolia and tested. Their dashboard is damn well done and really helpful. One of the best features is that you can search right away:\n\n\n\nOk, so technically, if you don't mind some manual work, you're done. You could simply reupload the JSON file every time you generate your site. And I know that sounds lame, but I can totally see people building a site that potentially gets updated pretty rarely. In that case it may be worth the (relatively minor) hassle.\nBut what if you do want to automate it? Here's where things get a bit... interesting. I knew that Algolia had REST APIs I could use. I also knew that Netlify has a deploy-succeeded event you can write custom code for. (See my blog post from last month on it.) My first issue was trying to determine how I'd know what changed since my last build. I couldn't really use the file system as Netlify checks stuff out to their system and the last modified values wouldn't match what I would expect. I toyed with the idea of using the date of my posts to grab the last new post. But this wouldn't handle edits to old ",
		"tags":[
	        
            "eleventy",
            
            "algolia"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Determining Food Popularity By Location",
		"date":"Tue Jun 23 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1592870400,
		"url":"https://www.raymondcamden.com/2020/06/23/determining-food-popularity-by-location",
		"content":"This is typically the kind of post I've write up on HERE's blog, but as I haven't blogged here in nearly two weeks, I figured I'm past due to share some content. As it stands, this is yet another one of my lame demos and nothing really serious, but I had fun building it and thought I'd share.\nOne of the API's that HERE provides is involves searching for POIs (points of interest) at a location. We provide two basic APIs for that. The first, Discover is useful for text queries, so for example, you remember dining at a place named &quot;Bob's&quot; something and you want to figure out exactly what it was. The other API, Browse, is better suited to category based searches, for example, finding all the banks near your current location.\nOne of the things I really like about our search APIs is the incredibly deep categorization feature. It's split into two versions, one that's generic and one that's based on types of food. The &quot;generic&quot; category API system goes very deep. So you can ask for &quot;nightlife-entertainment&quot; POIs, or more specifically a &quot;gambling, lottery, or betting&quot; establishment, or very precisely a casino.\nThat type of specificity also exists on the food types side. You can go from Asian, to Chinese, to over ten types of different Chinese cuisine. I thought I was pretty familiar with Chinese food, but I was blown away by the different types of Chinese food I had never known even existed.\nThe First Demo\nSo given all of this, I thought it would be kind of food to examine the types of food options available to you. For my first demo, I used Geolocation to determine your position, and then I asked for 100 restaurants near you. From that, I aggregated the types of restaurants as well as the food types and built a report. Here's how I built it. First, the front end:\n\nThis is fairly simple as all it does is show a loading message and then displays the food reports when done. The real magic is in the JavaScript. I'm going to share the entire CodePen below, so let me focus in on the important bits. First, my created event hook:\n\nI get the user's location (just using the browser's geolocation API) and then start working on my data. Next, I fetch all of my restaurant options:\n\nFor the most part, this is mainly just a call to the Browse API passing in the category ID we use for restaurants. Once I have my data I do a bit of manipulation and simplification. For example, setting up a top level open property. This comes from code I used in another demo and technically isn't even used in this demo.\nOnce I have my data, I then build my two reports. First, on restaurant types:\n\nYou'll notice I ignore the top level type so I can focus on more specific types. Then I get my food types:\n\nYou'll notice I look for a primary flag on the data. Any restaurant returned will have multiple food types, but only one will be marked as primary. I can totally see removing this restriction as it may provide better, or at least different results.\nBoth of the previous functions return simple JavaScript objects with the keys representing the name of the restauarant or food type and the value as the count. Both of these are passed to a simple utility function to return a sorted array.\nThe final result, for my location:\n\nAmerican - 24\nPizza - 7\nSeafood - 6\nInternational - 5\nItalian - 5\nSandwich - 5\nAmerican - Cajun - 4\nAmerican - Barbecue/Southern - 3\nGreek - 3\nBurgers - 3\nAmerican - Californian - 2\nThai - 2\nBistro - 2\nChicken - 2\nChinese - 2\nMexican - 2\nNatural/Healthy - 2\nIce Cream - 2\nBrunch - 1\nJapanese - Sushi - 1\nBreakfast - 1\nAsian - 1\nFrench - 1\nPastries - 1\n\nSome of this isn't surprising. While I live in the &quot;heart&quot; of the Cajun area, it's rare to find a proper &quot;Cajun&quot; restaurant. A lot of places will serve Cajun dishes, but not as their main attraction. I do think the Asian/Chinese numbers though are a bit low, as I can think of at least 5 off the top of my head.\nCheck out the complete demo below:\n\n  See the Pen \n  Food Report 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nDemo the Second\nFor the second demo, I wanted to kick it up a notch, mainly by making it easy to see what the food types look like in other areas. I start off with a full map of America (and that's my bias, I could use geolocation here and center on your location):\n\n\n\nIf you click on a location, I then get the data and report it.\n\n\n\nFor the most part, this code isn't too difficult, except that I removed Vue.js and kept it vanilla. The front end is rather bare so I'll skip it (will share full code in a bit), so let's focus on the JavaScript. I begin with code handling displaying the map:\n\nThis centers the map, sets a zoom, and adds touch support and basic UI controls. Next, I get some handlers to the DOM:\n\nNow I add a touch handler for the map. It needs to figure out where you are and then get the data:\n\nThe tap event is passed location information which let's me use our Reverse Geocode API to translate it into",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Netlify's Proxy Support for API Hiding",
		"date":"Wed Jun 10 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1591747200,
		"url":"https://www.raymondcamden.com/2020/06/10/testing-netlifys-proxy-support-for-api-hiding",
		"content":"For my blog post today I want to play with another Netlify feature, creating a proxy to another service. This is one of those simple things that just plain works. The docs are clear and to the point. That being said, I really wanted to see this for myself in action.\nIn a nutshell, Netlify lets you define a URL route for your site that will map to an external site. So for example, I can say a request for /cat will map to htts://thecatapi.com. That by itself isn't necessarily rocket science, but if you tell Netlify to use a 200 status code on the redirect, the network connection will be done entirely on Netlify's side and the end user never sees the redirect.\nThis means a lot of things. If you were using serverless functions just to hide an API key, you don't need to anymore. If you were using serverless functions to create an abstraction (in case you move from remote service A to remote service B), you may not need to do that anymore. While certainly not applicable to every case, it does mean that for simple key hiding and the such you don't have to write a lick of code.\nAs I said, this was documented well but I wanted to see it for myself. For my test, I decided to create a proxy to HERE's (my employer!) Geocoding and Search API. Now before I go any further, note that the keys you create for our services absolutely 100% allow you to use them in client-side applications and you can (should) use the host name restriction feature to ensure they can only be used in one place. My demo was simple - on load, make a request for places near a hard coded location within a hard coded category (food places). I live in Louisiana so I knew this would return upwards of three to four million results or so.\nHere's how it looks in client-side code:\n\nNote that the hard coded values for at and cat could absolutely be dynamic. I was testing Netlify here, not our API. When run in the browser, you see this in network tools:\n\n\n\nIf you dig into the headers and such nothing there will reveal what the destination is either. To make this work, I added a file, _redirects, which this:\n/geosearch/* at=:at cat=:cat https://browse.search.hereapi.com/v1/browse?apiKey=H2HPEplnWZvYwdCxIeyaFJf_RhOLUMzQXip2ADBNupY&amp;at=:at&amp;categories=:cat 200\n\nAs I said, the docs were good, but it did take me a minute or two to wrap my head around how query string parameters work. You do not include them in the route itself, but they after in a space delimited format. Order does not matter. I then map everything to the destination URL. Finally, I add the status code. Technically you don't need it if you aren't concerned with folks seeing the destination path. Why would you bother then? Because this will also fix cases where CORS isn't setup. I've often used the iTunes Search API in demos but stopped because some of the machines in their cluster don't support CORS. This would correct it.\nSo why wouldn't you use this versus a serverless function? If I use a serverless function I have more control over the data that's returned. An API might return 10-20 values where my code only needs 2-3. I can return a smaller subset and reduce the network traffic. I can also transform values if the API is, well, weird. Maybe it returns XML because it thinks we're still in the 90s. You get the idea. In these cases, a serverless function lets me massage the data before my front end works with it. Best of all, if I switch providers, I can take their data and reformat it to match the last provider. Of course, you could use the simple redirect feature for now and switch to serverless later using the same path!\nIf you want to see my simple demo in action, go to https://netlifydemos.netlify.app/test_proxy.html and you can see the complete source over at https://github.com/cfjedimaster/NetlifyTestingZone.\nOh, a quick note. Obviously a public GitHub repo is going to contain your _redirects file. In that case you would need to use a private repository instead. You could also deploy from the CLI, but than you use the CI/CD setup that Netlify shines at. If you really want a public GitHub repo than you'll need to use a serverless function and environment variable instead. (Not much work at all!)\nHeader photo by Martino Pietropoli on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Dual Selects Control in Vue.js",
		"date":"Mon Jun 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1591574400,
		"url":"https://www.raymondcamden.com/2020/06/08/building-a-dual-selects-control-in-vuejs",
		"content":"Earlier this week, an old friend of mine and all around good/smart guy Ben Nadel wrote up his experience on building a &quot;dual select&quot; control in AngularJS: &quot;Managing Selections With A Dual-Select Control Experience In Angular 9.1.9&quot;. If you aren't aware, a &quot;dual select&quot; control is one where two vertical columns of information are presented and the user can move items from one side to another. Ben had a great animated GIF on his blog entry that he was cool with me sharing:\n\n\n\nI've built these types of controls before but had not yet attempted to build it in Vue.js. With that mind, this weekend I worked on an example of it - both in a simple Vue.js application and also as a component version. While I'm sure this could be done differently (and I'd love to see examples in the comments below!), here's how I built it.\nVersion One\nAs stated above, I built my first version in a simple application. For thise I made use of CodePen which has recently added Vue SFC (Single File Component) support to their site. While not necessary for my demo I thought I'd give it a try for this first example. I began by building out my HTML. I knew I'd need two select controls with the multiple attribute and two buttons between them. One to move items to the right and one to move them back to the left.\nMy initial demo data consisted of an array of users, but to be clear this was arbitrary:\n\nI rendered the left select like so:\n\nNote that my option tags are iterating over my data but my v-model is connected to another value, leftSelectedUsers. The point of that is to let me have an array of &quot;initial&quot; data and an array representing values selected in the control. That value will be an array whether I pick one or more options.\nThe right side looks pretty similar:\n\nMy two buttons in the middle simply fired off respective calls to move data:\n\nYou'll notice I also use the &quot;double click&quot; event. This makes it easier to move one item quickly by just quickly clicking on an individual user. Alright, let's check out the JavaScript:\n\nIn both cases, I check first to see if anything has been selected. If so, I consider it an array and loop from the end of the array to the beginning. I do this because I'm going to be removing items from the array as I process them. The logic basically boils down to - for each of the selected items, I remove them from one array and add them to the other. Honestly that one part was the hardest for me. But that's it, and you can see it working below:\n\n  See the Pen \n  Vue Duel Select by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two\nAlright, so for the second version, I wanted to turn the above into a proper Vue component. I could have gone crazy with the number of options and arguments it took to allow for deep customization, but I decided to keep things simple and limit your options to:\n\nThe name of the left column.\nThe data in the left column.\nThe name of the right column.\nThe data in the right column.\n\nBecause CodePen can't (as far as I know) work with multiple SFCs in one pen, I decided to switch to CodeSandbox. On their platform, I created my component and set it up to support the parameters above. Here it is in it's entirety.\n\nIt's roughly the same as what I showed above (although this time you can see my lovely CSS styling), but with variables names that are a bit more abstract. Also note the use of the four props to pass in data. This then allows me to do this in a higher level component:\n\nWhich frankly I think is freaking cool. By binding the data I can now simply set/get the left and right side at will and let the user customize whats in each list. Here's the CodeSandbox version:\n<iframe\n     src=\"https://codesandbox.io/embed/duel-select-demo-ewsc9?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"duel select demo\"\n     allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n     sandbox=\"allow-autoplay allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n   >\nAs I said above, I'm sure there is a nicer way to build this and I absolutely wouldn't mind seeing examples below, and finally, thank you again Ben for the inspiration!\nHeader photo by Levi Stute on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Enhancing Your Netlify Build Notifications",
		"date":"Fri May 29 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1590710400,
		"url":"https://www.raymondcamden.com/2020/05/29/enhancing-your-netlify-build-notifications",
		"content":"One of the features Netlify supports is sending an email to you on various events. One of them is a successful build. Here's what it looks like:\n\n\n\nShort and sweet. But I really want a bit more information about the build, specifically how long it took. Netlify is quick, but my site is rather large. Every now and then I screw things up and one of the ways I can quickly tell is by seeing how long a build took. All of this information is available and I can go to the Netlify site to get those details, but it would be nice if my email simply passed that along.\nLuckily, Netlify supports triggers that let you fire off calls to serverless functions based on various events. Right now the details of the information sent isn't documented, so my initial work was just setting up a function (properly named, Netlify uses the name to associate it with the event) and using console.log to look at the payload. While kinda spelled out in the text, but not made clear, this data will be in your event.body value passed to the function and will be a JSON string. To look at the payload, I used this:\n\nThere's no way to retrieve logs from functions via the CLI so I used the Function tab in my Netlify site to view the output. It's hard to read so I literally copied it to my browser console, re-parsed it (it was in string form in the log of course), copied to my clipboard, and pasted it into my editor. Here's what payload looks like. There are a few values I think may be sensitive so I've replaced them with the name of my favorite character from My Little Pony.\n\nThat's a lot of data, but I can see what I need, published_at and deploy_time. I also think the summary messages are useful too. With that in mind, I built this relatively simple function to email me those details:\n\nI start off creating a body string that includes the bits I care about. I wrote a toMinutes function that pretty much mimics how Netlify itself renders build durations. I then pass this off to SendGrid to handle the mail. One thing I wish Netlify has that Pipedream does is a simple way to &quot;mail the owner&quot;. Ie I would love to do:\n\nAnd it would simply send it to the email address on file for the current site. And here's an example of the result:\n\n\n\nBy the way, &quot;Build Title&quot; is driven by the Git commit message and will be &quot;null&quot; in a manual build. I could support that in my email so it looks nicer, but I'm fine with &quot;null&quot;.  You can find the code for the function, and the rest of my site, up on GitHub: https://github.com/cfjedimaster/raymondcamden2020.\n",
		"tags":[
	        
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "Integrating Google Analytics with Eleventy",
		"date":"Thu May 21 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1590019200,
		"url":"https://www.raymondcamden.com/2020/05/21/integrating-google-analytics-with-eleventy",
		"content":"Before I begin, this article is not about adding Google Analytics to your site. Google provides a HTML/JS snippet you can just copy and paste into your code and that's about as simple as you can get. For Eleventy, you would do this in your main layout file so it's include everywhere. There ya go, if that's what you wanted, you can stop reading. ;) This article is about how to integrate Google Analytics data into your site, and is a followup to the blog post I did earlier this week demonstrating how to do that with Netlify Analytics.\nHopefully you've read that previous article as this one will follow a similar pattern. As with most things Google API related, I spent a huge amount of time with authentication and authorization issues and much less time using their API. It's gotten a point where I dread working with their APIs. Not because their APIs don't work well, but because authentication seems to be so difficult, especially if you're not using OAuth. Alright, enough of a rant, let's do this.\nGetting the Analytics\nGoogle Analytics has a REST API that lets you get any reporting information out via API calls that you would have available via the dashboard. Using it in a Node.js environment requires you to first create a service account. Generate the JSON key for that account and save it to your file system. (I'll use the file system for this information in the first portion of the article, and then talk about how to move away from that.)\nNext, you'll want to install the googleapis package. This contains wrappers for all of their supported APIs.\nNow for the crucial part. When you create a service account, it will include an email address in the data. It will look something like this:\n\nYes, &quot;damnga&quot; is &quot;damn google analytics&quot; - I was frustrated. Copy the email address, go to your Google Analytics dashboard, and add it as a user to the property your working with. This can be done via the Admin link.\n\n\n\nAll it needs is &quot;Read &amp; Analyze&quot; permissions. While in your dashboard, also click the &quot;View Settings&quot; link and get your View ID:\n\n\n\nAlright, once you've done that, let's look at the code, bit by bit at first. Start off initializing the library:\n\nNow you're ready to make reports. The API supports batching so you can ask for multiple things at once. In general most Google APIs are simple once you've gotten past the auth part, but the Analytics API is rather complex. I wanted a report over the past seven days of page views. Here's how I did it:\n\nFrom what I can gather, metrics is what you are asking for and dimensions is what you want back, in this case a report of the path that generated the page views. I do sorting and limiting as well. The result data is complex as well. I'll share it here but feel free to skim it:\n\nI turned this into simpler data like so:\n\nWhich gives me the simpler:\n\nWoot. That worked, now let's get this into Eleventy!\nIntegrating with Eleventy\nAs with my previous demo, I moved my Node code into an Eleventy _data file called popularpages.js. Here it is:\n\nOutside of &quot;shaping&quot; it into the format Eleventy wants, there's two main changes. First, I load in my Google auth via an environment variable. I took the JSON, removed the line breaks, and set it as an environment variable locally and as an environment variable in my Netlify site settings.\nThe second change is the filter call. For the site in question (JavaScript Cookbook), I only wanted to show popular articles and not include tag pages or other pages. As with the last example, this is the part you would want to tweak for your own needs.\nThen I put it on my home page.\n\nThe toData filter there is how I &quot;translate&quot; a path into the proper Eleventy data including the title and publication date. While the filter is pretty much the same as my previous example, here it is:\n\nAnd here's how it looks:\n\n\n\nYou can see it live at the JavaScript Cookbook and the complete code at the GitHub repo: https://github.com/cfjedimaster/javascriptcookbookstatic\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Updating my Reddit Workflow with Pipedream",
		"date":"Tue May 19 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1589846400,
		"url":"https://www.raymondcamden.com/2020/05/19/updating-my-reddit-workflow-with-pipedream",
		"content":"This was originally just going to be a tweet, but then I realized I wanted a bit more space to talk about it and figured I'd write it up as a post. And since this is my blog and I can do what I want to, you get to enjoy this little nugget of information.\nBack almost exactly a month ago, I blogged about using Pipedream to build a Reddit email report. The idea was that I wanted a daily email of posts from my subscribed subreddits for the past 24 hours of content. The implementation was a bit complex. I used one workflow to handle &quot;get a days worth of content from subreddit&quot; as a general &quot;API&quot; and another workflow connected to my authentication. It handles getting my subscriptions, hitting the API, and then generating the email. Here's an example of how that looked:\n\n\n\nThis worked well, but after a while of actually getting the email, I noticed some problems. The email takes all of the posts from all of my subscriptions and sorts them together. I thought this made sense to me, but I noticed it made it harder to actually read the content. Sometimes I don't care about a subreddit and mentally it just felt weird going from the movies subreddit to the Acadiana one. Also, some subreddits get a huge amount of traffic in a day. The email was hard to read and just too long.\nSo I decided to fix that. I didn't want to edit my original workflow because I wanted it to still be a reference for the older post. Luckily Pipedream makes that simple. I turned off the CRON schedule on the workflow and just used the copy command.\nNext, I edited the Node.js code step that combines and sorts my data. This:\n\nBecame this:\n\nNo more sorting together and I'm only getting the first ten entries from each subreddit. (Those posts should be date sorted already. Should be.)\nThen I modified the step that formats the email. I added in code to notice when a new subreddit start and added HTML to make it more visibly separated.\n\nBasically - notice when the subreddit changes and add a horizontal rule and header. Also notice I modified my code on when to show images. This seems to work much better.\nAnd that's it. My new workflow may be found here: https://pipedream.com/@raymondcamden/daily-reddit-posts-3-p_PAC9DV/edit?e=1c8nZNETuFxToNCya2eVCafjVAu I'm trying my best to make good use of the Readme feature to document what I've done.\n",
		"tags":[
	        
            "pipedream"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Integrating Netlify Analytics and Eleventy",
		"date":"Mon May 18 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1589760000,
		"url":"https://www.raymondcamden.com/2020/05/18/integrating-netlify-analytics-and-eleventy",
		"content":"Before I begin, know that I'm using an undocumented part of the Netlify API so you should proceed with caution. I've been waiting for them to release the docs for sometime now (although it didn't stop me from building my own demo) and I'm not sure if it will ever happen, but in the meantime, I'll continue to play with it. Alright, so with that out of the way, this weekend I worked on a cool little thing I've added to my blog. While you can see it on the right hand side, it's this list of links here:\n\n\n\nThis list was created by hitting the Netlify Analytics API for the site, getting the most viewed content in the past seven days, and then &quot;manipulated&quot; a bit before rendering. Let me describe the steps it took to get here.\nGetting the Analytics\nStep one was to get the raw data. First, I created a Personal Access Token. This is done under your user profile at Netlify in the Applications section:\n\n\n\nOnce I had the key, I first wrote a script to get all of my sites. This was just so I could get the ID of my blog.\n\nWith the ID, I then used the undocumented API to get pages with the most views. I filter to a date range from now till seven days ago. In case your curious, I discovered these API calls by using my browser developer tools.\n\nThis is how the result looks:\n\nRight away you'll notice the first result is for the home page, something I'm going to ignore. The second result, /recentPosts/, is a result of an optimization I did for the site that I'll explain in a bit, because it comes into factor for this how I added this feature as well.\nAlright, let's get this into Eleventy!\nIntegrating with Eleventy\nI began my integration with Eleventy by adding a new global data file named popularposts.js. This is - easily - one of my favorite features of Eleventy. By setting this up in my global data file I'm able to have it available for my pages later. Here is the code in the proper format with Eleventy:\n\nThere's a few things different though. First, note that the token and siteId are loaded via environment variables. I set these up in my site settings and noticed that it didn't work with netlify dev. This usually does work fine so I posted on their support forums to see what's up. In the meantime I just set the variables myself.\nNext, notice I added a filter to remove both / and /recentPosts/. I think most folks will need the first one, but not the second. It may have been better to use a regular expression to only match posts. Since my posts are all date based, I could have looked for /2*** for example.\nThat worked great but then I realized a problem. While the Netlify API returned the path to the page, it didn't return the title or date of the blog post. This is where things then got a bit tricky. At the time this data file runs, you do not have access to collection information, where my posts live. That's because data drives the pages so it has to load first.\nIn order to get this working, I did the following. First, here's my layout:\n\nI first see if I have popularposts (my data call could fail), and then loop over each result. For each, I use a filter, toTitle, to &quot;convert&quot; the path into page data that includes my title and date. (So toTitle isn't the best name.) This filter is defined in .eleventy.js:\n\nFor each path, I loop over the posts collection, look for a match, and reutrn the title and date if so. Notice I use a cache for performance.\nThis worked well, but when I initially put in my template, it required a rebuild of every single page in the site when run. Because of that I employed the same technique I used for my last five posts content. I put them both in a single file template (/recentPosts/) that's loaded via a quick jQuery call:\n\nI don't even use JSON, I just load the raw HTML right into the DOM on the side there.\nAnd that's it. Now, one thing you'll probably notice is that this data is only generated when I built the site. I can easily address that by scheduling a daily build. But as I blog once or twice a week very consistently and since this isn't &quot;business crucial&quot; information, I'm fine with it updating whenever I post a new blog entry (or make another tweak, like to my speaking page. If you want to see more of the code behind this, you can find it at the repo for this blog: https://github.com/cfjedimaster/raymondcamden2020.\np.s. I'm also planning on looking at a Google Analytics version of this. They've got an API so if I get time this week, I'll post a follow up!\nHeader photo by Isaac Smith on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Let's Make Everyone a Queen!",
		"date":"Fri May 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1589500800,
		"url":"https://www.raymondcamden.com/2020/05/15/lets-make-everyone-a-queen",
		"content":"Forgive the somewhat over the top title. For a while now I've been meaning to make an application with a nifty little JavaScript library called Tracery. Tracery is a library created by Kate Compton. It's a fascinating tool for generating text based on a set of inputs. I saw fascinating because sometimes it makes some pretty incredible little stories. For example:\n\n\nThis is a story about a faceless man. You know, the faceless man who hardly ever crys when they feel the forest. Well, I was listening to the faceless man, when we both saw this tree. Blinking, orange...well, more of a blueish white. We backed away because as everybody knows, trees don't exist. That was the last we saw of it. And now, the weather.\n\n\nMusic plays. You recall summertime and pain. You recall a lover and a friend. Operatic folk harpsichord echoes out into dissonance.\n\n\nYou know, I miss the tree. It was pretty terrible. I mean, really beautiful, for a tree. Eventually, I hope it comes back. We'll see it, glistening, grey...well, more of an indigoish indigo. But it'll be back. I mean, eventually. If not, it's just so bewildering.\n\n\nSo yes, that's a bit crazy at times. But there's something interesting about it. If you reload the site you'll see new random generated stories and I could spend quite a bit of time seeing what it does.\nI first became of aware of this library when I discovered @dragonhoards on Twitter. This is a bit that makes use of the library. Here's an example tweet that's both interesting and horifying at the same time:\nA magical dragon lives by an enchanted lake. She estimates her hoard, which consists of a heap of memoirs, a group of pies, and an unknowable amount of corpses. She is exhausted.&mdash; Dragon Hoards (@dragonhoards) May 14, 2020 \nAt the simplest level, Tracery works by combining different arrays of input values. So for example, given this input:\n\nYou can generate a random sentence like so:\n\nThe code starts with sentence and looks for tokens. For each token it will look for a corresponding array of values and select a random one. Here's an example result:\nThe turquoise lizard of the river is called Mia\nThat part is relatively simple, but Tracery gets very complex. So for example, it supports picking a random animal once and re-using the same value again if you need it. Honestly the complexity goes beyond what I think I can understand currently, but she's got a great testing utility you can play with here: http://www.crystalcodepalace.com/traceryTut.html. And of course, it's up on GitHub: https://github.com/galaxykate/tracery. Note that you want to make use of the tracery2 branch, not master.\nOk, so with that being said, I thought it would build a few demos with this.\nThe Web Site\nAs I said, Tracery is powerful, but complex. While I had a end game in mind (the second demo I'll be showing), I thought it would make sense to start with a web site first to keep it simple. As the title of this post suggests, it's all about making you a queen. I had my daughters in mind but anyone can be a queen if they want. Here's the end result:\n\n\n\nYou can demo this yourself here: https://queenof.netlify.app/#Lindy Notice I've included the name in the URL. You can change the hash mark to whatever, or just type whatever you want in the form field. The basic pattern is relatively simple: X is the queen of A, something of B, and something else of C. It's built using Vue.js because of course I'd use Vue for this. Here's the code:\n\nThe crucial bits are the origin value as that forms the basic structure of the random sentence. I leave off the beginning because that will be the name. The VUe parts then are pretty trivial. Setup Tracery and wait for you to enter a value (although note that mounted will notice the hash).\nIf you want, you can peruse the entire code base here: https://github.com/cfjedimaster/queenof\nThe Twitter Bot\nSo as I said, I had an endgame and mind, and that was a Twitter bot. I've got something of a problem when it comes to creating Twitter bots, but I'm sure I got stop whenever I want to. Using Pipedream, I built a Twitter bot at @generatorqueen. She works rather simply. Send her a tweet with &quot;queen me&quot; in the text and you'll get a response within a minute.\nYou are the Queen of the Earth, Chancellor of Smarts, and Creator of Plants.&mdash; Queen Generator (@GeneratorQueen) May 15, 2020 \nI built this using a Pipedream workflow you can find here: https://pipedream.com/@raymondcamden/queen-of-bot-v2-p_MOCQen/edit. Don't forget that one of the coolest features of Pipedream is that you can share workflows with others so they can fork and use for their own purposes! Let's break down the workflow bits.\nI began with a Twitter search event source. I blogged about these last week. They are a powerful way to build event driven workflows. In this case the event source is simply a Tweet that matches &quot;@generatorqueen&quot;.\nNext I have a custom Node.js step to do validation on the text:\n\nRemember that $end is Pi",
		"tags":[
	        
            "pipedream",
            
            "vuejs"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Want to Learn Vue.js?",
		"date":"Thu May 14 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1589414400,
		"url":"https://www.raymondcamden.com/2020/05/14/want-to-learn-vuejs",
		"content":"As my readers know, I'm somewhat enamored with Vue.js. My buddy Brian Rinaldi manages an online platform both presentations and virtual events called Certified Fresh Events. On June 16th, I'll be giving a three hour course on working with Vue.js:\n\n\n\nThe price is $49 USD and you can register, and find out more, here: https://cfe.dev/events/vue-workshop/. If you have any questions about the content, feel free to leave me a comment below or reach out on Twitter. I hope to see you there!\nHeader photo by Changbok Ko on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Notes on Upgrades to WSL2 (And Why You Should)",
		"date":"Fri May 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1588896000,
		"url":"https://www.raymondcamden.com/2020/05/08/notes-on-upgrades-to-wsl2-and-why-you-should",
		"content":"For a few years now (well, it feels like many years), I've been singing the praises of WSL - Windows Subsystem for Linux. It's one of the biggest reasons I switched to Windows after years on OSX. (Not the only reason, but you don't want to hear me rant about Apple.) The only real issue with WSL was the slowness of file operations. There were technical reasons for this of course, but honestly it only really bugged me when doing npm operations.\nAs you know, running npm i something tends to fetch an incredible amount of files. In my completely unscientific testing, I'd say these operations were 2-5X slower in WSL than in the native Windows shell. Another command that would be slow at times was git status. Outside of that though it was just fine. I'm no command line jedi but I definitely preferred the Unix shell to cmd.exe or Powershell.\nA while back Microsoft announced a major rework of WSL. You can look up the technical details if you want, but after going through the process of upgrading to WSL2 on two machines now, I can say that I'm absolutely blown away by the speed improvements.\nWith that in mind, I wanted to share a few tips on the process and how it impacted my development. This isn't meant to be an introduction to WSL or a complete guide to using it, just what I encountered and what I changed in the process. I had some great help from Microsoft folks on Twitter so I definitely encourage you reaching out if you run into issues as well.\nAlright, so start off by going to the WSL2 Install guide. You'll note that you need Windows version 18917 or higher in order to use WSL2. I'm currently on the Insider Slow Ring at version 19041.208. Insider Slow Ring gives you earlier access to new Windows features and at least for me has been really stable. If you don't know what version of Windows you're on, just run winver.exe, which by the way you can do from WSL.\nAs part of the install process, they document how to check your WSL installs. You can do this by running wsl -l -v:\n\n\n\nTo update, you simply run wsl --set-version Ubuntu 2 where Ubuntu is the name of your distro and may be different. You may get prompted to install stuff first:\n\n\n\nI followed the directions there which basically had you copy and paste stuff into Powershell (be sure to run Powershell as an Administrator). I then ran the command again and got:\n\n\n\nOnce again, follow the link, do the install, and you should be good. One one machine I had to tweak my BIOS, but on the other the install was good enough.\nNow if you run the update command again, you should hopefully get:\n\n\n\nOn my fancy new laptop, this was rather quick, unfortunately I don't remember how long. On my older desktop, this was not quick. I think it took maybe 30 minutes. When done though you can quickly confirm it's been updated:\n\n\n\nYou can also make WSL2 the default by doing this: wsl --set-default-version 2. I only run one distro on my machine so this really isn't an issue for me.\nOk, done! But wait, there's some very important things to note. I did some immediate testing the first time I did an update and noticed that npm was not faster. I was disappointed, reached out on Twitter, and was reminded that if I'm still using the main Windows filesystem (/mnt/c), file IO is still going to be slow. I did some testing under ~/ and right away saw a huge boost. I typically do all my work in /mnt/c/projects, but simply set up ~/projects as my new place to do crap.\nThis then quickly led to an issue - how do I edit files there? I've got two answers to that.\nFirst, you can browse your distro file system in Explorer.exe by going to \\\\wsl$:\n\n\n\nYou'll see your distros there and can work with your file system. In my testing, I've noticed that when I copy files over to Ubuntu, I sometimes get a copy with &quot;:Zone.Identifier&quot; in it. This is known and I believe has something to do with AV stuff. You can read more about it here: Zone.Identifier Files when copying from Windows to WSL filestructure. For now I'm just deleting when I see them, typically before I do a git commit.\nAlso, I believe I read that WSL is going to be more tightly integrated into Explorer in the future. So there's that.\nThe next thing was getting support for Visual Studio Code. This was also pretty easy if you add the Remote-WSL extension.\nYou add that extension, then run &quot;Remote-WSL: New Window&quot;. This will open a new window and do a one time download of support stuff. For me it took about 2 minutes I think. When done, you'll have nothing in the file explorer - at first:\n\n\n\nNote that it says &quot;Connected to remote&quot; and it has a &quot;WSL:Ubuntu&quot; marker on the lower left corner. Clicking to open a folder will bring up UI on top to browse:\n\n\n\nThis was a bit weird to me at first, but once you select a folder, everything returns to normal. So here's a screen shot of my editor right now. You can see the &quot;WSL&quot; marks so I know it's in the Ubuntu file system, but outside of that, everything is &quot;nor",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Looking at Pipedream's Event Sources",
		"date":"Thu May 07 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1588809600,
		"url":"https://www.raymondcamden.com/2020/05/07/looking-at-pipedreams-event-sources",
		"content":"Before I begin, know that everything I'm discussing here is currently in beta form. It may, and will, change in the future so please keep that in mind if you are reading this in some post-Corona paradise where we can actually do things out in public. The feature I'm talking about today adds a really fascinating feature to Pipedream - Event Sources.\nLet me start off by explaining why this feature came about. Imagine you're building a workflow based on a RSS feed. RSS feeds contain a list of articles for a publication of some sort. Each item will contain a title, link, some content, and more properties. Let's say you want to send an email when a new item is added to the feed.\nRight now you would build this like so:\n\nSetup a CRON trigger. Your schedule would depend on the type of feed. For my blog a once a day schedule would be fine. For something like CNN, maybe once every five minutes.\nParse the RSS feed. There's a RSS action that does this for you:\n\n\n\nBy that way, it may not be obvious, but that action actually supports multiple feeds which is pretty bad ass.\nThen take the items and email them. This is simple enough, but you've got a few problem. How do you know what's new? Luckily you don't have to worry about that, the RSS action Pipedream supplies uses the $checkpoint feature I blogged about last month to remember this for you. \n\nCool. So that's that. But this also assumes you're ok working with multiple items at once. In the case of &quot;email me new items&quot;, that makes sense. You want one email with all the new items. The same applies to a Twitter search workflow. You want a packet of results. But what about a scenario where you want to process each item individually?\nWell ok, you work in a loop. For every item do - whatever. Again, for simple workflows that would be enough. But for anything complex, you may have trouble. Pipedream workflows don't support a &quot;loop this step N times&quot; type logic. I know they are considering conditional steps, but I'm not sure about looping.\nOne solution would be to build a second workflow that takes a singular item in as input. You then have a two workflow solution. The first one is responible for gathering the data and creating a list (with optional filtering involved) and then it calls out to the second workflow which handles unique items. I used an approach like this here: Building a Reddit Workflow with Pipedream\nSo as I said, you have solutions, and that's good, but Event Sources really make this so much simpler. At a basic level, an event source is custom code you write to handle defining a custom workflow trigger event. By default, your workflows can be trigger by time (CRON), URL, email, or the REST API. Event Sources lets you define anything as a source for firing workflows.\nImagine you wanted workflow based on the full moon? Event sources would allow that. (Werewolves will love you.) A bit more realistically, what about a workflow that triggers on the first Monday of the month? That's not possible with CRON, but event sources would allow that as well.\nEvent sources consist of a schedule and your code. The schedule determines how often it runs. For something like the full moon or &quot;first monday&quot; example, once a day would make sense. The code is whatever your logic is. The &quot;magic&quot; part that makes it an event source then is that it simple emits data for every instance of an event. You can find out more at the docs, but let's look at an example.\nImagine our RSS scenario. Given that we can parse RSS and know what's new, our RSS event source would then emit data for every item:\n\nHere's another snippet for an event source that fires on the first X of the month:\n\nSo how do you use it? When you create a new workflow you can now select from Event Sources as a source:\n\n\n\nIn the screenshot above you'll see a number of items below SDK. Those are all previous event sources I've used. When you add a new event source, you configure it and name it, and it makes sense that you may want to use them again.\nIf you click on Event Source, you then get a list of available sources. (Note that you can add a 100% customized one using the CLI. Also note that you can edit the code of an event source.)\n\n\n\nOnce you select it, you can then set up the parameters. Each event source will be different.\n\n\n\nIn this case I used Pipedream's blog's RSS feed. At the bottom (not shown on the screen shot above) is a Create Source button. After doing so, your event source is configured and ready to be used in your workflow:\n\n\n\nWell almost. By default event sources are turned off. See the little toggle on the right. I believe they do this for cases where you may want to setup your workflow first before it starts firing off events. Just don't forget.\nEvent sources have their own administration panel at Pipedream. You can view them at https://pipedream.com/sources/.\n\n\n\nFor each event source you see a history of past events, logs, and configuration. You can also modify the cod",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Modifying Prism's Copy Plugin",
		"date":"Tue May 05 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1588636800,
		"url":"https://www.raymondcamden.com/2020/05/05/modifying-prisms-copy-plugin",
		"content":"This post definitely falls into the &quot;I'm Not Sure This is a Good Idea&quot; Department, but I thought I'd share on the wild chance it was useful to others. I've been using Prism for source code blocks for sometime now and I like it fine enough. Recently I was working on a presentation about technical documentation. While working on the slide deck, I came across a code sample that had some line breaks in it:\n\n\n\nIn the code sample above, the curl command is broken over multiple lines. This isn't valid, but is nicely readable. Compare that to this version:\n\n\n\nThis is also nicely readable, but did you know that Windows users can't use \\ to break commands like that? The main terminal, cmd.exe, uses ^ instead. It's a minor issue, but it's something I'm cognizant of when I write docs. Developers, right or wrong, will copy and paste things as they learn and something small like this could trip them up.\nOk, so back to Prism. It turns out that it has a plugin to support copy to clipboard support. When downloading, you simply select that plugin and it will be added to your code samples automatically. There's nothing else you need to do which is pretty nice. While this isn't a Prism tutorial, here's how the HTML looks:\n\nNote, that is not meant to be a real example of a curl call. You can see a demo of this here: https://prismcopytest.now.sh/v1/.\nCool. So given that Prism can add a &quot;Copy&quot; command to code blocks, can we modify it such that we can show a command line example like we have above but when put in the clipboard it's all one line? Turns out you can.\nThe first thing I did was redownload Prism without the Copy plugin. The docs for the plugin include all the code there so I knew I could use that as a base. I did make the mistake of not including the Toolbar plugin. It's required so you need to ensure that option is checked.\nNext, I added the Clipboard.js CDN above my prism.js load like so:\n\nIn a second you'll see the Copy plugin code and it actually supports loading this dynamically, but it felt better to include it manually.\nAlright, so looking at the source of the plugin, the portion that handles getting the text to copy is this:\n\nI surmised that env.code was the code block. I confirmed this by adding the plugin code to my page and just adding a console.log message above it.\nWith that in place, I then did a quick modification:\n\nBasically, split the code along new lines, join them back together in a string, and then replace whitespace with one space. And... it just plain worked. If you go to the next version (https://prismcopytest.now.sh/v2/) and click copy, you'll end up with this in the clipboard:\ncurl -x &quot;foo.html&quot; -z &quot;doo&quot; -B true -D {&quot;name&quot;:&quot;ray&quot;, age: 47, &quot;code&quot;:&quot;not real&quot;} &gt; output.txt \n\nThis would (should) work in any operating system and is nicely readable on screen. You could make the argument that a user may try to type it as they see it, which would break, hence me prefixing this entire blog entry with &quot;it may not be a good idea&quot; - but it works. Anyway, what do folks think?\np.s. While I think this is a good example, I typically despise places that mess with the clipboard. A lot of places will prefix selected text with something like, &quot;Copied from ...&quot; which I just find incredibly annoying.\nHeader photo by Eva Blue on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Playing with QuickChart and Vue.js",
		"date":"Sun May 03 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1588464000,
		"url":"https://www.raymondcamden.com/2020/05/03/playing-with-quickchart-and-vuejs",
		"content":"A while ago my buddy Todd Sharp shared a cool resource with me, QuickChart. QuickChart is a static image only version of Chart.js. By &quot;static image&quot; only I mean you can generate a chart via a URL request and get an image out, not a dynamic JavaScript-based chart. This is a good replacement for a service Google killed off about a year ago, Image Charts.\nWhy would you go for an image-based chart instead of a dynamic one? Emails are probably the best example. Also, I think many times you do not need interactivity. If you're showing a simple chart and the user doesn't need to toggle items off and on, than there's no real point in having an interactive chart. (And as I'll show, you can still have some interactivity with this library.)\nQuickChart works by essentially creating a server wrapper to Chart.js. (An open source server that you could host yourself if you want, but their free tier is pretty dang good.) You craft a URL that, for the most part, matches Chart.js, and then that's it. So for example:\n\nThe URL can get rather complex, but the end result is just an image.\n\n\n\nSince most of the work is done on the chart.js side, you'll want to have some basic familiarity with it before starting, but it isn't a difficult library to use. Also, QuickChart will return nice errors when you screw up. For example, I added an extra } to the URL below:\n\nAnd the result:\n\n\n\nCool, so given that it's just simple HTML, how can we integrate Vue with it? For my first demo, I setup an image that was bound to a computed property:\n\nThe chartSrc output above the image is just there for testing. On the Vue side, I moved my chart data into Vue's data block and set up my computed property:\n\nThe only thing really fancy here is ensuring months is output as a quoted string. Thank you StackOverflow for having a great solution. You can play with this in my CodePen:\n\n  See the Pen \n  QuickCharts1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nBecause I've tied my QuickChart URL to data and because Vue is so awesome, as soon as my data changes, so will my chart. Now I said above, if you want interactivity you probably want the &quot;real&quot; library. But I think for some simple use cases, it's fine to use this approach. I modified my code to update the values every three seconds. At that speed I'd definitely use the JavaScript charts instead, but I wanted something you (my favorite reader, yes you) could easily see.\n\n\n  See the Pen \n  QuickCharts2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo what about a &quot;real&quot; example? I built a chart based on API on CovidTracking.com's set of APIs. Specifically the data related to my home state, Louisiana. I modified my HTML a bit so as to not have the image rendered until data was ready:\n\nAnd then I updated my JavaScript to make use of the API:\n\nIn created I fetch my data and then immediately reverse is so the first values are the oldest. I then create an array of positives, deaths, and labels. I could then put this on a web page and every day the chart would have the freshest data, but still be just a simple image. Here's the CodePen for this version (feel free to fork and change the state):\n\n  See the Pen \n  QuickCharts3 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAll in all, I think this is a pretty sweet service. As I said, crafting the URL can be a bit delicate. I'd suggest using something like Postman to test, but once that's done, it's just an image tag. If you are interesting in more information about charting and Vue, I wrote up a comparison article a few months back you might find helpful.\nHeader photo by Isaac Smith on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "My Tech Stack (So Far) in 2020",
		"date":"Wed Apr 29 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1588118400,
		"url":"https://www.raymondcamden.com/2020/04/29/my-tech-stack-so-far-in-2020",
		"content":"From time to time I like to share my current tech stack, both in terms of what I use for development as well as what I use in production. It's been a while since I did this and a few days ago someone reached out and asked me about it:\n\nHello Raymond, I've read your ColdFusion blog for a while now and found it quite helpful. Lately it seems like you have largely switched to Node and Vue for back end and front end development respectively. I wanted to ask why you picked Node and Vue rather than some of the other back end frameworks like Django, Spring, and Rails or front end frameworks Angular and React. My apologies if you have already made a post discussing this but I am very curious.\n\nLet me start with my local development platform.\nOperating System - Windows 10\nI've been a Windows user for a few years now (and long ago of course). I started to get a bit dissatisfied with Apple a while ago and seeing WSL (Windows Subsystem for Linux) at a conference was all it took to push me back to it. It certainly is not perfect, but it works for me. I use the Windows Insider Slow Ring version to preview new features and generally it's been stable. I also liked that it gave me more choices in hardware. And on that note...\nLaptop - Dell XPS 15\nInitially my switch back to Windows was with the Surface Book (and then the Surface Book 2). The Surface Book is an incredible machine. I used it at some conference for a few minutes and immediately fell in love with the keyboard and just dug the machine in general. While I liked it, I realized I never used one of the core features - separating the screen from the keyboard. I'm one of those people where if I don't have my phone/tablet in a protector of some sort, I just don't use it. I'm paranoid I'm going to drop and shatter my device. (Oddly enough, I did drop my Surface Book, the complete unit, and had to send it in for repairs.) Given that it felt like I was paying for functionality I didn't want, I decided to go with Dell. Before I had switched to Macs, I was primarily a Dell person and while I've had trouble with their support in the past, I generally liked their hardware.\nThe XPS 15 is an amazingly fast machine with a great screen. My only real complaint is the sound. While I don't expect a lot from a laptop, but sounds really muffled, especially when it's on my laptop. Of course headphones remove that problem. I've also got a Dell desktop that I use when I'm not on the road, but with Corona, I've been 100% (mostly) laptop based while the kids are around. I'm going to replace that desktop with a newer version later this year.\nEditor - Visual Studio Code\nI've been using Visual Studio Code since it first came out and honestly I'm kinda blown away by how good it is. As with Windows itself, I use the Insider version so I can get newer features sooner. In the two years (I think) I've used Insiders, I've only had to &quot;revert&quot; to the mainline Studio version once or twice which I think is a pretty amazing record.\nI was a huge Adobe Brackets fan, but Adobe has left that to die unfortunately. They did have a recent release, but in general development has slowed to a crawl. (Looking at their blog, they had two releases in 2019. Two.)\nThe only thing I miss about Brackets is that extension development was somewhat simpler. I do have a few extensions released for Code so it's not impossible to do at all, just not... easy. Speaking of extensions:\nEditor Extensions\nThe extensions I use tend to fluctuate a bit, and honestly, looking at my extension list now there's some I simply forgot I installed. I'll try to list the ones I actually know I use on a semi-regular basis.\n\nBetterTOML - literally just for when I'm editing toml files related to Netlify.\ncoldfusion - for when I want to pretend it's 2005 and I still write ColdFusion. Ok, that was snarky. I do still write ColdFusion from time to time and this extension provides basic language support.\nEscape HTML - my extension! It lets you select code and will output (in a new panel) the escaped HTML version. I use this in blogging.\nMarkdown All in One - a great extension for folks who write a lot of Markdown.\nPeacock - fun little extension that can automatically change the Window colors of your Code editor. Nice when you've got multiple windows open and need to differentiate between them.\nPolacode - I don't use this often, but it provides screenshots of code. I'll use it if I'm building with Powerpoint, but lately I've been back on the Reveal.js bandwagon.\nSpell Right - for spell checking. I do a lot of technical writing in Code. Heck, probably more than I write code.\nVetur - for Vue.js support.\nVSCode Map Preview - for GeoJSON support in Code.\nWord Count - for - wait for it - word counts. Again, I do a heck of a lot of technical writing.\n\nBrowser - Microsoft Edge\nI was a Firefox user for some time (and have a huge amount of respect for Firefox) in general, but decided to give Edge a try when they switched to the Chromium engine. I was p",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Google Sheets Twitter Bot with Pipedream",
		"date":"Mon Apr 27 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1587945600,
		"url":"https://www.raymondcamden.com/2020/04/27/building-a-google-sheets-twitter-bot-with-pipedream",
		"content":"This is something that's been kicking around my head for a week or so and today I thought I'd try it. It ended up taking about 20 minutes total and 10 lines of code, of which 5 are a function I copied and pasted. While what I built is kind of trivial, I'm blown away by how much was done by built-in functions with Pipedream and how little work I had to do myself. In fact, most of my time was spent in setting stuff up outside of Pipedream itself. Alright, so what did I build?\nI've got a kind of fascination (ok, a problem) with building Twitter bots, especially those that share random content. Earlier this month I created a Twitter bot that uses Wikia APIs to scrape GI Joe content. One problem with my &quot;random bots&quot; is that, well, they're random, and I don't have full control over the data itself. It's possible there's something on the GI Joe wiki that I'm not aware of. And since it's a wiki, even if I check every single page now, in the future something may be added that I don't want my bot to pick up.\nSo I thought - what if the random bot was tied to content that I had full control over? Also - what if the content was in an easily editable form, something a non-developer could use. It occurred to me that Google Sheets could be great for this. With that in mind,  built moonpicbot. This is a bot that shares pictures of the moon driven by public domain NASA images.\nTriptych of the Moon pic.twitter.com/VNCEcTwymp&mdash; moonpicbot (@moonpicbot) April 27, 2020 \nWhile NASA may have an API (I'm pretty sure they do), I instead built a Google Sheet where I manually selected some pictures I thought were nice.\n\n\n\nI then registered my bot which is mainly painless now that I've done it multiple, multiple times. ;)\nWith my data in place, I designed the following workflow in Pipedream:\n\nUse a CRON trigger to schedule the tweets. Currently mine's once every two hours.\nConnect and read my Google Sheet.\nSelect a random row.\nUpload the image.\nTweet the text and the image.\n\nAlright, here's comes the cool part. Pipedream handled steps 1, 2, 4, and 5. I've shown their CRON trigger before, but here's the Google Sheet action. I connected it to my app and pasted in the sheet ID:\n\n\n\nMake note of the range. My sheet uses two columns so my range goes from A2 in one corner (A1 is the header) to B999 in the other. That means if I ever have one thousand rows I'll need to edit the range. That will take about 5 seconds so I'm not concerned, and again, since I'm manually controlling the data for this bot, I'll know.\nStep 3 is where I wrote code:\n\nThat's a bit over ten lines of which about half is a function to handle getting the random value. I slightly modify the image to include https (the NASA site didn't have this) and hard code a mimetype.\nAnd that's it. I'm done. I added a upload_media_to_twitter step and then a post_tweet step. I could share this sheet now with a non-technical user and they could control the bot as they see fit. You can see, and fork, the entire workflow here: https://pipedream.com/@raymondcamden/random-moon-p_WxC9jR/edit\nHeader photo by Sanni Sahil on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Reddit Workflow with Pipedream",
		"date":"Mon Apr 20 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1587340800,
		"url":"https://www.raymondcamden.com/2020/04/20/building-a-reddit-workflow-with-pipedream",
		"content":"Almost four years ago I blogged about a demo I built using Reddit's API. The demo was a multi-user application that made use of Mongo for persistence and Passport.js for user authentication. You would login, select subreddit's to subscribe to, and then once a day it would email you the new posts from that subreddit.  This was built in a &quot;traditional&quot; Node.js style with a server running full time to process requests. I thought it would be fun to build this again (although slightly different) using Pipedream's support for working with the Reddit API. I built two different versions of a simple workflow I'd like to share below.\nSo before I begin, how does Pipedream support Reddit? Currently, Pipedream doesn't have a &quot;service explorer&quot; or a way to see what pre-built actions are available. What I've been doing is going to a test workflow I use for, well, testing, and click to add a new action. I then browse what's available. If you know what you want, you can click the name of the app to filter. Apps will be at the end of the list of actions:\n\n\n\nAfter you click it, the actions are then filtered to items within it. In the case of Reddit, this is quite a bit:\n\n\n\nYou can type to filter even more. For my case I knew I wanted to get new posts so typing &quot;new&quot; was enough:\n\n\n\nFinally, once you select the action, note that the you probably still need to know about the API itself. Sometimes the properties are obvious, but sometimes they aren't. So in the example above, I knew what Subreddit meant but wasn't sure about the value of &quot;after&quot;:\n\n\n\nSo in that case, I simply used the Reddit API documentation.\nAlright, so with that out of the way, let me talk about what I built.\nReddit Demo Version One\nFor my first workflow, I attempted to recreate my Node POC in a simpler manner. When you add an application to your Pipedream workflow and authenticate it, it's tied to your account, so instead of trying to build something multi-user with Mongo and all that, I settled on a simpler idea.\n\nGet the new posts from one subreddit.\nEmail them to me.\n\nMy workflow ended up with the following steps:\n\nA CRON trigger set to run once a day.\nA Node.js trigger to specify the subreddit name:\n\n\n\nNext, I used the &quot;get new&quot; Reddit action. While this supports an &quot;after&quot; filter, that relies on the ID of a post. There is no way to apply a date filter. I figured I'd fix that later. All I specified here then was the subreddit:\n\n\n\n\n\nAs I said above, there's no way (that I know of) to filter to today via the API. So I added a Node.js step to filter to posts no more than 24 hours old.\n\n\nLet me just say I'm very proud of my filter and map usage there. Almost Google tech interview quality I'd say. ;)\n\nThe next step handles creating my value to be used in email. I used some of the logic from my old post in terms of handling things like recognizing when there's a proper thumbnail.\n\n\nBe sure to make note of that epic ternary operator in there. I'm a 10X developer, 9X minimum.\n\nAnd then finally, I added the email step. By default, Pipedream's email step requires the text of an email but makes the HTML property optional. To keep things easier, I supplied my HTML value for both, which is not what you would want to do. Since I know I can read HTML email, I figured that was ok.\n\n\n\n\nAnd that was it. Now I've got a daily report for my favorite subreddit (it's for my local area) that shows up in my inbox once a day.\n\n\n\nYou can view (and copy!) the complete workflow here: https://pipedream.com/@raymondcamden/daily-reddit-posts-p_dDCYOd/edit\nReddit Demo Version Two\nThe first iteration was nice, but a bit limited. For the second version I decided to kick it up a notch. I wanted a version where the email contained new posts from ally of my subscribed subreddits. Luckily Pipedream makes that part trivial as they have an action for that already. All I needed to do was put it together. But that raised a new issue. I knew I could take my first workflow and turn it into an API. Pass in a subreddit name and return the posts as JSON instead of emailing them.\nBut Pipedream doesn't support the idea of &quot;loop over this array and execute a step for each&quot; - at least not yet. Given that I knew I'd have a workflow as an API, I decided to use two workflows. One for the API, and one to handle making HTTP requests to that endpoint and &quot;collect&quot; the results.\nLet's start with the API. I began with a HTTP trigger which gave me a URL to hit. I then added the same &quot;get new&quot; Reddit action and tied the subreddit name to the query string:\n\n\n\nNote that after I had tested my URL with a query string value (subreddit), the editor was smart enough to suggest it when I added the step. It even (although it's not in this screen shot) showed a sample value. This was freaking cool and super helpful.\nMy next step was the &quot;filter and return&quot; step and used this code:\n\nAnd that's it. Now if I hit myurl?subreddit=Acad",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Processing Email Sentiment with Pipedream",
		"date":"Wed Apr 15 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1586908800,
		"url":"https://www.raymondcamden.com/2020/04/15/processing-email-sentiment-with-pipedream",
		"content":"Ok, a quick spoiler. Today's Pipedream post isn't really that interesting by itself,\nbut I wanted a way to highlight a couple of cool features while working on an example that I hope folks will enjoy. I've done blog posts in the past about text sentiment analysis. I.e., what is a person talking about and what moods/emotions/etc are being used. In the past I've used IBM's Watson APIs for this and I've also used Microsoft's. Both have pretty darn cool APIs, but I thought I'd try something else, the npm Sentiment module.\nThis is an entirely opensource, Node-based text analysis tool that looks at input and looks specifically for positive and negative words. It supports multiple languages as well as the ability to customize what's positive or negative.\nBefore I show using it, how did I find it? A day or so ago I noticed the &quot;Explore&quot; link in the top navigation of the Pipedream site. That brings you to https://pipedream.com/explore.\n\n\n\nThis page has a bunch of shared workflows that you can look at it, including one named Real-Time Sentiment Analysis. Once you open up a workflow, you can click the big green COPY button to put a copy in your account and play around with it.\nSo that's how I found the Sentiment package. Cool. I decided to build a demo based on customer service email processing. Imagine for a moment you've set up a &quot;support@yourcompany.tld&quot; email address. You may want to flag especially angry emails so that someone responds to them quicker. I built a demo of this workflow like so.\nFirst, I added an email trigger. This gives you a unique email address that will be checked often. When it gets new email, it will start the workflow.\n\n\n\nI next built a step that would serve to set up my constant values. I learned this technique from the Pipedream folks. It's not required - remember you can do everything in one step if you want - but I like this breakdown. In this case I've got one constant, the highest level I'll ignore. I.e., everything below this level is considered too angry. The Sentiment package returns a value from -5 to 5 based on how negative or positive it is.\n\n\n\nNext I built a step to do the analysis. This is a Node.js step with just a few lines of code:\n\nBasically it just runs the analysis and exports it by saying it to the this scope.\nMy next step does two things. It does a quick check to see if the sentiment is above our threshold, and if not, if formats text for mailing.\n\nThe only thing probably interesting there is I do a bit of inspection of the from values in the email and try to display it a bit nicer. I also check for a subject. If it's blank I use a default.\nThe last step is to have it email me. I configure it with the values I used earlier:\n\n\n\nThat just leaves testing! I sent a few angry emails (trust me, I've got some pent up quarantine rage going on) and watched as the workflow executed and processed my emails. Here's an example:\n\n\n\nOf course, for my initial testing, I did something a bit quicker than writing email, and you should definitely make note of it. Workflows have a &quot;Send Test Event&quot; button and on the right is a blue pencil icon:\n\n\n\nIf you click the pencil, you can edit the test event data. I did this to modify the content of the test:\n\n\n\nThis is a very cool feature. Do note though that for this particular trigger, the &quot;shape&quot; of the event data doesn't quite match what you get when you send a real email. A bug has already been opened for this and it may be fixed by the time you read this post.\nI hope this post showed you a few new things about Pipedream and if you want to fork my workflow, you can find it here: https://pipedream.com/@raymondcamden/email-sentiment-warning-p_ZJC9vo/edit\nHeader photo by MusicFox Fx on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Creating Both HTML and API with Pipedream",
		"date":"Tue Apr 14 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1586822400,
		"url":"https://www.raymondcamden.com/2020/04/14/creating-both-html-and-api-with-pipedream",
		"content":"First off, I apologize up front about the title. You don't &quot;Create API&quot;, you create &quot;an API&quot;. No one cares probably but it's bugging me and I can't think of a better alternative. Hopefully I will before I finish the post. (Spoiler, he didn't.) Yesterday I was hanging out in the Pipedream Slack when I asked the team how a person would support running both an API  on Pipedream as well as the HTML in front of it.\nSo imagine you've built an incredibly complex API to do, well, who cares. To do something. Doesn't matter but here's my workflow as an API: https://enk542004vp3drh.m.pipedream.net/?name=ray Change the name value and your response changes.\nNow imagine you want to host an HTML application that makes use of this API. You've got a few options. What follows is a mix of suggestions from Dylan Sather of Pipedream and my own demo code and such.\n\nUse a &quot;real&quot; hosting provider. Pipedream can absolutely host HTML (see the next suggestion), but if you are building a &quot;site&quot;, you really want to use a service optimized for that, like Netlify and Zeit. Your endpoints have CORS enabled automatically so you can easily hit it from there. I wrote up a quick Vue.js application to demo this:\n\n\nAll this does is call the Pipedream hosted API with input and render it. I then deployed it to Zeit: https://temp-pearl.now.sh/temp.html.\nThat works just fine.\n\nHost the HTML with Pipedream. Pipedream workflows can return HTML, even dynamic HTML. Consider this workflow:\n\n\nAs you can see, it inspects the query string to look for a value. It then returns HTML in a template string to return dynamic HTML. (If you're old enough to remember DHTML, it's time for AARP. ;) So we could build a workflow to spit out the HTML I used above:\n\nTo keep the code listing a bit short I didn't copy the entire string, but you get the idea. Do remember though that if your JavaScript template string itself contains template strings, you need to escape it. I totally knew that. Honest. I deployed this and you can see it here: https://enxfb1rcr2bvdsk.m.pipedream.net/\nThis works, but honestly feels a bit wasteful.\n\nSupport both HTML and data in one workflow. A final option to consider is having one workflow support both the HTML as well as the data itself. Your workflow code has access to the entire HTTP request. You've already seen me use the query string, but you can also check the path (/foo) as well as any request headers and form data. So in theory you could do something like, &quot;if the request content type is for html, return it, if it's for json, return data&quot;.\n\nThis workflow shows an example where if a request is POST, data is added, and if it's GET, HTML is returned. We can build our own version that has these steps:\n\nStart with an HTML trigger to give us a URL\nIf method is GET, return the HTML string.\nIf method is POST, assume it's an API call.\n\nI built a workflow that does this. It has 3 steps, with the first one just being the HTTP trigger. The second step handles GET:\n\nI'm not a big fan of &quot;surround the entire body with an IF thing&quot; but it gets the job done. Also note that in my HTML (which I trimmed a bit), I switch my URL to ${event.url} so I can dynamically pick up the current workflow URL.\nThe next step handles the POST:\n\nIt's virtually equal to my initial logic (which I don't ever shared) except it now looks for POST data instead of a query string value. I encourage you to check out the workflow for the complete code. You can run the demo here: https://enek3dg6pwsn2od.m.pipedream.net/\nMy Recommendation\nOk, so this was mostly me just playing around with Pipedream and seeing what I could do. Honestly while you can serve HTML with Pipedream, I'd more often than not use a &quot;proper&quot; HTML serving platform like Zeit or Netlify. It's cool that Pipedream is flexible enough to handle this though!\n",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "What are Red Flags to Me as a Developer?",
		"date":"Mon Apr 13 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1586736000,
		"url":"https://www.raymondcamden.com/2020/04/13/what-are-red-flags-to-me",
		"content":"This morning I was thinking about developer relations and it occurred to me that there are certain &quot;red flags&quot; I look for when trying out a new product/service. I thought it would be nice to write them up and explain why they are red flags to me. While not specifically related to developer relations, these are things that I tend to think about in my own role and try to exert influence over when I can.\nI want to be clear that this is absolutely an opinion piece. I've worked for, and work for, companies that make these mistakes. Some of the things I'll point out as a red flag are things your company may do, and for good reason. That's ok, but I'm still going to consider it a potential issue when I'm considering your service.\nDevelopers don't care about your good reasons. They care about getting stuff done.\nI don't expect everyone to agree and I'd love to see your feedback in the comments below. Alright, with that out of the way, here's my list in no particular order!\nDocumentation Behind Signup\nSimple - don't make me sign up to read documentation. When I see it I know immediately that someone's got a KPI for developer signups and are trying to artificially boost it a bit. I need to be able to browse your docs to see if your solution makes sense, and asking me to create one more account that could be hacked is a barrier to that. I've got a lot more thoughts on documentation that you can read here: My Thoughts on Documentation\nKnowing what your product does and how it works is not a trade secret - make those docs public!\nSignup Requires a Credit Card\nThis is - probably - the number one thing that makes me reconsider using your service. And I can honestly see why some companies may use it. You may not want to support &quot;tinkerers&quot; and that's fine. But I'll pretty much never play around with a product if I have to give my credit card. You can tell me all you want about how you won't charge it - I'll just do my best to avoid it at all costs.\nAgain though - you may specifically want to avoid working with random developers.\nTimed Trial\nAnother thing I run into quite often are timed trials. I always hesitate when I see this. I look at the trial period - I check my calendar. Will I have time to dig into the product within the time frame? Maybe I'm going to be busy next week. Maybe I'm not busy, so I sign up, and then something crazy happens and I get busy. Basically, I stress over it. You don't want to stress Raymond, do you?\nNot Using NPM (for the CLI)\nThis is unfair. I get it. Some platforms don't use Node as their development language. Heck, one of the services I've been incredibly happy with lately doesn't use NPM. This is less about NPM and more about onerous installation processes in general. IBM for example, used to have an old school InstallShield installer for their CLI. Even better, it required a restart. About two hours ago I needed to install the CLI again and it was much simpler (even though it wasn't NPM based) so they've definitely improved there.\nPricing Obscurity\nThis covers a number of issues. First, I expect it to be real simple to figure out what your service costs. The best is when there is a nice direct API call to cost table. 1K for free, then X cents after that per Y calls. I've seen a lot of bad examples of this. Most recently, I was digging deep into a certain large company's API. They have a per API rate. Then they have different charges for what data you get back in the API calls. All in all, it's the kind of thing you would need a spreadsheet to figure out what you're bill will be.\nIn the same area, don't make me dig to find my bill and what cost me money. Azure was (to me) pretty difficult in this respect, although after using it for a while I figured it out. IBM for a while would send me an email saying I had an invoice. I'd log in. Click a few times. Get the PDF and see I owed nothing. It always boggled my mind that they couldn't improve their process such that the email itself couldn't include the total and save me a few clicks. It's a small thing, but it annoyed me.\nAnd while I'm on a rant a bit, and I know there are commercial reasons against ever doing this, but I wish more services would simply support a &quot;Don't Let me Use Money&quot; option. I.e., I set something up, I use the free tier, and if I forget about it and it would have incurred costs, the service simply disables itself. It's absolutely in the best interest of the company to not do that, but as a developer I sure would appreciate that. If I'm just tinkering and building toys, I can promise you I'd much rather have my toy shut down then to get a 5 dollar bill. I can pay that, I just don't want to!\nMac/Unix Only Terminal Commands\nI get it. Apple makes some darn good machines (I mean, unless you want the keyboard to work, they fixed that, right?) and Unix is a great CLI environment. Windows adding Ubuntu to the OS was one of the biggest reasons I switched back after having been a Mac user for years. Bu",
		"tags":[
	        
		],
		"categories":[
            
                "developer"
            
		]

	},

	{
		"title": "How Pipedream Got Me Excited About SQL Again",
		"date":"Sat Apr 11 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1586563200,
		"url":"https://www.raymondcamden.com/2020/04/11/how-pipedream-got-me-excited-about-sql-again",
		"content":"So, I know how I'm supposed to learn something. You go the docs. You start at the beginning. You read to the end. Done. Except... I just don't work that way. I'll definitely go through an introduction and at least attempt to go through the docs one by one, but typically I want to try stuff as soon as I learn. That means leaving the docs, playing with what I learned, and then returning, hopefully, to keep learning. This means I'll sometimes miss interesting things. So for example, I was playing with something on Pipedream when I noticed this in the top navigation:\n\n\n\nSQL? People still use that?\nI'm only kidding of course. I spent many, many years working with SQL when I was primarily doing backend work with ColdFusion. I didn't mind it, but I didn't necessarily miss it either when I started using more NoSQL type solutions like Mongo and IndexedDB. The thing I liked best about object-store type databases is that they made object insertion and updating so much easier. I hated writing INSERT and UPDATE statements in SQL.\nOn the other hand, I absolutely loved the power of a SELECT statement. While I know I can do powerful queries in Mongo and IndexedDB, I really appreciated how &quot;expressive&quot; SQL could be.\nSo with that in mind, I clicked on the SQL tab and discovered that Pipedream had a SQL service baked into the product. The docs do a great job of explaining how it works, but I thought I'd point out some highlights.\nFirst off - every account has a database they can use to store information. Data is account wide which means one workflow has access to another workflow's data so when naming your tables you want to ensure they are unique.\nSecondly, their services gets rid of the thing I disliked the most about SQL, writing those insert statements. I still remember the first time I stored data in Mongo. I had an object. I stored it. I was done. I freaking loved that. Pipedream's SQL service (which uses a product called Preso let's you do the same thing. You take some data:\n\nAnd you just store it. Done. There are details, of course, on how data is mapped and you should check the docs especially if you are trying to store data values, but in general it &quot;just works&quot; and makes it simple to use.\nSpeaking of using it, and I'm going to show an example in a second, you can either write Node code to store information or use the SQL destination to handle it for you. The below is a screen shot I &quot;borrowed&quot; from the official docs:\n\nBefore I get into an example, there's two final details that are important. First, this is not meant to be a permanent data storage solution. The data retention docs say that currently your data is only stored for 30 days. That may be a deal breaker but don't forget you've got like an infinite number of other data storage systems you can use. Secondly, you do not yet have &quot;workflow access&quot; to the data. What I mean by that is you can run SQL queries against your data on the site (I'll be showing an example later in this post), but your workflows can't use code to access the data. This is a known issue and hopefully something added soon.\nAlright, so how about a simple example, and while we're at it, I can show you another cool Pipedream feature. When you create a new account, one of the workflows you have automatically is &quot;Global Error Workflow&quot;. This is the default error support you have for your account. Notice I said &quot;error support&quot;, not &quot;error handler&quot;, as it doesn't change how your workflows report errors, but rather handles processing the error after the naughty workflow has screwed up. I say &quot;by default&quot; because all new workflows have a setting for it:\n\nAnd because the Global Error Workflow is, itself, a workflow, you can click in there and check out the code. The workflow has the following steps:\n\nA trigger bound to error events. You can't do much with this.\nA &quot;filter and format&quot; step that looks at the error and workflow ID and uses logic to only inform you of unique errors per workflow per 24 hours. Yes, you can modify this. Maybe you want to be notified of every error.\nFinally, an email destination.\n\nSo as I said, you can modify this event as you see fit, and I thought it would be cool to store my errors in the database. I wanted my code to run before the filter so I added a Node.js step after the trigger. I used some of the same code from the format step:\n\nThe workflow has access to an error object as well as context about the workflow that fired it. (At the time I wrote this, you only have access to the ID of the workflow, not the name. There's an open issue to get access to that too.) I decided that I would log the workflow id, the time, the error code, and the message. I create a simple object called payload and then sent it to the SQL system with $send.sql. And that's it. I love how simple that is.\nAlright, now that I'm storing data, how do I use it? Well my original plan was to build a workflow that",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "MadLibs with Vue.js",
		"date":"Wed Apr 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1586304000,
		"url":"https://www.raymondcamden.com/2020/04/08/madlibs-with-vuejs",
		"content":"It's been a while since I built a game with Vue.js so I figured it was time to take a stab at creating another one. This time I've built an implementation of MadLibs. If you've never heard of this game it's pretty simple. You begin by asking a reader to enter various parts of speech, like a noun, verb, and so forth. You then put their input into a story for (typically) a funny result.\nIn researching options for this demo I found a few cool utilities. The first was the Madlibz API, an API that returns a random MadLib. I thought this was neat, but the content was pretty slim and I kinda wanted my final solution to be offline capable.\nAnother really cool resource was the Libberfy Mad Libs API. This one lets you pass text in and it spits out a MaLib version. You should check out his demo to see it in action. I didn't use this myself as my goal was to have pre-built MadLibs.\nIn the end I decided on a simpler approach. I'd have a data file that could be cached offline. I also wanted a data file that was easy to work with. Whenever it comes to stuff like this I always think JSON first, but for this application a text file made more sense. My format was rather simple. Use plain text for your MadLib and use single brackets around things you want to replace. Each MadLib is separated with a --- character. So here's a sample of how it could look:\n\nNotice the ## My Birthday part? I allow for Markdown in the MadLib as well. The idea was that a non-technical person could easily edit the file to add content. Shoot, I'm technical and I find this much easier than JSON as well.\nThe actual application breaks down into three parts. First, a simple title screen:\n\nI employed all of my incredible design skills there as you can see. I won't share the code for this part as it's just what you see.\nThe next route handles prompting for the parts of the MadLib. I show one prompt at a time. A user on Twitter suggested asking a progress bar here and I agree that it would be a good change.\n\nLet's look at the code for this. I'm skipping the styling part but you'll be able to see everything in the repository. (I'll share the link at the end.)\n\nSo initially my code called out to my Vuex store to setup a MadLib. Doing this will give me a MadLib set of prompts, an array of things like noun, country, and so forth. In my UI then I can render one at a time and accept user input. You'll see that when I've answered every prompt, I move the user on.\nThis was all pretty easy. What took me the most time was getting the darn focus() stuff working correctly. I wanted to make it easier for kids so they wouldn't have to use the mouse as much. I did get it working but I don't believe I've done it the best way possible.\nThe final view simply renders the MadLib:\n\nI'll skip the code for this route as well as it just uses a Vuex method to get the rendered HTML. Let's look at that store:\n\nSo there's a few things going on here but the main aspects are where I load the MadLibs and when I render one. Loading involves making an XHR request to my text file. I parse it (splitting on ---) and then doing some basic parsing of the values within tokens. The result is a MadLib object that has an array of prompts (as well as the original text).\nRendering then is a simple matter of replacing the tokens with the user's inputs. The final step is passing it through the marked library to convert the Markdown into HTML. Don't forget that when you pass HTML into a value to render for Vue, you need to use the v-html directive:\n\nAnd that's it. You can view the complete source code at https://github.com/cfjedimaster/vue-demos/tree/master/madlibs. I've got a version up and running at https://madlibs-sooty.now.sh/. Note that I did not go ahead and turn it into an offline-capable game. Honestly, I don't have the energy right now. (By the way, it's my birthday. I'm old.) However it wouldn't be too difficult to add that in.\nHeader photo by Siora Photography on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using State in Pipedream Workflows",
		"date":"Sat Apr 04 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1585958400,
		"url":"https://www.raymondcamden.com/2020/04/04/using-state-in-pipedream-workflows",
		"content":"I've been playing a lot with Pipedream lately and have been enjoying the heck out of it. If you didn't see it, my last post described how to build a simple Twitter bot using the platform. Today I want to demonstrate something else with Pipedream, a feature that is pretty simple but incredibly useful - managing state.\nMany times when working with serverless functions, it would be convenient to store information. Perhaps a simple cache or even just the time the function last run. There's a huge variety of data storage systems out there with easy to use APIs, but if your needs are really small, then they may seem like overkill.\nOne of my favorite features of Webtask (sigh, RIP Webtask) was that it supported a state system that let you read and write to a JavaScript object that persisted for your function. Obviously it doesn't replace something like MongoDB but for remembering a few values it was incredibly useful.\nPipedream has a similar feature, but done a lot better I think. Pipedream supports a variable called $checkpoint. This value will persist for your workflow. It can contain anything, either a simple value (maybe you want to store only one thing) or a full JavaScript object. Anything that can be serialized can be stored. Even better, while $checkpoint is global to the entire workflow (and is most likely the option you'll use), you can even have per step state if you use $this.checkpoint.\nWhen I first blogged about Pipedream, I described a simple workflow that did a Twitter search, formatted the results, and emailed them to me. One issue with the workflow is that it would (possibly) keep sending the same results over and over again. The Twitter API supports returning results after a previous tweet so this should be easy to fix, and with the $checkpoint variable, it's easy to implement. (See my note at the bottom.)\nBefore I describe how I did it, here's a quick refresher of how the workflow looked. In this case I'm using the nicer workflow Dylan Sather of Pipedream had setup. It was a bit more complex, and reusable than the simpler version I did. Anyway, here are the steps:\n\nThe first part of the workflow is a CRON trigger set to run every hour.\nThe next part is a Node.js script that specifies a few constants.\n\n\n\nThe third step is the search Twitter action. All I had to do there is connect my account and tell it to use the result from the previous step.\nThe next step handled formatted the result into a string.\n\n\n\nThe final step then emailed the result of that step to me.\n\nSo that's the workflow as it stands out - every hour (or whatever schedule, or manually if testing), search for a string, format the results, an email it. Now let's enhance it with state!\nFirst, I modified the step that defined my search string:\n\nMy modification (ignoring the search term) is to check for $checkpoint and see if I have a lastId key. If so, I export this.since_id. Remember that values you write to the this scope are exported. They act like step output.\nI then modified the Twitter search action to add the since_id param.\n\nThe next modification was to store a value I could use next time for step ID. I did this in the same step I format the tweets. Note, I could have done a separate step for this. Pipedream's &quot;step&quot; metaphor makes it easy to break things down as much as you want. Much like how you typically write a JavaScript function to do one thing only, you could apply the same to Pipedream too. I'm being lazy though and just keeping it simple.\n\nThere's two mods here. One is to see if we have any tweets at all. We may not. If so, I use the cool $end feature to immediately end execution. When this is used, it's rendered nice in the UI too:\n\nFinally, I need to store the ID of the last tweet (first in the array). Notice that I'm using an object for my value. I did that for two reasons. One, I didn't realize Pipedream let you store just a value. Secondly, I decided I may enhance this workflow in the future and store more values. That's me pretending I'm forward thinking and smart (spoiler, I'm not).\nAnd that's it. If you want to see the full workflow, and copy it to your own account, just hop over here: https://pipedream.com/@raymondcamden/email-me-new-tweets-improved-p_LQCOlq/?via=raymond. Thanks again to the folks at Pipedream for answering about 200 emails I sent in the last 48 hours on this and other topics!\np.s. Ok, way up top I mentioned I had a note about the Twitter API. While the API is generally easy to use, I had trouble getting this exact logic right. I think I did, but I'm not 100% sure. That's not really a Pipedream concern, but I just wanted to be honest about my own uncertainty!\nHeader photo by Martin Adams on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Quick Tip on Using Vue with Eleventy",
		"date":"Fri Apr 03 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1585872000,
		"url":"https://www.raymondcamden.com/2020/04/03/quick-tip-on-using-vue-with-eleventy",
		"content":"Ok, so this falls into the &quot;too obvious to blog&quot; category, but as I've made this mistake twice now I figured I'd share. Let's say your happily working on your Eleventy site and using Liquid as your template language. Your site is done and you realize you need to add some Vue.js to a page to enhance it. That's great because Vue is awesome for stuff like that. In my opinion, one of the reasons it's the best client-side framework out there is because it works great both for single page applications as well as simple page enhancement tasks. Anyway, you take an existing Eleventy template and begin to enhance it:\n\nThen you write your code.\n\nEasy-peasy lemon squeezy. Should just work, right? So you fire up your browser and see...\n\nOh crap - where's the dynamic data? If you're like me you immediately open up devtools and if you do, you won't find an error. So what could it be?\nWell, if you view source on the page, you'll see this (I removed the layout code for brevity):\n\nWoah wait - where's the template tokens for Vue? It's at this point you (hopefully) remember that LiquidJS and Vue use the exact same token syntax, i.e. {{ and }}.\nLuckily, there's a few quick options.\n\nWrap the code you want to be available in your built site with the raw and endraw tags.\n\n\nYes, I switched to a Gist for this because I gave up trying to double escape the code on my own Eleventy blog. ;)\n\nGiven the nature of how much other code you have in your template, don't forget that Eleventy lets you use one of many template languages. You could switch to EJS for example. That's what I did on my blog for a template where Liquid would have been difficult to use. Or heck, if you hate yourself you could also use Pug! (Sorry, but Jade/Pug just annoys me at a deep level.)\n\nAnyway, I hope this helps and I also sincerely hope I'm not the only one to have made this mistake!\n",
		"tags":[
	        
            "vuejs",
            
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Twitter Bot in Pipedream",
		"date":"Thu Apr 02 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1585785600,
		"url":"https://www.raymondcamden.com/2020/04/02/building-a-twitter-bot-in-pipedream",
		"content":"A few days ago I posted about the Pipedream platform and I've had a chance now to play with it a bit more and I'm even more impressed with it now then when I started. I decided to build (yet another) Twitter bot that shares random information. This time it's @randomgijoe, a bot that shares random GI Joe characters from the &quot;RAH&quot; (&quot;Real American Hero&quot;) universe. Here's an example:\nName: VoltarSpeciality: Field commanderLink: https://t.co/1Igpw6t1fL pic.twitter.com/Y0PJyppFnO&mdash; randomjoe (@randomgijoe) April 2, 2020 \nI did this by parsing information from the GI Joe wikipedia site and implementing it on Pipedream's platform. I'm going to share how I built it, but be aware that roughly 95% of the work was involved in getting my random character. The aspects that pertain to Pipedream were incredibly simple - which is what you want in a platform. Also, I once again want to thank Dylan Sather and Tod Sacerdoti for their help.\nCreating My Tweet\nSo as I said in the beginning, most of my work for this bot was in creating the actual content for the tweet. Wikis using the Wikia platform (which I'm not sure how to exactly link to) share an API that makes it simple to perform calls against the content of the site itself. For my bot, I wanted to do this:\n\nGet a random page in a category.\nUse the characters name, specialty, and picture in a tweet.\n\nThe first part was easy as it's a standard URL that looks like so:\nhttps://gijoe.fandom.com/wiki/Special:RandomInCategory/CATEGORY\nFor my bot, I wanted to focus on the RAH (Real American Hero) category so my URL looked like so: https://gijoe.fandom.com/wiki/Special:RandomInCategory/A_Real_American_Hero_characters. If you click that link, you'll end up on a random page within that category.\nSo step one was - request that URL and look at the request to see where I was redirect. I've never done that before with node-fetch, but it ended up being simple:\n\nSpecifically using redirect:'manual' lets me look at the headers and see where it redirected me to. This will be a URL with a page value at the end that I pop out. So a call to this may return something like: Decimator.\nEasy. The next step is to get the content of the page. The Wikia API lets you get both the rendered page or the original wiki source. I needed the original wiki source, so I wrote this function:\n\nI simplify the result a bit by looking for the title and wikitext. Again, sooooo simple, right? Now came the fun part. And by fun part, I mean the part that made me reconsider what I was doing.\nI noticed that the random characters shared something in common. They all had a box on the right:\n\nI noticed &quot;Specialty&quot; in the box and thought that would be nice information for the tweet. I had originally considered the first sentence of the main text, but I figured name, specialty, and an image would be enough. When I looked at the wiki text, I could see the box inline with the rest of the code. Here's a sample:\n{{Chardisambig|Shipwreck}}\n:''Shipwreck is a [[G.I. Joe Team|G.I. Joe]] character from the [[A Real American Hero]] and [[G.I. Joe vs. Cobra]] series.'\n'\n{{Joe character_infobox\n|imageBG=\n|image=[[File:Shipwreck_RAH.jpg|250px|center]]\n|name=Shipwreck\n|hidep=\n|realname=Hector X. Delgado\n|birthplace=Chula Vista, California\n|gender=Male\n|alias=\n|hidem=\n|branch=US Navy\n|graderank=Chief Petty Officer (E-7)\n|sernumber=\n|specialty=Sailor; [[Wikipedia:United States Navy SEALs|S.E.A.L.]]; Gunner's mate; Machinist\n|training=Naval Gunnery School; [[Wikipedia:United States Naval Special Warfare Command|S.E.A.L. School]]\n|weapon=[[Wikipedia:M16 rifle|M-16]]; [[Wikipedia:M14 rifle|M-14]]; [[Wikipedia:M2 Browning machine gun|Browning .50 cal]];\n20mm Oerliken anti-aircraft gun; [[Wikipedia:M1911 pistol|M1911A1]]\n|hideo=\n|factions=*[[G.I. Joe Team]]\n|subteams=\n|1stcomic=[[Hydrofoil|''G.I. Joe'' #40]]\n|1sttoon=\n}}\n'''Shipwreck''' has earned a reputation as being one of the more rambunctious members of the [[G.I. Joe Team]]. It's not tha\nt he is in any way disobedient or disrespectful of higher up officers. His arrogant, brash personality and stereotypical sai\nlor attitude has gotten him in trouble so many times. He prefers to take up his own actions and fight the enemy in his own w\nay. He also has a reputation for telling tall tales and for his poor culinary skills. It seems he is the only one who can st\nomach his own cooking. His superiors have had enough and shipped him to Navy S.E.A.L. School in the hopes the training there\nwould make him a better man. It made him better alright... a better fighter only.\nNotice this block: {{Joe character_infobox ... }}. You can see that inside it, we have formatted data in the form, |key=value. Here is where things got weird. Yes, wiki's have an API. But when authors write content, they don't always follow a standard format. One of the things I found right away was that the infobox had different styles with different names. That made my code get and parse the box a bit",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "A Look at Pipedream",
		"date":"Sat Mar 28 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1585353600,
		"url":"https://www.raymondcamden.com/2020/03/28/a-look-at-pipedream",
		"content":"Every now and then folks will reach out to me to share interesting tech/sites/etc that they would like my opinion on. I like this as it gives me a chance to learn something new and a lot of times it leads to blog posts that I can share with my audience. Nearly a year ago the folks at Pipedream reached out to me and - unfortunately - I never really got a chance to look into their product. Friday I was about to hit the Archive button as it was the oldest email in my inbox and sometimes you just have to let go. (It feels great to do that sometimes.) Instead, I clicked around a bit and I've got to say - I am incredibly happy I did so. Pipedream is a really neat product and something I absolutely want to tell other people about.\nPipedream describes itself as an &quot;integration platform built for developers.&quot; It focuses on what it calls workflow that are run on their platform at no cost. (As their pricing page says though in the future they may offer additional features on a paid tier.)\nWorkflows are - essentially - pieces of logic that you put together in a linear fashion. Your workflow will execute step by step. This isn't anything new. I know Lambda supports steps as does Azure Functions. For me, my experience with this type of setup was with OpenWhisk sequences. It was one of my favorite aspects of OpenWhisk as it made it incredibly easy to set up complex applications by piecing together different bits of logic. A lot of times I'd take parts that were pre-built and simply stick a bit of custom logic in the middle. Pipedream reminds me a lot of this.\nYour workflows are comprised of a few different parts. You begin with a trigger which is how the workflow is initiated. This can be via URL (ie, someone hits the URL to start it), a CRON schedule, or email (ie, you send an email to the address to start it).\nNext come your steps. These steps are bits of Node.js code that you can do pretty much anything with, or a pre-built &quot;action&quot; that encapsulates code for you. Steps are processed one by one. They can have their own data, take input, return output, etc.\nThe actions are where things get really, really freaking cool though. As an example, I like working with the Twitter API. I've built a few bots, search tools, and so forth. The Twitter API is rather simple to use. However the authentication part can be a bit hard to work with. Pipedreams literally turns this into a &quot;click and login&quot; flow and you're done. I cannot stress how cool that is (going to demonstrate it in a second) and how excited I am about using the platform to build my usual style of stupid demos.\nThat was a longish intro so let's walk through an example. I'm going to build a workflow that will search Twitter every hour for a keyword. It will take the results, format them nicely, and then email it. The following example is a modified version of one created by Dylan Sather - one of two folks at Pipedream who answered a crap ton of annoying questions from me. I'm rebuilding it from scratch to demonstrate that the process is like. (But get this - you don't have to. At the end I'll share a link to his workflow that you can copy to your account in one click.)\nOk, assuming you've signed up for Pipedream, your default view is a dashboard of your workflows:\n\nBegin by clicking the big obvious green New Workflow button.\n\nThis is the default dashboard for working with workflows and there's a lot going on in it. One of the issues with the site now is that they don't use a lot of alt text on their images and sometimes it's difficult to know what a particular UI item does. I've let them know this and they're working on correcting it. Right now though I'm going to focus on two parts. I'm going to give it a title (upper left hand side) and then select the &quot;Cron Scheduler&quot; trigger as I want a time based workflow:\n\nNote that the Cron trigger has both a simple and complex view which I really appreciate. Cron syntax makes regex look friendly so I like having a simpler way to define my timing schedule.\nOk, so notice the + sign below my trigger. Clicking it gives you a quick form to select your next step.\n\nI'm going to select the &quot;Search Twitter&quot; action:\n\nSo it was a bit hard to fit this into a screen shot but I want to point out a few things. First, I love the big obvious warning at the bottom. It's making it clear I need to connect this to an account. Then note the big red button in the step itself. That's what I'll click to connect the workflow to my Twitter account. Next notice params. It's showing you one param, q, which is your search term. There's more properties but this action takes a sensible view of, &quot;90% of the time all you need is this one param.&quot; I've noticed this in a bunch of actions and it's really nicely done.\nWhat you see only partially in the screen shot is the code. You do not have to modify anything here at all. But what's awesome is that if you do want to do a customization, you just click and s",
		"tags":[
	        
            "javascript",
            
            "pipedream"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Implementing Google Custom Search Engine's JSON API in the JAMStack",
		"date":"Sun Mar 22 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1584835200,
		"url":"https://www.raymondcamden.com/2020/03/22/implementing-google-custom-search-engines-json-api-in-the-jamstack",
		"content":"I've been a fan of Google's Custom Search Engine support for quite some time now. I use it here to handle adding search to my site. In all the time I've used CSE though, I've only used it via the &quot;embed&quot; option (again, as you see on my site), but Google also supports a JSON API which gives you more fine grained control over displaying search results. I recently had to build a demo for someone using ColdFusion, so I thought I'd take a stab at demonstrating how to do with with serverless functions and the JAMStack.\nBefore I begin, some high level things to know. In general, the API is relatively simple to use. You get an API key, you get your search engine ID, and then you make a HTTP request. Google provides you with 100 requests per day which is probably fine for most folks. As I said, in general it works just fine, but there's some details you should note.\nFirst, each search request will return the total number of results. That allows for pagination. But you are not allowed to ask for more than 100 results. So if a search for &quot;foo&quot; returned 250 results, you can only show the first ten &quot;pages&quot; of results. That's not too bad, I can't see most users clicking through over ten pages of results, but you want to ensure your code handles this correctly.\nSecondly, in some testing I saw the total number of results fluctuate while paging. So I'd search for &quot;foo&quot; and see X results. I'd go to the next page and still see X. But then on page 4, all of a sudden the total number changed. If I then went to page 5, the total went back to X again.\nThirdly, also related to paging, the total number of results you can return in one request is ten. That seems odd to me, but I guess Google really wants to ensure you use those 100 requests. Again, probably not a big deal to most folks, but it's something you want to keep in mind.\nA basic request looks like so: https://www.googleapis.com/customsearch/v1?key=KEY&amp;cx=CX&amp;q=TERM\nEverything there should be relatively obvious except for cx which is your search engine ID. If you go to the CSE portal, select one of your CSEs, you can see it here:\n\nThe other variable you would use is start which controls pagination. This number cannot go over 91.\nThere are many more parameters you can use that are covered in the reference guide.\nAlright, so let's consider a simple example of this using Netlify functions. I began by building the serverless function. I set up both my API key and CX value as environment variables.\n\nI begin with a bit of validation on the query string parameters passed to the function. query must be passed. start is optional and defaults to 1. I do a bit of basic validation on it to ensure it doesn't go below 0 or over 91.\nI then do a HTTP request. The response contains a lot of information, not all that I need, so to simplify things a bit I transform the response before returning it. I focus on two elements, searchInformation and items. searchInformation is exactly that, information about the search. Oddly, totalResults is a string so I fix that on the server side. For my items, I remove things that I consider to not be important. You may feel differently and if so, just remove that map call. The end result is a JSON packet that looks like this (I removed most of the items to keep the size down):\n\nAnd technically - that's all I need for the back end. For the front end, I built a simple Vue.js front end. Here's the HTML portion of my form:\n\nI've got a form up top and then a block to handle showing results. That block handles iterating over each result and optionally showing buttons for next and previous results. Now here's the JavaScript:\n\nIn general this is just a simple wrapper to a back end API, but pay particular attention to the created block. Some sites (not mine in it's current form) support having a search box in the header, or side bar, that let a user enter text, hit a button, and then sends them to a page to display results. In order to support that in my demo, I use created to look at the query string and see if a value is there. If so, I use that to update my form field value for searching and immediately fire off a request.\nSo I'd love to show this to you. It is live right now on a demo site. But since I've got a limit of 100 requests per day, I don't think I can safely share it. You are welcome to the source code but you'll have to trust me on how awesome it looks. Wait, don't trust me, look at this most excellent screen shot:\n\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Tips for Giving Remote Presentations",
		"date":"Tue Mar 10 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583798400,
		"url":"https://www.raymondcamden.com/2020/03/10/tips-for-giving-remote-presentations",
		"content":"As travel becomes more limited and conferences begin shutting down physical meetings, more and more presenters may find themselves switching to online platforms in order to deliver their talks. If you're an experienced presenter but have never given a presentation online, the change can be somewhat daunting. I thought I'd write up some tips to consider before giving your first remote presentation. And then I had an even better thought - why not ask people much smarter than myself for their advice. I reached out to friends and colleagues to get their advice for remote presentations and I've shared it below. I'll give my thoughts at the very end. (I did some minor editing on people's input for formatting.)\nBrian Rinaldi, Developer Advocate, Stackbit\nI think it's important to try to be more interactive when you are presenting online. Ask questions, do surveys, allow people to engage in chat and find a way to check that at regular intervals. The tough part about remote presentations is that it is easy for attendees to be distracted and lose focus. Being interactive keeps their attention and keeps them engaged.\nJen Looper, Developer Advocate Lead, Microsoft\nHere are my tips:\n\nTech check beforehand! It's very strange to use software to present to a crowd, and each one is a little different, so double-check.\nConsider asking the organizer to turn the computer towards the crowd - use a second computer, dialed in, if you have to - so that you can gauge your audience. It's very tricky to keep up energy if you can't see your audience.\nMake sure someone is monitoring the chat room and has access to a microphone so that s/he can raise a 'virtual hand' to ask you a question. Work with the organizer/moderator to handle questions as they come through. You could, for example, stop presenting every 15 minutes to take questions, or hold them all to the end.\nKeep in mind that live streaming folks have a different experience than folks dialed in via Zoom, so try to include them in discussions.\n\nJohn Papa, Developer Advocate, Microsoft\nStand. Smile. Use your voice. When remote presenting the audience cannot see all of you (or any of you if it is a screen cast). All you have is your voice ... so make the most of it.\nTracy Lee, CEO, This Dot Labs\nSurround yourself with stuffed animals that look really happy so you can replace the lack of immediate feedback you will get from a live audience.... jk... but generally don't worry about the lack of feedback they love you! :) Also, make sure to test your speaker notes with the setup you will have the day before - some livestream conferences do not allow for speaker notes due to lack of multiple monitors.\nChris Heilmann, Principal Program Manager, Microsoft\nPresenting remotely is fraught with quite some obstacles. For starters, you don't get any feedback from the audience other than a chat channel which you shouldn't monitor whilst you present.\nSecondly there is always the chance that you'll be disconnected or not understood and happily presenting away whilst your audience doesn't see you any longer.\nTo counteract these problems I made sure to not present from my machine but present a slide deck in whatever system I use to present. That way if I can't see it, I know I also lost the audience.\nIt is also vital to have a different channel open with a person at the audience end to tell you when things go wrong.\nWhen it comes to preparing presentations for remote viewing I found that being more wordy is helpful. Text on a slide is bad for an inspirational keynote, but remote presenting is more like sharing a document for discussion with an audience, so don't feel too bad for having more information on each slide.\nThe most important thing to do is to have your presentation to send out to the organizer and to make available to attendees beforehand. This makes remote presenting less lively and not as interactive as dealing with a real audience, but it prevents frustrating gaps in information.\nLast but not least - don't underestimate that not seeing an audience is making it more confusing to present. You have no idea if your jokes work and doing a throwaway remark can easily be seen as an attack if people can't evaluate it with your body language and presence. So maybe it is a good idea to use less &quot;funny&quot; things and stick to the information. In any case, recording you give the presentation as a video beforehand is a good way to test dealing with the lack of audience. And that video could also be a fallback in case everything breaks down.\nStacey Levine, Director of Developer Advocacy, OutSystems\nOn remote presentations  - odd thing - but smile when presenting, stand if possible. It comes across in your voice and makes a difference.\nDiana Rodriguez, Python Developer Advocate, Nexmo\nIn terms of remote presenting I keep it simple. I use streamyard for simple streaming, OBS is a great tool but it takes a bit to get acquainted with. Most podcasts or meetups are using zoom or hangouts so in ter",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Vue Quick Shot - Links, Links, and More Links",
		"date":"Fri Mar 06 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583452800,
		"url":"https://www.raymondcamden.com/2020/03/06/vue-quick-shot-links-links-and-more-links",
		"content":"Welcome to the final entry in my Vue Quick Shot series. I'll share all the links at the end of this post. I hope this week's series has been helpful and if you enjoyed it, please drop me a comment and I'll definitely consider doing another series in the future. For my fifth and final Vue Quick Shot, I'm not going to write a line of code. Instead I'm going to share links to resources that have helped me learn Vue and become a better developer. This is not meant to be an exhaustive list of everything out there for Vue, but rather a more focused list on things that helped me personally.\n\n\n\nThe Docs - It should come as no surprise that the core docs are where any new developer should start. I'd only point out that you shouldn't miss the excellent Cookbook as well. I didn't notice it for months.\nVue Forum - The official Vue forum. Pretty heavily used and you typically get an answer to anything relatively soon. There's also a pretty busy Stackoverflow tag for Vue.\nAmazing Vue - A huge list of cool things done in Vue. Basically if you need X for Vue, check here first.\nSarah Drasner on CSS-Tricks - Part one of a five part series on Vue. It's especially good in the animations area, a part of Vue I've never played with.\nDevtools Extension - The devtools extension is nice, and supported in most browsers, but may be more useful to people using Vuex. Honestly unless I'm using Vuex, I just use console messages.\nNewsletters - You can, and should, sign up for both the official newsletter and the\nVue.js Developers newsletter. For those of us who can't spend all day glued to Twitter, this is the best way to get the news about what's going on with Vue.\nAnd finally, I've got nearly 100 posts tagged with Vue and at least a few of them are worth your time. I also write about Vue from time to time for other publications and I keep a list of them on my About page.\n\nThat's it! I hope these small nuggets have been helpful to you this week, and as I said, leave me a comment below with your thoughts!\nQuick Shots\n\nPart One - Disabling a Submit Button While Waiting for an AJAX Call\nPart Two - Using a Loading Message\nPart Three - Copy to the Clipboard\nPart Four - Image Upload Previws\nPart Five - Links, Links, and More Links\n\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Image Upload Previews",
		"date":"Thu Mar 05 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583366400,
		"url":"https://www.raymondcamden.com/2020/03/05/vue-quick-shot-image-upload-previews",
		"content":"Welcome to the fourth entry of my Vue Quick Shots. Be sure to check out part one, part two, and part three. Today's entry is slightly more complex than the previous ones - adding an image preview to file upload controls.\nThis is something I've covered before, but not with Vue.js. Let's start with the HTML:\n\nIn my input field, pay attention to the attributes:\n\naccept=&quot;image/* tells the browser to filter files that can be selected to images of any type. However, the user can switch this filter to any file.\nI then use ref=&quot;myFile&quot; so Vue can have access to it. You'll see how in a bit.\nFinally, I specify the when the file input is changed, it should run the previewFile method.\n\nBelow the input field I have an img tag that will display the image when one is selected.\nAlright, now let's look at the JavaScript:\n\nMy previewFile method checks the file input field via $refs and looks at the first file available. If there's one, and it's an image, we then use a FileReader object to read in the data and create a data url. This then gets assigned to imgsrc so that the image can render it.\nAnd that's it! Here's a live version you can play with:\n\n  See the Pen \n  Vue Image Preview by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nI hope you enjoyed this quick shot - only one more to go!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Copy to the Clipboard",
		"date":"Wed Mar 04 2020 19:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583348400,
		"url":"https://www.raymondcamden.com/2020/03/04/vue-quick-shot-copy-to-the-clipboard",
		"content":"Welcome to the third of my Vue quick shots. Be sure to check out my first and second entries. I'll be posting a quick Vue.js tip every day this week (and let's pretend I was late with yesterday's entry, ok) for you to use in your projects.\nToday's entry is an example of how to add &quot;Copy to Clipboard&quot; functionality for a site. You see this fairly often in developer portals where keys or tokens are shared with developers for their code. A little bit of JavaScript tied to a button or some other UI is added to make it easier to copy the value. Today's tip will show one way of adding this feature.\nFor this tip I'll be using the Clipboard API. This is a newer way of accessing the clipboard (see this excellent StackOverflow post for a look at other methods) that is supported in everything but Internet Explorer and Safari.\nLet's begin with a simple application. I've got a form with two values:\n\nAnd here's the application behind it, which for now just sets values for the two fields.\n\nAlright, so how can we add a way to copy those values to the clipboard? I'm going to use a simple button that looks like so:\n\nThe v-if portion will handle hiding or showing the button based on if the browser supports the API. The click handler will pass the value to be copied. I can add it to the HTML like so:\n\nNow let's look at the JavaScript:\n\nI first added a boolean value for canCopy that will handle the toggle for showing the buttons. I then use created to check if the API exists. Finally I added the copy method. The clipboard API is an asynchronous one so I wait for it to finish and then alert the user. Let me just say that I am not a fan of using alert like this. The API runs so darn quick I think you could skip this part completely, but technically it's possible someone could click and try to paste at lightning speed and not get what they expect. I also think you could do the notification a bit nicer than the alert. You could add some text by the button or someplace else in the UI. Many UI frameworks support a &quot;toast&quot; event that will show a message that automatically dismisses. That would be a great option as well.\nYou can test this out yourself here:\n\n  See the Pen \n  Vue Copy to Clipboard by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo before I wrap this quick tip, let me point out this functionality could be done a bit nicer as a component or custom directive. If anyone wants to do this and share it in the comments below, please do!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Using a Loading Message",
		"date":"Wed Mar 04 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583280000,
		"url":"https://www.raymondcamden.com/2020/03/04/vue-quick-shot-using-a-loading-message",
		"content":"Well, my grand plan to do one blog post per day of Vue quick tips failed rather quickly, but I can get two out today and get back on track. Honest, I can. While I wasn't planning on making every tip link to the previous one, my first two tips do exactly that.\nMy first tip explained how to disable a submit button while you waited for an Ajax call to finish. (Or any async call, and I actually used window.setTimeout instead of Ajax.) Today's tip builds on that by adding a rather simple, but helpful, modification - a loading message.\nIn the previous example, when you hit the submit button I disabled it while we waited for the result. You can see that in the CodePen below.\n\n  See the Pen \n  Disable submit v2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWhile the disabled button lets the user know something is going on, it would be nice to be a bit more obvious. First, let's add a new conditional div to the layout:\n\nRight in the middle you can see a new condition, v-if=&quot;searching&quot; and a message inside. You could also generate an Ajax loader if you want...\n\nI then tweaked my JavaScript a little bit:\n\nI added a default value for searching and within doSearch, I set it to true before the search and back to false after. Here's a CodePen you can test with:\n\n  See the Pen \n  Loading Message by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThat's it for this tip. I'll have the next one up later today, and hopefully, one more for Thursday and Friday. Enjoy!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Voicemail Assistant with Vue and RingCentral",
		"date":"Mon Mar 02 2020 19:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583175600,
		"url":"https://www.raymondcamden.com/2020/03/02/a-voicemail-assistant-with-vue-and-ringcentral",
		"content":"I've been playing with the RingCentral APIs the past week or so and today I've got another one to share. When you sign up with RingCentral, you get access to a full phone system. By that I mean the ability to manage phone numbers assigned to users, work with forwarding, set greetings, and access voicemail. There's an admin portal UI to manage this along with APIs that provide the same power as well.\nOne of the cooler things you can do is access the call log for a user. This gives you insight into incoming and outgoing phone calls. For my demo I wanted to write an app that would report on incoming calls and look for voicemails. For calls with voicemails, I wanted the ability to play the audio via the browser. Here's the UI of what I came up with:\n\nFor each call (incoming call remember), I report on the time, duration, caller, and the 'result', which in this case is always voicemail. In a real application you would have calls that our picked up at times of course. Clicking the &quot;Play&quot; button retrieves the audio of the call and plays it in the browser.\nThe application was built with Vue.js on the front end and using Netlify serverless functions on the back end. I'll share a little secret. Even though I used Netlify, I never actually deployed this live. I did everything local with netlify dev in order to simply prototype my demo and explore the APIs.\nMy application consists of three main parts - the front end HTML, the front end JavaScript, and the back end serverless functions. Let's cover the front end first. Here's the HTML, which primarily just handles displaying that lovely table.\n\nAs you can see, I'm iterating over a calls variable. The API I'm using returns more information than you see used above, but I wanted to keep it simple. Now let's look at the JavaScript:\n\nAs a Vue app it's pretty simple. My data consists of calls and an audio object used to play voicemails. On created, I call off to loadCalls which hits my server side function to work with the RingCentral API. When I get the result, I do a bit of checking to see if I have a voicemail and if so, float up the id value a bit higher. This makes my HTML a bit simpler.\nWhen we do have voicemails, I've got another method that calls the server side function to ask for the URL of the audio bits itself. I've talked about these server side functions a bit now so let's take a look.\nThe first one gets the log of calls. This is really nicely documented along with some great testing utilities built right in the browser. I did a lot of testing right there before copying stuff over to my code. For this API there were three arguments I tweaked. I used the detailed view, set it to incoming calls only, and set the dateFrom to January 1, 2020. That date was totally arbitrary and I'd typically not use a hard coded value. Here's the serverless function.\n\nThe beginning of this function handles setting my credentials. The handler logs into the platform and then performs the HTTP call to the API. I'm using their npm package (@ringcentral/sdk) which makes the code pretty minimal. I return the entire dataset and as I said, there's more to it then I show on the page. As I said, their docs are pretty darn verbose and will help you.\nThe last part of this is how I handle the voicemail recordings. This function was a carbon copy of the previous one in terms of setup. I mainly just changed the API call and how I return data:\n\nI use their GetMessage API call. But once I have the result, I can look at the attachment URL and create a new URL that includes the current access_token. This is probably a bad idea, but it lets me return a URL the browser can just &quot;play&quot;.\nAnd that's it! Let me know what you think. This little demo isn't in a GitHub repo but I'm definitely willing to share.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Quick Shot - Disabling a Submit Button While Waiting for an Ajax Call",
		"date":"Mon Mar 02 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1583107200,
		"url":"https://www.raymondcamden.com/2020/03/02/vue-quick-shot-disabling-a-submit-button-while-waiting-for-an-ajax-call",
		"content":"Welcome to the first of a week long series of quick Vue.js tips. Each day (well, each week day) I'll be posting a real short, but hopefully practical, tip for Vue.js developers. For the first day we'll start with a simple tip but one that almost any application can use.\nIt is fairly typical for an application to make use of some sort of asynchronous process. Typically this is an Ajax call. (Although not always, and to be clear, today's tip will work with anything asynchronous!) You have a form of some sort. The user hits a button. You make a network call and when that call is done, you render the result. Let's consider a simple example of this.\nFirst, a quick form.\n\nMy form has one field and a button. On submit I'll run a method named doSearch. When I get a result, it will be displayed in a paragraph below.\nNow let's look at the JavaScript:\n\nMy doSearch method checks to see if anything was entered and if so, fires off a call to searchMyAPI. The details of searchMyAPI aren't relevant, but you can see I'm faking a slow process by making it wait for three seconds before returning the result.\nYou can test this here:\n\n  See the Pen \n  Disable submit v1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nWhen you test this, note that there's no indication that the search is actually doing anything. There's actually a few things we can do here, but today we're just going to do one - disabling the button while the search is being done.\nI'll begin by slightly tweaking the button:\n\nI've added a bound property, disabled, that points to a new value, searchDisabled. I then modified my JavaScript like so:\n\nFirst I added searchDisabled, defaulted to false. Before the search is begun I switch to false and when done, back to true. That's it! You can test this version here:\n\n  See the Pen \n  Disable submit v2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThat's it for today's quick tip. As I said, there's one more thing you could do to this form to make it even better and I'll be covering that in tomorrow's tip!\n",
		"tags":[
	        
            "vuejs",
            
            "vue quick shot"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "RaymondCamden.com now powered by Eleventy!",
		"date":"Thu Feb 27 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1582761600,
		"url":"https://www.raymondcamden.com/2020/02/27/raymondcamdencom-now-powered-by-eleventy",
		"content":"Another year, another new blog engine for RaymondCamden.com. For folks who haven't been around for the past seventeen years, this blog has gone through a few transformations.\n\nFor most of it's life, this blog was powered by a ColdFusion project I created called BlogCFC. I spent many years working on the project before moving on.\nIt then spent a short amount of time as a Wordpress site on Google's platform. Wordpress is an incredibly nice blogging platform, but I struggled to keep the site up and running. That's probably my fault but I didn't want to spend the time required to keep the server healthy.\nI moved to the JAMStack in 2015 I believe, using Hugo as my platform. It was a lot of work to get things moving smoothly, but the first time I published my site as a static set of files and realized I never had to worry about a server again, I was sold. (I tried to find the exact post where I announced the first time I went static but I failed. Sorry.) Hugo was incredibly fast, especially after I tweaked how my site is built to better work with the size.\nI moved to Jekyll in May of 2018. I had grown frustrated with how Hugo did things. Frankly, it was just wasn't for me. Jekyll was easier to work with and &quot;fast enough&quot; for my builds. It got even faster when it hit version 4. Overall, Jekyll was great, but relied on Ruby which was difficult to install and update, at least for me.\n\nSo back in October of last year, I discovered Eleventy. As a Node-based static site generator (SSG), I was really happy with the idea of no longer needing Ruby. It also just plain rocked. It was incredibly flexible and supported everything I needed out of the box. Frankly it is my favorite SSG now and I've been doing explorations on it over the past few months.\nWith that in mind, I started work on a conversion of this site from Jekyll to Eleventy. I considered a theme change too, but decided I was happy with the current UI for now. I ran into issues of course and thought I'd document them below. You should not consider this a &quot;Jekyll to Eleventy&quot; post (Paul Lloyd has a great post on this), but rather a look at what I ran into. My site is not typical in regards to what most people will hit. I've got a large set of existing content and it was crucial I maintained URL structure so as to not lose traffic. (I'll be checking Netlify Analytics to ensure that.)\nThere isn't any particular order, but here's a look at the issues I ran into.\nEscape Tokens\nWhen I use brackets in my code, like when I talk about Vue, I have to escape them because Liquid, the template engine Jekyll uses and Eleventy supports, uses them as well. Years ago when I blogged about Angular a lot, I had a particular way of escaping code that was ugly as heck. A year or so ago I found a simpler way that worked well with Liquid.\nOddly - the old style of escaping threw errors when Eleventy tried to parse it. So I had to go through all of my old Angular posts and edit them by hand. It took maybe 2-3 hours, but it was painful in terms of getting it just right. I don't have an example of this handy, and honestly I'm not sure how I'd escape the escape here, but if anyone needs to see the &quot;wrong&quot; way, let me know.\nMarkdown files\nSome of my Markdown files rendered a bit differently in Eleventy in terms of use of HTML. What I mean is that sometimes I'd sprinkle in HTML in my Markdown for things like forms. In Jekyll that was fine. In Eleventy this sometimes would result in the HTML being escaped and shown on screen. To fix it I literally just renamed the files in question, so for example I changed search.md to search.html. Eleventy still parses the files for front matter so my layouts worked just fine.\nSite variables\nJekyll has the idea of site variables, like site.foo. Eleventy does not. I fixed that by simply adding a data file named site.json.\nLiquid differences\nThis was the big one. A few weeks ago I blogged about how Eleventy ships with an older version of Liquid. This meant that some filters I used didn't work. To make matters worse, Jekyll adds it's own filters to Liquid.\nBut wait - it gets even better. Guess what Liquid does if it encounters an unknown filter?\nNothing.\nThis was first raised as an issue almost six years ago and is still an open bug. It boggles the mind. I can totally see where a person may want that behavior, but to not have it configurable and defaulting to silently ignoring is just crazy (imo). So what this means is that if you do this:\n\nMy name is {{ name | fancycapitalize }}\n\n\nAnd fancycapitalize isn't recognized, it just returns the value of name as is. While I can see the merit of that perhaps, for someone who wants to ensure things are actually working it's a royal pain in the rear.\nSo I ended up rewriting many filters and Eleventy makes this easy. You just drop in code inside your .eleventy.js file. You can see mine here if you want to dig.\nCategories\nEleventy automatically supports parsing tags in your front matter and maki",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with RingCentral Webhooks",
		"date":"Wed Feb 26 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1582675200,
		"url":"https://www.raymondcamden.com/2020/02/26/working-with-ringcentral-webhooks",
		"content":"As you know, I've been playing around with RingCentral lately. While working on another idea (for another post of course), I ran into some difficulty getting their webhook support working. While everything is documented, it didn't quite make sense to me and I had to get it working myself before I actually believed it worked. So with that in mind, here's how I got webhooks with RingCentral working.\nStuff to Know First\nFirst off, take a look at the docs for Webhooks on their platform. In some platforms, you simply specify a URL to hit for a webhook and you're good to go. RingCentral requires you to register a webhook via an API call.\nIn order to use webhooks, your application has to enable that permission (this is under your app, Settings, OAuth Settings):\n\nNext, note that your webhook must be up and running before you register it. That's not necessarily a bad thing, but it was kind of surprising. In most cases a platform just assumes your URL will work and leaves it it up to you.\nWhen registering your webhook, RingCentral is going to pass a header, validation-token, and if it exists, your webhook has to echo it back in a header and end the request.\nTo register a webhook, you tell RingCentral what events you care about. Getting that list was a bit difficult. If I read the docs right, the event is basically the same as the API related to that event. My code was working with voicemails, so I used this value: /restapi/v1.0/account/~/extension/~/voicemail.\nNow for the last bit, and this is the part I really don't like. When you register a webhook, it isn't permanent. No, it expires after a time. If you want, you can actually specify that you want the service to hit your webhook when it expires. And I guess you... just re-enable it again? Honestly I don't quite get this part. Maybe it's a security setting, but honestly it feels like a lot of burden on the developer. It's already difficult to setup the webhook compared to other services which just let you type in a URL, and this feels like pouring salt in the wound a bit. Now, there may be a very good reason for this setup.\nOk, good news! So I was about to post to a forum thread where I asked why this was necessary (https://forums.developers.ringcentral.com/questions/9774/purpose-of-webhook-expiration.html), and it turns out the API to register a webhook supports an expiresIn value that can be set to as high as 20 years. As Phong Vu explains, instead of trying to listen for a callback saying the webhook expired, if you know the exact time it will expire, you could just schedule the re-register yourself.\nSo this is good! I still think RingCentral should support a &quot;never expire&quot; option, but I can deal with twenty years.\nThe Code\nWhat follows is &quot;Get it Working&quot; code. You should not consider this production ready unless your production system is run by these fine people:\n\nMy code consists of two parts. First, I wrote a simple Node server with Express to handle my webhook. RingCentral has a Node example as well but I wanted to use Express because, well, I always use Express when I do server stuff. That being said, I realized today I had not used Express in ages. Serverless has made me a bit lazy. So again, do not consider this code to best practice. Here's my webhook which handles the validation and then just logs the incoming request.\n\nThe part you care about is at the end. You can see the validation support and then after that I simply dump the incoming data to the terminal. It's a pretty big object, but it's nicely documented here: https://developers.ringcentral.com/api-reference/Voicemail-Message-Event That's for Voicemails, but in the navigation you can see they define all the different event types.\nI ran this and then used ngrok to create an externally facing proxy to my laptop. I've blogged about ngrok before and if you haven't checked it out, definitely do so. It's incredibly helpful for cases like this.\nNow for the script I used to create the webhook. Here's the script I used:\n\nThe important bits are the call to the subscription endpoint. You can see me specifying my filter for voicemail and the URL I got via ngrok. As I said above, I now know I can specify expiresIn in my call (here's the full API doc for creating subscriptions) so I'd modify the above to specify the max value,  630720000.\nOk, after all of that... it worked. I called the number RingCentral has assigned my user, left a voice mail, and saw my webhook get called in about five to ten seconds! I hope this helps. As I said, I found this to be very difficult to get working, but honestly it probably only took me an hour or so and now that I've got it working, I think I could do it again easily enough. Let me know if any of this doesn't make sense!\nHeader photo by Chunlea Ju on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack",
            
                "javascript"
            
		]

	},

	{
		"title": "Sending SMS Messages for Form Data with RingCentral and Netlify",
		"date":"Tue Feb 25 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1582588800,
		"url":"https://www.raymondcamden.com/2020/02/25/sending-sms-messages-for-form-data-with-ringcentral-and-netlify",
		"content":"A few days ago I blogged about using RingCentral's APIs to send a SMS message when your Netlify-based site builds (&quot;Adding a SMS Alert for Netlify Builds with RingCentral&quot;). I thought I'd follow it up with a related example - sending a SMS with form data. To be clear, this post isn't too much different from the previous one, but I thought it was an interesting enough example to share.\nLast year I blogged about using Netlify serverless functions for form submissions. In that post I detail the data sent to the serverless payload (since, ahem, Netlify still doesn't document this). Based on that, here's the code I used to take the form submission and send it as a SMS:\n\nBasically - I create a formatted string and pass it to a function to handle sending the SMS. The result is much like my previous example:\n\nAs a reminder, that text watermark in front would not be there in a production-released RingCentral application.\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack",
            
                "javascript"
            
		]

	},

	{
		"title": "Adding a SMS Alert for Netlify Builds with RingCentral",
		"date":"Sat Feb 22 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1582329600,
		"url":"https://www.raymondcamden.com/2020/02/22/adding-a-sms-alert-for-netlify-builds-with-ringcentral",
		"content":"I'm currently sitting in the Atlanta airport waiting to fly home from one of my favorite conferences, DevNexus. While there, my buddy Todd Sharp introduced me to RingCentral. RingCentral is a telecom API provider that reminds me a lot of Nexmo and Twilio. I've enjoyed working with both of those companies APIs so this morning I spent some time playing with RingCentral as well.\nThey have a great developer onboarding experience. I was able to setup an account in a minute or so. I then looked at one of their first walkthoughs, SMS JavaScript Quick Start and was happy to see it worked right out of the box. Here's what their sample SMS sending code looks like:\n\nMy only complaint was that the lack of semicolons in the beginning made me twitch a bit. I would also like to see an async/await version of it (which you'll see in a second ;). Once I confirmed it worked, I then figured out a simple demo I'd build.\nOne of the cooler features of Netlify is the ability to run events on triggered events. One of those events is deploy-succeeded which lets you do something after a build is done. All you need to do is name a function deploy-succeeded.js and it will be executed automatically.\nUnfortunately, and I love you Netlify, honest, they still do not document, completely, the information sent to these events. In my case it wasn't necessarily important. I just wanted to know the build succeeded. I had to do some console.logs, copying and pasting, and formatting to get to the information. Again, Netlify, I love you, but take the 5 minutes to add this information to the docs.\n\nThat being said, I was able to find two values in the payload I thought would be useful, published_at and deploy_time, which reports the time in it took to make the build in seconds. I thought it would be cool to integrate RingCentral's SMS support and use these two values. Here's the function I built.\n\nThe top portion of my code is a block of statements simply copying environment variables into a simpler variable scope. Netlify lets you specify secrets in environment variables. Since my site was tied to GitHub, it would not have made sense to check in code with secrets. (Not that I've ever done that. Honest.)\nMy main handler code parses the event information sent in and grabs the values I care about. (And again, there's a lot more. I'd like to share my payload but I'm not sure if there's anything sensitive in there, and also, Netlify should document it!) I then call sendSMS. This is roughly the same logic as their sample code, but rewritten with hipster await awesomeness.\nWith that written (well, code like this), I committed and triggered a build. I then discovered that if you have a bug in your handler, the entire build fails. That's a good thing in my opinion. But it's not documented. (Are you sensing a theme here?) I fixed my bug and voila:\n\nNote that the first few messages were me testing, and the watermark goes away from &quot;real&quot; accounts. Let me know what you think. I'm going to be playing with RingCentral for a while so expect more posts!\nHeader photo by Myke Simon on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack",
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Another Template Language to Eleventy",
		"date":"Wed Feb 19 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1582070400,
		"url":"https://www.raymondcamden.com/2020/02/19/adding-another-template-language-to-eleventy",
		"content":"While at a conference last week, an attendee was asking me about Eleventy and specifically, how to add support for another template language. He mentioned that he was a user of MJML, a format I had never heard of, but apparently it's big for people who build responsive email templates. Doing HTML for email seems to be the &quot;last place&quot; where web dev is painful so this probably makes things quite a bit easier. I did some digging and this is what I turned up.\nOfficial Support\nSo first off, there is no official support, yet, in Eleventy for doing this. There's an issue for it: Custom File Extension Handlers It's closed but that's because the repo uses lodash style issue management. (I've got feelings about that, but whatever. ;) Note that this is different from the ability to provide a newer version of the supported template engines. I blogged about that earlier this month.\nAttempt One\nOk, so given that you can't do it (yet), I went through a couple of iterations to get this working. My first one is not what I'd recommend, but I'm sharing it as just a cool example of the flexibility of Eleventy in general.\nFirst, I created a folder for my testing (and I'll be sharing a link to the repository at the end of this post) called mjml_first. In that folder I put a file named test1.html. The extension is important. If I named it test1.mjml, Eleventy would not do any processing on the content, which is what we want. Unfortunately it means you lose syntax helpers and color coding in your editor, but remember that Visual Studio Code lets you overwrite the document type. (Or just wait to &quot;Attempt Two&quot; later in this post. ;)\nIn that post I copied in the sample MJML I had found:\n\nNow I want to tell Eleventy to use a layout for all the files in this folder. Eleventy lets you specify data files at the template and directory level. To do so, I added mjml_first.json to my folder:\n\nNext I built the layout file. Typically a layout file uses Liquid or some other template language and just includes the content somewhere in the middle. But again, Eleventy is incredible flexible. You can use JavaScript for your layouts as well. At the command line I installed MJML node support and then named my layout file mjml.11ty.js. The 11ty part is important. Here's what the file did:\n\nHonestly that could be even shorter, but you can see I'm just passing in my content to the mjml engine and returning the output.\nAnd that's it. When I run my build, I get HTML out which I won't share here as it's crazy long to work correctly in email clients.\nI liked this approach, but as I said, it required using .html files which kind of bugged me a bit.\nAttempt Two\nFor my second attempt, I focused on getting proper support for the mjml extension in my project. I created a folder named mjml and put two sample files in there. I also installed the Visual Studio Code MJML extension because of course there's an extension for that.\nSince mjml is an extension that Eleventy doesn't support, it's going to be ignored. Of course you can add addPassthroughCopy to copy it to the _site folder, but it would be copied as is with no parsing.\nSo instead I did two things. First, I added a data file named mjml.js which the following code:\n\nThis loads in all the mjml files and iterates over each. For each item it changes the path value to remove .mjml. We'll add back in a proper extension later. Then I add the mjml parsed version to an array and return it.\nThe end result is that Eleventy has access to data that contains a list of parsed MJML files.\nI then used my favorite Eleventy feature to define a &quot;pagination&quot; output of the data with a size of 1. Basically, make one file per record in the array of data. I named this mjmldocs.liquid.\n\nNow when I create my build, I get a mjml folder with subdirectories per input file and an index.html file with the parsed content.\n\nI was done. Except that I noticed when I edited mjml files, Eleventy didn't notice. I thought perhaps that would just have been a small paint point, but of course not, Eleventy is awesome. I added one line to my .eleventy.js file:\n\nThat's all it took!\nSo hopefully this is helpful to others. You can find the code I used to test here: https://github.com/cfjedimaster/eleventy-demos/tree/master/customext\nHeader photo by Artem Beliaikin on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionicons in Vue.js",
		"date":"Mon Feb 17 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1581897600,
		"url":"https://www.raymondcamden.com/2020/02/17/ionicons-in-vue",
		"content":"It's been a long time since I've written about Ionic. In general, I haven't done much in the hybrid mobile space over the past few years. I pay attention to their updates though (version 5 looks impressive) and noticed recently they did a major update to their Ionicons project.\n\nI've only used Ionicons with Ionic project, and while not required, it was useful as hell to have a robust icon library to use when building mobile apps. I knew that the project could be used outside of Ionic but I hadn't actually tested it out. On a whim, I thought I'd take a quick look at what you need to do to use it in a Vue app.\nSpoiler - it was ridiculously easy.\nI started off with a Vue application on CodePen. And by &quot;application&quot;, I mean just a CodePen with the Vue script tag added. I then setup some data for testing:\n\nI've got an array of drinks where each one has a name and type. To make things a bit simpler, the types also happen to correspond to icons supported by Ionicons.\nTo add support, and pay attention, this is really complex, I added this script src: https://unpkg.com/ionicons@5.0.0/dist/ionicons.js.\nAnd that's it. Done. Ionicons make use of web components to add in support for the icons. (For unsupported browsers, polyfills should be used. I did a quick test with IE11 and it worked fine.) Using them then is as simple as this:\n\nwhere &quot;something&quot; refers to the icon you want to load. You may not notice it at first but the home page has a search field that lets you quickly look for a particular icon by name. The usage page also details how to use variants, like filled icons versus outlined. You can even specify per platform (ios versus android) like so:\n\nMy guess it that every single browser outside of Safari will use the md version. In my quick test on my Windows machine, Firefox used the md version.\nSo given my data, I wanted to render my drinks and use the right icon based on the drink type. This is what I used.\n\nAnd here's the result:\n\n  See the Pen \n  Vue plus Ionic Icons by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo yeah, I love it when I decide to test something to see if it works, and it just does, and it doesn't get complex in any way whatsoever. I had not thought of Ionicons at all recently but now I'm absolutely going to use it in my Vue apps where it makes sense.\nOops, One More Thing\nI had my buddy and Ionic devrel Mike Hartington do a quick sanity check on the post. He wondered why I didn't run into the &quot;Unknown custom element&quot; issue. Turns out, I had run into it, just hadn't noticed. It's an warning thrown in the console, not an error, and it takes all of two seconds to fix. Basically, you tell Vue to calm down and don't worry about it like so:\n\nMy CodePen above has this added.\nHeader photo by Harpal Singh on Unsplash\n",
		"tags":[
	        
            "ionic",
            
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Text Linting to Eleventy",
		"date":"Mon Feb 10 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1581292800,
		"url":"https://www.raymondcamden.com/2020/02/10/adding-text-linting-to-eleventy",
		"content":"While going through the Eleventy's docs recently I discovered an interesting feature, Linters. This feature lets you define custom rules you want to apply to your logic such that you can throw warnings during the editing and publishing process. The docs have an example of this in action but I wanted to expand a bit on it and see if I can add in textlint support. textlint is a customizable text linting program built with Node.\nI first blogged about textlint back at the end of 2018 and described how I connected it to my GitHub commit process. I decided to take a look at how I could integrate it with Eleventy.\nFirst, a quick note about their linter API. First, this will not allow you to throw errors. I mean you could throw an error in your code, a manual Exception I mean. Instead you use this feature as a way to log out messages to the console during development.\nYou are passed three arguments: content, inputPath, and outputPath. Let's discuss the second two first. As you can guess, inputPath is the file being processed, like foo.liquid, and outputPath is the destination path which follows Eleventy's rules for such things. Using foo.liquid as input, it may be foo/index.html for output. These paths are relative, so keep that in mind if you need to do anything that requires the full path.\nThe content argument is the parsed HTML output of the template, which is very cool. It means you can lint the text the public will see. Well that's mostly cool. In my testing, I noticed that one of the plugins I used for text linting did not like HTML, so I removed it. But in general I think it's very good that you get the &quot;final&quot; content instead of content with embedded variables and things in it from the template language.\nAlright, so with all that, how does this all work? Let's consider their default example:\n\nThey begin by defining a set of source words they want to check for. Next they see if the input file was markdown. I think, in general, this is a good thing to do since a content site will probably be largely markdown with other ancillary files being Liquid or some other template language. So for example, my blog uses markdown for 100% of the blog content, but pages like my &quot;About&quot; and &quot;Speaking&quot; pages are Liquid. In theory I'd like to lint them too, but I'd be ok with just the markdown being check. When run, this is how it looks (assuming you have a few issues in your content):\n\nYou can see the messages sent out immediately after I started the server. The messages will be repeated every time I edit so the feature gives you good, constant, feedback in the console as you work.\nThat's the out of the box example, how about an example with the textlint project? First off, note that textlint has a large number of rules. Each rule is it's own project and must be npm installed. Each rule also has it's own configuration. In my testing (both now and the previous times I've worked with it), I've found mixed results from different rules. For example, I never could get spell check working well. Also, things like the alex rule, which looks for gender favouring or insensitive language, didn't work with HTML. I don't think that was documented at all. Basically this is my way of saying that while overall textlint is a cool project I think, you should expect a bit of roughness around the edges.\nFor my testing I decided to use some of the same rules I used in my previous test.\n\nalex which covers insensitive language\nno-start-duplicated-conjunction which attempts to find multiple sentences starting with words like &quot;but&quot; or &quot;so&quot; - I never got this test to work though.\nterminology which looks for the right spelling of certain technology-related words, like iOS versus ios.\n\nI made a new Eleventy project (which is just a folder) and npm installed textlint and then the three rule plugins. Here's my .eleventy.js:\n\nAlright, so on top, I started off adding Chalk, a cool utility that makes console.log messages stand out a bit. I then instantiated my instance of textlint.\nIn the addLinter block, I then pass the content of the file being parsed to the engine and take the result. The results an array where each instance of the array contains an array of messages. So I loop inside a loop and output the result. You can do more with the results, like provide alternatives and the like, but you'll need to check what's provided on a rule by rule basis.\nFor each message, I use chalk.yellow to make the message stand out a bit. Red may be better, but it's up to you!\nAs I mentioned earlier, the alex rule didn't like HTML. It didn't throw an error, it simply ignored the entire input. That was frustrating, but quick to fix with the regex you see.\nHere's the output based on some test files I created.\n\nAs I said, this is not a perfect solution. So for example, for terminology I had used javascript in my test and it wasn't picked up. Why? I had this as input:\n\nSee the comma after javascript? That was enough to &quo",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Checking (and Upgrading) Template Engines in Eleventy",
		"date":"Fri Feb 07 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1581033600,
		"url":"https://www.raymondcamden.com/2020/02/07/checking-and-upgrading-template-engines-in-eleventy",
		"content":"Yesterday a follower on Twitter encountered an interesting issue with Eleventy that turned into a bit of a bigger issue. Let's start with his question.\nSo it seems like LiquidJS added support for where filters https://t.co/nYFmA328WF but maybe that hasn&#39;t been rolled into Eleventy yet? https://t.co/4Ovu4aP8I0&mdash; Richard Herbert (@richardherbert) February 6, 2020 \nThe where filter in Liquid provides a simple way to select values in an array by simple property matching. So consider this array:\n\nI've got four cats with names and genders. By using the where filter on gender, I could select different cats like so:\n\nIf you run this in Eleventy though, you get this:\n\nThe assign works fine, but there's no filtering.\nWhy?\nTurns out Eleventy ships with an older version of the Liquid template engine. This then leads to the question, how do you know what version Eleventy ships with? If you go to the docs for Liquid in Eleventy, you'll see it isn't mentioned. I raised an issue on this saying the docs should make it more clear (for each engine obviously). It could actually be in the docs and I don't see it of course.\nLuckily though you can provide your own version of Liquid (or Nunjucks, or Handlebars, etc) by using eleventyConfig.setLibrary in your .eleventy.js file. The docs show this example:\n\nI gave this a shot. I made a new directory, did npm i liquidjs, and tried this code, but it threw an error. I checked the docs for liquidjs and saw that their initialization code was a bit different. I copied their code and ended up with this:\n\n\nWoot! But huge caveat here. Eleventy passes in it's own default options for Liquid. In my sample above I passed none so I'm using the liquidjs defaults instead. This could lead to backwards compatibility issues. This is discussed in another issue.\nSo what version of Liquid does Eleventy ship? The user @DirtyF commented that by using npm outdated in a repo with Eleventy you can see the following:\nPackage      Current  Wanted  Latest  Location\nejs            2.7.4   2.7.4   3.0.1  @11ty/eleventy\nhandlebars     4.7.1   4.7.3   4.7.3  @11ty/eleventy\nliquidjs       6.4.3   6.4.3   9.6.2  @11ty/eleventy\nmustache       2.3.2   2.3.2   4.0.0  @11ty/eleventy\nYou could use this as a way to figure out exactly what features you have available when using your desired template language.\nAs I raised in my issue, I think Eleventy needs some kind of &quot;statement&quot; or plan about how it does upgrades, when/how it handles backwards compatibility, etc. I don't think there is an easy solution for this but I'm hoping to be able to help the project with this effort. (If you can't tell, I'm rather enamored with it. ;)\nAn Alternative\nSo what if you don't want to muck with how Liquid works in Eleventy? Well you've got options, lots of em!\nOne way is to just use a conditional:\n\nWhile this implies looping over every record, keep in mind this is only done in development. In production it's just a plain static HTML file.\nAnother option is to use filters. Liquid filters support arguments, so you could build this generic utility:\n\nI named it where2 just for testing but you would probably want something else. This lets you use the same format that the newer Liquid uses:\n\nFinally, as yet another option, consider switching engines. What do I mean by that? While Liquid is definitely my preferred engine, EJS is incredibly flexible when it comes to code in your template. To be honest, it's too flexibly imo and encourages you to do stuff in your templates I think you should do elsewhere. But that flexibility could be a lifesaver, and one of the most awesome features of Eleventy is that you can easily switch one document to another engine by just changing the extension.\nHeader photo by Daniel Levis Pelusi on Unsplash\n",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding a Last Edited Field to Eleventy",
		"date":"Thu Feb 06 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1580947200,
		"url":"https://www.raymondcamden.com/2020/02/06/adding-a-last-edited-field-to-eleventy",
		"content":"Let me begin by asking for some patience here as this post may ramble a bit. It certainly ended up going in directions I didn't expect when I built my proof of concept. If anything doesn't make sense, or I may some silly mistake, absolutely leave me a quick comment below and let me know. Alright, so with that out of the way, what in the heck am I writing about today?\nNext week I'm going to be giving a presentation at Flashback Conference in Orlando about the &quot;dynamic&quot; web. As part of my presentation I talk about Apache Server Side Includes. This was a very early way to add some dynamic capabilities to HTML pages. One of the examples given is to include the date the page was last modified. If you're curious, it looked like so:\n\nIt occurred to me that you really don't see this much anymore on the web. Early on though it was pretty common for web pages to document when they last changed. That being said, it actually seems like a really good idea for technical documentation.\nI thought I'd check and see how this could be accomplished with Eleventy. I was also curious if this could be automatic. So for example, if I edit docs/lightsaber.md then I'd like the published site to be able to access the edit time by looking at the file's metadata.\nSo, first off, every Eleventy page has data automatically included in a page scope. Included in this is a date value you could use: {{ page.date }}\nThe docs have this to say about it:\n\nThe date associated with the page. Defaults to the content’s file created date but can be overridden. Read more at Content Dates.\n\nAs it defaults to the created date, this wouldn't work for me, but I was curious to see it in action anyway so I built up a quick demo. I made a new site, added a folder called docs, and configured it as a new collection:\n\nI then added a few files and used a layout:\n\nMy doc layout has this:\n\nI fired up my Eleventy server and hit my doc pages and confirmed they rendered right. I then modified one doc page, reloaded, and noticed something odd. Every single doc page had a new value for their date!\nI mentioned this in Slack (that's a great place to talk JAMStack stuff!) and I was reminded that when you edit content in Eleventy, it will rebuild the entire site on each change. (This may change with a command line flag.)\nAlright, so now it made sense. Given two doc pages, every time I edited one, then both were recreated. But then I saw something odd.\nImagine two doc pages, apple.md and banana.md. Both were made at 10AM. I edited banana.md sometime later. When I generated my site, I saw that the resultant HTML files appeared to be using the last modified value, not the creation time. In other words, I expected both to have the same date (or within miliseconds), but banana/index.html clearly had a later value.\nSo in theory kind of working like I wanted, but I didn't quite trust it. I quickly looked up how to get information about a file from the operating system and noticed something odd - apparently it had no &quot;creation&quot; value.\n\nI'm using Ubuntu via WSL on Windows, so I quickly checked what Windows had to say, and it clearly had a value:\n\nI did some digging and it looks like it might be this: When is Birth Date for a file actually used?\nSo if there is something with the operating system, my next question, does Eleventy try to get the creation date, fail, and then go to the last updated value? I filed an issue asking about that and I'm waiting to hear back.\nOk, still with me? Keeping in mind that I knew this wasn't the best approach, I did one more test. I put my code in a repository, added it to Netlify, and checked the result. On Netlify, the values for every doc page were the same. If I edited one doc, committed the change, and waited for Netlify to build it, I saw the same thing. Both docs had the same file and as far as I could tell, it was the &quot;created&quot; value after Netlify pulled the files down. To me this felt totally right in terms of how I expected things to work and my local environment was just... wrong. Kinda.\nThe next step than was to put the onus on the writers to include a date. That isn't horrible at all of course, and can easily be done in frontmatter. So for example:\n\nand\n\nWhen you use date in front matter, it overrides page.date as documented here. You could also use your own field, like lastEdited or some such, and if you do, then don't forget to address it as that, not page.whatever.\nTo make it look nicer, I added a filter as well:\n\nAnd then finally edited my layout:\n\nYou could also extend this idea a bit and support something like this:\n\nThis example shows both a date for the edit and a message about what changed. Since you've got access to all your data in any place in Eleventy, you could build a page that collected these messages and generated a change log automatically!\nSo if for some reason you want to see this in action, you can see a live view here: https://determined-liskov-3626af.netlify.com/. And the source may be f",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building Table Sorting and Pagination in Vue.js - with Async Data",
		"date":"Sat Feb 01 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1580515200,
		"url":"https://www.raymondcamden.com/2020/02/01/building-table-sorting-and-pagination-in-vuejs-with-async-data",
		"content":"Nearly two years ago I wrote a blog post (Building Table Sorting and Pagination in Vue.js) that detailed how to paginate and sort a set of client-side data with Vue.js. A day or so ago a reader asked how you would handle the same thing using data loaded from a remote API. Specifically, data where every sort, and page, is loaded from a remote API. I worked on a demo of this and while doing so learned something new about Vue. So here are my solutions and as always, let me know what you think.\nThe Data\nFor both of my demos, I used the API at Open Brewery DB. This is a free API that doesn't require a key and supports CORS. Even better, their brewery list API supports both paging and sorting which made it perfect for my tests.\nVersion One\nFor the first version, I updated the layout to show the name, city, and state values of breweries. The API supports more of course but I wanted to keep it simple. All three values can be sorted and the entire data set paginated. Here's the updated layout code:\n\nThe table itself isn't too different from the previous versions, I just changed the names of stuff, and obviously we iterate over new data, breweries. The pagination buttons are slightly different this time. I've added the ability to disable the previous button, but not the next one. Why? The brewery API doesn't return the total number of records, so there's no easy to way to know when we are at the end. It is something you could handle by simply seeing if the request for the next page returns no results, but for this demo I just ignored the issue.\nNow let's look at the code.\n\nThe most important part is the loadBreweries method. It requests data and contains information about what page to get, how many to get, and how to sort. The API asks that you sort by column name and include - when you want to sort descending, so I built a utility method, sortStr, to handle that for me. Now when the app loads, I immediately call the API to load my breweries and when you sort and page, all I do is change the current values for them. This version is actually easier than my previous ones since paging and sorting data is all handled by the API.\nAnd that's it! I should absolutely add a &quot;loading&quot; UI of some sort, but I'll leave that as an exercise for the reader.\n\n  See the Pen \n  Vue - Sortable Table Aync 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nVersion Two\nSo after finishing the first version, I noticed that I had used code that processed changes (paging, sorting) and then fired off a method, and it occurred to me that I should simply be using computed properties. Duh. So I went ahead and change the previous code such that breweries wasn't an array but a computed property... and then discovered that you can't do async computed properties. Duh. I mean everyone knows that, right?\nErr, no, I didn't. I'm sure I read that at some point, but this was the first time I ran into it. When you try, you don't get any errors, or warnings, but it just doesn't work.\nHowever, we're in luck! There's a plugin that makes this easy, vue-async-computed. You add this and then you can literally move code from a computed block to an asyncComputed block. The plugin also supports returning a &quot;loading&quot; value which is pretty neat.\nI added the script to my codepen and then modified my JavaScript like so:\n\nNote that now when I change paging and sorting, I no longer need to call my method to load breweries, it happens automatically. Here's the demo:\n\n  See the Pen \n  Vue - Sortable Table Async 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nRemember that both demos still should use a loading indicator of some sort to let the user know what's going on. Leave me a comment if you've got any feedback!\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue and Form Fields",
		"date":"Mon Jan 27 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1580083200,
		"url":"https://www.raymondcamden.com/2020/01/27/vue-and-form-fields",
		"content":"Vue has great support for working with forms. This is heavily documented in the Form Input Bindings section. I wanted to build my own demo/set of examples based on this as a way of exploring the different types of inputs and how Vue works with them. In order to do this, I built a demo that covered every single type of form field possible. (As far as I know, let me know what I forgot.) For each I tested setting an initial value and outputting the bound value in case it looked different than what the field displayed, input[type=color] is a great example of this.\nIf you don't want to read my long winded text, you can jump right to the CodePen here: https://codepen.io/cfjedimaster/pen/vYEMgjV?editors=1011\nOk, let's get started! Please note some of these are boring, i.e. they work as expected with no weirdness.\ninput[type=text]\nThe simplest and easiest of the fields, and what the &quot;fancy&quot; fields (like type=color) turn into when run on older browsers. I tested with this markup:\n\nAnd this data:\n\nNotice the second field makes use of maxlength. On initial display, both work as expected, but in the second one you can only delete characters, not add them, until the length is less than five.\ninput[type=button]\nI used this markup:\n\nand this data:\n\nAnd it just renders a button where the label is the model value.\n\ninput[type=checkbox]\nOk, this is a fun one. Checkboxes allow you to specify zero, one, or multiple items. I used this markup to dynamically render the checkboxes:\n\nHere is the data:\n\nA few things to note here. I've got N inputs based on the total number of items in the array. Each one has a specific value, but the v-model points to the selected value I've defined. Also note when I iterate I include the loop index, this lets me specify a dynamic ID value for each and use a label to make it easier to use.\nThe default value, if you want to specify it, is an array.\ninput[type=color]\nThe first one that may not be supported in your browser, it worked just fine in the shiny new Microsoft Edge. Here's the layout:\n\nand here is the data:\n\nThis is the first control where, by itself, you can't see the real value:\n\nWhen I first tried this, I attempted to set color1 to a named color, but that isn't supported, it must be an RBG value in hex. This is - of course - documented over at MDN.\nThe date inputs\nTo make things a bit easier, let's consider the date related field types: date, datetime-local, month, time, and week. Altogether, their markup:\n\nAll in all, there's nothing special about any of these markup wise, but UI wise they all render somewhat differently across different browsers (and not at all in Safari because of course not).\n\nSome work as expected, like date opening up a calendar (again, in Microsoft Edge), but then it gets more complex from there. Week, for example, shows this:\n\nEach of these had slightly different ways of specifying initial values:\n\nI had to guess at some of these. I'd specify a blank value, set the value, and then check my debug output. (I may have forgot to mention, but at the bottom of my markup I've got a debug region where I output every value.) Week, especially, was surprising.\ninput[type=email]\nAbsolutely nothing special about this - here's the markup:\n\nand the data:\n\ninput[type=file]\nHere's a fun one. First note that form fields are readonly, so doing this won't be possible:\n\nAnd:\n\nIn fact, Vue screams about it in the console:\n\nWhat's cool is that they tell you exactly how to fix it:\n\nThis can then be tied to a method:\n\nThis provides you access to information about the files allowing you to do fancy things, like figuring the size of images or doing client-side validation of files.\ninput[type=hidden]\nWorks as expected, hidden from the user, nothing to see here, carry on.\n\n\ninput[type=image]\nAlso nothing special here. I've never used this one in production before but I guess folks have used it.\n\nMy data was a URL path to the image:\n\nIn case you're curious it is acts like a submit button.\ninput[type=number]\nAgain, nothing really special... at first. So consider this markup:\n\nAnd this data:\n\nSo if you don't modify the value, what's actually there in the DOM?\n6? Nope.\n&quot;6&quot;\nPat yourself on the back if you knew this. I know this. Of course I do. I still manage to forget about 90% of the time. Vue provides a dang nice way to handle this though. Just add a .number modifier:\n\ninput[type=password]\nAgain, nothing special. Take this markup:\n\nAnd this code:\n\nAnd you get a password field where the value is hidden. Don't forget though that you or I can go into devtools and change the field type to text to see the value.\ninput[type=radio]\nAnother one with multiple items, but this one only takes one value, not 0 or more.\n\nAnd here is the data:\n\nNotice that the selected value is not an array but one value.\ninput[type=range]\nFirst the markup:\n\nAnd the value:\n\nRemember that the browser will not display any numbers with this control:\n\nYou could use the &lt;output&gt; tag to handle this but it's",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "The Last Damn Vue ToDo App",
		"date":"Sun Jan 19 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1579392000,
		"url":"https://www.raymondcamden.com/2020/01/19/the-last-damn-vue-todo-app",
		"content":"Welcome to the last, ever (ok, for me) iteration of the ToDo app in Vue.js. I started this series a few weeks ago (&quot;Another Damn ToDo App in Vue.js&quot;). In the first iteration, the data was ephemeral and lost every time you closed the application. I then followed up with a version that persisted the data using IndexedDB, &quot;Yet Another Damn ToDo App in Vue.js&quot;. In this third and final version I decided to kick it up quite a bit by adding Google Firebase.\nFirebase is a platform I've been meaning to play with for quite sometime now. It's got quite a few features but at minimum I knew it supported a cloud-based database and authentication. With that in mind, I wanted to add the following support to the previous version.\n\nStore data in Firebase.\nAdd authentication to the application.\nAssociate data with the current user.\nMake it so only logged in users can read and write data.\nMake it so you can only read and write your own data.\n\nI got it working, but I want to be super, duper clear that this is my first attempt at building such a thing. My assumption is that my security rules are NOT RIGHT. They seem right, but if you aren't 100% sure when it comes to security you might as well be 0% sure. I also think my code, in general, is a bit messy and could be organized a bit better perhaps. But as I got the basic features done I thought it was a good place to stop, take stock, and write about my experience.\nLastly, I intentionally did not look for Vue/Firebase plugins/modules/etc as I wanted to do everything &quot;by hand&quot; so to speak, at least for this first build.\nBasically - I hope this post gives you an idea about Firebase and Vue but please, please, please consider this a rough first draft that is incomplete.\nOk, ready?\nGetting Started\nI started off with the Cloud Firestore documentation. Firebase is the overall product whereas Firestore is specifically related to data storage. Initially this went rather well. Setting up the project was pretty simple. Although at the time I didn't realize that the project is like an overall... err... well project and you need an &quot;app&quot; under the project as well. Things got a bit confusing in the quickstart:\n\nNotice how step 1 takes you to another guide, kinda, and I tried to manage that section plus this section together and it was... weird. Looking at it now... I guess the idea is that you add Firebase, and then Firestore? Although step 2 has that already. Maybe it's just me. :)\nInitial Version\nSo going through the docs, my initial changes basically came down to:\n\nAdding script tags to my index.html. The Vue CLI uses it as a template that gets injected with your Vue app.\n\n\nNext it was time to work with Firestore. In the previous iteration, my component calls to a Vuex store. The store uses an API library that manages access to IndexedDB. My goal was to simply make a new API library and 'drop' it into the store. I was able to do that. Here's the new API file, firestore.js:\n\nLet's tackle it bit by bit. The getDB routing now handles Firebase initialization and grabbing the firestore object. As a method though it acts the same as the previous version, returning a databasr object.\ngetToDos makes use of Firestore's API to load every document. I manipulate the results a bit to store the ID the Firestore creates into the document object itself. But at the end, as before, I'm returning an array of todos.\nFinally, saveToDo makes use of the Firestore API as well. In my code I detect a new versus old todo by looking for the ID value. I'm pretty confident that Firestore probably has a &quot;storeThisThingNewOrOld&quot; method, like IndexedDB does, that would make my code simpler and if anyone wants to chime in below in the comments, I'd love it. But I'm also fine with it as is - it's very short.\nAnd that was it!! Honestly I was a bit surprised actually. I tested offline support and saw that it handled it mostly well. When I went offline (using devtools of course) and tried to store a value, I got an error in the console. But when I went online, Firestore automatically saved the data. That's awesome! So I guess all I would need to do is add my own support for noticing the error and let the user know their data would sync when online. In other words, I'd handle letting the user know, Firestore would handle the acutal synchronization, which is bad ass.\nBring in the Users\nOk, just to re-iterate what I said above, I do not have confidence that I did the security aspect right. It seems to be working but you should not take this as a complete 100% safe example.\nSo, working with the security aspect was a bit more difficult. I guess that's to be expected, and I did get it working, but it was definitely harder.\nI started off at the core docs, Firebase Auth. This had me then go to their UI library which reminds me a bit of Auth0. It was a bit weird going from &quot;official&quot; Google docs to GitHub, it also made me feel like this was not an official supported part of th",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Some Notes on Building Blogs with Gridsome",
		"date":"Sun Jan 12 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1578787200,
		"url":"https://www.raymondcamden.com/2020/01/12/some-notes-on-building-blogs-with-gridsome",
		"content":"I'm doing some research for an upcoming presentation on Gridsome (An Introduction to Gridsome on January 22) and ran into an issue understanding how one particular feature works that is of particular interest to folks building blogs with Gridsome.\ntl;dr\n\nThe frontmatter of your Markdown files become part of your GraphQL schema. You must use date as a field if you want URLs (paths) based on a date property. You will get additional properties (like excerpt) automatically.\n\nGridsome supports a filesystem plugin that let you point at a folder of files and automatically import them for use within your site. This is first shown in the docs for Import with source plugins. The specific code example show is this:\n\nThat's fairly straight forward. Point to a path, give it a type name that will be used for the GraphQL server and you're good to go. You need a bit more information though, and you can then checkout the docs for source-filesystem. This page brings up an important detail - that in order to use the filesystem a transformer must be used to parse your files. For Markdown, you are asked to install @gridsome/transformer-remark.\nSo far so good, but let's look at the sample code used in the plugin doc:\n\nOne important detail different is that this example shows that you need a template to handle displaying the blog posts. Templates are how Gridsome transform collections of data into pages. Personally I don't like the name as templates make me think of layouts, but that's just me. ;)\nOk.... so here's where I began to have issues. If you look at the template defintion above, you'll see this value: /blog/:year/:month/:day/:slug. While I admit I didn't read every single line of Gridsome documentation, I wasn't sure where slug, year, month, and day came from.\nTo make matters worse, when I tested, I used one Markdown file that looked like this:\nHi I'm *markdown*!\n\nThat's a valid example, but it was missing a crucial part, frontmatter.\nSo while it's mostly obvious, I guess, that a blog using Markdown files will use frontmatter, it wasn't necessarily called out how important this was.\nGridsome, the filesystem plugin, and the transformer plugin beneath that, will make use of your frontmatter in multiple ways.\nFirst, all the unique values of your frontmatter will be made available in the GraphQL collection. What do I mean by that? Imagine your blog has two files:\n \ttitle: Blog one \tcat: I like cats \t\nAnd...\n \ttitle: Blog one \tdog: I like cats too \t\nThe resultant GraphQL schema type will include: title, cat, and dog properties. But that's not all - I'll get back to the full schema later in the post.\nAlright, so what about :slug? A slug generally refers to taking a string and making URL safe (and lower case), so something like &quot;Ray's Happy World&quot; turns into &quot;rays-happy-world&quot;. When I used :slug in my testing, I got this error:\n\nOk... so... I guess if my frontmatter included slug then I'd be fine, but for now I simply switched to using :title and it used that value from my frontmatter.\nAlright, so what about :year/:month/:day? In my testing this only worked if your frontmatter uses a value called date and it follows the UTC date format, then it will be picked up and parsed automatically. So like so:\n---\ntitle: Goo\ndate: 2020-01-05\n---\nThis is goo\nThis will output a URL path like so, /2020/01/05. I didn't find this till this morning, but it is documented on the templates page.\nUnfortunately I can't see anyway to change this behavior if you use another field for your date. To be clear, if you use another date field, like edited: 2020-01-10 then Gridsome (and the relevant plugins) will recognize it as a date and make it available as a date type in GraphQL, but I don't believe you can use it in the template path. Wait, I lie. It absolutely can be if you write a custom path function. Here's how the templates doc demonstrate that:\n\nSo in my case I could use values from node.edited.\nFinally, if for some reason you don't specify a date field in your frontmatter, the output will include &quot;Invalid date&quot; in the path: http://localhost:8080/blog/Invalid%20date/Invalid%20date/Invalid%20date/ray-rules/ So don't do that. ;)\nOk... there's more. When Gridsome (and the relevant plugins again) parse your frontmatter, you get a lot of fields in your collection. For a test, I used three Markdown files with frontmatter like this:\n---\ntitle: dude\ndate: 2020-01-05\ncover_image: goo.jpg\ntags: php, perl\n---\nThis generated this GraphQL schema:\n\nThere's a lot there to note. For example, excerpt is useful for sure and timeToRead is nice. I'm guessing these come from the transformer, but I'm not sure where this is documented. excerpt is documented, I believe, way down in the gray-matter code which is a couple chains down from the top plugin, but I really wish this was closer to the &quot;main&quot; Gridsome docs if that makes sense. headings seems to come from the fact that the transformer plugin supports automatically creating an",
		"tags":[
	        
            "gridsome"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Yet Another Damn ToDo App in Vue.js",
		"date":"Wed Jan 08 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1578441600,
		"url":"https://www.raymondcamden.com/2020/01/08/yet-another-damn-todo-app-in-vuejs",
		"content":"So last week I published my thrilling post on building a ToDo app in Vue.js (&quot;Another Damn ToDo App in Vue.js&quot;). As promised, I'm following up on that post with an &quot;enhanced&quot; version of the application. The previous version was quite simple. It used Vue as a script tag, not a full Vue application, and stored the ToDos in memory. That meant on every reload the data was lost.\nIn this version I made three main changes:\n\nFirst I switched over to a complete Vue application.\nI added Vuex as a way to put my all my data access code in one place.\nI used IndexedDB to persist the ToDos over every load. This is still only per device so if you open the app on another machine or in another browser, it won't have the same data.\n\nLet me explain each step of this.\nSwitching to an Application\nThis part should be relatively straight forward. The original version of my application (which you can see here) was built with just a script tag and some code, no build process. There's nothing wrong with that! But with the idea that I'm enhancing this application to make it more powerful, it made sense for me to move this into an application.\nI simply used the Vue CLI to scaffold a new application, using the -b option to keep it clean of stuff I didn't need.\nWith the new application, I copied over the HTML, CSS, and JavaScript from the first version and ensured everything still worked. A tip I like to share from time to time is to take baby steps as you develop.\nAdding Vuex\nI then added Vuex to the application. The idea being that my application components will ask for their data from Vuex and Vuex will handle retrieving, updating, and so forth. This required changes in the front-end component, so let's take a look. First, the HTML as the change here is super minor.\n\nSo literally the only change here is in the index in my loop. Previously my todos didn't have a primary key so I had to use the loop index as the key. Now my todos do have one so I use that instead. ANd that's it. The JavaScript changed quite a bit more though.\n\nFirst, I import mapGetters. This Vuex utility makes it easier to use getters from Vuex, which act like computed properties. My created method calls an action on the store that will fetch our data. Both saveToDo and toggleDone now call the store to handle their logic.\nImplementing IndexedDB\nFor the most part, I copied the work I did back in October last year when I first discussed this topic, Using IndexedDB in Vue.js. My store handles the data, but the persistence is handled by another script, idb.js. (That isn't the best name, but whatevs...) Here's my store:\n\nNote that I'm importing that second, new script, and I don't actually ever manipulate the state values. I load them from logic in the script. I manipulate a copy in my getter. But reading and writing is done in idb.js. That code is pretty much exactly the same as the blog post mentioned above, but here it is:\n\nAgain, if you want more details on how this works, check out my earlier post (and feel free to ask me in a comment below).\nAnd that's pretty much it. You can see the complete source code of the application here: https://github.com/cfjedimaster/vue-demos/tree/master/todos2. I also have a live version you can run here: https://todos2.raymondcamden.now.sh/\nHeader photo by Glenn Carstens-Peters on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Another Damn ToDo App in Vue.js",
		"date":"Fri Jan 03 2020 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1578009600,
		"url":"https://www.raymondcamden.com/2020/01/03/another-damn-todo-app-in-vuejs",
		"content":"It's become somewhat of a trend these days to build ToDo apps in various languages. In fact, it's a rule that you can't release a new programming language, or framework, unless it can build a ToDo app. Seriously, I read it on the Internet.\n\nYou can even find an entire website dedicated to showing you different examples of the ToDo app: http://todomvc.com/\nIt's gotten to a point where I just really don't like seeing ToDo apps even if I recognize their appeal. The functionality is rather simple. We've all got a basic idea of what they do. Etc. That being said, I just never wanted to actually write a post like this.\nBut during the holiday break, I was thinking about how I could use the classic ToDo app as a way to show different iterations of the same app with Vue.js. Specifically I want to write three blog posts.\nThe first post (this one!) will demonstrate the simplest form of the app and be completely in memory, meaning that as soon as you close the browser tab the data will go away.\nThe second post will update the code to add in Vuex and IndexedDB to persist the data. I've talked about IndexedDB and Vue a few times already, but I think showing the upgrade to add it's support will be useful.\nFinally, the third post will show storing the data using Firebase. Firebase has been on my own &quot;todo&quot; list to learn for sometime now and I thought it would make an awesome final iteration of the project.\nNow I'm starting this right before heading to my first CES so there may be a bit of break between posts, but I'm sure yall are fine waiting a bit while I do my best to survive the madness in Vegas.\nAlright, with that out of the way, let me describe how I built the simplest version of my ToDo app in Vue.js. First let's look at the UI/UX:\n\nInitially you're presented with a header, no todos (because remember, the data doesn't persist), and a form field to add a new one. Type in some text and hit the button and you get:\n\nYou can add as many as you like, each one appearing on top of the list.\n\nThe button to the right of each todo lets you mark it done. There is no edit or delete. Do the damn task. When clicked, the item is crossed out, moved to the bottom, and you have the option to &quot;re-open&quot; it so to speak.\n\nAnd that's it. So what's the code look like?\nI began by defining my data which consists of the array of todos and the variable that will be bound to the form field.\n\nTo add a new todo, I use this simple HTML. It could definitely have some validation and nicer UI.\n\nThis is tied to this method:\n\nThe logic is simple. If the value in the field is blank, do nothing. Otherwise add to the front of the array an object containing a text field and done property defaulted to false. Lastly I reset the field so you could type in another one.\nNow let's go back to the HTML, here is how I render the ToDos and the button to mark them complete/incomplete:\n\nBasically I loop over sortedToDos (which I haven't shown you yet, don't worry, I will in a second) and do something\ndifferent for each one based on the done property. In the text, I dynamically add the todoDone class when done is true. This adds the gray and strikethrough. In the second column, I toggle the text of the button based on the done state.\nThe toggleDone method does exactly that - changes true to false or false to true:\n\nFinally, sortedToDos is a computed value that takes the original array and sorts them such that done items are at the end of the list.\n\nYou can view the entire sample and run it at the following CodePen:\n\n  See the Pen \n  ToDos 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThat's it. As I said, the next version is going to kick it up a notch and add both Vuex and IndexedDB for storage.\np.s. As a total aside, I've been loving the hell out of the Microsoft ToDo app. It's got desktop and mobile clients, built in syncing, and a lovely UI.\nHeader photo by Glenn Carstens-Peters on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Goodbye 2019, Hello 2020",
		"date":"Tue Dec 31 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1577750400,
		"url":"https://www.raymondcamden.com/2019/12/31/goodbye-2019-hello-2020",
		"content":"For the past several years, I've done &quot;round up&quot; posts where I talk about what I accomplished, what I wanted to do for the next year, and also what my favorite assorted &quot;media&quot; was over the past year. Last year's post (which was technically this year, I posted it on January 1st) was somewhat different given the significant changes that had gone on in my life. I had decided that this year's only goals would be my physical and mental health.\nI think I did rather good in that regards. I'm in a very happy place emotionally, having found someone I love (and luckily loves me in return). The physical side is a somewhat different story. I'm pretty healthy, but one of the side effects (I guess) of being in a new relationship and having a lot of fun is that it's somewhat harder to keep the weight off. I've gained a lot of my lost weight back. On the other hand, I was a bit too low this year so my goal for this year is to simply get to about &quot;half way&quot; where I was previously. I'm still exercising a lot and every checkup I get is glowing, so I'm going to do my best to not stress over one number.\nSo yeah... personally I'm pretty damn happy. I still have things to work through and see my therapist on occasion, but I'm not going to question my happiness and just try to enjoy the hell out of it.\nProfessionally it was an interesting year. After discovering that Amex was absolutely, 100%, not a good fit for me, I looked for work elsewhere and started a new job at HERE back in August. I absolutely love this job and am still learning new things every day. Next week I get to attend my first CES in Las Vegas. I've been able to give more presentations this past year and see more developers out in the wild.\nLast year I gave fifteen presentations which is actually much more than I thought I would have. I think that's a great number for the year and I'm going to try to meet that goal for 2020. (I've got three booked so far!)\nFor folks curious about my travel stats, this year I had 32 flights, hit 3 different countries and 10 cities. I spent almost 4 days in the air and flew 35K miles. I used JetItUp for these stats. It's having some website issues now but in general, it's a damn good utility.\n\nOne of my other goals for this year was to get better at Vue, especially in regards to the CLI and applications. I hate to brag, but I think I kicked butt in that regards. To be clear, I don't think I'm some Vue master. But in 2018 I'd have said I was a strong beginner and now I'd peg myself as a strong intermediate user. I wrote nearly forty blog entries on Vue in 2019 and that's not including my external articles. I'm also working on my first Vue.js book! I'm only a contributor, but I haven't worked on a book in a while so I'm pretty happy about that.\nOverall I wrote 64 blog posts this year which is a far cry from my earlier years but as I'm writing more and more for other sources, and my job, I'm fine with that. To be honest I think a good half of what I blogged about a decade ago are things that I now use Twitter for - basically short bits of information that I want to share with other developers.\nFor next year, my professional goals are:\n\nKeep learning and practicing Vue\nKeep building web-based games in Vue, even if I'm the only one who plays them\nLearn more about mapping (duh, that's my job ;)\nKeep writing about the JAMStack\nLearn Firebase\nLearn to do voice assistant skills on Google's platform\nConvert my blog to 11ty and pick a new design\n\nFavorite Media of 2019\nTo be honest, I don't really want to put a lot of thought into this. My &quot;favorites&quot; always tend to me the most recent things, and when someone says, &quot;But what did you think about X&quot;, typically I'm like, &quot;OMG that was amazing too&quot;. That being said, I figured I could share a bit of what I really liked this year and hopefully there will be some new things here for you to check out.\nFavorite Movie(s)\nI already reviewed &quot;Rise of Skywalker&quot;, and while I liked it a lot (and want to see it again), it wasn't my favorite movie of the year. That would definitely have to be &quot;Knives Out&quot;, which had an amazing story and an incredible cast. It's one of those rare films that looks great in the trailers and ends up being even better than you thought.\nPretty much all the comic book movies were great. &quot;Endgame&quot; was awesome and frankly, Marvel has accomplished something amazing over the past decade or so with the MCU. &quot;Joker&quot; was very interesting and while not my favorite version of the character, I'd say it's a strong number two.\n&quot;It Chapter Two&quot; was ok... but honestly didn't feel as strong as the first part.\nWhile not completely new, this is the year I discovered the &quot;John Wick&quot; trilogy. I watched all three over a day or two and damn was that a good trilogy. It may be a mild spoiler, but all three movies take place pretty much in the same time period so watching it all at once m",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Another Vue Game Demo - Hangman",
		"date":"Thu Dec 26 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1577318400,
		"url":"https://www.raymondcamden.com/2019/12/26/another-vue-game-demo-hangman",
		"content":"I decided to wrap up my year with one last post... and yet another web game built with my favorite framework, Vue.js. Many, many years ago (like, 2010) I built a Hangman game using Adobe AIR. For folks who don't remember, AIR was a product that let you use Flash, Flex, or HTML to build cross-platform desktop and mobile applications. I thought it was pretty neat, but it's gone the way of many of Adobe's developer products and is best left in the past. I thought I'd take a stab at building the game in Vue.js.\nFor folks who may not know, Hangman is a word guessing game. You're presented with a series of blank characters and must select letters you think make up the word. Every correct choice will make the character show up in the word. Every incorrect choice will bring you closer to &quot;death&quot;, death being represented by a stick figure that gets closer to completion on every mistake.\nMy game would need to:\n\nSelect a random word\nDisplay the word as blanks (or dashes)\nLet you type to pick letters\nUpdate the display based on your choice, either filling in correct letters or drawing the stick figure\n\nI got everything working and if you want to stop reading this boring blog post and just play, head over here: https://hangman.raymondcamden.now.sh/\nNow let me share some of the tidbits on how I built it.\nFinding Words\nThe first thing I did was find my source of words. I found this repository (https://github.com/first20hours/google-10000-english) which contains the ten thousand most common English words. One list had the swear words removed so I used that. It ended up as 9894 words which felt like more than enough.\nOriginally my plan was to store the words in IndexedDB and select a random result from there (Selecting a random record from an IndexedDB Object Store). But then I realized that the word list was only 74k. While I still think it would make sense to cache this locally, I decided it was ok to skip that for now.\nI set up an action in Vuex to handle fetching the words, splitting the text file by new lines, and handling the random selection. Here's that code:\n\nAs you can see, I do cache the network call so if you play multiple times in one session, it won't need to reload the data.\nThe Game\nSo I described the steps of the game above. I showed the random word selection logic above, let me share a few more interesting bits.\nWhen you play the game, the word you have to figure out is displayed as a series of dashes, like so:\n\nThis is done via a Getter that handles recognizing what letters you've guessed:\n\nIn the same area, I use a Getter to return the image to display, based on the number of incorrect guesses.\n\nThe images themselves come from the Wikipedia page and could be fancier, but it works.\nPlaying the game requires keyboard input which I detailed back in August (and have used multiple times since then).\nThere is one interesting part of the keyboard handling code - I used a hack I found multiple times to see if the input was a letter:\n\nHonestly the hack feels a bit dangerous, but as I said, I saw this used a lot so I figure, it's got to be safe, right?\nThe last bit I think I is interesting is how I handle checking if the game is over:\n\nChecking if the maskedWord equals the real word feels smart which probably means I did it wrong.\nAm I doing this right?\nMost of the logic is done in my Vuex store and honestly, it felt a bit off to me. I've been spending this entire year working on getting more practice with Vue applications and Vuex in particular, but I still feel like I'm figuring out to best place to put my logic.\nI try to keep &quot;complex logic&quot; in a separate file and let Vuex simply handle proxying calls to it. In general, and I want to write about this in a longer form, I'd setup my Vue apps like so:\n\nMain components handle UI and use code to handle events.\nOn those events, it calls out to Vuex to handle loading and storing data.\nFinally, business logic is handled in their own specific files.\n\nThis is flexible of course, but it's generally where I'm trying to organize things. Like I said, I'm going to write this up in a more formal sense later on.\nAnyway, it's a game and it's fun to play! If you want to see the complete source, check out the repo here: https://github.com/cfjedimaster/vue-demos/tree/master/hangman. You can play it yourself here: https://hangman.raymondcamden.now.sh/ As always, let me know what you think by leaving me a comment below!\nHeader photo by Jelleke Vanooteghem on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Non-Spoiler Review of The Rise of Skywalker",
		"date":"Fri Dec 20 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1576800000,
		"url":"https://www.raymondcamden.com/2019/12/20/a-non-spoiler-review-of-the-rise-of-skywalker",
		"content":"As I said in the title, nothing in the main blog text here will contain spoilers. However, the comments are a free fire zone and I'll be adding my own spoilers there. As always, I don't pretend to be a &quot;real&quot; movie reviewer or even slightly unbiased when it comes to Star Wars, but I'll do my best to be honest about what I thought.\nFor folks who may be new here, I am a lifelong fan of Star Wars. Some of my earliest memories are of seeing the original trilogy as a child, then the prequels years later, and the new trilogy on opening night. I work in an office surrounded by hundreds of Star Wars toys (not pristine, my collecting criteria is &quot;does this look cool&quot;) and I've permanently marked my skin with not one, but three different Star Wars tattoos. When I ride my bike, my mind immediately goes here...\n\nTo be clear, I'm not saying I'm more of a Star Wars fan than anyone else. I'm just saying that for me, Star Wars has been a huge part of my life from pretty much day one. I have no misconceptions about it's value compared to other media and I certainly don't think it's perfect. But I love it.\nWhen I was very young, I remember reading that Lucas had imagined his Star Wars saga as nine movies. I distinctly remember thinking how sad it was that I'd probably never see the &quot;whole&quot; store.\nAnd then we got the prequels.\nI still remember going to some random movie - I think it was an Adam Sandler comedy - literally just to watch the teaser. When it was available online, I remember watching it hundreds of times.\n\nThe prequels ended up disappointing a lot of people. Personally, I loved parts of them. I thought Obi Wan and Palpatine were perfect. I loved how the Jedi were duped. But... they failed to give Vader a good origin story. Failed completely. That's a shame, but it didn't stop me from enjoying them for what they were.\n\nFast forward another decade or so and we get not only the announcement of the final trilogy, but other movies as well.\n\nI loved The Force Awakens. I especially love Rogue One, which is my absolute favorite Star Wars movie, hands down. (Yes, even more so than Empire.) I thought Solo was far underrated and wished it had gotten a better reception from audiences. I thought The Last Jedi was fascinating. If I could point out issue with The Force Awakens is that it felt like fan service at times. I'm a fan and that's ok, but The Last Jedi felt like it was made by someone who wanted to challenge me and my preconceived notions of what Star Wars should be. I like that and respect that.\nSo - that's a hell of a lot of preamble. :) The Rise of Skywalker was damn enjoyable. It did not feel like as much of a &quot;fan service&quot; movie as The Force Awakens and I can honestly say it surprised me more than I thought it would. There were multiple moments where I got choked up, and the ending itself was quite beautiful. I mean, we're looking at a story released over forty years. There was a lot riding on this conclusion and I've got to say I'm happy with it.\nWas it great? Eh.... not really. But I'd say it was really good. It isn't in my top three Star Wars movies, but certainly nowhere near the bottom. The editing in the beginning felt really rushed and was a bit offputting, even while really, really cool stuff was being shown. The things I'd really thought were great will have to wait for the comments, but I'm looking forward to watching it again. And soon.\nI'm kind of torn. On one hand, we've gotten some damn good Star Wars stuff lately. The Mandalorian is... incredible. I didn't feel like I could call Rise of Skywalker great but I have absolutely no problems calling Mandalorian great, amazing even. The books (and comic books) the last few years have been awesome as well. Fallen Order was... Ok... but I know a lot of folks really like it. It's a great time for Star Wars, but it does feel like a huge chapter is done. I'm excited about what's next, but sad that the Skywalker story is finally over.\nAnyway - stop what you're doing - grab a giant bucket of popcorn - and get your butt in a theater seat soon!\nHeader photo by Oliver Zenglein on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Building Sudoku in Vue.js - Part 2",
		"date":"Thu Dec 19 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1576713600,
		"url":"https://www.raymondcamden.com/2019/12/19/building-sudoku-in-vuejs-part-2",
		"content":"Earlier this week I blogged about my attempt to build a Sudoku game in Vue.js. At the time, I felt like I had done a good majority of the work, but that I was at a good stopping point to write it up and blog. Well last night I &quot;finished&quot; the app (to be clear, there's absolutely room for polish) and I'm kind of embarrassed at how little I had left to do. I'm going to assume I'm just far more intelligent than I think and am an awesome coder despite failing the Google test more than once.\nIn this update I tackled three things:\n\nAdded the ability start a new game with a custom difficulty.\nMarking incorrect entries. Which again is a personal preference, it wouldn't be too hard to make this optional.\nAdded the ability to notice when you won.\n\nLet me tackle each part separately. For difficulty, I began by adding the supported difficulty levels to my state:\n\nI then modified initGrid to handle an optional difficulty:\n\nFinally, over in my main App.vue, I added UI to render the difficulties and a button to start a new game. There's no restriction on when you can do this. First the HTML:\n\nAnd here's the code behind this.\n\nI'm using mapState to bring in the difficulties and then added a method, newGame, that calls initGrid with the selected difficulty.\nNow let's look at marking incorrect values. I modified setNumber in my store to simply check if the new value matches the solution value:\n\nThen in Grid.vue, I check for this value and apply a class:\n\nFinally, to handle if you've won the game, I further modified setNumber by adding in this code:\n\nAs the comment says, it really felt like this should be it's own method. Looking over my code now, I'd probably consider moving my Sudoku &quot;game&quot; logic in it's own file and keep my store focused on just the data. I say this again and again but I still struggle, or not struggle, but really think about, where to put my logic when it comes to Vue and Vuex. I love that Vue is flexible in this regard though!\nThe final part of handling &quot;game won&quot; logic is a simple conditional in the main component:\n\nThat's pretty simple and could be much more exciting, but I'm happy with it. You can see the code at https://github.com/cfjedimaster/vue-demos/tree/master/sudoku. If you want to see it in your browser, visit https://sudoku.raymondcamden.now.sh/. Please let me know what you think by leaving me a comment below!\nHeader photo by Tienda Bandera on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building Sudoku in Vue.js - Part 1",
		"date":"Mon Dec 16 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1576454400,
		"url":"https://www.raymondcamden.com/2019/12/16/building-sudoku-in-vuejs-part-1",
		"content":"While sitting at my local airport yesterday, I decided to take advantage of a ninety minute delay by working on another Vue.js game - an implementation of Sudoku. No, not that guy...\n\nBut the game where you must fill in a puzzle grid. The grid consists of 9 rows of 9 cells. Each row must contain the numbers 1-9. Each column as well. And each &quot;block&quot; of 3x3 grids must always contain the numbers. Here's how a typical puzzle may look...\n\nAnd here's the puzzle solved.\n\nI am - shall we say - slightly addicted to this game. It's a great way to pass some time and I enjoy the feeling of completing the puzzle. I'll typically play one to two puzzles per day and I'm slowly getting better at it. I thought it would be fun to take a stab at building my own Sudoku puzzle game in Vue.\nTo be clear, I didn't want to write the code to build a puzzle or solve it. That's some high level algorithm stuff that I simply suck at. (Ask me sometime about how I failed these tests trying to get a developer advocate job at Google.) But I figured if I googled for &quot;sudoku javascript&quot; I'd find about a million results and I wasn't disappointed. I came across a great library at https://github.com/robatron/sudoku.js. It generates puzzles, solutions, even possible candidates for empty cells it had everything. It was a bit old, but I figured that just meant it had some experience and why hold that against it?\nI've worked on this off and on over the past two days and I've gotten it about 70% done. I figured it was a good place to take a break, share what I've done so far, and then continue on to wrap the game later in the week. (And the good news is that when I couldn't sleep last night, I thought about another game I'm going to build in Vue later!)\nSo, let's take a look! First, what do I have working so far?\n\nI have the puzzle being generated and displayed.\nYou can click an empty square to select it.\nYou can type a number and it fills in.\n\nWhat's left?\n\nSee if you solved the puzzle\nLet you start a new game and select the difficulty\n\nHonestly there isn't a lot left, but I really felt like I hit a milestone tonight, and I'm tired, so I figured it was a good place to stop and blog.\nI'll start off with the App.vue page. Right now it's pretty minimal.\n\nBasically it just calls the Grid component and then asks the grid to initialize itself. I'm using Vuex in this demo and most of the logic is there. Let's look at the Grid component.\n\nLet me start off by saying that I am DAMN PROUD OF MY CSS! I honestly didn't think I'd get the design right.\nI am *incredibly* proud I was able to style this Sudoku table with CSS. It was just a few border commands, but I honestly thought I couldn&#39;t do it. pic.twitter.com/l8rzF2049E&mdash; Raymond Camden 🥑 (@raymondcamden) December 15, 2019  \nOutside of that my display just renders the table. I've got some basic keyboard support in (see my article) on that topic) as well as the ability to select a cell. You have to pick a cell before you can type in a number. But that's it. The real meat of the application is in my Vuex store.\n\nThis is somewhat large, so let me point out some interesting bits. First off, this line:\n\nI honestly guessed at this. The Sudoku code I used defines a sudoku object under window and is typically loaded via a script tag. I was going to add the script tag to my index.html but decided I'd try that. It worked, but I didn't know how to actually get to the methods. After some digging I found I could do it via sudokuModule.sudoku.something(). Again, I was just guessing here and I really don't know if this is &quot;best practice&quot;, but it worked.\ninitGrid does a lot of the setup work. I generate the puzzle, which is a string, and then convert it to a 2D array. The library has this baked in, but I made my own grid and store additional information - candidates, solution, and a locked value to represent numbers that were set when the game started (you can't change those).\nsetNumber simply sets a cell value, it doesn't validate if it's ok. I'm probably going to change that. When I play I like automatic alerts when I've picked the wrong value. That's probably cheating a bit, but I only guess when I'm frustrated with a hard puzzle and I'm fine with that.\nFinally, setSelected is how I select a cell. I also use this to deselect anything picked previous. Make note of Vue.set. This is required when working with nested arrays/objects and it's probably something everyone using Vue runs into eventually. Check the docs on it for more details: Change Detection Caveats\nThat's it for the first part. You can see the code as it stands currently at https://github.com/cfjedimaster/vue-demos/tree/master/sudoku. If you want to see it in your browser, visit https://sudoku.raymondcamden.now.sh/.\nHeader photo by James Sutton on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Bearer for Easier OAuth and API Calls",
		"date":"Wed Dec 11 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1576022400,
		"url":"https://www.raymondcamden.com/2019/12/11/using-bearer-for-easier-oauth-and-api-calls",
		"content":"For the past few days I've been playing with a new service that I'm really excited about, Bearer. At a high level, Bearer gives you a proxy to other APIs to provide monitoring, logging, incident reporting, and more. At a lower level, there's one aspect of Bearer (and again, this blog entry is on one aspect of Bearer) that really got my attention.\nWorking with OAuth isn't terribly difficult, especially if you can use a library like Passport to simplify it a bit. I first blogged about my experiences with Passport back in 2016. Things get more interesting when you then work with APIs that require OAuth first, as you typically (or at least in my experience) have to follow up the initial OAuth flow with a call to get a &quot;bearer token&quot; and then call your API.\nAgain, not terribly difficult, but not exactly fun either. It's also something you can't do 100% client-side. (Auth0 helps here, I'll talk about it a bit more at the end.) With serverless functions it's possible to have a &quot;mostly&quot; client-side JAMStack type site but what if you could skip that entirely?\nBearer will give you the ability to login with OAuth flow and handle the process of getting bearer tokens for you. Finally, it lets you use it's JavaScript library to make calls to remote API, CORS or not, by proxying via it's network. It took me a few tries to get it working correctly, but once I did, I was incredibly impressed. As an example, I'd like to share a demo I built.\nBack in 2016, I create a Node.js demo that retrieved images from a Twitter account: Getting Images from a Twitter Account I built this because I follow (and have created) a number of Twitter accounts that only (or mostly) post pictures. My tool would let you specify an account, fetch the pictures, and just display them in one big wall of media.\n\nIf you look at the repo for that demo, you can see a lot of code involved in the OAth flow and then handling the API calls to Twitter. Again, not terrible, but &quot;work&quot;. I don't like work. So what was this like in Bearer?\nThe first thing I did was sign up at Bearer of course. Then I registered a new Twitter API.\n\nThis involved me making an app on Twitter's developer portal first and then providing those credentials to Bearer. Once registered, if you intend to use their API, you must go into Settings, scroll down to Security, and toggle Client-Side API Calls.\n\nDon't forget this. I did.\nOnce enabled, it's time for the code. At a basic level, it comes down to doing the auth first, which can look like this:\n\nThe resulting authId value is then used in later API calls:\n\nNote I only use the ending portion of the URL for Twitter API calls. Bearer knows how to handle it. And that's basically it. With that in mind, I rebuilt my previous demo using Vue.js. I didn't built it exactly the same as the previous one. I didn't add the &quot;lightbox&quot; effect for example. But I got everything done in one simple(ish) component. First - the template:\n\nI'm using Vuetify for the UI layout. Initially the button prompting for login is displayed, and after you've authenticated, I then show a form where you can enter a username and ask for their images. I defaulted to oneperfectshot as it's a damn cool account. Here's how it renders.\n\nNow for the JavaScript:\n\nOutside of the Vue stuff, this is mostly a repeat of what I showed before. One call to auth and one call to the API. In this case, I'm using Twitter's API to search for tweets from a user, that have media, and then filtering out to get the image URLs.\nWant to try it out yourself? I'm hosting it here: https://twitter-image-search.raymondcamden.now.sh/ You can find the source code here: https://github.com/cfjedimaster/vue-demos/tree/master/twitter-image-search\nAnd that's basically it. As I said, Bearer does more. As one more small example, here are the included logs for my demo.\n\nI also like the simpler stats on the dashboard:\n\nAs I said, I'm really impressed by their service and how easy it was to get going with an entirely client-side application. Earlier I mentioned Auth0. Auth0 obviously does login really simple. What it doesn't do simply is the bearer token stuff. It is definitely possible and my buddy Bobby Johnson showed me an example. I couldn't get it working, but I trust his worked and that it was my issue. But honestly, I was really surprised Auth0 didn't make this as simple as Bearer did. All in all, Bearer just feels easier to use. (I should add that while I worked at Auth0, I never worked with their main identity product. My experience there was with their serverless platform.)\nAnyway - I'd love to hear from anyone who may be using Bearer. Please leave me a comment below and tell me what you think.\nHeader photo by Annie Spratt on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Sunday Quick Hack - Eliza in Vue.js",
		"date":"Sun Dec 08 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1575763200,
		"url":"https://www.raymondcamden.com/2019/12/08/sunday-quick-hack-eliza-in-vuejs",
		"content":"I tend to be a bit hesitant when I go to blog what I consider to be totally trivial examples, but while working on this project this morning, I ran into a few little issues that I thought might be helpful to share for folks who don't waste their time building dumb games in Vue. Before I get into the code (and the small little issues I found), a quick history lesson.\nEliza, or more correctly, ELIZA, is an old program (circle 1964) that attempts to parse your input and respond intelligently. The &quot;intelligence&quot; really wasn't. All Eliza really did was try to match patterns and then parrot them back to you. So telling it you don't like cats could give you a response of &quot;Don't you really like cats?&quot;\nThe creator of Eliza (Joseph Weizenbaum) was trying to demonstrate the &quot;superficiality of communication&quot; between people and machines, but was surprised by how people responded to it. Instead of noticing the shallowness of the responses, multiple people felt an emotional connection to Eliza. You can read much more about Eliza at it's Wikipedia page and if you Google, you will find implementations of Eliza in pretty much every language possible. And as an interesting aside, there's also PARRY, another early chatbot meant to simulate a person with paranoid schizophrenia. Of course, folks connected the two and you can see read one of their conversations if you're interested.\nSo - that's a long winded way of saying - I felt like finding a simple Eliza JavaScript implementation and building a Vue.js demo around it. I did some Googling and one of the first ones I found was here: https://www.masswerk.at/elizabot/. This code is nearly fifteen years old but was the first I found that was the easiest to &quot;plug and play&quot; into another application. It definitely doesn't follow what we would consider to be &quot;modern best practices&quot;, and in fact, it comes in two separate JavaScript files, with no minification, and pollutes the global variable space.\nBut it works.\nAlright, so given that, let me share the end result so you can see it in action. I'll then explain the code. You can run Vue Eliza here: https://cfjedimaster.github.io/vue-demos/eliza/\nHere's a screen shot of in action, with all my design skills at play:\n\nAs you can see in the conversation above, it isn't terribly intelligent, but it comes close. If you didn't know better you could (possibly) be fooled into thinking you were talking to a real, if lazy, therapist. (And for folks curious, a real therapist isn't like this at all!) Let's look at the code. First, my HTML.\n\nYou can see I start off loading up Eliza, and like I mentioned, it's two different script files. The second one just provides data for your bot and - unfortunately - uses the global name space. Luckily my Vue app is completely separated from that (except for it's own instance) so I don't really have to worry about it.\nThe UI consists of a chatbox, an input for your typing, and a button to send in the results. You can also use the enter key and make note of @submit.prevent=&quot;&quot; to stop the form from submitting itself. I've never used an event handler pointing to an empty string before, but Vue seemed to handle it perfectly. My confidence isn't terribly high on that but I tried it in Firefox and Chrome and it worked. (I just tested in Edge and it worked fine there too.)\nNow let's look at the JavaScript.\n\nNot much to it, but let's point out some of the interesting bits.\nFirst, I begin by making a new instance of the Eliza bot. The library supports multiple bots so in theory, you could have a conversation with multiple Eliza's at once. I track the chat using one large string object where I keep appending new messages. I'm using HTML to break up newlines so note how I use v-html in my template to render it. I feel like this would be more memory efficient as an array perhaps but if you're having that long of a conversation with my bot... you should just stop.\nWhat's happening in $nextTick? Two things actually. First, I wanted to ensure that the div displaying the chat was always scrolled to the bottom. I found a simple one liner of doing that at StackOverflow (and I credited in the code above.) However, due to Vue updating the DOM asynchronously, I needed to wait until it had written out my new chat. You can read more about $nextTick on this blog post I wrote earlier in the year.\nSecondly, I also check to see if the conversation is over. The bot provides a simple boolean value, quit, that you can check and respond to if you wish. In my demo I simply alert the user and then reload the page. Another option would be to make a new instance of the bot and clear the chat. It would be all of maybe 2-3 more lines of code but I took the easy way out with a reload.\nAnyway, that's it, and let me know if you have any questions by leaving a comment below. You can find the complete source coe for this demo here: https://github.com/cfjedimaster/vue-demos/tree/master/eliza\nHeade",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Sanitizing HTML in Vue.js",
		"date":"Tue Nov 26 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1574726400,
		"url":"https://www.raymondcamden.com/2019/11/26/sanitizing-html-in-vuejs",
		"content":"As part of my goal to learn more about Vue (and, honestly, find things to blog about), I came across this interesting StackOverflow post: How to Sanitize HTML Received from an API Call in Vue.js. I did a quick Google search and came across a nice little library that makes this easy - vue-sanitize. I thought it would be nice to give it a try (especially since I was suggesting it as a solution) so I whipped up a quick demo.\nBefore I start though, it's good to remember how Vue treats HTML in data in general. Consider the following data:\n\nThis is a string with three HTML tags in it. Nothing scary, but let's see what happens if you try to output it:\n\nThis will return:\nMy milkshake brings all the boys to the yard\nAnd they're like, it's better than yours\nAs you can see, the HTML is escaped. Not ideal, right? If you know you can trust the data, you can use the v-html directive:\n\nThis will return what you expect. Cool! But... it's very black and white. You either escape all HTML or allow all HTML. What if you want something in between? This is where vue-sanitize comes in. Not only will it allow you to use a whitelist of &quot;safe&quot; HTML tags, it will remove disallowed tags rather than escaping them.\nUsing it is pretty simple and covered in the docs. Add the NPM package, and once done, you can then add it to your Vue.js code. From what I can see there's no support for &quot;script tag Vue&quot;, so you'll need to have a proper Vue application.\nOutside of that, there's only one main API, this.$sanitize(someVariable). This will return a string with unsafe HTML tags removed. You still need to use v-html to render the safe HTML of course.\nThe docs don't mention the defaults, but as the library wraps another library, sanitize-html, you can check their docs for the defaults:\n\nLet me demonstrate an example before I show how you can customize the defaults. First, my main.js, which just loads in the library.\n\nAnd now my test:\n\nSo I begin with two simple tests related to what I said before - the default behavior in Vue and the use of v-html. I don't use the sanitize code until cleanMessage. I've got that bound to a computed value that returns the sanitized version. The output is:\n\nIn this case, there's no difference between the built-in version and the sanitize version. I only used three simple HTML tags. Let's see what happens when we change the defaults.\nIn order to change the defaults, you create your own object containing the defaults you would like. The main sanitize-html site has some good examples on how to slightly modify the built in defaults. For my testing, I wanted to allow everything the defaults allowed, except for the &lt;strong&gt; tag. This is how I did it.\n\nBasically - loop through the array of allowedTags and remove when the tag name is strong. It's easier if you just want to define a short list of tags you want - just pass an array of strings.\nThe result is as you expect:\n\nNotice though that the &lt;strong&gt; tag wasn't escaped, it was removed. That's much better than escaping it (typically). I could see this being really useful for allowing all the format tags but removing &lt;a&gt; for example. (And &lt;iframe&gt; and probably other's I've forgotten.)\nAnyway, I hope this is helpful. I've got a CodeSandbox with this running and you can play with it below.\n<iframe\n     src=\"https://codesandbox.io/embed/vue-template-025et?fontsize=14&hidenavigation=1&theme=dark\"\n     style=\"width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;\"\n     title=\"Vue Sanitize Example\"\n     allow=\"geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb\"\n     sandbox=\"allow-modals allow-forms allow-popups allow-scripts allow-same-origin\"\n   >\nHeader photo by Oliver Hale on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Google Calendar to your JAMStack",
		"date":"Mon Nov 18 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1574035200,
		"url":"https://www.raymondcamden.com/2019/11/18/adding-google-calendar-to-your-jamstack",
		"content":"This is something I've had on my &quot;To Write&quot; list for a very long time. The plan changed over time but it never actually got done because I couldn't get what I wanted to do actually working, which as you can imagine put something of a crimp on getting this post done.\nI'm a huge Google Calendar user and I know many other people are as well. I thought it might be interesting to see if you could add upcoming events, driven by a Google Calendar, to a static website. Of course, you already have a simple way of doing this. If you go into your calendar settings, &quot;Integrate calendar&quot;, you'll find an &quot;Embed code&quot;:\n\nIf you click &quot;Customize&quot;, you can turn on or off various things, but the end result is a bit... meh.\n\nThis is an example of the calendar embedded in a simple Bootstrap-driven site. The calendar is fully interactive in read-only mode. I just don't find it terribly pretty. You can find the online version here: https://site.raymondcamden.now.sh/ Go to December 2019 to see an example of an event.\nThat's the super easy, get it done in one minute solution. But we don't like easy solutions, right?\n\nAlright - so before I even started thinking about integrating events into a static site, I tried to write a simple Node script that would get my events. This is the part that took a year.\nTo be clear, it wasn't a year straight of working on it. I've got a job, yo. But I'd take a stab at it. Fail. And then try again a few months later. Why did I fail?\nGoogle provides a Node library for all of their services and they even have a quickstart for integrating with the Calendar API in Node. However, the documentation assumes an OAuth flow. So basically, a solution that would work for the scenario where you want a website visitor to login via OAth and then you can display their information on your site.\nBut that's not what I wanted. I wanted access to one specific calendar. I knew Google supported &quot;service accounts&quot;, which let you create a virtual (may not be the right word) access for their APIs. Jackie Han (either a fellow GDE or Google employee) pointed me to this StackOverflow post: Inserting Google Calendar Entries with Service Account\nThis was a good post, but it was PHP based. And unfortunately, outside of the Node quickstart which used OAth, I found the rest of the docs to be really, really freaking hard. Here's the script I got working that I'll do my best to try to explain.\n\nI start off loading in the Google API package. Then I load in my credentials. That comes from Google's Service Account panel where I generated a key and selected the JSON output format. If we go into the main function, I create an auth object that makes use of that JSON data and defines the scope of my use, in this case just Google Calendar.\nSo that part was like half a year to figure out. Maybe I'm being overly dramatic, but I literally had to guess at it for the most part.\nNext I make an instance of the Calendar library and I can use the same code as the quick start (except I added a display of the location part of the event). The calendar ID part was a pain. If you go to the same part of the calendar settings you would use to get the embed code and look at the various URLs, you will see they all include an email address in them. For example:\n\nThe email address is 4p6qtp2jeu40piuul6bklfra94%40group.calendar.google.com. Change the %40 to @ and you've got your calendar ID.\nI ran this little script and got the following:\n\nWoot! Ok, so we've got code that can suck down events. The quick start demo code was already filtering on future events, sorted properly, and limited to ten, which is actually exactly what I want, but obviously you could tweak that to meet your needs. I decided to use Eleventy to build my demo as it would let me set up a script to load events and display them on my page. To give you an idea of what I mean, let's look at the final result. First a screen shot:\n\nAnd here's the template behind this:\n\nNote that this isn't terribly creative, but it gets the job done. I'm looping over events which is driven by a file called events.js in my Eleventy's _data folder:\n\nThis is - essentially - the same logic as before with some minor tweaks. I have to return a promise since the Google API wrapper is using a callback. I also take the start value the original code used and write it to a new key, startValue, that I use in my template. I could further massage the event data here if I wanted.\nAnd that's it. You can see it live here: https://site.raymondcamden.now.sh/test\nConsiderations\nSo, this solution isn't perfect. If you add, edit, or delete an event, it won't be reflected on the site. However, you could simply do daily builds of your site via a CRON job let it be updated at that point. Or do a manual update if you want.\nAnother solution would be to use a serverless function with similar logic and JavaScript on the front end. To me, that seems like overkill for most organizations who may be ch",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Serverless JAMStack AndCats Demo",
		"date":"Thu Nov 14 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1573689600,
		"url":"https://www.raymondcamden.com/2019/11/14/serverless-jamstack-andcats-demo",
		"content":"Many years ago, I built a demo called &quot;AndKittens&quot;. This was a simple Node application that used wildcard subdomains and the Bing Image Search API to let you find pictures of kittens and... whatever. You would use the subdomain to specify what you wanted, so for example, bacon.andkittens.us would return pictures of kittens and bacon. I thought it might be interesting to rebuild this in the JAMStack with a serverless backend.\nI've been kind of down on Microsoft Azure lately. While I really like the platform, I don't like that it doesn't have a good free tier. To be clear, it does have multiple free tiers and such, but it's tricky to ensure you remain within them. Some things, like Azure Functions, are technically free, but you have to pay for the disk space to store them. To be clear, I don't think Azure is overpriced. But it doesn't support the &quot;tinker/play&quot; developer model well. I had pretty much decided I'd stop using it completely, but I really wanted this demo to work with the Bing Image API so I decided to bite the bullet and try it again.\nAnother change I decided on was to skip the dynamic subdomain part. You can absolutely to wildcard domains with Netlify and Zeit and other platforms, but I decided on a simpler solution - a search box. Here's an example of how it looks.\n\nThe picture rotates every five seconds so in theory you could just leave it up and watch forever. (Although I only fetch 50 images from the API.)\nLet's take a look at the code. The complete repository may be found here: https://github.com/cfjedimaster/andkittensserverless\nFirst, the front end. The HTML is rather simple:\n\nThe most interesting part of this I think is the full image background CSS I got from CSS-Tricks. I love that site and I absolutely recommend folks bookmark it. I've got a minimal amount of code to handle rendering stuff, first the image and then a form. I'm using Vue.js for my interactivity, and here's the code for that.\n\nBasically - wait for the user to click for search, and when they do, hit my serverless API, get the results, and iterate over each one in an interval.\nThe last bit is my wrapper for the image API. Bing's Image API supports a lot of different options, but I kept it simple - search for some term and cats (not kittens this time), keep it kid safe, and look for wallpaper size results.\n\nI'd call out two things of importance here. First note I hide my API key using Now secrets. That's how it shows up in process.env.key. Then note I map the results a bit to make them much smaller. Bing's API returns a lot of information about each result, but I only need a few. I actually use less than what I'm returning here and could further optimize this if I wanted to.\nAnyway, if you want to give this a try, and hopefully not put me over the free tier, check it out at https://rckittens.now.sh and let me know what you think.\nHeader photo by Maria Shanina on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Custom Sound Board with Vue and IndexedDB",
		"date":"Tue Nov 12 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1573516800,
		"url":"https://www.raymondcamden.com/2019/11/12/building-a-custom-sound-board-with-vue-and-indexeddb",
		"content":"Pardon the lack of updates around here. I've been writing more for my work blog and have started a new (small) book project. Plus, well, holidays and life. I had hoped to keep to a pace of one post per week minimum, but I've fallen a bit behind there. That being said, I think what I've got to share today is really freaking cool and I hope you do too.\nA few weeks ago I wrote up how I integrated Vue.js and IndexedDB. That post was actually some prep work for what I'm sharing here. I'm a fan of &quot;sound board&quot; apps, those apps that collect sound files from a particular source and let you play them back. So for example, the official Star Wars app (which is more than a sound board) has this as a feature:\n\nI like the idea so much, I built my own using Ionic and Cordova about three years ago (&quot;Cordova/Ionic Sample App: My Sound Board&quot;). The most painful part of that process was handling the file system, but outside of that it wasn't terribly difficult.\nI wanted to see if I could rebuild this application 100% web-native. To handle storage I'd use IndexedDB, which has no problem with binary data. Before I get into the code, you can browse the complete source here: https://github.com/cfjedimaster/vue-demos/tree/master/idb-sound-board. You can also check it out online at https://idbsoundboard.raymondcamden.now.sh/.\nSetup\nTo begin the application, I used the Vue cli to scaffold a new application and enabled both Vuex and Vue Router. I also added Vuetify for the URL. I'll point out right away that I'm not 100% happy with how the application looks, it could definitely be better. (I'm thinking of switching to cards instead of list items.)\nTo support audio recording, I used this excellent open source library: web-audio-recorder. It worked great, but the docs were a bit hard to grok at times. This article was very helpful: &quot;Using WebAudioRecorder.js to Record MP3, Vorbis and WAV Audio on Your Website&quot;\nThe App\nThe first iteration of the application focused on everything but audio. I built the UI first. The initial page is a list of sounds with a button to add a new one. Clicking the title would play the sound.\n\nThe next page is where you add new sounds. It lets you record, play, and add a title to the sound.\n\nI had initially thought about adding &quot;edit&quot; support, but I decided you could just delete a sound and record it again. Yes, I'm being lazy.\nI set up my code to persist sounds, but just the title value at first. Once I had the flow done (adding sounds, listing sounds, and deleting), I then added in the recording functionality. The web-audio-recorder library uses a callback that returns a blob. So I was able to use it like this:\n\nAll I do here is copy out blob into my Vue data so I can store it later:\n\nMy store was simply persisting the sound object as is, so when I went from saving just the title to the title and the audio blob, nothing there had to change. IndexedDB stored the string and binary data perfectly.\nTo play that blob, I just used this:\n\nA slightly better implementation would handle not letting you play two or more sounds at once, but I kinda like that you can do that if you want.\nThe Best Part\nThe absolute best part of this whole thing is - I forgot that I had enabled PWA support when I scaffolded the application. When I was done and deployed my build version, I noticed the service worker in play and did a quick test. Using Chrome DevTools, I turned off network support and reloaded. The entire application had been cached locally and it worked perfectly. Since all the sounds are stored in IndexedDB, there's no remote resources to hit. If I wanted to, I could replace the icons spit out by default and provide my own, but I'll probably only bother with that if folks actually like the application.\nSo that's it. I feel like I didn't share a lot of code here, but most of the code was done in the Vue/IndexedDB example from a few weeks ago. You can check out the full code on the repo and try it yourself here: https://idbsoundboard.raymondcamden.now.sh/.\nHeader photo by Abigail Keenan on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Search to your Eleventy Static Site with Lunr",
		"date":"Sun Oct 20 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1571529600,
		"url":"https://www.raymondcamden.com/2019/10/20/adding-search-to-your-eleventy-static-site-with-lunr",
		"content":"I recently came back from connect.tech (one of my favorite conferences). I had the honor of giving not one, but two different talks. One of them was on static sites, or the JAMstack. This is a topic I've covered many times in the past, but it had been a while since I gave a presentation on it. During my presentation I covered various ways of adding dynamic features back to the static site, one of them being search.\nFor my blog here, I make use of Google's Custom Search Engine feature. This basically lets me offload search to Google, who I hear knows a few things about search. But I also give up a bit of control over the functionality. Oh, and of course, Google gets to run a few ads while helping find those results...\n\nTo be clear, I don't fault Google for those ads, I'm using their service for free, but it isn't something a lot of folks would want on their site.\nThere's an alternative that's been around for a while that I've finally made some time to learn, Lunr. Lunr is a completely client-side search solution. Working with an index of your creation (a lot more on that in a moment), Lunr will take in search input and attempt to find the best match it can. You are then free to create your search UI/UX any way you choose.\nI was first introduced to Lunr while working at Auth0, we used it in the docs for Extend. (Note - this product is currently EOLed so the previous link may not work in the future.) If you use the search form on the top right, all the logic of running the search, finding results, and displaying them, are all done client-side.\n\nLunr is a pretty cool project, but let's talk about the biggest issue you need to consider - your index. In order for Lunr to find results, you need to feed it your data. In theory, you could feed it the plain text of every page you want to index. That essentially means your user is downloading all the text of your site on every request. While caching can be used to make that a bit nicer, if your site has thousands of pages, that's not going to scale. This is why I didn't even consider Lunr for my blog. You also need to determine what you want to actually search.\nConsider an ecommerce site. Adding search for products is a no brainer. But along with text about the product, you may want to index the category of the product. Maybe a subcategory. Shoot, maybe even a bit of the usage instructions.\nAnd even after determining what you want to index, you need to determine if some parts of your index are more important than others. If you are building a support site, you may consider usage instructions for products more important than the general description.\nLunr isn't going to care what you index, but you really think about this aspect up front. I definitely recommend spending some time in the Lunr docs and guides to get familiar with the API.\nSo, how about an example?\nOur Site\nFor my test, I decided to build a simple static site using Eleventy. This is my new favorite static site generator and I'm having a lot of fun working with it. You can use absolutely any other generator with Lunr. You could also absolutely use an application server like Node, PHP, or ColdFusion.\nMy static site is a directory of GI Joe characters sourced from Joepedia. I only copied over a few characters to keep things simple. You can see the site (including the full search functionality we're going to build) at https://lunrjoe.raymondcamden.now.sh/. Here's an example character page.\n\nAnd how it looks on the site:\n\nOur Search Index\nI decided to build my index out of the character pages. My index would include the title, URL, and the first paragraph of each character page. You can see the final result here: https://lunrjoe.raymondcamden.now.sh/index.json. So how did I build it?\nThe first thing I did was create a custom collection for Eleventy based on the directory where I stored my character Markdown files. I added this to my .eleventy.js file.\n\nI am embarrassed to say it took me like 10 minutes to get my damn sort right even though that's a pretty simple JavaScript array method. Anyway, this is what then allows me to build a list of characters on my site's home page, like so:\n\nThis is also how I'm able to look over my characters to build my JSON index. But before I did that, I needed a way to get an &quot;excerpt&quot; of text out of my pages. The docs at Eleventy were a bit weird about this. I had the impression it was baked in via one of the tools it uses, but for the life of me I could not get it to work. I eventually ended up using a modified form of the tip on this article, Creating a Blog with Eleventy. I added his code there to add a short code, excerpt, built like so:\n\nNote that I modified his code such that it finds the first closing P tag, not the last.\nWith these pieces in place, I built my index in lunr.liquid:\n\nOur Search Front-End\nBecause I'm a bit slow and a glutton for punishment, I decided to build my search code using Vue.js. Why am implying this was a mistake? Well it really wasn't a ",
		"tags":[
	        
            "vuejs",
            
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using IndexedDB with Vue.js",
		"date":"Wed Oct 16 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1571184000,
		"url":"https://www.raymondcamden.com/2019/10/16/using-indexeddb-with-vuejs",
		"content":"It's been a while since I've talked about one of my favorite APIs, IndexedDB. Mainly because as cool as it is, there hasn't been much new about it recently. That being said, I was thinking about how I'd use it in Vue.js and decided to build a few demos to test it out. This post is not meant to be an introduction to IndexedDB, please see that previous link for a good guide. You can also check out jsMobileConf where I'm giving a talk on client-side data storage next month. For my exploration, I built two demos. The first one is rather simple and makes use of CodePen and Vue embedded directly on the page. I.e. a &quot;non-app&quot; use of Vue. (I'm still struggling with the best way to refer to that!) The second demo is more complete, uses a full Vue application, and works with Vuex. As always, I'm hoping folks will provide feedback, suggestions, and share their own examples.\nExample One\nAs I said above, the first example was meant to be as simple as possible. With that in mind, I built a demo that lets you work with Cat data. You can add cats, but not via a form, just a simple button that adds random data. You can delete cats. And that's it.\nAs it's rather short, let's start with the HTML.\n\nYou can see the button used to add new cats, the list of existing cats, and then a delete button for each one. The JavaScript is where things get interesting. I tried my best to separate out the Vue methods such that event handlers focused on their own thing and other methods were specifically targeting IndexedDB calls. This will (hopefully) make a bit more sense when you see the code. Let's start with the created handler:\n\nThis does three things. First, it initializes the IndexedDB database and waits for the db object so it can be used later. Then it asks for any existing data. Let's first look at getDb:\n\nThis is fairly boilerplate IndexedDB stuff. Open the database and setup an object store the first time you run the application. Our object store (&quot;cats&quot;) uses autoincrementing primary keys. I don't specify any indexes on the store as I'm keeping it simple. In order to use async and await, I return a promise from the method and I resolve it in the onsuccess handler for the database. Now let's look at getCatsFromDb:\n\nThis method opens up a read transaction, then a cursor, and will iterate over each object until done. As before, I wrap this up in a promise so I can use async\\await.\nWhew, ok, almost there. Let's look at the 'add cat' logic. As I said above, to make this simpler, I just created random data. I've written enough forms in my life, I'm allowed to skip them from time to time.\n\nThis method is primarily just concerned with the UI/UX of the operation. It chains out to addCatToDb for the actual persistence.\n\nWhile not much more complex, I liked separating this out. And as before, I'm wrapping my calls in a promise. The final bit is deletion and it uses a similar pattern. First the method you call when clicking the delete button.\n\nAnd then the actual deletion:\n\nAll in all not too bad. If you want, you can play with the complete demo here:\n\n  See the Pen \n  IDB1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nExample Two\nFor the second example, I went all in. A full Vue.js application, routing, Vuex, and more. I built a full CRUD that lets you view cats, add and edit them, and then delete it.\n\nAll of the code for this demo may be found in my GitHub repo: https://github.com/cfjedimaster/vue-demos/tree/master/idb\nYou can run this version in your browser here: https://idb.raymondcamden.now.sh/\nI won't share all of the code as it's mostly UI stuff (and you can browse it yourself at the link above), but I will describe my general approach. I built the first version of the app such that IndexedDB wasn't used at all. Instead, Vuex kept the data in memory. This allowed me to build out the UI, routing, and so forth, and then simply edit the store later. Here's the initial version of my store.\n\nJust three methods, all working with a simple array of data. This worked perfectly though and let me focus on the flow of the application. Switching to IndexedDB then was a completely separate job. Here's the current version of the store.\n\nAs you can see, it's actually somewhat simpler. That's because the actual storage work is being done in a new component, idb. In this version, Vuex simply handles managing the data, but not storing or retrieving. I could replace IndexedDB with API calls and no one would be the wiser. Let's consider idb.js now.\n\nIn general, this is pretty similar to the code used in the first version. I've got IndexedDB calls wrapped in promises. I cache the database handler too so it's only opened once. I could make this easier too if I used one of the many IndexedDB wrapper libraries out there, but as I was a bit out of practice working with IndexedDB, I kinda wanted to do things &quot;by hand&quot; as a way of remembering.\nSo - I hope this helps. If you want to learn more, definitely look at the MDN docs on",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Why I'm Digging Eleventy",
		"date":"Sat Oct 12 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1570838400,
		"url":"https://www.raymondcamden.com/2019/10/12/why-im-digging-eleventy",
		"content":"It's been quiet around here lately and for that I apologize. Between the new job and a bunch of trips and presentations, I've not had much time for exploration. Next week I'm giving a presentation at connect.tech on static sites (The Platform Formally Known as Static). It's been a while since I presented on static sites (the JAMStack) so I've been updating my slide deck in preparation. When I present on the JAMStack, I typically focus on one particular engine to give folks a &quot;feel&quot; for what it's like to work with static sites. On a whim, I decided I'd give Eleventy a try. I've been hearing about it for a while and thought it would be nice to do a bit of research.\nOne of the things I've found is that I either love a static site generator or hate it. These apps tend to be very opinionated about how they do things and if you don't like their opinion then it's not fun to work with them. I love the JAMStack but there's only a few generators that I enjoy working with.\nWhile it's still rather early (I've been looking at Eleventy for a grand total of about three days), I'm absolutely blown away by Eleventy. Why?\nBuilt on Node\nMy current favorite generator is Jekyll. I really like it. However it's built on Ruby and getting it to work on Windows has been, at times, hellish. I've got nothing against Ruby in general, but it's not a platform I'm familiar with and I've never worked with the SDK before. I got a new laptop about two months ago and I was dreading getting Jekyll up and running on it. I knew once I did I'd be fine, but I was not looking forward to the process.\nTo be fair, it actually went OK. The main issue I had was that Jekyll 4 came out recently and upgrading caused me a bit of trouble. (Maybe 1-2 hours of work, so not really that bad.) Also, you don't need to know Ruby at all to be successful with Jekyll. I've written one custom plugin for Jekyll in the year I've used it, but outside of that, the Ruby language isn't a concern for me.\nThat being said, the fact that Eleventy is built on Node is a big plus for me. It's a platform I know well, I'm comfortable with, and know will work reasonably well on any platform, even my &quot;Linux on Windows&quot; platform with WSL.\nMultiple Template Languages\nI enjoy working with template languages, some more than others. Eleventy gives you 11 (oh my god, I just noticed that) different templating options. That's a huge amount of variety and you can pick the one that works best for you. For example, they support Handlebars, and while I like Handlebars, it can be a bit too strict at times for me. It also supports Liquid, which is the template language Jekyll uses, so I can re-use my existing skills.\nYou're also free to mix and match languages as you see fit. So for example, you can use Markdown (which also lets you use Liquid inside it) for content and then Liquid for your layouts.\nSimple\nWhile there is a huge amount of customization and power behind Eleventy, it is also incredibly easy to get started with. You can take one markdown file, run the CLI, and get an HTML file out. If you aren't worried about layouts or other features, you can take source files and immediately generate HTML output. The default behavior of Eleventy covers most use cases but you also have deep configuration options for tweaking how it behaves.\nCustomizable to the Extreme\nAs stated above, there's a rich configuration API to modify how Eleventy behaves. The part that I think is really need is how Eleventy provides hooks into the various template languages. Things like Handlbars helpers for example. In a typical Node.js application, you would load in Handlebars, add your helpers, and get to work. Because Eleventy loads up the engines for you behind the scenes, that isn't an option. Instead, they provide an API to directly add stuff (like helpers, and more) to your engine.\nSo pretend I'm using Handlebars and want to build an uppercase helper to use for the following template.\n\nTo make this work, I'd add a .eleventy.js file in the root of my folder, and then use the following:\n\nAnd that's it. But holy crap - it gets better. Eleventy actually supports adding stuff to multiple engines at once. While this isn't supported across all 11 templating engines, you could change the code above to:\n\nThis adds the helper (called a filter to Eleventy) to Liquid, Nunjucks, and Handlebars.\nAnother example of the flexibility comes with permalinks. Most engines provide a way to change the default &quot;source file to output file&quot; logic. Eleventy allows this too, and is really useful in cases where you want to output non-HTML files. So for example, I could have a data.json.liquid file. By default Eleventy will output this to /data.json/index.html. This normally makes sense for HTML files. In this case, I can change the output like so:\n\nThis particular use case is one of the reasons I stopped using Hugo for my blog. Hugo was incredibly fast (although not much faster than Jekyll 4) but it felt like anythin",
		"tags":[
	        
            "eleventy"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Building a Netlify Stats Viewer in Vue.js",
		"date":"Sat Oct 05 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1570233600,
		"url":"https://www.raymondcamden.com/2019/10/05/building-a-netlify-stats-viewer-in-vuejs",
		"content":"I'm in somewhat of a &quot;stats building&quot; mood lately as this is my second (see last month's post on building a stats page for Untappd) post on the same topic. For today's demo I'm building a stats viewer for Netlify's cool Analytics feature. (You can read my review of the feature from when it launched.) This particular demo actually has less stats than Netlify but it does have a cool feature they don't support yet - changing the date range.\nIf you want to check out the code, you can find it up on GitHub: https://github.com/cfjedimaster/vue-demos/tree/master/netlify-stats.\nThe Vue application makes use of the following parts:\n\nVuex - nothing special here really.\nVue Router - I made use of my first navigation guard here.\nvue-chartjs - I just made one chart so it's not terribly deep integration.\nBootstrapVue - I made use of their dynamic table stuff which was pretty cool.\nAnd of course, Netlify's API.\n\nI'm not going to share all of the code in the blog post as you can read it yourself at the repo, but I'd like to call out a few things. Here's a screen shot so you can see how it looks with my site.\n\nOAuth Flow\nLike my last demo, I make use of Netlify's OAuth flow so I can make calls to the API with your data. For the most part this was simple except for a few hiccups. First off, when you define your application in Netlify's administrator (this is done in your profile settings as it isn't site specific), the redirect URL is listed as optional. That is not the case. I could never get it to work when leaving it blank and passing it in my application. Maybe I did something wrong, but you want to keep it in mind.\nMy OAuth flow begins with a button. When you click it, I fire off this method:\n\nMy Vuex store has my clientID value, hard coded, and I pass this to my Netlify API library to have it generate a URL. Here's that method:\n\nNote the hard coded callback path. That's built in my Callback.vue file and all it does is store the access token returned by Netlify:\n\nDisplaying Your Sites\nThe Sites view of my application first asks for your sites via the API and then filters it to sites using the Analytics feature. Remember that this is a paid feature so your sites won't have it by default. This is how it's called:\n\nAnd here's the Netlify call being made:\n\nI render the sites using Bootstrap Cards. I've only got one so it isn't too exciting:\n\nCurrently I don't handle the &quot;you have no available sites&quot; option but I'd gladly take a PR adding it. To give you an idea of how Bootstrap handles the cards, here's the source of that part of the view.\n\nThe Analytics\nAlright, now for the fun part. As I said, my analytics are pretty limited, I mainly wanted to handle date filters. I report on three things:\n\nPage views\nTop pages\nTop sources\n\nCurrently the Netlify Analytics API is not documented, but if you use devtools while on their site you can clearly see the calls being made. Each endpoint had a pretty simple API where you could pass a max count where it made sense and use date values (as times since epoch) for filtering. So here's those calls:\n\nEach one is pretty darn similar. I only do some mapping in getPageViews as I didn't like the original shape of the result.\nFor page views I made use of a Vue wrapper for ChartJS. The docs were a bit weird at times, but I got it working. To be honest I definitely need to use it a heck of a lot more to be comfortable with it, but I loved the result. The other two reports make use of BootstrapVue tables which support binding to data. Last time I had used their &quot;simple&quot; table but I really like how well the more advanced ones did things. Column customization is powerful, but I don't think I 100% understand how they work. Here's one example.\n\nTo be clear, I only needed the customizations to get links in my first column and formatting in my second. There may be simpler ways of doing this.\nThe Navigation Guard\nThe final bit was handling cases where you reload and have not logged in yet. I did this using a navigation guard, one of the more advanced features of the Vue Router, although it was pretty easy to use:\n\nI could have done the to.name part easier with route metadata. I'll do that next time. :)\nAnd that's really it. You are absolutely welcome to try the online version, but obviously it will only work if you are a Netlify user and have sites with analytics.\nhttps://netlify-stats.raymondcamden.now.sh/\nHeader photo by Dominik Schröder on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using OAuth and Vue.js to Build an Untappd Stats Page",
		"date":"Sat Sep 28 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1569628800,
		"url":"https://www.raymondcamden.com/2019/09/28/using-oauth-and-vuejs-to-build-an-untappd-stats-page",
		"content":"Every now and then I try to remember to remind folks - I hope that my readers assume when I share stuff like this that I'm sharing as I learn. This is my first time doing anything with OAuth and Vue.js so most likely there's ways to do it better. As always, I strongly encourage my readers to leave me a comment below if they have any suggestions whatsoever. I wanted to build something with Vue.js that would use OAuth to talk to a service. At the same time, I also wanted to do something with Untappd. Untappd is a &quot;social network&quot; type app for beer drinkers. I pretty much ignore the &quot;social&quot; aspect and just use it to record the unique beers I drink. Whenever I try a new beer I'll record and rate it in the app.\nI've been a member of the site since March of 2011. It was also one of the first popular apps built using Cordova/PhoneGap. I've known for a while now that they've got an API and I thought it would be kind of neat to build a &quot;stats&quot; page using their service. Now to be clear, they already have stats available. You can go pretty deep at my profile page: https://untappd.com/user/cfjedimaster. And if you support the site you get even more stats. But of course, I didn't let that stop me from building something that I thought would give me more experience with Vue, and as I said, try to work with OAuth.\nTo begin, I read over the API documentation and created my application. Authentication with the API works like so:\n\nYou link the user to an endpoint on Untappd.\nThe user will be prompted to login there.\nThe user is redirected back to your site, where you will use server-side code to fetch an access token.\nYou can then use the access token to make authenticated requests to the API.\n\nNothing too crazy, but obviously step three there requires a hybrid solution, you can't do it all in Vue.js. I decided to use the simple serverless functionality provided by Zeit (see my blog post in it earlier this month) as a way to handle that aspect.\nFor my stats, and again, most of this is on the site, I decided to show the following:\n\nTotal number of unique beers.\nTotal number of checkins (I don't usually checkin a beer I've already recorded).\nAverage ABV, IBU of my beers.\nMy average rating.\nMy favorite and least favorite beers.\nMy favorite styles by number of checkins. I could have also done it by average rating and that would be better, but I kept it simple for now. (For folks curious, my truly favorite style is Märzen.)\n\nHere's the initial screen prompting you to login:\n\nAfter clicking the login screen, you'll be prompted to login over at Untappd:\n\nBack on my site, I use the API to get your checkins and then render some lovely stats:\n\nOK, so let's look at the code. Before I begin, note that you can find the entire codebase here: https://github.com/cfjedimaster/vue-demos/tree/master/untappd.\nThe initial state of the application assumes you are not logged in. I'll show in a bit how we detect that but here's the HTML for the login button:\n\nYou'll note that I'm using BootstrapVue again. Here's the login method:\n\nUntappd requires me to pass a redirect_url which is where, as you can guess, the user will be redirected to after logging in. This points to the serverless function I wrote. My CLIENTID value is from the application I created and is safe to use here in client-side code. Once redirected to Untappd and then returned, they hit my serverless function, auth.js:\n\nPretty small, right? Untappd sends me a code. I use that code, my CLIENTID and CLIENTSECRET values to then request an access token value. When I have that, I redirect the user back to the Vue app with the token in the URL hash. Back in the Vue app, my created handler picks up on it:\n\nNow we get down to business. Untappd has an API limit of 100 calls per hour per user. The most beers I can get in one API call is 50. So I wrote functionality to:\n\nGet 50 beers at a time, to a max of 90 calls (4500 beers)\nCache the results for one hour using LocalStorage.\n\nLet's take a look at this code.\n\nI begin by seeing if I have cached information. You can see that logic in hasCache and getCache. Typically I wouldn't store a large blob of JSON in LocalStorage, but IndexDB felt a bit too heavy for this. Feel free to argue with me about this! If I don't have a cache, I start off by first getting the user profile. Then I start getting your beers. This is done in a loop to handle pagination. I use the simple named x variable as my way of ensuring I stay within API limits. And yes, I screwed this up multiple times.\nOnce I've got all the data, I have another method that prepares this data for rendering.\n\nThis is mostly boring things like getting averages and sorting and stuff. The only part really fun for me was using array methods in chain to filter and sort and the like.\nAnd that's it. If you want to try it (although if you don't use Untappd it won't work too well for you), simply go to https://untappd.raymondcamden.now.sh. Let me know what you think!\nHeader ",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Sending a Notification to Alexa when Netlify Builds Your Site",
		"date":"Fri Sep 20 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1568937600,
		"url":"https://www.raymondcamden.com/2019/09/20/sending-a-notification-to-alexa-when-netlify-builds-your-site",
		"content":"This will be a quick post and credit for the idea goes to Stacey Higgenbotham and her post from last year, &quot;How to trigger custom Alexa notifications from a smart home event&quot;.\nIn her post, she describes how to use the Notify Me skill on Alexa to allow for custom notifications. When you add the &quot;Notify Me&quot; skill to Alexa, you get an email with a unique access code. You can then head over to the web site, https://www.thomptronics.com/about/notify-me, and check out the docs. At the simplest level, you can just hit a URL like so:\nhttps://api.notifymyecho.com/v1/NotifyMe?notification=Hello%20World!&amp;accessCode=ACCESS_CODE\nThat's the entire API, seriously. You can pass an additional title attribute and the API is flexible in terms of accepting GET, POST, or PUT. Here's an example of how it looks on my Echo Spot.\n\nIn this case, the text of the notification is not visible, just the title, but if I ask her for my notifications, I'll hear the full text.\nGiven that you've enabled the skill and gotten your access code, then how would you set it up to get notifications on builds?\nLog in to Netlify, go to your site, Settings, &quot;Build &amp; deploy&quot;, and then finally &quot;Deploy notifications&quot;:\n\nClick the &quot;Add notification&quot; button and select &quot;Outgoing webhook&quot;:\n\nFirst figure out what you want to be notified on, most likely &quot;Deploy succeeded&quot;, and in the URL enter the URL in the form I shared above. Perhaps something like this:\nhttps://api.notifymyecho.com/v1/NotifyMe?notification=Build%20Done&amp;title=Build%20Done!&amp;accessCode=ACCESS_CODE\nRemember that only the title will be visible, but you could include more information in the notification part to provide context, perhaps the name of the site that was deployed.\n\nAnd that's it! If you want you can go to the &quot;Deploys&quot; menu and hit &quot;Trigger deploy&quot; to force a new build. I wish there was a bit more control over the UI of the notification, but for a free service I'll take it.\nHeader photo by Prateek Katyal on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An iTunes Audio Enabled Search Built in Vue.js",
		"date":"Wed Sep 18 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1568764800,
		"url":"https://www.raymondcamden.com/2019/09/18/an-itunes-audio-enabled-search-built-in-vuejs",
		"content":"(Before I begin, a quick note. The iTunes API will randomly throw CORS issues, most likely due to a misconfigured server in their network. To use this in production I'd add a serverless proxy. You may, or may not, run into this while testing.) When I present on Vue.js, one of the demos I show makes use of the iTunes Search API. It isn't necessarily that exciting of a demo, and I don't use iTunes very often, but the fact that it has interesting data and does not require a key of any sort makes it a good candidate for simple demos. I thought I'd quickly demonstrate this with Vue.js in the simplest form possible, and then work through some updates to improve the application.\nVersion One\nIn the first version, I'm just going to do a search against the API and render the results in the simplest way possible. I will provide some feedback when the search begins so the user knows what's going on, but that's pretty much. Let's start with the HTML:\n\nWe've got a simple form on top where you enter your terms with a button that will kick off the search. Beneath that are three divs. The first renders the results. I chose to show the artwork, artist, track, and release date. The API returns more information but I figured that was enough. The second div is only shown when no results are returned. The final div is used to indicate that the search is in progress. Now let's look at the JavaScript.\n\nMy code begins by defining a filter formatDate that makes use of the Intl spec. (If this sounds interesting, read the article I wrote on the topic.) The application itself is fairly simple. I've got one method that fires off the request to the API. Note that I'm limiting both the total number of results and the media type to music. When done, I set the results, set the flag for no results, and that's it.\nYou can demo this version here: https://cfjedimaster.github.io/vue-demos/itunes-search/ajax-search/. Try searching for &quot;duran duran&quot; because I said so. ;)\n\nVersion Two\nThe second version is virtually the same, except for the addition of the Audio API to play the samples returned by the API. The only thing changed in the HTML is the result view so I'll just share that part:\n\nIn the JavaScript, I've added support for the play method. Here's the code:\n\nNote I have an audio object defined in my data. I need a &quot;global&quot; audio object so I can cancel a previous preview if you start a new one. (For fun, disable that logic and then play a bunch of previews at once.) And that's literally it. For this demo you should search for &quot;hatchie&quot;, one of my favorite new bands.\n\nYou can demo this here: https://cfjedimaster.github.io/vue-demos/itunes-search/ajax-search-2/\nVersion the Third\nFor the third and final version I put some lipstick on the pig and added BootstrapVue. As you can guess, this is a Vue component library that wraps the Bootstrap UI project. I didn't do a lot to it, but you can see the result here:\n\nThis is my first time using BootstrapVue (well, first time in quite some time), and in general it went ok. I don't like how you have to hunt sometimes to find random properties, for example it took me a while to figure out how to do spacing. (And to be fair, &quot;a while&quot; was maybe two minutes or so.) Since the JavaScript didn't change at all, I'll just show the HTML update.\n\nYou can see I'm loading in various Bootstrap libraries (both JS and CSS) in my head. I've pretty much changed all of my HTML tags into Vue components. I assume most make sense as is, but obviously you can check the BootstrapVue docs for more information. (You can ask me too of course!) All in all it was a mostly painless process, but I wish they had more of a dark theme. (They may, but I couldn't find it outside of dark UI elements.)\nYou can test this version here: https://cfjedimaster.github.io/vue-demos/itunes-search/ajax-search-3/\nFinally, all of the code may be found here: https://github.com/cfjedimaster/vue-demos/tree/master/itunes-search\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A National Parks Service API Demo with Vue.js",
		"date":"Mon Sep 09 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1567987200,
		"url":"https://www.raymondcamden.com/2019/09/09/a-national-parks-service-api-demo-with-vuejs",
		"content":"This weekend I was on the road and had some time to build (yet another) application with Vue.js. I don't think this one necessarily does anything terribly cool. At minimum it was more &quot;exercise&quot; for my Vue muscles and provides another demo I can share with folks. As always though, if you have any suggestions or feedback in general, just let me know. If posts like these aren't helpful, also free free to share!\nLet me start by giving a high level overview of what I built. I'll start with a few screen shots. The initial page shows a list of all fifty states.\n\nSelecting a state will then make a call out to the National Park Systems API to ask for all the parks within that state. I then render them out:\n\nBehind the scenes I'm using the following technologies:\n\nVue.js of course. :)\nVue Router\nVuex to handle calling my API and caching (this is somewhat interesting I think).\nVuetify for the UI.\nZeit for my serverless function.\n\nBefore I dig into the code more, you can find the complete repository here: https://github.com/cfjedimaster/vue-demos/tree/master/nps_gallery. You can run the demo here: https://npsgallery.raymondcamden.now.sh/\nAlright, so I'm not going to share anything about the first view of this page. I've got a hard coded list of the 50 states (and abbreviations) I store in my Vuex store and I simply fetch them to render. The only part that was interesting here is that I discovered the &lt;router-link&gt; will correctly handle URL encoding values:\n\nIn the link above, note that I can safely use the state value without worry. I should have expected this, but I was happy to see it worked well.\nIt's the state view where things get interesting. First, the main view component, which is pretty simple since my complexity lies elsewhere.\n\nYou can see I'm rendering values by binding to a parks variable that comes from my store. You'll notice I'm calling two things in my created related to the store. I first call clearSelection and then loadParks. clearSelection removes any previously loaded parks from the view and loadParks obviously fires off the request to load parks. Let's look at the store now because here is where things get a bit deep.\n\nSo the biggest thing I want to point here is that I'm using the store to wrap calls to my API and as a simple cache. Anytime you ask for parks for state X, I first see if it's cached and if so - return it immediately. Otherwise I make a call out to the API. It's a pretty simple system but I love how it came out, and performance wise it works really.\nThe API part is actually two fold. You can see I load in './api/nps', which is yet another wrapper:\n\nAll this does is call my serverless function. The NPS API doesn't support CORS so I need that to handle that aspect. I also do a bit of filtering to ensure we get images back. (Although this doesn't seem to work perfectly - I think some parks have images that 404.) The final bit is the serverless function:\n\nIf you want to know more about serverless and Zeit, check out the article I wrote a few days on it.\nAnyway, that's it! As I always say, I'd love some feedback, so leave me a comment below.\nHeader photo by Sebastian Unrau on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Look at Zeit's Zero Config and Serverless Platform",
		"date":"Fri Sep 06 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1567728000,
		"url":"https://www.raymondcamden.com/2019/09/06/a-look-at-zeits-zero-config-and-serverless-platform",
		"content":"For a while now my go to service for hosting static sites &quot;for fun&quot; has been Surge. While I host my blog on\nNetlify and absolutely consider it the &quot;gold standard&quot; for static sites, I try to reserve my usage\nthere for &quot;real&quot; sites, i.e. not things I'm playing around with or temporary examples. I had heard of Zeit of course and knew of their cool command line deployment, but outside of a few Node.js demos, I hadn't really thought of it.\nOver the past few weeks I've had a chance to dig deeper and have to say I'm incredibly impressed by Zeit, specifically their new zero config and serverless features. I've pretty much decided it will be my new default place for quick ad hoc demos. Let me expand on that and why I'm excited about the service.\nWhat is Zero Config?\nZero Config (at least in terms of Zeit) simply means you can upload your code in a commonly known format and Zeit knows how to handle it. Want an example? Imagine I scaffold a new Vue application: vue create zeroconfig1\n\nAnd then cd into the directory and type now:\n\nAnd... that's it. Zeit's platform knew how to handle the Vue application, both in terms of how to build it and then how to serve the final result. I did nothing. No config. No special JSON file. Nothing. Obviously this isn't just for Vue but supports, according to them, &quot; any framework or tool you can think of.&quot;\nAnd yes, the URL in the screen shot above is up and live at https://zeroconfig1.raymondcamden.now.sh/. It isn't that exciting but it took longer to scaffold the application then deploy it. That's freaking cool.\nHow about that Serverless?\nServerless is also zero config too. You add an api folder and then drop in either a TypeScript file, JavaScript file, Go, or Python, and that's it. Given /api/cats.js, you can hit it via the url /api/cats. If you need to install NPM modules, the platform auto parses your package-lock.json file and will install what it needs to. It all works incredibly easy. Consider the simplest example of building a proxy to an API that doesn't support CORS.\n\nI'm making use of HERE's Weather API to make a hard coded request to the forecast for Lafayette, LA. (You don't need to look at the results. It's hot. It's always hot.)\nTo test, I can use the command line to run a local server: now dev. This fires up a local server and I can then hit my API at http://localhost:3000/api/weather. I can then edit, debug, etc, all locally and quickly fine tune the serverless function. I can then deploy with now and... again. I'm done.\nYou can see this API here: https://zeroconfig1.raymondcamden.now.sh/api/weather I hope you can see how great this would be for a Vue (or other frontend framework) app that needs a few back end APIs to support it.\nAll in all it's rather painless. I did have a bit of trouble working with secrets. Zeit does support, and document, working with secrets. You can specify secrets via the CLI with a simple call: now secrets add somename somevalue. However, that isn't enough, and the zero config thing breaks down a bit here.\nIn order for your serverless function to get access to secrets, or other environment values, you must create a now.json file that looks like this:\n\nIn this example, VARIABLE_NAME is the name your code will use, not as a global, but for example, process.env.VARIABLE_NAME. The @variable-name is the name of the secret or environment variable.\nNow - stick with me a bit because this tripped me up. I used uppercase secret values in a test and found that my secrets weren't working. Why? The CLI lowercases secret names. I don't know why, and I feel like it's a bug, but if you make a secret named FOO it will be called foo. So my now.json file looks like this:\n\nHonestly this is my only real complaint. I can't imagine why I'd define a secret I wouldn't want to use and it would be nice if you could skip now.json if you were fine with all the secrets just being available. But it's a minor nit I can live with.\nIf you remember, a few weeks ago I blogged about a Twitter bot I wrote to post pictures from the National Park Service. I built this on Azure Functions, and while I like their service, they do not have a 100% free tier. I got my first bill this month (a bit over a dollar) and used this as an excuse to migrate from Azure to Zeit. Ignoring the issue I ran into with case above, the &quot;process&quot; was about five minutes. Zeit doesn't support scheduled tasks so I just made use of EasyCron, a free service that can hit URLs on schedules.\nPrice Details\nFor folks curious, you can checkout the pricing information for what's supported at what tier. Currently they only have Free and Unlimited. For serverless, you're limited to 5000 a day which seems to be more than enough for testing, demos, and the such. The paid plan starts at $0.99 so if you do need to start shelling out money, you're starting at a pretty good place.\nAs always, if you're using this, drop me a comment below. I love to hear about real world uses.\nHe",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack",
            
                "serverless"
            
		]

	},

	{
		"title": "Using Geolocation with Vue.js",
		"date":"Sun Sep 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1567296000,
		"url":"https://www.raymondcamden.com/2019/09/01/using-geolocation-with-vuejs",
		"content":"I decided to spend my lazy Sunday morning working on a quick Vue.js post. Geolocation is one of the older and simpler APIs you can use with your web browser so this article won't necessarily be that exciting, but I thought a quick demo of the API with Vue, and a few variations, could be useful to folks. As a reminder, web pages that use Geolocation must be run on either localhost or an https server. This is a security precaution and... let's be honest - there is zero reason to be using a non-secure server in 2019.\nExample One\nFor the first example, let's build a simple Vue application that will:\n\nAutomatically try to get your location\nDisplay a &quot;loading&quot; type message while this is happening\nAnd properly support error conditions.\n\nFirst we'll build the front end:\n\nI've got three divs here. The first handles displaying an error. The second is the loading message. And the final div displays our location. Now let's look at the code.\n\nI'm using the created method to start requesting location as soon as the application is ready. I do a quick check to see if the API is supported. After that, I simply use the API. It's all rather simple, but even this code could be improved. You'll notice that my front end is addressing the result as location.coords.latitude. If I know for a fact that I only need latitude and longitude, I could copy those values out. My front end code could then look something like this:\n\nThat's a bit better in my opinion as the layout code is simpler and not directly tied to knowing that the Geolocation API was used. You can play with this example here:\n\n  See the Pen \n  Geolocation 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nExample Two\nIn my next example, I'm going to switch the code so that it doesn't request your location until the user actually needs it. In this case I'm going to use a simple button to kick off that process. Here's the HTML:\n\nMost of the layout above is the same with the exception of the paragraph and button on top. For the code, I decided to abstract things a bit. The locateMe method referenced by the button will be simpler as I've migrated out the Geolocation stuff. Let's take a look.\n\nIf you focus on locateMe, you can see it is much simpler. I use async and await to call getLocation. My method handles things like the loading screen and errors, and the result, but the actual mechanism of the location request is now abstracted away. getLocation makes use of a Promise to properly work with async and await, but outside of that it's mostly the same as before.\nYou can test this version here:\n\n  See the Pen \n  Geolocation 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOption Three\nFor one last example, let's do something fun with the location. Most people can't translate a longitude and latitude into something useful. It would be cooler if we could use reverse geocoding (which is the process of attempting to map a latitude/longitude to a place with a name) to display the user's location in a friendlier name. For this example I'm going to be making use of the Geocoding API by HERE. Disclaimer - I started working for HERE last week so I'm talking about my employers products. This API (and many more) have a free tier so you can play with them all you want!\nThe API is rather extensive (you can see the docs here) but I'll focus on the simplest example. To begin, I created a new JavaScript project in my HERE account. This gave me an API key I could then use in my code. I added two HERE JavaScript libraries and then this bit of initialization code:\n\nNote that you can specify a domain whitelist for your API keys which will make the code above perfectly safe for your public web pages. Once you've configured your geocoder, to do a reverse geocode you can simply do this (pseudo-code):\n\nHere's the updated JavaScript for getLocation:\n\nFor the most part this is just a simple update to the previous example, but do note that when I leave the function, I &quot;dig down&quot; into the Geocoder result to simplify things a bit: resolve(results[0].Result[0].Location);\nThe HTML now uses this:\n\nIf you remember what I said about Option One, I kind of don't like my HTML having too much knowledge about the data so a nicer solution would probably just store Address.Label to location. You can run this here:\n\n  See the Pen \n  Geolocation 3 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAs always, let me know what you think and ask any questions in the comments below. There's also multiple options for Vue components to simply Geolocation for you. One is vue-browser-geolocation.\nHeader photo by Paula May on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Starting My New Role at HERE!",
		"date":"Sat Aug 24 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1566604800,
		"url":"https://www.raymondcamden.com/2019/08/24/starting-my-new-role-at-here",
		"content":"For the past year (ok, nearly a year), I've worked as a Developer Experience engineer at American Express. My work there had me interfacing with product owners to help them present the best developer experience possible. This involved everything from simple documentation improvements to helping work on tools to improve developer facing APIs. While I worked with some great people, the role wasn't a great fit for me. Also, I've had some changes at home (good changes!) that will allow me to have a more public facing role, to spend more time on the road, and generally do what I love - help others.\nStarting on Monday, I'll be a Lead Developer Evangelist for HERE. HERE works with location services, think mapping, routing, and related technologies. I'll be blogging, making videos, presentations, and so forth. I'll also be looking at our documentation and working to make things better and easier for developers. As a reminder, if you want to see where I'm speaking next, you can view my speaking page and if you would like me to present to your group, just drop me a line. I'm really excited about this new phase and I can't wait to share more with you!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Sailing the Seas with Vue - My Take on Taipan",
		"date":"Mon Aug 19 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1566172800,
		"url":"https://www.raymondcamden.com/2019/08/19/sailing-the-seas-with-vue-my-take-on-taipan",
		"content":"As a young kid, I spent a hell of a lot of time on my Apple 2. I played many different games, but one of my favorite was Taipan!.\n\n\nBy Source, Fair use, https://en.wikipedia.org/w/index.php?curid=8888638\n\nTaipan was a basic trade simulator based in the far east. You had a ship with storage capacity and would buy and sell goods across multiple ports. The game had basic combat, a money lender, and other details to make things interesting, but for me, my enjoyment came from pure grinding. I'd play it for a few hours at night just to see how much money I could make. (Of course, once I found the money lender bug it became trivial to get rich.)\nAs part of my basic &quot;get more experience with Vue apps&quot; goal this year, I decided to rebuild the game (to the best of my ability) using, of course, Vue.js. I didn't want an exact rebuild though and in my version I made a few changes.\n\nFirst, I got rid of combat. I hated the combat aspect of the game as it felt incredible slow. I liked the fact that it added risk to the game, but didn't like how it killed the pace. In my version, you can be attacked by pirates but they simply do damage and steal some goods.\nI got rid of the money lender. It's an interesting aspect, but it also slowed down the pace of the game when arriving at port.\nI got rid of the 'shake down' aspect via Li Yuen. I liked this aspect too and may eventually bring it back.\nI got rid of the warehouse. To me this always felt like a distraction.\nI also skipped making one of my goods illegal.\n\nThat's pretty much it but there's a few other smaller mods as well. My game feels quite a bit more snappy and quick compared to the original which feeds into how I enjoyed playing it.\nI also tried to make use of the keyboard as much as possible. You can read about my work in that area here: Working with the Keyboard in your Vue App. I didn't make everything keyboard accessible, but navigation from port to port can be done entirely by keyboard and while playing it felt like a really good setup. So before I get into the code, if you want to give it a try, you can play here:\nhttps://taipan.raymondcamden.now.sh/\nAnd you can view the source code here:\nhttps://github.com/cfjedimaster/vue-demos/tree/master/taipan/\nAlright, so let's take a look at the code a bit. I'm not going to go over every single line, but rather talk about the more interesting bits (to me) at a high level.\nTaipan makes use of both Vue Router and Vuex. My router use wasn't anything special. There's an home route which introduces you to the game. A &quot;setup&quot; route which just asks for your name. Then the game route were most of the work is done. Next is a &quot;travel&quot; route which handles going from one port to another. Finally there's a end of game route which shows your final stats.\nMy Vuex usage was interesting. As with my Lemonade Stand game, I spent a good amount of time thinking about what should go in my views versus what should go into the store. I definitely think I have a few things in views that should not be there. I think this particular aspect of Vue development is something that will change over the iteration of an application.\nLet's look at how gameplay happens. Each turn consists of the following logic.\n\nFirst, I ask Vuex to consider random events. This was - truly - the most difficult aspect of the entire game. The core &quot;turn to turn, buy, sell&quot; etc logic wasn't too hard. But handling &quot;special events&quot; was definitely problematic.\nMy view prompts for input. This can be one of - buying goods, selling goods, repairing damage, upgrading the ship, or moving to another port.\n\nThat &quot;prompts for input&quot; aspect is related to the keyboard. My solution involved showing a menu based on the current 'state' of what you are doing. So initially the state is - show the menu. But if you want to buy something, I switch to another menu prompting you for an amount and good. You can see this in play in the layout for Game.vue.\n\nI moved my a lot of my display stuff into components which lets the layout of this page mainly focus on responding to your inputs. The keyState value is how I handle dynamically changing the current menu. Here's the JavaScript:\n\nThat's quite a bit and I apologize. Probably the most interesting aspect is doCommand, where I respond to keyboard events and based on the current state I handle the input. I feel like this could be done better, but for a first draft, I'm happy with it.\nOne part I'm not happy with is all of the items in computed that simply reach out to the Vuex state and their getters. I know I could use mapState to make it a bit cleaner but I decided to hold off on that for now. (I'm going to force myself to use it in myh next demo.)\nOutside of that though most of the code here just handles input and interacts with the store. Here's a quick screen shot of my awesome design.\n\nLet's take a look at Travel.vue. This is an temporary screen you see while moving between ports.\n\nThe most int",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "My Thoughts on Documentation",
		"date":"Wed Aug 14 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1565740800,
		"url":"https://www.raymondcamden.com/2019/08/14/my-thoughts-on-documentation",
		"content":"I am incredibly opinionated about technical documentation. While my degree was in English, my focus in college was technical writing. I've written over six thousand blog posts and worked on around fifteen books. I am not trying to brag about my ability (which, trust me, can always use the skill of an editor), but rather to provide some context as to why, when I'm testing a cool new utility or API, I judge it based on the level of documentation and how much care (or how little) is put into it.\nWith that in mind, I thought I'd share some opinions and suggestions I have in regards to developer focused documentation. I don't pretend to know everything and I'd highly encourage you to share your opinions below.\nDocumentation is a Required Feature\nI feel silly leading with this, but experience has shown me that developers are often fine shipping code and simply skipping the documentation. You (or your company) must develop a mindset that the feature is simply not complete until the documentation is written. You wouldn't (hopefully) ship something without a security review. Or tests. But for some reason documentation is often considered an afterthought or simply something that can be done at the last minute.\nIn order to address this, make documentation part of the process. Have it reviewed just like you have code reviewed. Even better, have it reviewed by someone who didn't work on the feature. When the developer writes the docs it's far too easy for them to make assumptions about what the reader knows. You get so close to the code you don't properly understand what it may be like for a new developer just coming in.\nBalance What You Include (and When)\nAt one of my more recent jobs, one of the things I did while reviewing the documentation was find a lot of things to remove. The documentation for this product had stuff towards the beginning that covered the history and theory of what the product covered. It was all... factual information but also completely unnecessary and distracting from actually learning how to use the product.\nMy goal when I'm just learning a product is to focus on the basics of how it works and what I can do with it. I don't need to know everything at once. Give me the basics, walk me through building something simple, and give me an early success to get me motivated. After that introduction it's time to get deeper.\nSo in abstract, this is what I like to see in the docs:\n\nAn introduction, light weight, and a quick read.\nInstallation, walk me through getting the product installed and ready to use. It is completely ok to focus on the simplest path and cover other methods later.\nGetting Started, a quick demo where I can see the thing in action, and as I said, get excited/motivated to do more\nEverything Else\n\nOk, so that last bullet is a bit broad, but in general, the topics after installation and getting started are things I may not need to read immediately or even in order.\nConsider the Vue Router docs:\n\nWhen I was learning the Router, I focused on the first three bullet points, and actually stopped after &quot;Dynamic Route Navigation.&quot; That literally got me to where I needed and has covered most of my usage since then. I recently needed to learn about guards so I simply skipped ahead to the part of the docs.\nNow of course I should have read everything from start to finish, but let's be honest, developers don't do that.\nBy focusing on the &quot;let me get you started and running on your own&quot; approach, the docs become so much more useful to me. And as I said, I can come back later for specific topics when the need arises.\nCode Samples\nThis one's a bit hard to define. Yes, developer documentation should have code samples. But there's a lot to consider when using them.\nFirst, how much code do you include in the sample? So for example, if I wanted to demonstrate a computed property in Vue, this is what I'd see in the docs:\n\nThere's the HTML needed to demonstrate the use and the JavaScript. But what's not there? In the HTML, we don't include the &lt;html&gt; or &lt;body&gt; tags. We don't include the script tags used to load Vue or the code.\nIn the JavaScript, we have the bare minimum Vue.js application in order to make it work. The JavaScript could have been shorter, perhaps just showing this:\n\nHowever by showing it in the scope of a greater application the reader sees it in a context that may be more clear.\nThe authors here handled this very well. You have enough context for the code to learn the feature and minimal &quot;noise&quot; that distracts from what is being taught.\nUnfortunately there's no magic formula here for how to do this. In general you want to keep your code listings &quot;short&quot; but what that means will depend on what you're trying to show and even the language itself.\nThere's also the question of - should the reader be able to literally copy and paste the code? In the Vue example... they actually couldn't do that. If you copied and pasted both into one HTML file th",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with the Keyboard in your Vue App",
		"date":"Mon Aug 12 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1565568000,
		"url":"https://www.raymondcamden.com/2019/08/12/working-with-the-keyboard-in-your-vue-app",
		"content":"This weekend I started working on another game in Vue.js (if you're curious, you can take a peak at it here if you want). For part of the game I wanted to really make use of the keyboard for interaction. My goal, and I won't make it 100%, is a game where you can use the keyboard for the entire time you play. I knew that JavaScript had access to keyboard events, but I had never tried using them in Vue. Before I share what I found, I want to give a shoutout to LinusBorg of the Vue forums. The good stuff below is all him, the bad stuff and mistakes are my fault.\nAlright, so let's start with a simple example. If you look at the Vue docs for event handling, you'll find a specific section that talks about key modifiers. This section discusses how you can add shortcuts to listen for specific keys. While not exactly what I was looking for, it reassured me that working with the keyboard was going to be easy. So for example, this will fire an event on every keyup call:\n\nThis modification will only fire when the enter key is pressed:\n\nCool! But notice how the event is bound to an input field. For my needs, I wanted keyboard handling at the &quot;app&quot; level, by that I mean without having to use an input field first. Consider this example.\n\nI've got multiple uses of keyup here. I'm passing a label to my test handler as well as the $event object. I listen, twice, at the div level, and then once for each input field. My handler just echoes out what was passed in:\n\nThe result is interesting. If you type outside of any input field, nothing is registered. But if you first click into one of the two input fields, things work as expected. Both the input handler and div handler will fire. You can test this yourself at my Codepen.\nSo a bit more Googling, and I came across this Vue.js forum post: Capture keypress for all keys. In it, the poster asks about responding to any and all keypress events globally across the app. LinusBorg came up with a simple solution that boils down to this:\n\nIn my testing, this worked great, but I ran into an interesting issue. My game makes use of routing and I only need to listen for keyboard events in one route. When I'd leave that route and return, the event listener would get bound again. The more I did this, the more duplicate event handlers were being bound for keypress.\nI struggled with this some more, and again, LinusBorg came up with a solution. I knew about window.removeEventListener, but it doesn't work with anonymous functions. The solution was to just use a Vue method for both registering and removing the event. That may not make sense, but here's a simple example:\n\nAnd that's it! Of course, things are a bit more complex in my game, but I'll leave those bits for the post describing my game. As always, I hope this helps!\nHeader photo by Csabi Elter on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Drag and Drop File Upload in Vue.js",
		"date":"Thu Aug 08 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1565222400,
		"url":"https://www.raymondcamden.com/2019/08/08/drag-and-drop-file-upload-in-vuejs",
		"content":"This won't be a terribly long post. I had to build a small demo for a friend demonstrating drag/drop along with uploading so I thought I'd share the code for others. Honestly this is mostly for me so that when I need to build this again in a few months I'll Google and end up back here completely surprised that I had already written it.\nI'll start off by saying I'm not going to cover the mechanics of drag and drop here. The MDN Web Docs have a great article on this (of course they do): HTML Drag and Drop API. In my case, I'm not concerned with making a DOM item dragable but rather making my code respond to drop events.\nFor what I need I have to handle two events, drop and dragover. Handling drop makes sense. I'll be honest and say I'm not quite sure why I need to handle dragover, but the code is incredibly small as you just need to prevent the default behavior.\nWorking on this demo also taught me something else about Vue. I'm used to building my Vue apps like so:\n\nWhere my div is then passed to Vue:\n\nHowever, what if I wanted to do something with &lt;div id=&quot;app&quot;&gt; app itself? Turns out you can add Vue directives there just fine. I guess that makes sense but I'd never tried that before. I was able to specify that my entire Vue application &quot;area&quot; was covered by drag and drop support.\nOk with that out of the way, let's look at the code. I'll start off wth HTML.\n\nOn top, you can see my two event handlers. As I said, for dragover all we need to do is prevent default behavior which makes that part short and sweet. The drop event, addFile, is where I'll handle generating the list of files.\nInside the div I keep track of the files you want to upload. For each I output the name, the size (passed through a filter kb), and add a simple button to let you remove the item.\nFinally I've got an button to fire off the upload. For my demo I don't bother using a &quot;Loading&quot; widget of any sort, nor do I clear out the files when done. If anyone wants to see that just ask!\nAlright, now the code.\n\nOn top you can see my simple kb filter to render the file sizes a bit nicer. Inside the Vue app I've got one data item, files, and note how uploadDisabled works as a nice computed property.\nIn addFile, I use the Drag/Drop API to access the files (if any) that were dropped. This demo lets you drag over one file, or 100 (don't do that). I then iterate over each and add them to the files value. Remember that when a user intentionally provides a file to a web app you now have read access to it. That's how I'm able to show the file sizes. I could do a lot more here like validate file type, set a max size per file, or even set a total size allowed.\nFinally, my upload method just hits httpbin.org which will echo back what it was sent. I create a FormData object and just append each file. Remember by the user dropping the files on the app we can read from them.\nAnd that's it. I hope this simple demo helps!\nHeader photo by Jimmy Chang on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Book Review: Progressive Web Apps",
		"date":"Tue Aug 06 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1565049600,
		"url":"https://www.raymondcamden.com/2019/08/06/book-review-progressive-web-apps",
		"content":"\n \nA while ago the author of &quot;Progressive Web Apps&quot;, Jason Grigsby, graciously shared with me an advance copy of his book. Things happened and I fell a bit behind, but I finally found time to finish reading the book and thought I'd share my opinion.\nProgressive Web Apps (PWAs) are easily one of the hottest topics now. I've been doing my own part to learn, write, and present, on the topic for about two years now, but honestly feel like I'm still just scratching the surface. It doesn't help that the technology behind PWA feels like it's changing every day. Not only do you have new APIs to learn, you have entirely new browser behaviors to figure out as well. It's a huge topic and one that I think will continue to be talked about heavily for years to come.\n\nThe last book I read on PWAs was way back in 2017 (&quot;Review: Building Progressive Web Apps&quot;). That book was very well done and very heavy on code examples. The book I'm reviewing today, &quot;Progressive Web Apps&quot;, is completely different.\nGrigsby's book has, perhaps, 20 lines of code in the entire book (a bit over 150 pages). Right away that may raise a red flag for you. But instead of spending time sharing code, Grigsby goes into incredible detail about the why and the how of every single aspect of PWA development. Let's be honest, you can easily find code samples related to offline caching and push, but Grigsby tells you why you would consider these features and what they mean for users. Time and time again I found myself nodding along as I read as Grigsby did his best to make you consider why you would use (or not use!) a particular API.\nAnother way this book shines is by how far it goes into explaining how browsers will react to certain features, like saving to the home screen. He shows screen shots, compares multiple browsers, and just goes to incredible lengths to show you the result of the code you would use rather than pages and pages of JavaScript.\nI can absolutely recommend this book if you want to get a deep introduction to PWAs before you start writing code. It's also the perfect kind of book you could share with a non-technical manager. (And to be clear, I'm not saying it isn't appropriate for developers. This developer was very happy with it!)\nHere's the table of contents:\n\nDefining Progressive Web Apps\nThe Case for PWAs\nMaking It Feel Like an App\nInstallation and Discovery\nOffline\nPush Notifications\nBeyond PWAs\nProgressive Roadmap (my favorite chapter)\nA Web for Everyone\n\nIf you've read this book as well, please leave me a comment below telling me what you think.\n",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "development"
            
		]

	},

	{
		"title": "Creating a One Click Visual Studio Code Snippet to Wrap Content",
		"date":"Fri Aug 02 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1564704000,
		"url":"https://www.raymondcamden.com/2019/08/02/creating-a-one-click-visual-studio-code-snippet-to-wrap-content",
		"content":"Here's a quick tip for you regarding Visual Studio Code snippets. If you aren't aware, snippets let you define keyword shortcuts for quickly entering content into your code. I've got a bunch tailored for my blogging. For example:\n\nThis is the snippet I use for my Unsplash credit text on most of my blog entries. Unsplash has amazing art you can use for free. They ask that you credit them and I figure it's the least I can do, however their &quot;Copy&quot; button only copies the text of the credit, not the link:\n\nI built a snippet so I could quickly insert proper text and then copy in the URL manually. I then type in the image author's name.\nAnother one I use is for my images. I keep all of my images on Amazon S3. This snippet not only outputs the right base URL, but also outputs a dynamic year and month, matching the organization rules I use.\n\nOk, so with that in mind, I realized yesterday I needed a new snippet for a very specific use case. I use Jekyll for my static site generator. I also write about Vue.js a lot. It just so happens that both Jekyll and Vue use the same tokens to reference variables - double brackets. So for example: {{ name }}. When I write a blog post with Vue code in it, Jekyll picks up on the variables and tries to render the values, which typically just results in white space.\nLuckily there is an easy fix, wrap the content with {% raw %} and {% endraw %}. (And to get that text to render was messy!) I wanted to see if I could build a Visual Studio Code snippet that would let me select some text, hit a key, and then wrap it with the code above. Turns out it took a few steps.\nFirst, I defined my snippet:\n\nI use the special variable $TM_SELECTED_TEXT to represent the currently selected text. This works well, but I usually activated snippets by typing their prefix and hitting the space bar. That won't work with selected text.\nLuckily there's a F1 command you can run that lets you select a snippet and execute it:\n\nThat works, but requires about 3 clicks. What I wanted to do was simply have a keyboard shortcut that would do the same. Luckily that's supported as well. I opened my keybindings.json and added:\n\nI specified the language and name of my snippet and also set that it only works when I've got an active selection. Now I just select some Vue code, hit ctrl+r, and I'm good to go.\nHeader photo by Andrew Neel on Unsplash\n",
		"tags":[
	        
            "visual studio code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Playing with Vue and Vuex - Lemonade Stand",
		"date":"Thu Aug 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1564617600,
		"url":"https://www.raymondcamden.com/2019/08/01/playing-with-vue-and-vuex-lemonade-stand",
		"content":"One of my goals for this year was to play more with Vue &quot;apps&quot; (ie, not simple page enhancement demos) and to dig more into Vuex. I really like Vuex, but I'm struggling with the &quot;best&quot; way to use it, what makes sense with it and what doesn't, and generally just how to approach it. I figure one of the best ways to get more comfortable is to just build stuff and see what feels right. With that in mind, today I'm sharing a game I built called Lemonade Stand.\n\nLemonade Stand was a classic PC game from way, way back in the old days. I remember playing it on my Apple II at home and at school. I also remember editing the code so I'd have a lot of money, which wasn't really as fun as you'd imagine. (Although ask me about editing Bard's Tale saves, that was cool.)\nThe game is a really simple economic simulator. You run a lemonade stand and every day you're given a weather report, a cost of materials, and you have to decide how many cups of lemonade you want to make as well as the cost. You can also buy advertising signs to help improve your sales.\nThe entire project is hosted up on Code Sandbox - https://codesandbox.io/s/lemonade-stand-oxbfq?fontsize=14&amp;view=preview. You can both play the game and edit the code (and if you do, let me know in a comment below). Let me break down how I built this.\nFirst, the application makes use of Vue Router to handle the different states of the game. There's an initial page that provides a basic introduction.\n\nThe next view is the &quot;planner&quot; where you determine how many glasses you want to make, how many signs you want to buy, and what price you want to sell your goods.\n\nWhen you figure out your options, you then go to the next screen and see the results.\n\nNow let's look at the code. I'm not going to show every single line but will rather focus on what I think is important. Don't forget you can use the link above to see the code and fork it.\nFirst, let's look at main.js, the top level setup for my game.\n\nThe unique parts here are loading a router, loading a Vuex store, and setting up a global filter for displaying money values.\nThe router is trivial as I only have three views:\n\nThe first view is called Home.vue and is mainly just text, but make note of the &lt;router-view&gt; to handle navigation.\n\nSo far so good. Now let's look at the next view, Game.vue.\n\nThere's a lot going on here. The component begins with the layout which is essentially a report on top and three form fields.\nThe first thing the code does in the created handler is to ask the store to generate a forecast: this.$store.commit(&quot;generateForecast&quot;);. I'll share the store code soon, but basically every aspect of the game that relates to numbers and logic is placed in the store. You can see this in multiple places, like where we ask for the price of signs. While that value won't change, I set it up as a constant in my store so I can change it in one place.\nThis is why you see a bunch of computed values that just call out to the store. There is a nicer way of doing this (see mapGetters) but I just didn't feel like using that.\nAnd note the last bit of code, initiateSales, simply gets the values and asks the store to try to start selling lemonade. If there aren't any errors, we go on to the report page. Let's look at that next.\n\nIn general, all this view does is report back to the user what happened. My store will know how many items were sold, the profit, and so forth (and you'll see the store next), so my view just has to ask for the values. The only slightly weird part is probably this, this.$store.commit(&quot;updateAssets&quot;);. This store mutation updates your assets and I do it here so you don't see a split second change in the previous view after sales are tabulated. Something tells me this could be done nicer.\nAlright, now let's take a look at the store!\n\nMy store contains state that represents the current values of the game, but it also has &quot;config&quot; information that won't change. Things like the price of a sign. As folks play the game and provide feedback, I could tweak that value higher or lower.\nThe FORECASTS constant represents the types of weather that can happen in the game. Each weather type has an impact on sales (salesRange) and a chance of rain. You can see this logic employed in doSales. After I calculate if your sales values were ok (i.e. you aren't spending more than you have), I determine what percentage of your glasses you sold. This is based on the weather, how many signs you made, the cost of your lemonade, and whether or not it rained. This will increase (or decrease) the percentage of glasses sold.\nOnce that's known the values are all stored in the state so they can be used on the report view.\nAnd that's pretty much it, but I wanted to call out some specific things that occurred to me while I was building this.\n\nOne of the things I like best about Vuex is how it gives me a nice abstraction for my logic. My store ends up complex, my Vue app ",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Multiple Ways of API Integration in your JAMStack",
		"date":"Thu Jul 25 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1564012800,
		"url":"https://www.raymondcamden.com/2019/07/25/multiple-ways-of-api-integration-in-your-jamstack",
		"content":"This is something I've been kicking around in my head now for a few weeks and I'm finally\ntaking the time to write it down. I've actually covered this before, but not in an explicit\nmanner and I wanted to organize some thoughts I've been having on the matter lately. Before I begin though, a quick note. I was a somewhat late adopter of the &quot;JAMStack&quot; moniker. Frankly, the name bugged me. Why not just call them what they are - static sites? But as static sites have become more powerful (thanks to various generators, APIs, and platforms like Netlify), the term &quot;static sites&quot; simply doesn't fit anymore. When you say &quot;static&quot;, especially to a client who may have a tiny bit of technical knowledge, you imply a whole set of limitations that simply don't apply now. &quot;JAMStack&quot; (JavaScript, APIs, and Markup) doesn't have those connotations and really helps frame what we're talking about a lot better.\nAlright, so with that out of the way, what exactly am I talking about today? When adding interactivity to a JAMStack site, typically you think of APIs, remote services that can be used to get dynamic data which is then rendered on your site with JavaScript. But there's multiple ways of using those APIs, and JavaScript, that may not be apparent to you at first. In this post I'm going to go over these options and discuss when you may want to use one form over the other. I'm going to be using Netlify as my example host, but everything I'm discussing here would apply to (most) other hosts as well. I've not hidden my obvious love of Netlify so I'm somewhat biased, but again, these principles will be applicable elsewhere.\nOption One - Direct Access to a Remote API\nThe most direct and simplest way to work with an API on your JAMStack site is directly accessing it from your JavaScript. In this form, you simply make a HTTP request to the resource and render it. Here's a quick one pager using Vue.js and the Star Wars API:\n\nYou can view this live at https://jamstackapiapproaches.netlify.com/test1.html.\nNice and simple, right? However it has a few drawbacks.\n\nFirst, it assumes the remote API enables CORS, which allows your domain to directly access its domain. Many APIs allow this, but not all.\nSecondly, it assumes anonymous access. This is actually not the norm as typically an API requires some kind of identifier. Sometimes this isn't a big deal. The API has a generous free tier and is not likely to be abused. But as soon as you put an API key into your code, anyone who can view source can then take that key and use it themselves. Some APIs will let you lock down what domains can use that key, and in that case, you're pretty safe. But you absolutely want to keep that in mind.\nFinally, you are tied to working with data from the API in only the form it provides. That may not sound like a big deal, but what if the API returned a lot of data you don't need? You're putting that burden on the user which means (potentially) a slower web site and a (again, potentially) frustrating experience. This is where GraphQL really shines as it lets you specify exactly what data you need.\n\nAll in all though this is the simplest and quickest way to add dynamic content to your JAMStack.\nOption Two - An API Proxy\nThe second option is pretty similar to the first, with the main difference being that your code hits an API running on your server. The &quot;server&quot; could be just that, an app server running somewhere in house, but typically will be a serverless platform instead. Basically, instead of your code making an HTTP request to some remote domain, it requests your code which then itself requests data from the remote domain.\nConsider this example using the Weather API from HERE. (A cool company I'll be blogging about more later.) Their API requires two specific authentication values, an app_id and app_code. If I put that in my client-side code, anyone could use it, which wouldn't be desirable. I'm going to use a serverless proxy set up with Netlify Functions to proxy requests to HERE's API from my client side code.\n\nIn general this is just some trivial Node code, but I want to point out some specific tweaks I did here. First, HERE's weather API supports returning astronomy data. For my demo I want to know about the moon, so you can see me filtering that out in the map call. This will result in less data going to be my client-side code. Also note that the API has slightly different casing going on. So for moonrise it's all lowercase, but then they use moonPhase. There may be a good reason for that, but to me it wasn't what I expected so I took the opportunity to reformat the data a bit as well.\nOnce this was in place, I could then use it with some more Vue.js code. (To be clear, you don't have to use Vue, but I recommend it. ;)\n\nYou can view this here: https://jamstackapiapproaches.netlify.com/test2.html\nSo, this one is a bit more work, but depending on your app platform, it could be easy. As I said, I used Ne",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Quick Netlify Dev Tip for Complex Static Sites",
		"date":"Mon Jul 15 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1563148800,
		"url":"https://www.raymondcamden.com/2019/07/15/quick-netlify-dev-tip-for-complex-static-sites",
		"content":"Hey everyone, this tip will probably have a very limited audience, but it was a huge deal for me so I wanted to share it with others in case they run into the same issue. In case you don't know it, Netlify Dev is a way to run the Netlify Platform locally. Which means features like redirects, functions, and more will work locally.\nAs a practical example, I use the redirects feature quite a bit as my site has gone through different engines and domains over the years. Being able to quickly test that support locally is awesome.\nHowever, I ran into an interesting issue with my site. This blog has over six thousand entries. A complete build takes a bit over ten minutes. So when I work locally, I use a different config file that modifies the exclude parameter:\nexclude: [_posts/2003,_posts/2004,_posts/2005,_posts/2006,_posts/2007,_posts/2008,_posts/2009,_posts/2010,_posts/2011,_posts/2012,_posts/2013,_posts/2014,_posts/2015,_posts/2016,_posts/2017,_posts/2018/01,_posts/2018/02,_posts/2018/03,_posts/2018/04,node_modules]\n\nI also wrote a quick shell script called start.sh to make using this config easier:\n\nAs a quick aside, the jekyll CLI does support a &quot;only render last N posts&quot; option, but I discovered that after I had used the exclude feature. Like with most things there's more than one way to solve the problem.\nThis different configuration takes my typical build time down to about five seconds which is more than quick enough.\nSweet!\n\nUnfortunately, when I started using Netlify Dev, I noticed immediately that my builds were taking the usual, very long, time. I was ok with it a bit as it let me do testing of my redirects but it was definitely less than ideal.\nSo of course I went over to the forum and posted a question about this.\nAnd since I posted a question, I, of course, discovered how to do it about a minute later.\nTurns out the CLI supports a way to bypass the normal startup command that Dev uses. It's as simple as passing -c and the command you need. This is what I use for my blog:\n\nI modified my start.sh to use that and I'm good to go. Running both Jekyll locally and Netlify Dev.\n\nHeader photo by Sam Truong Dan on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Netlify Analytics - An Initial Look",
		"date":"Fri Jul 12 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1562889600,
		"url":"https://www.raymondcamden.com/2019/07/12/netlify-analytics-an-initial-look",
		"content":"A few days ago (wow, just two, really?) Netlify announced a brand new feature for their platform, Analytics. As you can imagine, this is a feature focused on giving you analytics about your site with the main benefit of being able to skip using a client-side library like Google Analytics.\nI've been a Google Analytics user for over ten years now, and while I like the product, it isn't always the easiest to use. I put that blame on me not taking the time to learn the product more, but I always wished it had a more... friendly or simper interface. In fact, I've done multiple blog posts here on extracting data from GA and rendering simpler views.\nSo when I heard the announcement I was incredibly excited. I've liked pretty much every Netlify feature I've used so far and assumed this would rock as well. Unfortunately, this is not a free service. Now, I say &quot;unfortunately&quot; but to be fair, Netlify gives you a crap ton of really good free features. I deployed a serverless function to a test site last week. For free. And I'll take free tier serverless over analytics any day. That being said, I do still wish they had a free tier. I'd love to use this feature to track smaller &quot;toy&quot; sites, and I just don't see me doing that for sites that may get less than a few hundred page views, if that.\nThe price is definitely reasonable - nine dollars. That price is for sites with less than 250K page views per month so it should cover most folks. For sites with more than that it's... well I don't know. The pricing page says &quot;custom&quot; so it's probably determined on a case by case basis.\nThis is rather important to me as Google Analytics reported that my average page views per month was less than 100K per month. I used to hover around 130K but my traffic has been slowly trending down the last year or so.\nSo imagine my surprise when I enabled Analytics (more on that in a second) and saw numbers significantly higher than that. In fact, right now I apparently have right under 300K page views per month! I expected that my traffic was a bit higher than GA reported due to folks blocking GA and other reasons but holy crap!\nThat being said, while I absolutely love the Analytics feature and had planned on disabling GA and gladly paying nine dollars for it, I've got an email out to support now to see if my price will remain the same despite being a bit over the first tier limit.\nAlright, so what do you get?\nWhen you enable Analytics (which for me required enter my credit card), you immediately get stats. Every stat is marked with a message saying it is incomplete, and as you reload you can see the data get more and more complete. I would estimate that it took about thirty minutes for that process to complete.\nRight now the data is only for one month. I'm not sure what will happen a few months down the line. I'd like the ability to specify a custom date range, or see &quot;last month&quot; versus &quot;last year&quot;, but for now it's the past thirty days and that's it. The stats are absolutely near real time. I'm writing this blog post at 2:28PM CST and I have data for 1 PM. So not up to the minute, but I'm fine with that. The &quot;real time&quot; GA report is pretty neat, especially when you're getting slammed because of something cool you've written, but I've had that happen maybe three times over the lifetime of my blog. (For example, my blog entry on an iOS update back in 2008 got 971 comments.)\nRight now you get seven reports. The first is a top level summary:\n\nNext up is a line graph of pageviews:\n\nAnd then an hourly chart of unique visitors:\n\nNext up is a list of your top pages. This was very interesting to me. One of the things I had noticed was the my current content wasn't nearly as popular (stat wise) as my older content. I was happy to see three entries in my top ten from this year. I'd still like to my more of my recent content, but it's better than I thought.\n\nNote that my top four entries are related to Vue.js. (Woot woot!) Next up is the 404 report:\n\nI've already corrected a few of these. Then there's a source report. I was really surprised by the amount from dzone.\n\nAnd then finally, a bandwidth report:\n\nYou'll notice the lines seem a bit off on that. It goes from 750MB to 1GB to 2GB and the scale seems wrong. That's a minor issue I suppose.\nAnyway, that's it for now. As I said, I think this is a damn good service and makes my stats much easier to deal with than Google Analytics. The price is... fair. Absolutely fair. But I won't be using it on toy/demo sites unless they had a free tier. (I may consider creating a &quot;demo&quot; site with multiple demos under it though.) Obviously this is all brand new and it's only been two days, but I'm sold enough to kill off Google Analytics once I confirm my bill.\nHeader photo by Stephen Dawson on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Replacing a Dynamic Regex Match with the Same Number of Spaces",
		"date":"Fri Jul 05 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1562284800,
		"url":"https://www.raymondcamden.com/2019/07/05/replacing-a-dynamic-regex-match-with-the-same-number-of-spaces",
		"content":"This post is 100% thanks to my friend Peter Cooper. I couldn't find any solutions online (or I may have Googled poorly) so I wanted to write this up in case other folks have the same problem. Imagine you have a string of HTML and you want to remove the tags. An easy solution would be something like this:\n\nThis works perfectly well, but my situation was a bit different. I needed to pass the result of this to a tool that reported on misspellings. When it did, it would report on line numbers and columns. With my initial solution, the string no longer had text in the same spaces as it did before. It was close, but in a large file the differences became worse towards the end.\nSo my question how - given a regex that is dynamic in size (&lt;.*?&gt;), was there a way to replace with space characters of the same length?\nWhen I searched for a solution, my focus was on a regex expression of some sort that could help. Turned out the answer was simple. As Peter pointed out, the replace function lets you specify a substring for the replacement or a function. This function is passed the matched string (along with other arguments) so you can easily check the length and return the right number of spaces. Here's an example:\n\nPeter's solution was actually a bit more concise. I love arrow functions, but when teaching, I still like to show the &quot;old&quot; way first. I still remember when arrow function syntax confused the heck out of me:\n\nYou can test this in the CodePen below.\n\n  See the Pen \n  replace code and html (1) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nAnyway, that's it, and I hope this helps! Also take this as my one millionth time reminding my readers that the MDN Web Docs are the best damn resource on the Internet.\nHeader photo by Florian Olivo on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Random Pictures of Beauty with Azure Functions and the NPS",
		"date":"Mon Jul 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1561939200,
		"url":"https://www.raymondcamden.com/2019/07/01/random-pictures-of-beauty",
		"content":"I'm a sucker for randomness, especially when it leads to something interesting or a new discovery. For example, the Twitter account OnePerfectShot shares stills from movies throughout all of cinematic history.\nBATMAN (1989)Cinematography by Roger PrattDirected by Tim BurtonHere&#39;s a list of weird facts about this movie: https://t.co/xA3EHdSC5r pic.twitter.com/tAjzRHBlLK&mdash; One Perfect Shot (@OnePerfectShot) July 1, 2019\n\nAnother example is Dragon Hoards. This is a bot that posts random microstories. It doesn't always work, but when it does, damn is it fascinating.\nA yellow dragon lives on the shore of an ocean. She inventories her hoard, which consists of a good amount of moons, boxes full of unicorn hair, and way too many pies. She is paranoid.&mdash; Dragon Hoards (@dragonhoards) July 1, 2019 \nAnd then finally, one of my own creation is RandomComicBook. I blogged about this over three years ago and is still one of my favorite things I've created. Here's an example:\n&quot;The Amazing Spider-Man (1963) #12&quot; published May 1964https://t.co/18BRdt7UkS pic.twitter.com/gkJ0d4ygZH&mdash; Random Comic Book (@randomcomicbook) July 1, 2019 \nSo with that in mind, last week I had an inspiration. I discovered that the National Parks System has an API. A whole set of APIs actually but one in particular stood out, the Parks API. According to the docs, this provides:\n\nPark basics data includes location, contact, operating hours, and entrance fee/pass information for each national park At least five photos of each park are also available.\n\nSpecifically the fact that it provides photos for each park. I thought it would be kind of neat to create a bot that picked a random park and a random photo and shared it via Twitter. My logic ended up pretty simple:\n\nThe API lets you search by state, or states, so step one is simply picking a random state.\nThe API lets you get a list of parks with a limit, but in my testing even California had less than 50, so I figured just asking for 100 should cover my basis.\nFrom that list, filter to those with images. Yes the docs said they all do, but I figured it couldn't hurt.\nPick one from that list and select a random image.\nTweet.\n\nAnd that's it. I decided on Azure Functions as I still feel like I need to dig a lot more into it. Netlify does serverless too, but as I didn't plan on having any HTML content for this project, I figured it wasn't a good fit. I used Visual Studio Code extension which worked perfectly fine except for a few hiccups that were (mostly) my fault. Finally, I also tied my deployment to a GitHub repo. This is documented well except for one small bug that took me a while to fix. And by &quot;took me a while&quot; I mean begging Burke Holland for help until he caved in and found my issue. (It's a critical detail missing from the doc. I filed an issue for it so it may be fixed by now. If not, you can see my comment at the bottom with the correction.)\nThe function is all of about 120 lines. I'll share it first than go over the bits.\n\nAlright, so what's going on? The first real bits involve me loading the Twit library, my preferred way of working with the Twitter API. Skipped over the 50 states in JSON (I should convert that to one long line), the main function starts off by selecting the state. My data includes the abbreviation and full name because I thought I might end up using both, but that didn't pan out. I could optimize that later.\nI then hit the API with my state, filter the results to those with images, and select one by random. With that park, I then select my image. Posting media to Twitter requires you to upload it first which is a bit of a hassle, but async/await makes everything a bit nicer. Twit uses a callback style API so I wrap it in a promise so I can await it. Once I have my media uploaded I can then reference it in a tweet. The text I use is a bit minimal and could be improved a bit I think. I'm open to any suggestions. And that's it. You can see the results at https://twitter.com/npsbot. Here's a few examples.\nPicture from Augusta Canal National Heritage Area. More information at https://t.co/8jO0mzgyBT pic.twitter.com/AJiEee2gvc&mdash; npsbot (@npsbot) July 1, 2019 \nPicture from Weir Farm National Historic Site. More information at https://t.co/GI1GilXcO3 pic.twitter.com/PlmQfsiNsO&mdash; npsbot (@npsbot) July 1, 2019 \nIf you want, you can view the entire code base here: https://github.com/cfjedimaster/npsbot. I will keep the bot running for as long as Azure doesn't charge me. :)\nEdit - one quick note I forgot to mention. The last thing I had to do was switch the function from a HTTP driven one to a scheduled one. I thought that would be simple. The file, function.json, determines the types of connections your functions can use (along with other settings), but the docs for scheduled tasks always showed a subset of the function.json file, not the whole thing. My first attempt to add the right values broke the function because I discovered you ",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "My Podcast on Views with Vue",
		"date":"Fri Jun 28 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1561680000,
		"url":"https://www.raymondcamden.com/2019/06/28/my-podcast-on-views-with-vue",
		"content":"Pardon what will be an incredibly short blog post, but as I don't want to assume all of my readers use Twitter and may have missed it, I recently did a podcast with the Views on Vue folks. You can find my episode here:\nhttps://devchat.tv/views-on-vue/vov-066-nativescript-with-raymond-camden/\nAs a reminder, I'm happy to speak on any podcast out there - just send me an invite anytime!\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Components FTW - vue-country-flag",
		"date":"Fri Jun 21 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1561075200,
		"url":"https://www.raymondcamden.com/2019/06/21/vue-components-ftw-vue-country-flag",
		"content":"Sorry folks - it's been too long since I did one of these &quot;Vue Component&quot; reviews. What can I say, life happens! That being said, I hope these entries are interesting to folks. You can browse the older ones on the tag page and send me suggestions for ones you would like me to review. Today's example is so simple I almost passed on reviewing it, but I ran into an interesting issue that made me think it was worth my (and your) time.\nFirst off, the component in question in today's entry is vue-country-flag.\n\nAs you can imagine, this component will render the flag for a country. Like so:\n\nAnd that's it. Oh, it does support sizes too, from small to normal to big. But yeah, pretty simple. However, while working on a demo in Code Sandbox I ran into an interesting issue. While the component loaded fine and no errors were reported in the console, the flag icon simply didn't render.\nOn a whim, I exported the project. Code Sandbox makes this easy and sends you a zip. Don't forget to run npm i after you've extracted the folder. Only an idiot would do that. I did that.  Anyway, as soon as I ran the demo on my local machine, the component worked fine.\nMy guess is that it's something wrong with Code Sandbox, but as it may be an issue with the component, I filed an issue just to be safe.\nThat being said my take away is ... as cool as Code Sandbox is if you run into an odd issue like this, simply try running it locally to see if it helps.\nOk, so how do the flags look? I started with this demo:\n\nAnd here's how it rendered:\n\nOk, not terribly exciting. In order to make it a bit more real world, I whipped up some JSON data representing a list of cats:\n\nI hosted this up on jsonbin.io, a free service for hosting JSON data. It's a cool service, but note that if you write some code and decide to log in after you've written your first thing, you'll lose that data after logging in. Oops. Anyway, here is an updated component showing hitting the API and rendering the results:\n\nBasically - loop over each cat and render the values, but pass the country value to the component. And the result:\n\nAnd that's all. Again, let me know if you find these useful, if you have suggestions, or any other feedback!\nHeader photo by Liam Desic on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "vue components ftw"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Two Tips for NativeScript and Vue Development on the Playground",
		"date":"Wed Jun 19 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1560902400,
		"url":"https://www.raymondcamden.com/2019/06/19/two-tips-for-nativescript-and-vue-development-on-the-playground",
		"content":"Alright folks, as the title says, here's two tips to keep in mind when using the NativeScript Playground. One will be kind of ranty/angry (sorry!) and one will, I hope, really save you sometime in the future. Let's get the angry one out of the way at first!\nSave, then Save, then Save Again\nOne of the things I quickly discovered about the Playground is that it's possible to &quot;lose&quot; your projects if you don't save correctly. I filed an issue on this back in March when I first encountered it. Recently though I ran into a new version of this that really, really ticked me off. The bug works like this:\n\nWork on a project while not logged in\nSave the project\nRealize you didn't log in first (oops!) and log in\nNotice that the Save UI is disabled because you just saved it.\nClose tab\n\nGuess what? The project was saved, but it wasn't associated with your user. You just lost your project. The simplest solution is to just ensure you always login first. If you forget though, be sure to change something about the project to re-enable the Save UI and then save it again. I'd recommend going into the code and adding this:\n\nHeh, I did say I was a bit angry, right? To be clear, this doesn't stop me from loving the Playground. I used it for an article I just wrapped up and it was perfect for it. I just hope they (Progress) can address the issue soon.\nErrors and Damn Errors\nThis one really drove me batty for a while and I can't blame anyone but myself. The article I mentioned above concerns Vue, NativeScript, and navigation. I think you're going to love it when it comes out. Truly, it will change your life. But while working on it I ran into a weird issue. My demo had two pages. The first page linked to the second via the manual routing API.\nThe API is super simple to use. But when I'd click to start the navigation, nothing would happen. I didn't get an error anywhere it just didn't... well navigate.\nI was basically stuck when I noticed this in the logs:\nNativeScript-Vue has &quot;Vue.config.silent&quot; set to true, to see output logs set it to false.\nMy first thought was that changing this wouldn't help. I didn't have an error it just didn't do squat. But I figured it couldn't hurt so I went ahead and uncommented this line:\n\nAnd... voila:\n[Pixel 3 XL]: [Vue warn]: Unknown custom element:  - did you register the component correctly? For recursive components, make sure to provide the &quot;name&quot; option.\nStackView? What the hell is StackView? Oh yeah, it was this:\n\nAnd guess what? That's supposed to be StackLayout. So why didn't I get an error? Honestly I don't know. As the message above states, it would be possible for me to define my own component called StackView and that would be valid. However the fact that navigation failed seems like more than a warning to me.\nThat being said, I'm going to (hopefully) remember to try changing the logging value in the future if I encounter weird errors like that. My &quot;regular&quot; errors show up just fine so I won't change it by default, but I'm definitely going try this first next time.\nHeader photo by Aaron Burden on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Reading Image Sizes and Dimensions with Vue.js",
		"date":"Thu Jun 13 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1560384000,
		"url":"https://www.raymondcamden.com/2019/06/13/reading-image-sizes-and-dimensions-with-vuejs",
		"content":"A few weeks back, I wrote up (&quot;Reading Client-Side Files for Validation with Vue.js&quot;) an example of using JavaScript to check files selected in an input field to perform basic validation. It uses the fact that once a user has selected a file, your code has read access to the file itself. I was thinking about this more earlier this week and thought of another useful example of this - validating a selected image both for file size as well as dimensions (height and width).\nThe code in this entry is heavily based on my previous example so be sure to read that blog entry first.\nLet's begin by addressing the two main requirements - getting the size of the file and image dimensions.\nFile size is easy. Once you've selected a file, it's available in the size property of the file object. There's other properties available as well, like the last time it was modified, and you check the File docs at MDN for more information.\nGetting dimensions is also pretty easy, as long as your careful. You can use JavaScript to make a new image object and assign the source:\n\nAt that point you can immediately check img.height and img.width, but you will find that you sometimes get 0 for both results. Why? The image hasn't loaded yet! Luckily this is easily fixable:\n\nOk, so given that, let's begin with a simple example that just displays the information. First, the layout:\n\nThe second div tag shows up conditionally and you can see I'm displaying all three properties we care about. Note I've added an accept=&quot;image/*&quot; to the input field. This will help direct the users towards images.\nHere's the code and note I'm going to focus on what's different from the previous example.\n\nFirst off, the size value is trivial - we just copy it from the file object.  We read the file using readAsDataURL, which is different from the previous example. This will return a URL encoded with a base64 version of the image data. Once we have that, we can assign it to a new Image, wait for onload, and then get the dimensions. You can see this yourself below:\n\n  See the Pen \n  vue file image thing by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nNow that you've seen the basics, let's consider an example using validation. We'll specify a max size in bytes, a max width, and a max height. Here's the updated HTML:\n\nThe only real change here is an optional div shown when an error is thrown. Now let's look at the JavaScript.\n\nFor the most part this is pretty similar to the last example, except now we've got checks for the size, width, and height. Note that my code will only throw one error, so for example if both the width and height are too big, you'll only see the first error, but that can be changed rather easily too. Here's the code in action:\n\n  See the Pen \n  vue file image thing (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nHeader photo by Clem Onojeghuo on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using NativeScript and Vue.js to Turn Your Friends into Cats",
		"date":"Tue Jun 11 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1560211200,
		"url":"https://www.raymondcamden.com/2019/06/11/using-nativescript-and-vuejs-to-turn-your-friends-into-cats",
		"content":"An incredibly long time ago, OK, way back in 2016, I wrote up an experiment concerning Ionic and the Contacts API wrapper (&quot;Working with Ionic Native - Contact Fixer&quot;). The idea was simple. Given that you have a large set of contacts on your device, what if there was a simple way to add pictures to those contacts without one? And if we're going to add pictures to contacts, surely the best picture possible would be of a cat, right?\n\n\nWouldn't you love to get a call from this guy?\n\nAs an excuse to work with NativeScript more, I thought it would be fun to rebuild this and of course, take the opportunity to use NativeScript Vue. The end application is incredibly simple so it isn't that exciting, but the end results are kind of neat. There is, of course, a plugin for working with contacts and NativeScript, NativeScript Contacts. The plugin worked well for me in general, but there is an open issue with updating existing contacts. My code seemed to work despite this issue, but you'll notice a try/catch around the operation. I also made use of nativescript-permissions to handle Android specific permission stuff. This was easy too, but I almost wish it was baked into NativeScript as it feels like something you'll always need.\nLet's begin by taking a look at the code. It's a &quot;one view&quot; app so I've only got one component to share, and as I said, it's almost stupid simple so I'm not sure how useful it is. I'll start with the layout first.\n\nAt the top, you can see a label bound to a status value that I'll be using to report on, well, status of the application. Loading contacts on my device took about 4 or so seconds so I needed a message to let the user know what was going on.\nBeneath that I've got a button and a list of contacts. The button is what you will use to &quot;fix&quot; your contacts and notice it only shows up when we're ready to update them. The contacts are displayed in a ListView using a GridLayout to show their names and pictures (which will be blank at first).\nNow let's look at the code.\n\nUp top we've got the required libraries being loaded in and beneath that, two helper functions. getRandomInt does exactly that, a random number between two values, and getRandomCatURL is how I handle generating a new cat photo for contacts. It uses the (newly resurrected) placekitten.com image placeholder service. In this case we're simply generating random dimensions between 200 and 500 pixels wide.\nBeneath that comes the Vue specific code. My created method handles loading all contacts, but note that we filter both by contacts with pictures already and those that don't have a name of some sort. The end result is an array of contacts that could be fixed. They are saved to the contacts value and then rendered out in the ListView.\nFinally, you can see the fixContacts method that handles getting those random cat pictures. I make use of imageSource.fromUrl to load in an image from a URL. This returns a promise so I use Promise.all to then assign those results to my contacts. (In case you're curious, you can use Async/Await in NativeScript, Alex Ziskind has an article here discussing it, but as it involves a small workaround, I decided to avoid it for today.)\nAnd that's it. Let's look at the result! First, here are the contacts on my virtual Android device before running the app. Notice the boring icons by their names:\n\nWhen I run the app, it will load all of my contacts as none of them have a photo. Notice the bug in the last row:\n\nThis comes from the Vue filter I used to display names. The basic idea was, look for a nickname, and if it isn't there, use first and last name:\n\nUnfortunately, the Discord contact didn't have a first name. Discord is just Discord.\n\nHowever, I thought &quot;null Discord&quot; sounded like a cool name anyway so I kept the bug in. Or I'm lazy. You pick.\nAfter clicking the button, each contact was assigned a random cat URL which was automatically updated in the ListView:\n\nAnd what's cool is you see this right away. I went back to my Contacts app on the virtual device and saw great results. First the list of all contacts:\n\nAnd here's two examples:\n\n\nAnd that's it. Stupid fun, but nice to build. If anyone wants the complete source code, just ask!\nHeader photo by Q'AILA on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using the MailChimp API with Netlify Serverless Functions",
		"date":"Wed May 29 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1559088000,
		"url":"https://www.raymondcamden.com/2019/05/29/using-the-mailchimp-api-with-netlify-serverless-functions",
		"content":"I first wrote about Netlify's new Functions feature way back in January of this year (&quot;Adding Serverless Functions to Your Netlify Static Site:). Since that time, Netlify has had multiple updates to their platform with the most important (imo) being Netlify Dev. I'm probably being a bit dramatic, but Netlify Dev is an absolute game changer for me when it comes to their platform. I'll explain how but first let me start off by describing what I wanted to build.\nIn that earlier blog post, I described how I used a serverless function to get a list of issues for the CodaBreaker newsletter I run with my buddy Brian. I actually ended up removing that function and using a build script instead, but I was able to reuse 99% of my code so it was still a good learning experience.\nI wanted to add a new serverless function that would handle adding subscribers to the newsletter and keep them on the site. MailChimp's signup form wasn't bad, but if I could do it all on my side, why not? MailChimp's APIs support this quite easily, and not only that, support adding an email address and not caring if they already existed or not. You just do a PUT request to https://us6.api.mailchimp.com/3.0/lists/LISTID/members/ where LISTID is the ID of your list.\nOne of the frustrations I had with the first function I created was the build process. I would write my code, commit to GitHub, quickly ask Netlify to rebuild (it would automatically, but I was impatient), test, curse, and repeat the process.\nNetlify Dev changes all of that. It allows you to run the Netlify platform, completely, on your local machine. On the simpler side, it lets you do things like test the redirects feature locally. I use that heavily as I've got a crap ton of content and have migrated my blog multiple times. On the more complex side, it makes testing functions a heck of a lot easier.\nI began by using the CLI to scaffold the function:\nnetlify functions:create\nThis prompts you to select from one of like 100 or so functions (ok, not 100, it is a lot and I think they may want to trim it a bit) but I just chose a simple hello-world template. Once done, I started coding, and damnit, it just plain worked. If you follow me on Twitter you know I ran into some hiccups, but they were all my fault. The only issue I came across that I couldn't correct was that the environment variable I had set in the Netlify dashboard for the site wasn't transferred down to the code. (You can track this issue at my forum post). I whipped up the following bit of code. It isn't the best code, but it's mine and I love it.\n\nI then wrote up some simple Vue code to hit against my function (the endpoint is at /.netlify/functions/newsletter-signup) and that was it, you can see it live on the site now: https://codabreaker.rocks/\nSo when I started this blog post, I imagined it being a bit more detailed. To be honest, it just worked. It was simple. (Again, ignoring issues that were mostly my own fault.) I'm truly shocked and how well this platform is working!\nHeader photo by Charles PH on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "FusionReactor and Your ColdFusion Queries",
		"date":"Sat May 25 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1558742400,
		"url":"https://www.raymondcamden.com/2019/05/25/fusionreactor-and-your-coldfusion-queries",
		"content":"In my last article on FusionReactor, I talked about slow pages and how the tool helps you find them. In that article I specifically avoided talking about one of the biggest culprits of slow pages - database queries. My history with ColdFusion goes back to about version 2 and even back then database queries were the primary culprit in poorly performing applications.\nThere's multiple reasons why database queries can be a choke point for your application:\n\nA poor connection to the database.\nA poorly configurated database.\nA poorly configurated table.\nA poorly written query.\nAnd so forth.\n\nIn an ideal world, your organization has a DBA (database administrator) who tunes the database and tables and then crafts a beautiful SQL (or stored procedure) you can simply drop into your application. Unfortunately very few of us live in that ideal world. It's also very easy to simply ignore the problem. SQL, like any language, let's you get stuff done quickly and it can be easy to not consider the performance aspects of your query. Like any poorly written piece of code, a &quot;slightly bad&quot; query in a request can then be joined by another slightly bad one and slowly compound into a poorly performing page.\nBeing that database activity is such an important part of performance, it's no surprise FusionReactor has specific reporting tools focused on just that area. In this post I'm going to share what that looks like and share some examples of the kind of reports you can find.\nJDBC Reports\nIn my last post, I explained that JDBC stands for Java Database Connectivity. Any time you use a cfquery tag (or queryExecute function), you're making use of JDBC to allow your ColdFusion templates to speak to a database. Within FusionReactor, you'll want to start with the JDBC icon on the left:\n\nUnder here you've got a variety of options:\n\nJDBC Activity - i.e. what's going on right now.\nJDBC History - historical activity.\nJDBC Error History - past query errors.\nActivity Graph and Time Graph - a &quot;live&quot; graphical report of JDBC activity.\nLongest Transactions and Slowest Transaction - to be honest this was a bit confusing. Wouldn't the longest transaction also be the slowest transaction. The &quot;Longest&quot; report will show the transactions that have taken the longest to execute, no matter how long. The &quot;Slowest&quot; report is a report of all transactions over a particular threshold. So it may be possible that you have nothing there as your queries are performant, but the &quot;Longest&quot; report will still rank them for you.\nTrans. By Mem - reports on queries with high memory usage.\nStack Trace Filter - it's possible that multiple different things are making use of your database. The stack trace filter lets you reduce the amount of &quot;noise&quot; you may get from other applications. By default there's filters set up for .cfm, .cfc, and .jsp.\nDatabases - this gives a really cool report on how you're using your databases. I'll be sharing an example of this later.\nSettings - this is where you can configure how FusionReactor logs and monitors your database transactions\n\nExamining Database Requests\nLet's take a look at how FusionReactor reports your database requests. First we'll open the &quot;JDBC History&quot; page. Remember that the first option shows a &quot;live&quot; version and unless your site is actively getting hits, you won't see anything.\n\nAs with the previous examples I've shown from FusionReactor, take note of the controls on the top right allow for filtering, reloading, and so forth. What isn't obvious from the screen shot is that the &quot;All SubFlavors&quot; button actually lets you filter by the type of query, select, insert, and so forth. That's pretty neat.\nThe main table of data reports on the app that was being used (I'm just working in my default Lucee home directory) and the SQL that was used. You can see the file name as well as timing information. Note the Time column which shows you how long the particular query took.\nNotice how the SQL is reported as well. One of the features of FusionReactor is to automatically replace queryparam values with their 'real' values when reporting on the query. You can enable or disable this feature under the &quot;JDBC/Settings&quot; page. While this is a cool feature, it means it's difficult to see where you've forgotten to use queryparams. I've reported to the FusionReactor folks that it would be nice if it was obvious when such a replacement has happened, maybe by using bold tags or some such. That way if you a query is not using queryparams it will be easier to find and correct.\nThe detail view is very deep. Here's the main tab of information:\n\nThere is almost an overwhelming amount of information here, but I'd probably focus mostly on the execution time values under JDBC and the memory section. Here's the JDBC tab:\n\nAs before, there's a lot of information, but I'd focus in on the row count. If you've ever seen someone select everything from a table and ",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Reading Client-Side Files for Validation with Vue.js",
		"date":"Tue May 21 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1558396800,
		"url":"https://www.raymondcamden.com/2019/05/21/reading-client-side-files-for-validation-with-vuejs",
		"content":"Folks new to web development may not know that form inputs using the file type are read only. For good reason of course. You wouldn't want nefarious (I've been waiting a while to use that word) JavaScript programs setting the value of the field and doing uploads behind the scenes - it would be a great way to steal information off your computer. However, just because the field is read only doesn't mean we can't do cool stuff with it. In fact, once a user has select a file (or files, remember the multiple attribute!), you can not only see the file type, name, and size, you can read it as well. This offers you some interesting possibilities.\nLet's pretend you've got a form for a mailing service. You want to seed a list of recipients with a set of email addresses. You could allow the user to select a text file from their machine and upload it. Before they do so, however, you could pre-emptively check the file and display the names to the end user.\nAnother option would be a form that allows for uploads of JSON-formatted data. Before that file is sent to the server, you could read it, check for valid JSON data, and then potentially render out the information. You could also do other checks, so for example, maybe you require your JSON data to be an array of objects with keys name and gender being required while age is optional.\nAs always, you need to have server side validation for anything your users send, but being able to pre-emptively check files and provide feedback to the user could save them a lot of time. I thought I'd share a few examples of this using Vue.js, but of course, you could this with any (or no) framework at all.\nReading a File\nFor the first example, let's just consider a super simple example where we -\n\nnote when a file is selected in the input field\ncheck to see if it's a text file\nread in the contents and display it\n\nFor my HTML, I keep it nice and simple:\n\nThis is pretty standard Vue stuff, but note the ref usage. This is how we'll read the value later.\nNow the JavaScript:\n\nSo the main action here is the selectedFile method. This is run whenever the input field fires a change event. I use this.$refs.myFile to refer to the original DOM element I had used and to read the value. Notice that this is an array so I grab the first value only to keep things simple. In theory the end user could use dev tools to add multiple and then select multiple files, but I won't have to worry about that.\nNext, I use the FileReader API to read in the file. This is asynchronous and you can see two handlers to respond to the onload and onerror events. For onload, I simply pass the value to this.text which will render in the textarea. You can see this in action in the CodePen below.\n\n  See the Pen \n  vue file 1 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nA List of Names\nImagine you've been wronged. Badly wronged. And you've got a list of names. People who have it coming to them. Just as an example, let's call you Arya.\n\nTo help process this list of names, let's build some code that will read in a text file of names, report on the total length, and show the top ten. The list may be incredibly huge but by showing a small subset, the user can quickly determine if the file was correctly setup, or lord forbid, they selected the wrong file. Here's a simple example of this in action.\n\nThe top portion prompts for the file and uses similar attributes to the first example. Next I've got the display. I print out how many names were in the file and then iterate over a names value. This is going to be a virtual property of just the first ten values. (By the way, I don't like using allNames.length. While I appreciate Vue lets me do a bit of logic in my HTML, I would have preferred to use a simple boolean instead for the v-if and another value for the length.)\nAlright, so here's the JavaScript:\n\nIn general, the only interesting bits are in the reader.onload event. I'm still checking the file type, but now when I read it in split it on newlines and remove the file value is blank. This will set the allNames value. The names value is in the computed block and only consists of the first ten values. You can play with this below - just make your own list of names. Please do not include my name on it.\n\n  See the Pen \n  vue file 2 by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe Cat File\n\nLook, it's a cat file. Get it? Sorry, I've been waiting a long time to use that gif. So in this scenario I'm going to demonstrate an example that parses a JSON file. It will first check to see if the file contains JSON text, and then if so render the results. Unlike the previous example I'm just going to render every row of data. The data will be an array of cat. Did you know a group of cats is called an Awesome? It is - I read it on wikipedia.\nHere's the layout:\n\nI'm using a table to render the cats and yeah that's it. Here's the JavaScript:\n\nThe important bits here are how I test for valid JSON, a simple try/catch around JSON.parse. And tha",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using HTML Form Validation without a Form (Kinda)",
		"date":"Wed May 15 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1557878400,
		"url":"https://www.raymondcamden.com/2019/05/15/using-html-form-validation-without-a-form-kinda",
		"content":"This will be a quick one. I've been a huge fan of HTML-based form validation for some time now. Even though it is far from perfect (and must always be coupled with server-side validation), I love the fact that it can catch errors early in the submission process and create a better experience for users. My first experience with server-side programming was writing Perl scripts to handle forms so anything that improves the process is pretty freaking important to me.\nWhile thinking about another demo I wanted to write (and I sure as hell hope I wrote it down in Trello because I'm drawing a blank on it now) I realized that I'd need to validate some email addresses. While I was fine with a &quot;not perfect&quot; solution, I was curious if there was some way to tie into the browser's email validation when using:\n\nBasically, I wanted the exact same validation as the field provides, but without using user input and a real form. Turns out you can, and it's rather easy, but you still have to use a form.\nFirst, I added a field, and then hid it with CSS, because CSS is awesome like that:\n\nI then create a set of data. This is hard coded, but imagine it comes from some other process.\n\nThen to test these values, I just got a reference to the field, set the value, and ran checkValiditity on it:\n\nAccording to MDN, checkValidity does this: &quot;Returns true if the element's value has no validity problems; false otherwise. If the element is invalid, this method also causes an invalid event at the element.&quot;\nAnd here is the result, modified to write out results to a div tag:\n\n  See the Pen \n  js check field by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nTo be clear, this is not meant to be perfect email validation. Every time I blog about anything related to the topic, folks point out the 500 edge cases that break it. Again, I'm just looking for something to do more of a &quot;soft&quot; validation on the input. And as I said, I was curious if I could &quot;chain&quot; into the HTML logic without using a real (visible) form. Has anyone used anything like this in production? Let me know in a comment please!\nRound Two!\nI wrote this blog post last night, but didn't actually promote it online. I was planning on doing that today. But after I posted, all around smart guy Šime Vidas posted a great tip in the comments below. I keep forgetting you can create HTML elements in JavaScript. He modified my code such that there is no HTML form field and no CSS required and you simply create the field in JavaScript like so:\n\nHere's his CodePen:\n\n  See the Pen \n  js check field by Šime Vidas (@simevidas)\n  on CodePen.\n\n\nThanks Šime!\nHeader photo by Klaas on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Plex Server Duration Search with Vue.js",
		"date":"Tue May 14 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1557792000,
		"url":"https://www.raymondcamden.com/2019/05/14/building-a-plex-server-duration-search-with-vuejs",
		"content":"A few days ago a good friend asked me a question about Plex. If you've never heard of it, Plex is an awesome media server that creates an easy to use UI for media (videos, music files, pictures, etc.). It's very popular and you can use it via the web, mobile devices, and smart TVs. It's relatively easy to use and you can share your (legally acquired of course) content with friends. My friend mentioned that it would be cool if Plex had a way to find a movie of a particular length. In this case, dinner was in an hour or so and it would be neat to find something of that particular length. Plex lets you sort by duration but you can't filter to a particular length (or range of lengths).\nSo of course I took this simple request and spent numerous hours building a demo that was way overengineered but fun to build. It also gave me an opportunity to play with a &quot;proper&quot; Vue.js application. If you've read this blog you'll note that 99% of what I build with Vue.js is on the simpler side, just a script tag and some basic code. I rarely actually play with full Vue.js apps and I really wanted the chance to. I also wanted to use CodeSandbox more, and that worked incredibly well for this project. When I finished, I clicked a button, and my site was published to Netlify in about two minutes.\nLet me begin by showing the final result. I don't plan on sharing the URL, but you can view the repository here: https://github.com/cfjedimaster/plex-movie-duration-search/\nThe application begins with a simple signin form:\n\nAfter a successful login, you then enter your server address.\n\nAt this point, the application will hit your server, load information on all your movies, and present them with a UI control on top to allow filtering to a range of movies.\n\nIt isn't terribly obvious because the movie posters are big, but that's a scrollable list of all the movies available on the server. If you filter, the list automatically updates.\n\nAlright, so let's talk about how I built this.\nThe Plex &quot;API&quot;\nSo this was a bit interesting. Plex does have an API documented here: Plex Media Server URL Commands. Notice they call this &quot;URL Commands&quot; and not an API. It begins by documenting how to get an authentication token. This is a simple POST hit to the main Plex server that returns a large set of user data where the only thing you'll need to care about is the authentication_token.\nAfter that, the remaining API calls go against your own server. API calls allow for getting your libraries, listing library content, and getting specifics for an item. You can also request Plex to scan and refresh a library.\nBut wait - there's more. You can find a wiki page documenting even more api &quot;stuff&quot; you can do, including asking for JSON data, that doesn't seem to have ever been officially documented by the Plex folks. For me all I cared about was getting JSON, but you'll want to check that link as well for more information.\nMy needs ended up boiling down to two needs:\n\nLogin\nGet all libraries, and filter by those that are movie related.\nFor each movie library, ask for all the movies.\n\nThis isn't too difficult honestly. Let's look at the API wrapper I built for my Plex calls. Note that Plex does not support CORS. I could have built a serverless proxy for it, but decided to just use http://cors-anywhere.herokuapp.com/. This is not something I'd recommend in production but it worked for the demo. In the code below, you'll notice two methods hit URLs prefixed with the wrapper.\n\nThe login call isn't too complex, just a post, but do note that they are strict on the header requirements. They don't seem to care what you pass, but you must pass something there.\nFor getMovies, I first ask for all the libraries. I filter them by type being equal to movie. Once I have that, I can then make a request to each library for the assets and copy them all to an array. Note that in the loop I set two values to make things easier in the rest of my Vue code, poster and duration. This is just a shortcut for - as I said - simplification.\nI'm still &quot;guessing&quot; my way through async and await but my God do I love them.\nThe Vue.js Application\nI've already shared screenshots above, but how does the Vue application break down into parts? I've got:\n\nA login screen\nA &quot;set server&quot; screen\nAnd a &quot;show an filter movies&quot; screen.\n\nLet's tackle these one by one. Note that I'm making use of Vuetify for my UI layer. I like it, but sometimes the &quot;layout&quot; parts confuse me. UI widgets for the most part are easy to understand, but the grid/layout system still boggles me a bit. Anyway, the login screen:\n\nThe layout consists of a login form with an alert dialog that shows up on error. The one method, login, does exactly that. Note I'm using an incredibly simple Vuex store to remember values. Now let's move on the set server screen:\n\nThis is virtually a repeat of the previous screen except this time I'm just asking for one prompt, the se",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Handling Errors in Vue.js",
		"date":"Wed May 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1556668800,
		"url":"https://www.raymondcamden.com/2019/05/01/handling-errors-in-vuejs",
		"content":"I've been spending the last year working with, writing about, and presenting on my favorite framework, Vue.js, and realized that I had yet to look into error handling with Vue. I'd like to say that's because I write perfect code, but I think we all know the truth of that. I spent some time the last few days playing around with various error handling techniques provided by Vue and thought I'd share my findings. Obviously this won't cover every scenario out there, but I hope it helps!\nThe Errors!\nIn order to test out the various error handling techniques, I decided to use three different kinds of errors (initially anyway). The first was simply referring to a variable that doesn't exist:\n\nThis example will not display an error to the user but will have a [Vue warn] message in the console.\n\nYou can view this example here:\n\n  See the Pen \n  Error1A by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFor a second example, I tried a variable bound to a computed property that would throw an error:\n\nThis throws both a [Vue warn] and a regular error in the console and doesn't show anything to the user.\n\nHere's an embed for this.\n\n  See the Pen \n  Error1B by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nFor my third error, I used a method that would throw an error when executed.\n\nLike the last one, this error will be thrown twice in the console, one warning and one proper error. Unlike last time, the error is only thrown when you actually click the button.\n\nAnd here's the embed for this one:\n\n  See the Pen \n  Error1C by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nOk, before we go on, I just want to be clear that this isn't representative of every type of error you can create, it's just a baseline of a few that I think would be common in Vue.js applications.\nOk, so how do you handle errors in Vue applications? I have to say I was a bit surprised that the main Vue Guide did not have a clearly defined section on error handling.\n\nYes, there is one in the guide, but the text is short enough to fit in a quote:\n\nIf a runtime error occurs during a component's render, it will be passed to the global Vue.config.errorHandler config function if it has been set. It might be a good idea to leverage this hook together with an error-tracking service like Sentry, which provides an official integration for Vue.\n\nIn my opinion, this topic should really be called out a bit more in the docs. (And frankly that's on me to see if I can help the docs!) In general, error handling in Vue comes down to these techniques:\n\nerrorHandler\nwarnHandler\nrenderError\nerrorCaptured\nwindow.onerror (not a Vue-specific technique)\n\nLet's dig in.\nError Handling Technique One: errorHandler\nThe first technique we'll look at is errorHandler. As you can probably guess, this is a generic error handler for Vue.js applications. You assign it like so:\n\nIn the function declaration above, err is the actual error object, info is a Vue specific error string, and vm is the actual Vue application. Remember that you can have multiple Vue applications running on one web page at a time. This error handler would apply to all of them. Consider this simple example:\n\nFor the first error, this does nothing. If you remember, it generating a warning, not an error.\nFor the second error, it handles the error and reports:\nError: ReferenceError: x is not defined\nInfo: render\nFinally, the third example gives this result:\nError: ReferenceError: x is not defined\nInfo: v-on handler\nNote how the info in the two previous examples is pretty helpful. Now let's check the next technique.\nError Handling Technique Two: warnHandler\nThe warnHandler handles - wait for it - Vue warnings. Do note though that this handler is ignored during production. The method handler is slightly different as well:\n\nBoth msg and vm should be self-explanatory, but trace would be the component tree. Consider this example:\n\nThe first error example now has a handler for it's warning and returns:\nWarn: Property or method 'name' is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.\nTrace:\n(found in )\nThe second and third examples do not change. You can view embeds for all three below:\n\n  See the Pen \n  Error1A with Handler by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n\n  See the Pen \n  Error1B with Handler by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\n\n\n  See the Pen \n  Error1C with Handler by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nError Handling Technique Three: renderError\nThe third method I'll demonstrate is renderError. Unlike the previous two, this technique is component specific and not global. Also, like warnHandler, this is disabled in production.\nTo use, you add it to your component/app. This example is modified from a sample in the docs.\n\nIf used in the first error example, it does noth",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Audio in NativeScript - Part Two",
		"date":"Tue Apr 30 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1556582400,
		"url":"https://www.raymondcamden.com/2019/04/30/working-with-audio-in-nativescript-part-two",
		"content":"A few days ago I blogged about my experience working with audio and NativeScript (Working with Audio in NativeScript - Part One). Over the weekend I got a chance to wrap up the demo application I was working on and thought I'd share the result. As always, my work ends up being a mix of stuff I think went well and stuff I think... could be done better. I'll do my best to call out the code I think could be improved and would love any suggestions or comments.\nAs a reminder, the end goal of this research was to create a custom sound board application. As a user, you would record a sound to play back later. Sound boards are a dime a dozen on app stores but I always thought it would be fun to have one I could setup myself. Normally I think this when one of my kids say something funny and then they never say it again. Because of course.\nHere's how the application ended up looking. The UI/UX is rather simple and I didn't bother with nice colors or anything. Initially, the application lets you know you don't have any sounds ready:\n\nClicking the &quot;Record New Sounds&quot; (really should be singular) button takes you to the next view:\n\nIn this view you name and record your sound. The play button lets you test to ensure you got it right.\nOnce you've recorded a few sounds, they show up in a simple list on the main page:\n\nFinally, to delete a sound (and the UI should provide a hint for this), you &quot;long press&quot; on a sound:\n\nAnd that's it. So really the app came down to two core aspects:\nFirst was recording audio to the file system and playing it later. This was really easy and covered in my last post so I won't go deep into it here.\nThe next aspect was remembering your sounds. This one was a bit trickier. I wanted to let you assign names to each sound and hide the actual file names from the user. NativeScript supports some nice client-side storage methods (you can read my article on it!) so the only question was which would I use. In this case, I made a decision I think may not be best. I went with ApplicationSettings. In general my &quot;rule&quot; for picking between a simple key/value system and a &quot;proper&quot; storage system is - will my data grow based on the user's use of the application? What I mean is - a set of &quot;know&quot; values like, &quot;preferred theme&quot; or &quot;last product viewed&quot; is a particular set of data that doesn't grow over time. Data like notes, where the user can write a million a day or so, have no limit.\nIn my case, it is possible for the user to create a million sounds (ok, not a million) but I figured reasonably they wouldn't make more than thirty. I was only storing a name and a file path so I figure a JSON array of that size would be &quot;ok&quot;.\nI reserve the right to call myself stupid for this decision later on. Ok, let's look at the code! You can find the complete repository at https://github.com/cfjedimaster/ns-soundboard.\nI'll start with the home view, trimmed a bit to remove unnecessary things:\n\nThe UI is pretty minimal so there isn't much to discuss there. I did, however, have some issues with the longPress event. It did not work well on the ListView directive. I had to move to the label. Another issue is that longPress also fires itemTap, which I think is a bug, but honestly felt ok about this for now. It's something I think I may want to address later. Code wise there's only a few methods and in general the only real complex one is the delete handler. You can see I set up a confirmation dialog. I then manually delete the file and ask my soundsAPI library (more on that in a minute) to remove the file. Those two lines feel wrong to me. Mainly the issue is that soundsAPI handles just remembering the data but doesn't handle any file IO.\nAs I said - it feels wrong and could do with a refactor, but for an initial release, I'm ok with it. ;) Let's switch gears and look at that sound library. You'll note I named it soundsAPI which feels like a bad name, but names are hard.\n\nIn the end, this &quot;API&quot; is just a wrapper for one value in ApplicationSettings. What's nice though is that I can revisit the storage later and keep the API as is. Now let's look at the second view.\n\nAlrighty, this one's a bit more intense. This view lets you record audio and has to use a bit of logic to handle a few cases:\n\nFirst, what do we name the file? For that I use a library to generate a UUID (generateUUIDv4).\nIf you record a sound - and then record it again - we delete the first one. So we have to remember we made a prior sound and clean it up. Note I do not support &quot;clean up&quot; if you use the back button. Oops.\nYou are only allowed to save the sound record if you name it and do a recording. So the button logic gets a bit complex. You can see that in the computed section. Outside of that though most of the code is related to the first blog entry on the topic.\n\nAnd that's it! What do you think? Leave me a comment with any suggestions or feel free to submit a pul",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with Audio in NativeScript - Part One",
		"date":"Thu Apr 25 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1556150400,
		"url":"https://www.raymondcamden.com/2019/04/25/working-with-audio-in-nativescript-part-one",
		"content":"This post was originally meant to be more of a demo application, but I decided to &quot;pause&quot; while working on the app I had planned and share some things I've learned about working with audio in NativeScript. The end goal is a recreation of a demo I did a while ago with Ionic and Cordova - a custom sound board.\nThis application was rather simple. You recorded a sound, gave it a name, and could play it back later. If you don't want to read the article about it, here's how it turned out:\n\nIn general it was easy, once I struggled to get persistent recordings saved to the device. File IO with Cordova was never really a pleasant experience and as long as I'm sharing old images, I might as well bring this one back:\n\nOk, so that's Cordova - how difficult was it to record, save, and play audio in NativeScript?\nI began by searching for &quot;audio&quot; on the marketplace and came across nativescript-audio. It supports recording and playing audio so in theory it has everything I need. Unfortunately the docs were a bit slim on the recording side so I had to guess a bit. The plugin does have a sample app written in Angular and I swear, I can barely grok Angular now. (Mainly it's trying to find out which file actually has the logic. That's not a fault of Angular just an issue with me now being incredibly rusty with it!)\nLooking at this plugin also forced me to be exposed to file system access in NativeScript. You could say I approached this with a bit of trepidation. NativeScript has a built-in File System module. Right away I saw this and was happy: &quot;All file system operations have synchronous and asynchronous forms.&quot; One of the hardest issues with the FileSystem API in Cordova was managing all the multiple deep async calls. I'm not saying async is bad of course or poorly engineering, I'm just saying I hated to work with it. I also saw a lot of things built in that (I don't believe) existed in the Cordova plugin, like being able to clear the contents of a folder and path normalization.\nAlso, like Cordova, they make it easy to get to folders you would use more often. So for example, let's say I want to use a folder in my app to store recordings, here's basic pseudo-code to handle this:\n\nSo knownFolders is your shortcut to important folder aliases, currentApp() is the app, and getFolder will return a Folder object that will also handle creating if it doesn't exist. I love how simple that was!\nWhen actually writing, the folder object has a .path property, so I could construct a filename like so: audioFolder.path+'/recording.mp4'.\nAll I can say is that after being burned so many times trying to work with the file system in Cordova, this actually made me start thinking about other ways I could use it (as opposed to avoiding it).\nSo with that knowledge in place, the first version of my demo simply has two buttons. One to record, and one to play. I always record to the same file name (an issue I'll fix in the next blog post) and always plays the same file. Here's the complete code.\n\nOk, let's talk about this top to bottom! The top portion handles the UI which in this case is just two buttons. Recording is handled in the doRecord method. For the most part I just copied and pasted from the official docs. I added an &quot;auto stop&quot; feature with a setTimeout so I wouldn't have to add in UI for it. My real app will support that of course. Also I want to apologize for all the console.log statements. Normally I clean those up before publication, but as I struggled a bit with this app I kept them as evidence of how I work. :)\ndoPlay handles actually playing the file. You can get information about the file, like duration, but for my purposes I just wanted to play and not worry about it. In my real app I'm going to need a way to stop the playback if another sound file is played (maybe - it could be fun to play multiple at once).\nAnd that's it. Oh and I kinda skipped this above but I did add the plugin as well to get this working. So as always, ask me any questions you have and in the next part I'll (hopefully!) have a fully working custom sound board in NativeScript!\nHeader photo by Jonas Zürcher on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Vue Components FTW - VGauge (and a love letter to CodeSandbox)",
		"date":"Fri Apr 19 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1555632000,
		"url":"https://www.raymondcamden.com/2019/04/19/vue-components-ftw-vgauge-and-a-love-letter-to-codesandbox",
		"content":"When I began this series I had a few things in mind - highlighting Vue components that were cool and easy to use. As part of my arbitrary guideline for &quot;simple&quot; I only wanted to use components that included support for script tag installation. In other words, you didn't have to use a full Vue application but could simply add a script tag to your page. This made it especially easy to use with CodePen which I've been a huge fan of lately. It's not that I'm opposed to components that don't support this or think it's a terrible thing to not support script tag usage, I just want to show my appreciation for a component supporting both use cases.\nToday I'm making an exception. While browsing the Awesome Vue list to pick my next component to review, I discovered VGauge, a super simple component that required npm installation. On a whim, and after Jen Looper recommended it, I took a look at CodeSandbox.\n\nCodeSandbox is (yet another) online editor, but right away I was blown away by how performant it was. It supports templates for numerous different types of projects including numerous different frontend and backend frameworks. I was able to get a full Vue application up and running in less than a second. The online editor works well and the automatic preview is snappy as hell. You can even pop it out into a new tab and it will automatically update as you work.\n\nIt's got great npm support, great GitHub integration, and just a shit ton of really freaking good features. I'm planning on using it extensively from now on to give it a good shakedown and I absolutely recommend folks take a look at it as well.\nAlright, so with that out of the way, as I said the component I picked today was VGauge. This is a Vue wrapped for gauge.js. If you don't know what a gauge is, here's a simple example:\n\nThe component includes numerous style changes and also has a nice animation style when changing values. That makes it especially useful for a value that may update over time.\nAs I said above, this particular component requires npm installation which means you'll be using it for a Vue application only, not a simple script. At your terminal you can simply do npm i vgauge --save. In CodeSandbox, this is done via the Add Dependency button:\n\nIt doesn't come across in a screenshot, but when I typed &quot;vgauge&quot; to search the responses were incredibly quick. I don't know what the developers did behind the scenes but this is easily one of the snappiest web applications I've ever seen.\nOnce installed, usage is really simple. Here's an example modified from the project's readme:\n\nAs I said, you've got multiple options for the look and feel of the gauge, as well the ability to set what the min and max values are so the needle is properly positioned. I create a quick demo that I thought showed off the animation really well. Here's the code.\n\nI begin with an initial value of 40. I then use a two second interval to move the gauge up or down from one to ten points. Want to see it in action? CodeSandbox has one click deployment to Zeit's Now service and Netlify as well (although support is in beta). I'm testing Netlify deployment while I write this blog post. This is a screen shot of it in progress:\n\nThe entire process took about 2 minutes and you can see the result here: https://csb-042l64jx5l.netlify.com/\nAnd for completeness sake, here's an example of how CodeSandbox does embeds:\n\nYou can also share a QR code which would be freaking cool at a conference I think. Anyway, as always, let me know what you think by leaving a comment below.\nHeader photo by Wassim Chouak on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "vue components ftw"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Blocking Web Sites from Prompting for Notifications",
		"date":"Wed Apr 17 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1555459200,
		"url":"https://www.raymondcamden.com/2019/04/17/blocking-web-sites-from-prompting-for-notifications",
		"content":"In general, I'm a fan of new capabilities that come to the web platform. Unfortunately, sometimes a new feature is released that is abused as hell by web sites making you wish the feature had never even been considered. In this case, I don't necessarily blame web developers, as I think they already know that a particular feature is being abused, but rather managers who insist that they know what they're doing and &quot;users really want this&quot;.\nAnd hell, I'm old and cranky so maybe I'm just in the minority. If you are too, then this post is for you.\nHave you noticed that nearly every damn site you go to prompts you to enable notifications?\n\nImage from Mozilla.org\nThis is part of the Push API which in itself is a good idea, but apparently 90% of the internet decided that on your very first visit to their web page they were going to prompt you to enable notifications.\nThis is basically the same as meeting someone for the first time and them asking if they can bring a few things over to put in your bathroom for when they spend the night.\nI wouldn't mind this so much if it wasn't so obtrusive. You could wait till the person has visited a few times, or include UI in the header/footer some place where the user could click to initiate the process. That's never going to happen. Luckily you can disable it.\nThanks go to Dan Callahan of the Mozilla organization for sharing the following two links.\nFirst, here is how you disable it in Firefox: https://support.mozilla.org/en-US/kb/push-notifications-firefox#w_how-do-i-stop-firefox-asking-me-to-allow-notifications\nWhile you should follow that link in case it changes, the gist is:\n\nClick Options from the menu button.\nSelect Privacy &amp; Security and scroll to permissions.\nClick Settings next to Notifications\nClick the checkbox on Block new requests to allow notifications\n\nThis is the global block and Firefox does allow site by site settings.\nAs an aside, Firefox considers this notification spam an issue too and is taking steps to reduce it: Reducing Notification Permission Prompt Spam in Firefox\nAnd here is how you do it in Chrome: https://www.ghacks.net/2016/02/19/disable-show-notifications-prompts-in-google-chrome/\nAs I said with the Firefox link, you should hit the above URL in case it changes, but the process is:\n\nGo to chrome://settings/content.\nClick Notifications\nAnd then toggle the top setting from Ask before sending to Blocked. Chrome wants you to think Ask is recommended. Screw that.\n\nAnd finally, believe it or not Safari also supports Web Push. While I couldn't find a URL to share, you can go to Preferences, then Websites, and click on Notifications. They also allow a global &quot;Leave me the frack alone&quot; setting:\n\nThat's it. I hope this helps!\nHeader photo by Jason Rosewell on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Getting Location in NativeScript - Part 2",
		"date":"Sun Apr 14 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1555200000,
		"url":"https://www.raymondcamden.com/2019/04/14/getting-location-in-nativescript-part-2",
		"content":"A few days ago I blogged about working with Geolocation in NativeScript (&quot;Getting Location in NativeScript&quot;). That post was a bit short as I was writing during a layover on my way to NativeScript Developer Day (which was pretty damn cool!) in Amsterdam. Now I'm on my way home, stuck in Atlanta due to storms causing chaos, and I thought I'd share a quick update to my previous post.\nWhile I mentioned that the Geolocation plugin worked very similarly to the web standards API, it did have one super useful addition that I wanted to highlight - the ability to return the distance between two points. Sure this is just math you can Google and copy and paste, but having it baked into the plugin is really darn useful.\nTo demonstrate this, I modified my previous application to use a service that returns a list of locations, each with a longitude and latitude. It's static now but set up to be used asynchronously.\n\nI placed this in an api folder. Next I updated my Home component to support:\n\nGetting the list\nGetting your location\nUpdating the list with the distance between you and the location\n\nHere's the complete component:\n\nHere's a few things I want to call out. First, inside my ListView, I'm outputting the label value of my location. That doesn't actually exist, but is instead added to the result in my Vue code. There's multiple other ways of doing this, but this seemed simple enough for now.\nNext, notice that my created method now has the async keyword. This lets me do things like the await call inside. I could probably refactor the code that gets your location as well, and I thought about it, but decided to keep my changes more simpler for now. Also, I've been busy the last few days.\nIn order to work with distances, you created Location objects. You assign the longitude and latitude. And then you can get the distance between any two like so: Geolocation.distance(firstLocation, secondLocation)\nAnd that's pretty much it. Here's how it renders in the Android simulator.\n\nNot terribly exciting, but you get the idea. Note that the plugin also supports a watchLocation method that will continuously check your device location. You could use that to keep the list updated as the user moved.\nThat's it! I plan on doing more posts on simple NativeScript examples, so as always, if you have questions, or feedback, just leave me a comment below!\n",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Getting Location in NativeScript",
		"date":"Wed Apr 10 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1554854400,
		"url":"https://www.raymondcamden.com/2019/04/10/getting-location-in-nativescript",
		"content":"As I prepare to get on an 8+ hour flight to Amsterdam for NativeScript Developer Day, I thought it would be nice to work on a quick little NativeScript demo. It occurred to me a few days ago that one of the things I did while learning Cordova and Ionic was to build a crap ton of simple demos that used various plugins as a way to learn the ecosystem. I've decided to try my best to repeat that process with NativeScript. What follows is the first of two articles I'm going to write on using geolocation with NativeScript. This is just a simple introduction while the next one will be a slightly more complex example.\nFirst, I want to start off with a little warning. When I Googled for geolocation and NativeScript, I ended up here: Location. The docs here have a few issues and in my opinion, you should avoid them. (I plan on filing a bug report on the issues when I get a chance from this trip!) Instead, I'd check the core docs for the plugin at https://github.com/NativeScript/nativescript-geolocation.\nAssuming you've got a NativeScript project created, you'll want to begin by adding the plugin:\ntns plugin add nativescript-geolocation\nOk, so that's easy. Using the plugin is mostly easy, but you do have to handle permissions as well as handling errors from retrieving the data. In my opinion, the main readme doesn't do a great job of showing this in a complete example (although more on that in a bit), so I had to guess a bit to figure it out, but here's what I came up with as a general &quot;flow&quot;:\n\nThe code begins by enabling location access in general. On my Android this resulted in a prompt the first time but not again. Then isEnabled call will return true or false and how your application handles that is up to, well, your application.\nNext, you'll actually get the location. It is very important that even if you are fine with the defaults, you must pass an empty object! If you pass nothing than the request is never made. That seems like a small bug to me, but it's easy enough to work around.\nOnce done, your result variable includes latitude and longitude, altitude, and more. The docs do cover this very well.\nSo how about a complete, if simple, demo of this? I'm using NativeScript-Vue but obviously similar code would work in Angular, it just wouldn't be as cool. I built everything within one component:\n\nMy application UI consists of three labels, each used to represent different states of the application. The initial label acts as a &quot;loading&quot; message of sorts and will go away once the location has been retrieved or an error has been thrown. The second label handles displaying an error and the the final label points to a computed property that will display our results.\nIn my created event, I've got code based on the outline above. Ask for permissions, ensure I've got it, and then request my location. Once I get it I can simply store it and my Vue computed property will nicely render the result. Here's an example.\n\nAnd that's it. In my next post I'm going to dig a bit deeper. The plugin has some useful methods you may be familiar with already from the web API, but it also has more including a super useful distance API built in. As always, let me know if you have any questions by leaving a comment below!\nHeader photo by  Sylwia Bartyzel on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Adding Visual Feedback to an Alexa Skill",
		"date":"Mon Apr 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1554076800,
		"url":"https://www.raymondcamden.com/2019/04/01/adding-visual-feedback-to-an-alexa-skill",
		"content":"It's been a while since I've blogged about building Alexa skills, but this weekend I played around with something I've been meaning to take a look at for quite some time - visual results. In case you weren't aware, there are multiple ways of returning visual results with an Alexa skill response. There are multiple Alexa devices that have screens (I've got an Alexa Show and Spot) and whenever you use the Alexa app itself, visual results are displayed there. To be fair, I'd be willing to bet a lot of people aren't even aware of the Alexa app or that it can show previous uses. This is something I've meant to look at for sometime and dang if I wish I had looked at it earlier. You can add simple visual feedback in about five minutes of work!\nNote that Alexa devices support two kinds of visual feedback. The simplest one, the one I'm covering today, is called a Card. This is supported &quot;everywhere&quot; by which I mean it will always show up in the app even if you are speaking to a device without a display. There's a second method of support called &quot;display templates&quot; for devices that ship with a screen. I'm not covering this today but you can read more about it at the docs.\nOk, so let's demonstrate how to do this.\nMy Initial Skill\nBefore I continue on, note that how I built the skill is totally not relevant. I think it was kind of neat so I wanted to share, but you can definitely skip on to the next section where I discuss modifying it to add card support. I'll also note that I'm not submitting this one for release by Amazon so you can't test this yourself, but remember that Amazon makes it super easy to build and test these skills on your own devices which is awesome!\nMy skill is called &quot;My Monster&quot; and it simply selects a random monster from Diablo 3. After giving myself a Nintendo Switch as an early birthday present, I've been playing the heck out of it and have really enjoyed the port. I played quite a bit on the PC and can say that the Switch does an incredible job with it. I don't miss a mouse at all.\nThere's a great wiki for Diablo at, of course, https://diablo.fandom.com/wiki/Diablo_Wiki, and as every wiki has an API, I built some code to parse their data.\nThe first thing I did was simply ask for all pages in the &quot;Animals&quot; category. I did this via a simple Postman test at this URL: https://diablo.fandom.com/api/v1/Articles/List?category=Animals&amp;limit=999999. This returns a list of pages. You can then get more information about a page by going to https://diablo.fandom.com/api/v1/Articles/AsSimpleJson?id=36097 where the ID value comes from the initial list in the previous URL. I wrote a quick Node script to get every page and save it to a local JSON file:\n\nNote the awesome use of Promises to run the HTTP calls in parallel and then my epic use of reduce to work with the text. Basically my idea was to end up with a set of JSON data I could use &quot;as is&quot; for my skill versus parsing data on the fly for each call. I missed one or two things and could have updated this but left it as is.\nThe end result was a big array of monsters - here's part of it.\n\nNext I built a Webtask.io serverless task to select a random monster and return the result to the Alexa device. Here's the code, with again most of the monsters trimmed out for space.\n\nYou can see I modify the text a bit. As I said earlier, the script I built to parse and save the data could have been updated so I'm not doing this on the fly. Or heck, I could write another Node script to read in the output and fix it. As I wasn't planning on releasing this skill I didn't worry about it. Here's a sample result via Alexa's testing console.\n\nThat one's a bit long for a response, but again, I'm just testing. Ok, so how do we add a card to the response?\nFirst - read the excellent docs! Include a Card in Your Skill's Response There's multiple different types of cards but the easiest is a simple card. You can test it by simply adding the card object to your response:\n\nIn the example above I've added a simple card with a title and the same content as the audio response. This is the returned in the card key. Obviously you don't have to do that. You could use the text response as a way to include things that may not make sense over audio. So for example, I can imagine a case where acronyms are included in the text but not the audio:\n\nThat's a pretty minor difference but you get the idea.\nAdding an image is pretty easy too. Switch the type to Standard, change content to text (which feels like a dumb change, mainly because I missed it), and then include an image object with up to two keys: smallImageUrl and largeImageUrl. I had the images for each monster already but didn't notice the wiki doing a server-side redirect to the proper image file. I modified my code to handle &quot;guessing&quot; the right URL so this isn't exactly perfect, but here's what I ended up with:\n\nAnd that's it! The result:\n\nHere's a pic I took of it running o",
		"tags":[
	        
            "alexa"
            
		],
		"categories":[
            
                "javascript",
            
                "serverless"
            
		]

	},

	{
		"title": "Finding (and Fixing) Your Slow ColdFusion Pages with FusionReactor",
		"date":"Mon Mar 25 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1553472000,
		"url":"https://www.raymondcamden.com/2019/03/25/finding-and-fixing-your-slow-coldfusion-pages-with-fusionreactor",
		"content":"In my last article I described how I wanted to help introduce FusionReactor to ColdFusion developers with a special focus on helping solve practical problems and navigating terminology that may not be terrible familiar if you haven't used Java before. In that first article I focused on using the FusionReactor portal to find and diagnose pages that were throwing errors. In this follow up I'm going to highlight another great use of FusionReactor - finding slow pages.\nSlow pages can be difficult to find. A page that runs fast on your local server could run much slower in production. A page with a complex set of inputs, like an advanced search field with numerous filters, can only run slow when a particular unique set of choices are made. Sometimes your code can be absolutely perfect, but an external factor is the culprit. Perhaps you're integrating with a database that you have no control over, or maybe you're using a third party API that has performance issues of it's own.\nIn this article I'm going to highlight multiple examples of slow pages and how FusionReactor can help you find, and hopefully fix, each of them! Ready?\nFinding Your Slow Requests\nBefore we begin writing some horribly slow code (I'm a pro at this!), let's look at where FusionReactor displays these requests. In your left hand menu, under Requests, select Slow Requests:\n\nYou'll notice the display is split between currently running slow requests and requests that have already finished but were considered slow. Which begs the question - what's &quot;slow&quot;?\nUnder Requests, go to Settings, and then select WebRequest History. Here you will find multiple settings you can configure including what the threshold is for something being considered slow. In my FusionReactor 8 instance this was set to 8 seconds.\n\nTo me this is a bit high and I'd consider switching it to a lower number. Performance is an incredibly important topic for web sites and while most of the discussion concerns what happens on the client side (rendering of JavaScript, styles, and so forth), the browser can't even render anything until you actually return the HTML. While there's a lot of resources out there on how fast a page should load, the general consensus should be two seconds. If your ColdFusion page is taking more than that to return a result to the browser, than you have already failed. (Because remember, the browser still has to work with your result to render it!)\nAnd just to be re-iterate, this is important even if your ColdFusion page isn't returning markup. If you are using ColdFusion to power a client-side application with JSON data, then you still need to ensure you're returning a speedy response.\nSo yes, this is very, very important stuff. I'd go ahead and change that number from 8 down to 1 or 2. See what works best for you.\nAnother view of your slow requests is the Longest Requests view, also found under the Requests menu:\n\nThis is a sorted view of requests with the slowest being displayed first. This is not filtered by the Slow Request threshold but covers everything. Also note it includes non-ColdFusion files as well!\nBring the Slow\nLet's start off by demonstrating two slow pages. One is always going to be slow and one uses an external resource that will also always slow. Here's the first one:\n\nIt simply uses the sleep function to pause the execution for 30 seconds. If you've never seen cfflush before, it tells the ColdFusion server to send out existing output to the browser. As a user, you would see the initial HTML, the browser continue to load, and then the rest of the results.\nThis will definitely cause a slow request to be logged. Unfortunately, unlike the error conditions we saw in the previous article, it's a bit more difficult to diagnose. Let's see why. First, here's the details page for this particular request.\n\nThere's a few things to note here. First, the slow time is nice, bold, and red. You can't miss that and that's a good thing. Now make note of JDBC. Don't know what that means? JDBC is the acronym for Java Database Connectivity. Basically this is how ColdFusion speaks to databases. I started ColdFusion development way back in 96 or so, before ColdFusion ran on Java, but even back then the biggest culprit for slow requests was issues at the database layer. We aren't doing any database requests on this page but nine times out of ten you want to turn your focus here. Every single one of those metrics is crucial and can be a flag for an underlying issue.\nAs a real example of this, I was working with a client and discovered they had about 1000+ database queries being run in a request. How did this happen? They had queries in an Application.cfm file, queries in a header file, queries in a file loaded by a header file, and so on and so on. 90% of these queries were the exact same query run multiple times. They weren't stupid - they just didn't realize everything going on in one particular request. This can happen as a web application grows larger an",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Getting Started with FusionReactor (for ColdFusion Devs)",
		"date":"Tue Mar 19 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1552953600,
		"url":"https://www.raymondcamden.com/2019/03/19/getting-started-with-fusionreactor-for-coldfusion-devs",
		"content":"As a ColdFusion developer, you may know that it's running as a J2EE server but also may have zero to no idea what that means in a practical sense. ColdFusion has always been easy to use, especially for developers from a non-traditional background, and this sometimes means there's aspects of the platform that aren't quite as easy to understand as others. A great example of this are the things that are more Java-centric. FusionReactor integrates with your ColdFusion server from a Java-perspective, which means it may be use terms that may be unfamiliar to the developer who only knows CFML.\nAnd yes, you can, and should, consider learning more about Java, but at the same time, we don't always have the opportunity to pick up a new language! You may need to get things working now and what I'd like to do in this article is help you, the CFML developer, better understand how Fusion Reactor reports issues and get you to a point where you can quickly identify (and fix!) your problems.\nMy assumption is that you've already installed FusionReactor. If not, head over to the downloads and grab a copy. You can get your trial key there and use it to test and evaluate the product. As I'm writing this for CFML developers who aren't familiar with Java, I strongly urge you to use the &quot;Automated Installers&quot;. I'm testing on a Windows machine with Lucee but obviously you can use Adobe's ColdFusion product as well.  I'm also assuming you've set up a FusionReactor instance pointing to your ColdFusion server so you can start monitoring and debugging. Once you have, you can open that instance.\n\nLet's Break Something!\nThere's a heck of lot to FusionReactor but in this article I'm going to focus on just one particular aspect - errors. Luckily, I'm a born error creator. One of the best. I should probably get endorsed on LinkedIn for writing buggy code. Thankfully that's made me something of an expert in debugging a file and figuring out what went wrong. That's rather easy while I'm writing and testing code. It isn't necessarily helpful when the code is in production and being used by the public.\nLet's consider a simple template that seems to be bug free.\n\nThis script simply outputs the value of a variable passed in the query string, name, and then reports the length of the value. Given that the file is named ray.cfm, you can test this like so:\nhttp://127.0.0.1:8888/ray.cfm?name=raymond\nWhich gives you:\n\nOk, astute readers will recognize the issue with this code. What happens when you don't pass the name value in the query string?\n\nIn this case it's probably obvious what you did wrong. This is easy enough to fix by either setting a default or adding logic to redirect when the value isn't defined, but what if you miss this and deploy the code to production?\nAssuming you've done that and got a report from your users (or even better, your manager at 2AM on Saturday), let's quickly see how FusionReactor can help you find this issue.\nTo the Requests, Batman!\nAlright, so you've got a report about something going wrong. In a perfect world, that report includes the URL, query string, what the user was doing, the phase of the moon, and more. Also know that ColdFusion has excellent built-in error handling that can send you detailed reports... if you added that to your project.\nBut let's pretend you have nothing, or something as useless as this:\n\n\"Hey, the web site is broke.\"\n\nLet's begin by looking at the history of requests in the FusionReactor instance. In the left hand menu, mouse over Requests and select History:\n\nThere's a lot to this table, but let's focus on a few things in a somewhat descending order of importance:\n\nStatus Code: When a request is made over the web, a server returns a code that represents how it was handled. 200 represents a good response. 500 is a bad response. (And note how FusionReactor is flagging that already!) There's a great many different status codes out there and you should take a look at them sometime.\nURL: This tells you the URL of the request and normally, but not always, will give you an idea of the file that was requested. Many people will use URL Rewriting to &quot;hide&quot; the fact that ColdFusion is running. In this case it's pretty obvious: http://127.0.0.1:8888/ray.cfm. Given that the URL path is /ray.cfm I can figure out that it's the ray.cfm in my web root. But you can't always count on it being that easy. Also note that the error in this view is Java-related: lucee.runtime.exp.ExpressionException. Don't worry - we're going to dig into this.\n\nThat was the general request view, but most likely you want to focus in on just the errors. In that same left-hand nav, you can do so by selecting: Requests, Error History:\n\nThis is showing the same information, just filtered to requests with errors.\nLet's Get the Error\nAlright, so you've found a request with an error, how do we diagnose it? On the right hand side is a &quot;book&quot; icon which will load details. Let's do that and see what we get.\n\nHol",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "An Example of Nuxt.js with a Custom Generator",
		"date":"Tue Mar 12 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1552348800,
		"url":"https://www.raymondcamden.com/2019/03/12/an-example-of-nuxtjs-with-a-custom-generator",
		"content":"Last week I had the pleasure of speaking at DevNexus on multiple Vue.js topics, one of which was the Nuxt.js framework. I had previously looked at Nuxt many months ago and decided I didn't like it. The main issue I ran into were documentation issues and - well to be honest - just a gut feeling. When I was asked if I could give a Nuxt presentation to cover for a speaker who had to cancel, I was happy for the opportunity to give Nuxt another look. In the end, I came away with a much different opinion. I still think the docs need a bit of tweaking (I'll mention one issue in this post), but overall I'm pretty damn impressed. Nuxt adds multiple shortcuts which let you follow a convention to skip a lot of boilerplate code. It's really a nice framework that I'm hoping to use, and blog on, a bit more this year.\nOk, so now that the intro is over, let me get into the meat of what I want to cover. Nuxt works best as a universal (server-side rendering) application, but it also supports static output. When creating static output, it can either create a &quot;traditional&quot; set of HTML files and the like, or a SPA (Single Page Application). So far so good.\nOne of the feature of Nuxt that I really like is the easy way to create routes in your Vue application. Want /cat to work? Simply add cat.vue and you're good to go. Nuxt also easily supports dynamic routes. Given that cat.vue returned a list of cats, you can create a dynamic route by adding cats/_id.vue, where the underscore represents a dynamic value. (You can read more about this feature here). Again, so far so good.\nBut what happens when you generate a static version of your Nuxt app? The docs covering static generation have this to say:\n\nIf you have a project with dynamic routes, take a look at the generate configuration to tell Nuxt.js how to generate these dynamic routes.\n\nThis leads you to the generate configuration docs which then say:\n\nDynamic routes are ignored by the generate command.\n\nBummer. However, Nuxt supports the ability to use a function to return routes in your generate configuration. This lets you add whatever logic you want. Let's consider an example of this. I built a simple Nuxt app with two routes. The home page retrieves a list of films from the Star Wars API:\n\nFor the most part I assume this is rather self-explanatory (but as always, please ask if not!), with the only exception being the forEach where I grab the end of the URL value used for getting specific information about the film. (The Star Wars API &quot;list&quot; commands actually return full data so this isn't the best example, but let's ignore that for now.)\nI then created film\\_id.vue to handle displaying the detail:\n\nAgain, my assumption is that this is trivial enough to not need any additional context, but just let me know if not. Alright, so in theory, if we generate a static version of this Nuxt app, it will simply ignore the dynamic routes and just render the first page. Right?\nWrong.\nTurns out, Nuxt seems to pick up on the dynamic route and use &quot;SPA&quot; mode when generating static output. Remember I said Nuxt could output static content in two forms, a more &quot;traditional&quot; page per route or a SPA. The docs here are a bit misleading (and I've filed a bug report on this) since it seems to work just fine. You can see this live here: https://deserted-squirrel.surge.sh/\nOk, but if I wanted the &quot;non&quot; SPA version and waned to test that custom generate support? Let's see how it looks! This is what I added to nuxt.config.js:\n\nAnd yeah, that's it. Note that those console.log messages do work and will show up in your terminal which can be real helpful for debugging. This created a directory called film (it was smart enough to create it when it didn't exist) and then a folder for each id with an index.html file underneath it.\nEasy enough! You can actually get even more complex with this support and I encourage you to check out the docs for more information. Finally, you can check out this version here: http://typical-jump.surge.sh\nAny questions? Leave me a comment below!\nHeader photo by Matt Howard on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Assets and Stuff from My Vue.js Presentations",
		"date":"Sat Mar 09 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1552089600,
		"url":"https://www.raymondcamden.com/2019/03/09/assets-and-stuff-from-my-vuejs-presentations",
		"content":"This post is really just for those folks who attended my DevNexus presentations this past week, although anyone is welcome to grab the assets if you want. I've zipped\nup both slide decks and demos. I've gone back and forth between putting presentations up on GitHub and if I give these again they move there, but for now you can just grab the zips.\nhttps://static.raymondcamden.com/enclosures/vuepreso.zip\nhttps://static.raymondcamden.com/enclosures/nuxtpreso.zip\nIn both sessions I mentioned the &quot;Awesome Vue&quot; resource, a list of, well awesome Vue.js related stuff. You can find that here: https://github.com/vuejs/awesome-vue.\nThank you to everyone who showed up!\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "What is $nextTick in Vue and When You Need It",
		"date":"Fri Feb 22 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1550793600,
		"url":"https://www.raymondcamden.com/2019/02/22/what-is-nexttick-in-vue-and-when-you-need-it",
		"content":"I've been using Vue heavily for a while now and this week I ran into an issue that I've never seen before. It's something documented and pretty well known (when I tweeted about it I got a reply in about 60 seconds) but I just had not hit it before. Before I get into $nextTick, let me explain what I was doing and what went wrong.\nI have a hidden form on a page that needs to have a dynamic action value. Consider this markup:\n\nAnd this code:\n\nLooks simple enough, right? Probably the only interesting thing here is the use of ref and this.$refs to handle accessing the DOM directly with Vue. I call it fancy because it isn't something I usually need to do with Vue. So what happens when you test this? Try it yourself and see:\n\n  See the Pen \n  what the tick?!?! by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nInstead of POSTing to my server, it sends the POST directly to CodePen, and just today I discovered they support echoing back the data which is kind of cool! (Note, in the embedded CodePen above, the POST echo doesn't work. It may not be a feature of the embed.)\nAlright, so what the heck went wrong? Well, if you're like me, you may not have read the &quot;Internals&quot; section of the Vue.js documentation, specifically this part: Async Update Queue.\n\nIn case you haven’t noticed yet, Vue performs DOM updates asynchronously.\n\nRaise your hand if you hadn't noticed this yet.\n\nLuckily there's a simple fix for this and if you actually read the title of this post, you have an idea of what it is: this.$nextTick. This function lets you provide a callback to execute when Vue is done propagating your changes to the DOM and it's safe to assume it reflects your new data. The fix is pretty simple:\n\nAnd if fat arrow functions still confuse you a bit (nothing wrong with that!), here's a simpler version:\n\nYou can see the corrected version in the CodePen below.\n\n  See the Pen \n  what the tick?!?! (fixed) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo to answer the question of &quot;when&quot; - I guess I'd say when you need to ensure the DOM 100% reflects your data and in this case it's kind of obvious - I needed my form POST to use the correct URL. Out of all the times I've used Vue this is the first time I needed this precise level of control but I'm sure I'll run into more examples. If you can, please share an example of when you've used it in the comments below!\nHeader photo by Franck V on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Vue Components FTW - vue-pure-lightbox",
		"date":"Wed Feb 20 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1550620800,
		"url":"https://www.raymondcamden.com/2019/02/20/vue-components-ftw-vue-pure-lightbox",
		"content":"\nThis post is part of a series of articles looking at simple, easy to use components that\ncan be added to your Vue.js application. You can view the entire series here and drop me a line with suggestions!\n\nToday's simple Vue component is vue-pure-light, a very lightweight and simple &quot;lightbox&quot; component. If you don't know what a lightbox is, it's the UI/UX feature where a picture can take over the entire screen to let you focus on it. You've probably seen it on real estate listings or art sites. The component supports npm installation as well as directly dropping in a CSS and JS tag in your HTML:\n\nOnce installed, you can then use the &lt;lightbox&gt; tag in your application. There's a grand total of three arguments - one for the thumbnail (the initial image), one for an array of image URLs, and an alternate text value.\nAnd that's it. You can also provide a custom loader but I found the one out of the box easy enough to use. So here's a CodePen example provided by the author:\n\n  See the Pen \n  vue-pure-lightbox demo by Dariusz Czajkowski (@DCzajkowski)\n  on CodePen.\n\n\nPay special attention to the CSS panel. While the docs mention there's custom styles in place it doesn't actually enumerate them. The CSS panel here is a handy reference as to what you can customize. Also, he used cats, so therefore I love him. Case closed.\nHow about a slightly more advanced example? (And I really mean, &quot;slightly&quot;...) I began with the following markup:\n\nIf you don't recognize the URL for the thumbnail, I'm using PlaceCage, a placeholder image service comprised entirely of Nicolas Cage pictures. I've specified that my images are being sourced from data in the Vue instance, so let's take a look at that.\n\nIn this case I've just created 10 dynamically sized images from the service. You can run this example here:\n\n  See the Pen \n  vue-pure-lightbox by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nSo while writing up this blog post I discovered that my favorite placeholder service, placekitten, is back up and running! Screw Nicolas Cage! Here's a fork of the previous example with kittens. MUCH BETTER!\n\n  See the Pen \n  vue-pure-lightbox (2) by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nIsn't that nicer? As always - if you have any comments or suggestions about this series, drop me a comment below.\nHeader photo by Dane Kelly on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "vue components ftw"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Book Review: Learning GraphQL",
		"date":"Fri Feb 15 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1550188800,
		"url":"https://www.raymondcamden.com/2019/02/15/book-review-learning-graphql",
		"content":"Over the past week or so I've been spending time learning what I can about GraphQL. I've already attended a few conference\nsessions on the topic and had a basic understanding of what it was, but I wanted to really begin cementing my knowledge\nof the topic in preparation for some things we're doing at work. Randomly I saw a mention of &quot;Learning GraphQL&quot; via Twitter so I decided to give it a read. The book, written by Alex Banks and Eve Porcello, is a bit below two hundred pages so it can be comfortably read rather quickly, but packs a lot of material. I'll go into detail below but the gist is that I definitely recommend it and I'll be purchasing a hard copy to keep at my desk for the next few months.\nAs I said, the book clocks in at right below two hundred pages (198) and consists of the following sections:\n\n\n&quot;Welcome to GraphQL&quot; provides a great introduction to the &quot;why&quot; of GraphQL. As I said, I already had a good understanding of the why but this section is really well done and could be a great way to convince management of why it should be adopted. Even if you've already sat in a few intro sessions, I'd suggest reading this piece just to cement in the principles.\n\n\n&quot;Graph Theory&quot; covers some of the academic aspects behind GraphQL and I have to be honest - I was going to skip this section. I'm really glad I didn't. It provided a great background to the concept of graphs and some fascinating historical background. I appreciated how many of the examples were tied to existing social networks and apps which really helped them make sense. I can't tell you how many times I've object orientation-related books use examples that have no correlation to practical development, so the use of existing samples in the wild really helped it sink in.\n\n\n&quot;The GraphQL Query Language&quot; goes really deep into the syntax of GraphQL and even though I've seen a lot of this before, I learned quite a bit and came out even more impressed about the power of GraphQL. As I said, I've sat in presentations on GraphQL before and I totally get that you can only cover so much, but I was really surprised by some of the more advanced aspects of the query language.\n\n\n&quot;Designing a Schema&quot; acts a good flip side to the previous section. It describes how you define the schema that will then work the query language. As with the previous section, I'd seen parts of this before, but seeing how deep you could go was eye opening. Basically this section and the previous one turned me from &quot;Yeah, GraphQL looks neat&quot; to a &quot;I never want to use REST again&quot; kool-aid drinker.\n\n\n&quot;Creating a GraphQL API&quot; walks you through building a real, if simple, application. In general, this section was really well done. I was able to follow along for about 75% of the chapter building the application along with the text. At some point though it became harder to do so. To be clear, there is a GitHub repo with the complete code for every section that you can grab at any time. Also, I don't know if the intent was for the reader to actually build the app while reading. But I'm a big believer in typing in code as a way to help me learn and it feels like the authors could have done just a bit more work to enable that. Despite my issues, I did like the fact that the chapter ended with how to add login and authorization to the app, something I had not seen with GraphQL before.\n\n\n&quot;GraphQL Clients&quot; goes into detail on integrating with GraphQL APIs. It covers everything from pure cUrl calls to good Node libraries and front-end utilities. The example here is React-based so I didn't try building it myself, but I plan on looking at Vue support soon.\n\n\n&quot;GraphQL in the Real World&quot; covers subscriptions - a way to use websockets to listen to data changes, and then goes into some great topics like file uploads, security, and performance. I had never seen these topics before (again, you only have so much time in a conference presentation) and the book did a great job of introducing them.\n\n\nOverall, a very good book and one I recommend. As I said, I plan on buying a physical copy, something I only do rarely\nfor technical books. There's two issues I'd like to point out though.\nFirst, the book assumes familiarity with ES6 syntax. If you haven't yet gotten used to fat arrow functions, spread operators, and template strings, you may have some issues following the code. I do not point this out as a problem, just as a warning. If you need some help in this area, I'd check out the excellent tutorials at MDN, or heck, as me in a comment and I'll do my best to answer.\nSecondly, and this complaint will only make sense if you know GraphQL, and I'm not even sure I'm right so I'm hoping someone can clarify for me. In the section where a database is connected in the resolvers, it isn't spelled out how filtering on fields is done. What I mean is - I ask for all my photos, but only fields A and B. Yet in th",
		"tags":[
	        
            "graphql"
            
		],
		"categories":[
            
                "books",
            
                "development"
            
		]

	},

	{
		"title": "Vue Components FTW - Toasted",
		"date":"Wed Feb 06 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1549411200,
		"url":"https://www.raymondcamden.com/2019/02/06/vue-components-ftw-toasted",
		"content":"\n\nBefore I begin this post, a quick bit of context. A few days ago I was reading an excellent post on Vue and Internationalization (How to add Internationalization to a Vue Application), and while it is a great article in itself, towards the end the author mentions a small, random little component to make it easy to display country flags (vue-flag-icon). I was really intrigued by this and thought it would be interesting to start looking into the options available to us as Vue developers. \n\n\nWith that in mind, I hopped on Twitter and asked what folks would think about a regular series where I talk about components. The idea is to focus on small, easy to use components that integrate well into existing projects. \"Small\" is relative of course, but in my mind, things like Vuetify) would not apply. (And to be clear, Vuetify is pretty awesome!)\n\n\nI also had one more \"rule\" that I reserve the right to ignore later. I wanted to focus on components that supported both npm installs as well as script tag use (i.e., add this script tag to your HTML) file. I think folks may disagree with me but I really think it's important for a Vue component to support both \"build process\" Vue apps (not a great phrase, sorry) as well as simple \"I'm dropping Vue into a regular HTML page\" use cases. \n\n\nFor now I'm going to try to make this a weekly series, but honestly I think it will be more like twice a month. And I'm going with Vue Components FTW as the tagline because this is my blog and I get to be as silly as I'd like!\n\n\nOk, sorry for the long preamble! For my first Vue component I'm reviewing vue-toasted which is a simple &quot;Toast&quot; library. Don't know what a &quot;toast&quot; is? Don't feel bad. In this content (the web, and not your kitchen), toast is simply a notification that appears and (typically) disappears automatically. Something like, &quot;You've got new mail!&quot;. Remember when getting email was cool?\nYou can see an example of this below - just click the cat.\n\n  See the Pen \n  Vue Toasted Simple by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nThe component has an easy API but also supports quite a few options out of the box:\n\nThe ability to automatically dismiss.\nThe ability to theme the toast.\nThe ability to add buttons with different actions to the toast.\nThe ability to define 'global' toasts for easy reuse across an application. (And these even support dynamic options so you can create a global error handler, for example, but allow for specific error messages.)\nIntegration with popular icon fonts.\n\nInstallation is either via npm or by adding a script tag:\n\nOnce added, you then tell Vue about it:\n\nSo how easy is it to use? An eternal, never-ending immortal Vampire toast can be created like so:\n\nNote that the result of this call is a toast object which you could use to destroy it later. I'd call it woodenStake but that's me.\nAdding duration is as simple as passing an object with options:\n\nAnd yeah, there's many different options. Here's an example of adding an action button to the toast:\n\nDefining global toasts is also pretty easy - and remember you can define these to take arguments for on the fly customization as well. (This example is taken pretty much as is from the docs.)\n\nAnd then finally, an example of using an icon pack. Note that you must include the icon pack before you do this. For my CodePen demo (you'll see it in a bit) I simply added the URL in the CSS panel.\n\nHere's a CodePen demonstrating everything above. It also demonstrates an interesting issue with the component. If you do a toast for &quot;Foo&quot;, the component will nicely size it to fit the content. If that toast is still visible and you then toast &quot;My Kingdom for a Beer&quot;, you'll notice the earlier toast resizes to match the same size as the new one. I guess that's not a bug but it surprised me a bit.\n\n  See the Pen \n  Vue Toasted by Raymond Camden (@cfjedimaster)\n  on CodePen.\n\n\nPretty simple, pretty useful, and should be easy to drop into your next Vue project. If you've used vue-toasted before, let me know in a comment below. And if you like this series (so far anyway) give me a comment as well!\nHeader photo by Mani Kim on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "vue components ftw"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Netlify's Build Process for Somewhat Static Data",
		"date":"Tue Jan 22 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1548115200,
		"url":"https://www.raymondcamden.com/2019/01/22/using-netlifys-build-process-for-somewhat-static-data",
		"content":"A few days ago I blogged about how I was using serverless functions at Netlify to build an API proxy for MailChimp (&quot;Adding Serverless Functions to Your Netlify Static Site&quot;). This worked really well for me and I built a simple &quot;one pager&quot; for my web site making use of that function. But something really gnawed on me.\nEven though it worked really well and was pretty fast, it seemed like overkill to load data that changes only twice a month. I wasn't worried about being charged for it - I was easily within MailChimp's free limit and easily within Netlify's free tier - but it still seemed like too much. It's then that I remembered that Netlify let's you specify a script to run when your site is built.\nThis is a feature I've used for a while now - but basically just to tell Jekyll to build my pages (and before that, Hugo, but let's not speak about Hugo). I didn't really think about the implications of how I could use this for more complex logic. Phil Hawksworth wrote up a good example of this (&quot;Keeping a JAMStack Fresh with Recent Tweets&quot;) where he describes how he uses a build script to update data files used by his static site generator.\nMy one page site didn't need a static site generator, but I could still use a similar process. I began by creating a simple Node.js script that was nearly a copy of my serverless API wrapper:\n\nThere's a couple things I want to point out here. First, my console.log messages will show up in the Netlify build web page which makes it nice for debugging. Second, note how I use process.env.MC_API. This is the environment variable I built to store my MailChimp API. I built it for the serverless function but it's available here as well.\nFinally - I simply hit the remote API and write out the content to my site as static.json. The last bit was to update my Vue.js app to hit /static.json instead of the serverless API. You can see this in action in the completely amazing and awesome site I built for the music newsletter I'm running with Brian:\nhttps://codabreaker.rocks\nI was almost done. The next thing I did was update my build script command I've set in netlify.toml:\n\nAnd this is the relevant line in my package.json:\n\nYes, I'm still using serverless functions &quot;in general&quot; on the site, but mainly now as a testbed for experimentation. As the site is just a &quot;one pager&quot; I don't mind using it for other tricks as well.\nOk, so I'm almost done. The very last step was to configure MailChimp to trigger a build on Netlify. In my &quot;Deploy Settings&quot; for my Netlify site, I went to &quot;Build hooks&quot; and created a new one. This creates a unique URL that can trigger a build on a POST call:\n\nThen I added it as a hook to MailChimp:\n\nAnd that's it! Now when we post the next newsletter, MailChimp will POST to Netlify, Netlify will create a new build, run my simple script, update the JSON, and that's it.\nAnd yes... you can absolutely make the case that using Vue and Ajax for this is also overkill. Instead of writing out to static.json, I could read in index.html, look for some kind of token, and replace it with HTML. Then the page would be really, really static. As always, there's multiple ways to skin the cat here.\nLet me know what you think about this approach, and don't forget to check out my awesome design skills.\nHeader photo by Randy Fath on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Emotional Tone Analysis to Your Contact Form",
		"date":"Fri Jan 18 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1547769600,
		"url":"https://www.raymondcamden.com/2019/01/18/adding-emotional-tone-analysis-to-your-contact-form",
		"content":"A few days ago I blogged about adding customized form handling to your static site at Netlify. This was done via a simple serverless function that listened for form submissions and used the SendGrid API to send an email. While this works just fine, I actually had something more interesting in mind that I had to delay a bit. Imagine if instead of just getting emails about contact form submissions, you actually got something with a bit of a warning in terms of their content:\n\nIn the image above, you can see some basic information about the contents of the email based on their tone. This would be a great way to know what to prioritize in terms of reading and responding. To build this, I made use of the IBM Watson Tone Analyzer service. I've used this multiple times in the past with various serverless demos with OpenWhisk, but I thought I'd give it a shot with Netlify and Lambda. Here's the full script (and be sure to read the last entry for context on how it works) with the new feature added:\n\nAlright, let's break this down. First, I load in the Watson Node.js SDK. While this isn't necessary, I had issues using the REST API for Tone Analysis directly and decided to simply take the easy route out and use their package.\n\nWhere does the process.env.TONEANALZYER key come from? Don't forget you can define custom environment variables for your Netlify sites.\n\nNext, let's see if we have data to check. In this case I'm assuming I've got a field called comments and it's a block of text. You can make this more generic, or even use hidden form fields as a way of saying what should be checked.\n\nNote the fancy use of await. As a warning, please note I'm still fumbling my way around async/await. Let's look at analyze:\n\nThis basically just wraps the call to the Tone Analyzer API and returns the result data. I kept this mostly generic. Now back to the caller:\n\nAs the comments say, you get an array of tones back and they do not appear to be sorted. I did a quick &quot;quality&quot; filter by removing tones with a score less than 0.5. That was arbitrary. I then map out just the name and finally make a string.\nBy the way, I'm 99% sure those three things could be done in one fancy line of JavaScript by someone who can work at Google. I don't work at Google.\nThe final bit is to simply add the tones if we got em:\n\nAnd that's it! So let's have some fun with this. Warning, adult language incoming. If the adult language doesn't make sense to you, ask your kids.\n\nThis returned what you would expect: Contact Form Submission [Tone: Anger]\nNow check this input:\n\nWhile I know Watson isn't perfect, but wow, check the result: Contact Form Submission [Tone: Tentative] I'd consider that near perfect.\nYou could imagine connecting this with some rules in your mail server such that customer service folks with a history of handling angry customers automatically get those emails, and so on. Anyway, let me know what you think by leaving a comment below. As a reminder, this is all being done via a so-called &quot;static&quot; site. Pretty damn impressive, right?\nHeader photo by Aliyah Jamous on Unsplash\n",
		"tags":[
	        
            "javascript",
            
            "watson"
            
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "Customized Form Handling on Netlify with Serverless Functions",
		"date":"Tue Jan 15 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1547510400,
		"url":"https://www.raymondcamden.com/2019/01/15/customized-form-handling-on-netlify-with-serverless-functions",
		"content":"A couple days ago I wrote up a look at serverless functions at Netlify (&quot;Adding Serverless Functions to Your Netlify Static Site&quot;) and today I want to look at a particular aspect of that feature - connecting functions to Netlify events.\nPer the docs, you can write customized logic for the following Netlify events:\n\nWhen a deploy is begins to build, succeeds in building, fails to build, is locked, or unlocked\nWhen a split test is activated, deactivated, or modified\nWhen a user signs up or tries to log in\nAnd of course, when a Netlify-controlled form is submitted\n\nSo to be clear, Netlify doesn't care about the forms on your site unless you specifically tell it to. This is covered in the form docs, but basically, you either add netlify or data-netlify=&quot;true&quot; to your form tag as a way to signal to Netlify that you want them to handle the submission. Out of the box you get things like spam protection, captcha, and redirects on submission as well as the ability to integrate with third party utilities via Zapier. You should first play around to see if you even need customization via a serverless function as your needs may already be met.\nGiven that your pretty sure you do want to write some customized logic, let's take a look at how that's done. First, here is a simple form I built for testing:\n\nIf you want, you can see this at https://codabreaker.netlify.com/contact.html. I only supplied default values in order to make my testing a bit easier. It's nothing related to Netlify support or anything like that.\nOk - so the first thing you need to is create a function with the name, submission-created.js. You can only have one handler per Netlify site and event, but as your function is passed information about the event, you could definitely add support for multiple sources. From what I see of the supported list of events, forms are probably the only time where you would probably care.\nLike other Netlify serverless functions, your basic function signature looks like this:\n\nAlthough you can skip the callback argument. In my testing, calling the callback, both with and without an error, had no impact on the form submission or anything else.\nAccessing the form data can be done via event.body, which is a JSON string, and within there you would access the payload value. So for example:\n\nWhat does payload look like? Here's an example:\n\nYes, that's a heck of a lot of data. You can see some interesting things going on here. First off, if all you care about is your form data, then you can find it within the data block. Notice that an ip value was added automatically.\nSecondly, it appears as if Netlify is trying to do some basic parsing of the form. Notice how it picked up a first and last name by simply splitting my input. It made note of the email address. It also provided &quot;human&quot; versions of the form fields which I'm guessing is probably going to do basic parsing as well. I renamed email to email_address, and Netlify still called the human form email. I guess if you have large, ugly forms with poor naming, this could be useful.\nFinally, note that it recognizes the name of the form, the site, and that this is the 24th submission. All things you could use in your logic.\nHere's a complete example that makes use of both SendGrid and code I had built for OpenWhisk in the past:\n\nNote that I dynamically build the content based on the form submission which would work nice with Netlify and multiple forms, but you could also hard code a set of key-value pairs here.\nThat's basically it. I've got an interesting idea for how to take this a bit further, but I'm waiting for IBM to unlock my dang developer account before I try it. If you've got any questions, let me know by leaving a comment below!\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "Adding Serverless Functions to Your Netlify Static Site",
		"date":"Tue Jan 08 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1546905600,
		"url":"https://www.raymondcamden.com/2019/01/08/adding-serverless-functions-to-your-netlify-static-site",
		"content":"Over the past week or so I've been playing around with the (kinda) new serverless feature at Netlify, built-in Lambda Functions support. One of the reasons I got into serverless was because of how well it works with static web sites, and I was pretty curious to see how Netlify's integration worked. It took me a while to get things going, but I have to say, this is yet another damn impressive addition to the Netlify portfolio. I've long said that they are the &quot;gold standard&quot; for static web site hosting and this just proves again that they are completely nailing it. I did struggle a bit getting things going so what follows is a simple introduction with a focus on the things that confused me.\nPrerequisites\nIn terms of what you need to know before getting started, there really isn't much necessary. Yes, this feature is built on Amazon's Lambda Serverless platform, but you don't need to really know anything about that to build stuff. I've only barely touched Lambda because of how complex it is. It's on my list of things to pick up this year, but my lack of knowledge didn't hamper me from using it with Netlify.\nThat being said, some previous serverless experience will be helpful, especially in terms of knowing what makes sense. What does make sense here? Given that you're using (or considering using) Netlify to host a static site, there may be cases where you still need something dynamic. In my case, I needed to make a call to an API that required a key I couldn't use in client-side JavaScript. My only functionality was to call that API, shape the result a bit, and return a set of data.\nI could have done that on any serverless platform, but having it in the same project as the rest of my static site was incredibly appealing. It just felt better to have everything in one place. I'm not saying that will always make sense, but it certainly did for my project.\nYour First Function\nTo begin, you'll want a folder to store all of your code. As of right now, Netlify supports JavaScript and Go. You can name your folder anything you want and put it anywhere in your directory tree, but note that you files can not be put in subdirectories under the directory you select. So if you decide on /funcs as the location of your serverless functions, you can not create subdirectories for different groups of functions. (It's lame, but if you end up with a lot you could simply use file names, like dao.get.js and rss.generate.js.)\nYour JavaScript code must follow this format:\n\nThe event object contains information about the request whereas context seems to be more related to Netlify specific information, like integration with their authentication support. I didn't use either in my testing so I can't really comment on it. The callback argument is how you return information to the caller and follows a pretty standard form of: callback(error, result). The result value will be a simple JavaScript object like so:\n\nA few things to note here. First, statusCode seems to be optional, except when testing locally (more on that coming soon). So I'd just include it.\nNext, body must be a string. From what I read on the Lambda docs, body can be anything that can be passed to JSON.stringify, which to me means it's automatically turning non-simple results into JSON. Maybe I'm wrong, but for Netlify you absolutely have to use JSON.stringify yourself. Here is a full example:\n\nDeploying and Testing\nNow that you have your file, you deploy it to Netlify like any other static site. For me, I had a site tied to a GitHub repository so all I had to do was commit the code. In your Netlify settings, you want to go into your Site Settings and then select the &quot;Functions&quot; tab. Under &quot;Deploy settings&quot; you must set the functions directory. This tells Netlify where to find your functions.\nSo far so good, but how do you actually call your functions? This is where I hit my first brick wall. This is documented, but it didn't feel very clear to me.\nNo matter what you set for your functions folder, the URL will be: yoursite/.netlify/functions/foo where foo is the name of your JavaScript file minus the .js extension.\nOnce you've done all this, if you hit the Functions tab for your site, you can see a list of all your deployed functions:\n\nClicking on one particular function gets you a real time log. It's usable but it would be nice to have a searchable filter.\n\nAnd that's it... for simple stuff!\nTesting Async Functions\nThis was another thing that I struggled with. I wish the main docs had clarified this or shown a quick example. It may be a &quot;known&quot; thing for Lambda, but as I said, a quick example would have saved me some time. In order to work with an async function, you are allowed to return Promises. So for example:\n\nSo that works fine, but the whole reason I wanted to do an async function was to build an API wrapper. For that I wanted to use request-promise from NPM or some other library, and that's where I ran into my most difficult t",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "serverless",
            
                "jamstack"
            
		]

	},

	{
		"title": "Creating a Live Time Duration Component in Vue.js",
		"date":"Mon Jan 07 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1546819200,
		"url":"https://www.raymondcamden.com/2019/01/07/creating-a-live-time-duration-component-in-vuejs",
		"content":"Pardon the somewhat awkward title of the post. Today's Vue.js demo is based on something I saw recently on the cnn.com site - a live update of how long the government shutdown has been going on. They don't have it online now, but it basically showed this...\nPartial government shutdown has gone on for 11d 15h 49m 7s\nThe values were updated in real time. You could argue (and I'd agree) that it was a bit overly dramatic for a news site (and maybe that's why it isn't there now), but when I saw it, I thought it would be something fun to build in Vue. I ended up with not one, but two iterations of the idea and I'd like to share them below. As always, I welcome your comments about what could be improved. Let's get started.\nVersion One\nThe initial version began with a simple set of features. The component should accept a date value (either in the past or future) and then simply display the duration while updating it automatically. Here's an example of how it could be used:\n\nAnd here's the JavaScript code behind it. First, just the Vue app itself:\n\nAs you can see, all I bothered to add was a value for the date. The real meat is in the component:\n\nAlright, so let's tackle it from the top to the bottom. The template is rather simple, and hard coded, to display the duration as:\n\nThere's no options here to change that. The next block handles the data for the component with the only interesting part (in my opinion) being the math set up to remember various millisecond based intervals.\nNext look at mounted and destroyed. mounted is responsible for setting up a second based interval to update the display (and running it right away). destroyed handles removing the interval if the component is removed from the DOM completely.\nFinally, updateDiffs just handles doing the math. Something tells me this part could probably be written in less lines of code by people smarter than me, but it worked so I left it alone.\nYou can view the complete code (and play with it) in this CodePen:\nSee the Pen time-since vue test by Raymond Camden (@cfjedimaster) on CodePen.\n\nPretty cool, right? But let's look at how we can kick it up a notch.\nVersion the Second\nSo one of the issues with the first version is that it forces a particular kind of output. What if you wanted to customize the display a bit? That's where slots come in. Check out this version:\n\nIn this version, I'm using a slot and customizing the labels used for the intervals to make it a bit closer to the CNN version. If I wanted to, I could even get rid of the seconds value to make it a bit less distracting. Let's look at the updated component.\n\nThe change was rather minor. Now the template supports default output (the same as the previous version) but also binds values for all four intervals that can be used in the markup. The text inside that slot will only be used if you don't pass a slot in. Now the component supports the same output as before but also complete customization.\nYou can see the output here:\nSee the Pen time-since vue test 2 by Raymond Camden (@cfjedimaster) on CodePen.\n\nOk, so what do you think?\nHeader photo by Djim Loic on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Goodbye 2018, Hello 2019",
		"date":"Tue Jan 01 2019 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1546300800,
		"url":"https://www.raymondcamden.com/2019/01/01/goodbye-2018-hello-2019",
		"content":"For years now I've done a few posts towards the end of the year as a way to recap. I'd typically cover my &quot;favorite media&quot; (books, movies, etc.) and then talk about myself and what I accomplished and hoped to accomplish for the next year.\nI'm bucking the trend this year because, well, things are radically different than they were last year at this time. If you have not already read my news or seen my update, I'll summarize by saying that over the past year I lost both a wife and a job. Stressful does not even begin to describe it.\nI'm starting 2019 in a better place, with &quot;better&quot; being extremely relative. The holidays are done (and frankly I'm happy for that). I've got a job. I've got my health (approaching 30 pounds lost since August). I'm going to do my best to take care of myself and my kids while trying to enjoy life as much as possible.\nTypically I spend some time talking about my plans for the new year, focusing on professional stuff. Not this year. I absolutely have &quot;plans&quot; - try to kick butt at my new job, become better at Vue - but I've decided to set my &quot;resolutions&quot; to things that are 100% focused on my mental health and happiness.\nMy blogging activity has steadily declined over the past few years (if you're truly curious, you can check the stats) but I've been writing elsewhere as well. That will probably continue for 2019. I love my readers, and as always, hope you will comment, make suggestions, etc., and thank you for the time you spent here last year.\nOnward and upward, right?\np.s. Ok, so I can't leave without at least a quick media review. &quot;Solo&quot; was a really good Star Wars movie. Not great, but enjoyable as heck, ignore the reviews. &quot;Spider-Man&quot; for the PS4 was easily the best video game of the year, and &quot;Into the Spider-Verse&quot; was easily my favorite movie. Finally, if you like music (and of course you do), consider signing up for CodaBreaker, a bi-monthly-ish music newsletter set up by Brian Rinaldi and myself as a way to share cool/interesting/new music picks.\nHeader photo by Simon Matzinger on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Adding Automated Text Linting to My Blog",
		"date":"Fri Dec 28 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1545955200,
		"url":"https://www.raymondcamden.com/2018/12/28/adding-automated-text-linting-to-my-blog",
		"content":"I've been doing technical writing for nearly twenty years now, and if there's one thing I've grown to appreciate it is the power of a good editor. I've had editors at various parts of my career and frankly there's no doubt my writing was far improved with their help. While I believe that a real person looking over my work would be best, I was curious about whether I could add a bit of automation for this using a tool I ran across last week, textlint.\ntextlint is a linting service built on various rules that look for different issues in your text. The list of rules is rather long, and frankly, a bit overwhelming. As an example, the second documented rule has this functionality: &quot;This rule check no start with duplicated conjunction.&quot; Raise your hand if you know what a &quot;duplicated conjunction&quot; is.\nI went through the list and added what I thought would make sense. Each rule also has configuration options but for the most part I kept things at their default. I used the following rules:\n\nno-start-duplicated-conjunction: I'm still confused by what this actually does, but it seems to prevent multiple sentences starting with words like &quot;But&quot;, &quot;So&quot;, etc.\nno-dead-link: This one is very cool - it checks your links to ensure they actually resolve.\nterminology: Looks for things like &quot;Javascript&quot; instead of &quot;JavaScript&quot; and &quot;NPM&quot; instead of &quot;npm&quot;\nno-unmatched-pair: Basically using &lt;a&gt; ( and forgetting the &lt;/a&gt;).\nalex: This one is really fascinating - it looks for text that can be offensive or not inclusive. So as a simple example, using &quot;mailman&quot; instead of &quot;mailperson&quot; or &quot;postal worker&quot;. Obviously this is the kind of thing you may not care about, but it definitely appealed to me.\nspellchecker: I use a spellchecker in Visual Studio Code, but sometimes I just miss the warnings. Unfortunately this rule is broken for me now and not flagging any issues, but I've got an issue open on their repository.\n\nWith these rules in place, I can run a manual check on an article like so:\n./node_modules/.bin/textlint ./_posts/2018/12/13/2018-12-13-using-alexa-to-mess-with-your-kids-because-why-not.md\nAnd here is the output:\n\nPretty cool! Now came the fun part - integrating this into an automated process. Elijah Manor had a great article on this (&quot;Run npm scripts in a git pre-commit Hook&quot;). In this article, he talks about how to automate the running of linters when doing commits via Git. While the article is good, he now recommends another tool for this called husky. Finally, I had to also add lint-staged, a tool to handle recognizing what's about to be committed and only running your linter on that.\nAfter adding both tools to my repo, I then modified my package.json to add the following:\n\nAnd then did a test:\n\nNice. I totally agree with the repo/repository comment, but the second two don't apply as I'm talking about a man in the singular. (And if I'm wrong, leave me a comment below!) I can now get feedback before I release an article and if I don't agree, I simply: get commit -m &quot;something&quot; --no-verify to bypass the check.\nWhat do you think? Drop me a comment below and let me know if you think this process makes sense or if you would do things differently.\nHeader photo by Hayden Walker on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Alexa to Mess with Your Kids, Because Why Not?",
		"date":"Thu Dec 13 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1544659200,
		"url":"https://www.raymondcamden.com/2018/12/13/using-alexa-to-mess-with-your-kids-because-why-not",
		"content":"As you can tell, I'm on somewhat of an Alexa thing lately (&quot;Adding Ice Bear to Alexa, Because Why Not?&quot;), mainly because now I've gotten it to a point where I can deploy an (admittedly simple) skill in about thirty minutes. Also, certification seems to have gotten quite a bit simpler too. That could also be tied to me building incredibly simple skills but I'm not going to complain. For today's waste of timeincredibly useful Alexa example, I've built a little skill for the sole purpose of messing with my kids during this wonderful Christmas season.\nMy skill, &quot;Santa's List&quot;, lets you ask if your child is on the naughty or nice list. The &quot;mess with&quot; aspect comes from the fact that it always answers in the affirmative. If your kid is acting up, you simply ask if they are on the naughty list. If they are being the little angels that you know they are, then you ask if they are on the nice list.\nIn order for this to work, you need to build a skill that uses &quot;slots&quot;. Slots are variables within your phrases. So for example:\nIs Carol on the nice list?\nIs Jacob on the naughty list?\nIs Weston on the naughty list?\nIs Jane on the nice list?\nIn all four examples, only two things changed - the name of the child and the type of list. Let's talk about names. As a programmer, do I have to specify what names are? Nope! Alexa supports a crap ton of built-in slot types that match various different types of words, including names. You simply set up your skill to listen for a particular type of slot and Alexa will handle figuring it out. Your code then simply gets a name.\nWhat's really cool about this is that if you use a date slot, it will convert stuff like &quot;tomorrow&quot;, &quot;next Monday&quot;, etc, into real date objects. It really makes your code a heck of a lot simpler.\nYou can see all the different slot types at the reference docs but just know that nearly every &quot;broad&quot; category of variable has already been covered and is ready for you to use.\nWhat about naughty and nice? For cases where Alexa doesn't have a built-in slot, you can simply create a custom one and list out the options. Mine only had two so it wasn't difficult to do. Here is a screenshot from the Alexa developer console - you can see my two slots here, each with a name and type.\n\nAfter defining my slots, I can then use them in my sample utterances. Remember that a skill has intents, which are the broad ways of talking to it, and the sample utterances describe those intents. My skill only has one intent (ignoring the built-in ones), so I simply added that one intent and wrote a set of utterances.\n\nAll that was left now was the code. Here's the complete code for the skill, hosted on Webtask.\n\nIn general, the code breaks down into two main actions depending on the intent. The first one, LaunchRequest, handles people who just do &quot;Alexa, open Santa's List&quot;, and I use it to provide help on how to use it. The second is where the fun comes in. Alexa will convert both the name, and naughty or nice selection, into code that's passed into my intent. You can see me accessing them via intent.slots.Name.value or intent.slots.List.value. I then select from a list of random strings and do a string replacement from $NAME to the actual name passed to the skill.\nWhat I like about this is that when I think of new responses, I can edit this code (via my browser, Webtask has an incredible online editor) and just write it, save it, and I'm done. There's nothing I need to do on the Alexa side at all. This is neat but it also brings up one of the negatives about Alexa development. If you do screw up your server-side code, you'll never know unless you log in to the portal and check for yourself. Alexa doesn't have any way of pinging you when things go wrong.\nNot that I think it helps any, but you can browse the Amazon page for the skill here. Let me know what you think and try building your own Alexa integration!\nHeader photo by Chun Yeung Lam on Unsplash\n",
		"tags":[
	        
            "alexa",
            
            "webtask"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding Ice Bear to Alexa, Because Why Not?",
		"date":"Wed Nov 28 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1543363200,
		"url":"https://www.raymondcamden.com/2018/11/28/adding-ice-bear-to-alexa-because-why-not",
		"content":"Before I begin, let me stress that this is not a post meant to introduce you to Amazon Alexa development. I've got a series of blog posts talking about Alexa development and I plan to create an updated tutorial early next year. Instead, I simply wanted to share a simple skill I built a week or so ago - purely for fun - as a way of demonstrating how easy it is to do. As a reminder, you can build any skill you want for your Alexa devices and do not have to release them to the public. That means you can build crazy, dumb, strictly personal stuff for your own devices. As an example, I've got a skill for my Alexa devices that allows me to ask Carol whose fault it is. Because honestly, look at this face, it can't be her fault, right?\n\nAll the skill does is simply pick from a random list of her siblings, Obama, and Trump.\nYes, stupid, but it made her happy as hell and the point is - I think it's dang cool that you can hack on Alexa devices like this. Last time I checked you still couldn't do this with Google Home devices and that's why mine has been mostly ignored.\nAlright, so on to Ice Bear. Who, or what, is Ice Bear? Ice Bear is a character from the &quot;We Bare Bears&quot; show.\n\nThis is a children's show that - like many of them nowadays - can be pretty fun for adults too. (Or adults who still play with Star Wars toys.) Ice Bear is one of three bears and speaks in the third person. For example...\n\nIt probably doesn't translate well here, but in the show it is funny as hell. I thought it would be cool to build an Alexa skill that would select a random Ice Bear quote. Here's how I built it.\nFirst, I Googled for &quot;ice bear quotes&quot;, and not surprisingly, found a wiki page containing a list of them. I then inspected one of the quotes to look at the DOM:\n\nI've called out two things in particular in the screen shot above. You can see the main quote is a simple P element. But then I noticed that it was wrapped in a table with class cquote. Cool. So I switched to my console and entered:\n\nThe CSS selector there should match the P tags inside the tables. It returned a NodeList of 133 items, which seemed correct to me. But now I needed to get the text. I created a new empty array (again, all of this is in my browser console):\n\nAnd then loop over quotes to get the textual content.\n\nI ran a quick test to see:\n\nCool. So the extra line break at the end bothered me a bit. I fixed that with a regex:\n\nNote I'm using hipster JavaScript fat arrow functions here and Google totally should have hired me despite my sucky Sudoku solution tester. The final step was to get this data out of the browser. Luckily, you can copy to the clipboard via the console:\n\nWoot. Ok, I've got an array of quotes. Now to write the server side code. I created a new Webtask, my current favorite serverless platform, and whipped up the following:\n\nNote that the quotes variable above is heavily truncated for this post. All my code does is random select a quote and then output the correct JSON response for Alexa. Again, I'm not going to go into detail about working with Alexa, but for very simple skills like mine, this was all that was required. I went into the Alexa development portal, created my new skill, and set up the one intent it needed to operate. Intents are like broad categories of communication with your skill. For example, when you go to Starbucks and speak to the person behind the counter, in general your conversations are limited to ordering and asking about the menu. You can think of that as two intents - &quot;Order&quot; and &quot;Menu&quot;. Each intent will have a set of sample utterances which are basically ways to express the intent. You do not have to list every single possible iteration but the more you do, the more flexible your skill is. Here's a screen shot to give you an idea of the interface.\n\nI then had to point it to the URL of my webtask, and for my own purposes, that was all I needed to do. But for the hell of it, I decided to attempt to release it publicly. There's a number of things you have to do to release an Alexa skill publicly, one of involves adding a complex layer of security to your skill. Luckily you can do that in a few seconds via the method I described here: Using Alexa with Webtasks.\nNormally my &quot;release&quot; process is - I submit the skill - Amazon finds bugs - and I repeat. It is a painful process at times, but the Amazon reviewers do a damn good job of giving you very explicit steps to recreate the bugs they found. In this case, I actually passed on the first try. (Surely they missed something.) If you've got an Alexa-powered device, you can ask her right now: &quot;Alexa, ask ice bear for a quote.&quot; You can also visit the &quot;product&quot; page here: https://www.amazon.com/Raymond-Camden-Ice-Bear/dp/B07KJN4K13. I'm kinda surprised no one had claimed &quot;ice bear&quot; as an invocation name, but I guess I got lucky.\nAnyway, I hope this is interesting, and if you have any questions about w",
		"tags":[
	        
            "alexa",
            
            "webtask"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Review of NativeScripting's Vue Intro Course",
		"date":"Mon Nov 26 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1543190400,
		"url":"https://www.raymondcamden.com/2018/11/26/review-of-nativescriptings-vue-intro-course",
		"content":"Good morning programs, as I continue my journey into NativeScript via Vue, I thought I'd share a quick review of a new online course that may be of interest to people. &quot;NativeScript-Vue Introduction&quot; is a great course that introduces people to both NativeScript and Vue development. It covers:\n\nSetting up your development environment\nWorking with UI (this is perhaps the biggest topic for folks new to NativeScript)\nWorking with Vue (obviously, and you do not need to know Vue before you start)\nUsing Vue Components\nAdding Routing\nStyling\n\nWhile you do not need to know Vue before you start, I would perhaps spend a bit of time going through the introduction so you aren't going in completely blind.\nThe course is very nicely paced with each section being roughly 2-3 minutes in length. This makes it much easier to learn at your own pace and spend some time playing around a bit while you learn.\nI also really like the video platform used on the site. It's one of the simplest platforms I've seen and I appreciate that it isn't too cluttered.\n\nThe course was created by Alex Ziskind, founder of the site itself, and you should definitely check out the other courses while you are there, although this is currently the only Vue-related one available.\nThe course is priced at $49, which seems completely reasonable to me and worth the price. If you sign up and mention my name, you get absolutely nothing, but I still recommend you do so anyway.\n",
		"tags":[
	        
            "vuejs",
            
            "javascript",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with the Camera in a NativeScript Vue App",
		"date":"Thu Nov 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1542240000,
		"url":"https://www.raymondcamden.com/2018/11/15/working-with-the-camera-in-a-nativescript-vue-app",
		"content":"So far my journey into NativeScript Vue has been pleasant. The development experience is really well done and using Vue with NativeScript just feels like a natural fit. This particular topic (working with the camera) is the only time I've really been frustrated, and as I've been doing now for 6000 posts (yes, this is blog post number 6000), when I get frustrated with something I try my best to write it up in a way that will help others. Before I go any further I want to point out that I got some great help from folks on the NativeScript Slack group. Most recently, @bundyo, helped me get to the finish line for this demo and was helpful in my last post as well. Finally, I want to point out that the client I'm working on only needs to support Android, so I only tested this with Android. There's only one part that concerns me in terms of what iOS would do differently and I'll try my best to point that out so folks know what to look out for.\nThe goal of this post is actually a bit more than the title suggests. Yes, I wanted to test the camera in NativeScript, but I also wanted to test file uploading to a server. In both cases, I ran into quite a bit of difficulty. This was a bit surprising, as &quot;camera picture/upload demos&quot; are pretty trivial in Cordova. I'm still not 100% happy with my resolution here but if this is the worst I have to deal with in NativeScript, I'm still pretty damn happy about it. Alright, that's enough backstory, let's get into it.\nWorking with the Camera\nLet's immediately begin with the first &quot;gotcha&quot; - unlike the Cordova Camera plugin, the NativeScript Camera plugin only supports the Camera, and not the gallery. That isn't a big deal as there are other plugins for that (you'll see the one I chose in a second), but you'll want to be aware of it.\nNo big deal - but when I first started looking at the docs, I ran into some issues. The docs mention that permissions are required to use the camera and show this API:\n\nNewer API levels of Android and iOS versions are requiring explicit permissions in order the application to have access to the camera and to be able to save photos to the device. Once the user has granted permissions the camera module can be used.\n\ncamera.requestPermissions();\n\nThat's trivial - but the docs never really explain how this API works, what the result is, and so forth. Shown as it is in the docs initially:\n\nThe implication is a blocking request that can be used before you call the rest of the camera API. Nor do the docs mention what the behavior is in general after the app has been used.\nBased on my testing, the API actually returns a Promise. That's pretty crucial information that doesn't seem to be documented (issue #139).\nAnother piece of crucial information, and one that's obvious but I had not used the camera in a while, is that you must add this to AndroidManfest.xml:\n\nI lost maybe two hours of time because I missed this. Literally one dang line in the docs in that permissions section would have helped (issue #140).\nSo with that out of the way, let's look at a simple example.\n\nLet's first consider the layout, all two items in it. I've got a button and an Image element that is initially blank. Nice and simple. The code, specifically takePicture() is the crucial part. You can see my call to requestPermissions() first, and in the result I then attempt to take a picture. All of the options passed in there are optional, and I highly recommend not using saveToGallery:true while testing, otherwise your photo roll gets filled with a crap ton of test pictures. Consider this beautiful example.\n\nThat's not bad. Again, it was painful due to doc issues, but once I got past that, it was all rainbows, unicorns, and kittens. Let's kick it up a notch!\nAdding in Gallery Support\nIn my application, I need to let the user take a new picture or select from their gallery. As I said above, while this is baked into the main Camera plugin for Cordova, it isn't included in NativeScript's Camera plugin. No worry - there's a plugin for that: NativeScript Image Picker. This has a simple to use API and lets you do things like allow for one selection or multiple. I had zero problems with this one. I updated my demo to add a second button and then defined the results as an array of images.\n\nHere's a quick screen shot - and the images are probably too small to even decipher what they are - but I took two pictures and selected two from my gallery.\n\nWoot! EVERYTHING ELSE WILL GO PERFECTLY!\nUploading Files to Hell I Mean the Server\nAlright... so... here's where things took a dark turn. Like, really dark.\nMy soul is freezing. I fear seeing another day—another day filled with this emptiness. pic.twitter.com/9W3bGxDbRz&mdash; Black Metal Cats (@evilbmcats) November 13, 2018\n\nSo, in the past I made use of the FileTransfer plugin with Cordova. Nowadays I'd probably use the Fetch API instead. However, I saw a lot of people recommending the nativescript-background-http plugin. I found this ",
		"tags":[
	        
            "vuejs",
            
            "javascript",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Review of My Arcade1Up Machine",
		"date":"Fri Nov 09 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1541721600,
		"url":"https://www.raymondcamden.com/2018/11/09/a-review-of-my-arcade1up-machine",
		"content":"As a child of the 70s, I grew up in the beginning of the video game boom. But while I had a console (the venerable Atari 2600) pretty young, for me, the real video game experience was to be found at arcades. I cannot even fathom the amount of quarters I sunk into these machines growing up and as much as I appreciate the power of modern consoles, I really miss the arcade experience. I still get the opportunity to play a proper arcade game every now and then, but it will never quite live up to the memory of loud, noisy, buildings packed to the rim with arcade games trying to get your attention (and money).\nAs a kid, one of the shows I remember watching often was Silver Spoons. This was a (pretty bad) 80s sitcom about a single father raising his son in a mansion. And while the show is pretty much forgettable, there's one aspect that I remember noticing the very first time I saw the show:\n\nIn their house (ok, mansion) - they had - arcade games. Like, real, arcade games. As a kid, this blew me away. The idea of having the arcade at your house and being able to play for free, whenever you wanted, seemed like a fantasy, surely no one could really do that, right? In fact, for most of my life my definition of &quot;rich&quot; was having arcade games in your own house.\nOver the past couple of years, I've definitely look into getting my own arcade machine, and while most options weren't &quot;stupid&quot; expensive, it certainly wasn't in the &quot;impulse buy&quot; range. Also, I'm not terribly good with hardware and from what I know the older machines need a lot of TLC that I don't have the skills (or time) to give. Now, at this point, someone's thinking, &quot;Why doesn't he just make a MAME cabinet?&quot; Well again, see my comment about now being good with hardware, and secondly, MAME ROMs are really difficult to get right. My um, &quot;friend&quot; has noted that while it is ridiculously easy to get HD copy of a new movie, it can be near impossible to get a good copy of a classic arcade game. More trouble than it's worth for sure.\nThen I heard the news about Arcade1Up. Arcade1Up offers arcade machines at a good price ($299) and a smaller scale - 3/4th size. Here's the image they use on their site to illustrate the size:\n\nHere's my machine in my office (pardon the mess) to give you an idea of what this really means:\n\nArcade1Up offers six different machines, each with a different set of games. There's also a &quot;12 in 1 Atari bundle&quot; you can only get at Best Buy that I'm fairly sure will be my next purchase.\nIf the size seems too small, you can buy a cheap(ish) riser, but honestly, I like the size. When I play I roll my office chair over, play a game, then roll it back.\nWhen I made my purchase, I had two main concerns - how difficult it would be to put together, and how it would feel to actually play. Let me address each.\nPutting it Together\nOk, so when I said I don't like to build things, let me reiterate. I. Don't. Like. To. Build. Yes, I know that's like a sin for a programmer, but outside of LEGO I just don't enjoy putting things together. I'm constantly afraid I'm going to screw something up or break something with my big clumsy hands.\nWhile they have a nice five minute video showing you how to put it together, I stuck to the written instructions and took my time. In total it took me a little over an hour. Out of the 25 steps or so, only two caused me issues and it was due to slightly awkward positioning of something I had to screw in. Also, it was a bit dark where I was building and things improved when I had one of my kids hold up a light for me. Obvious, right? Again, I suck at building.\nI think most folks could probably get it done in 30 minutes. The hardware is &quot;firm&quot;, not flimsy at all. I believe they list the weight at 60 pounds, but it feels a bit less than that. I had no trouble moving it.\nIn case you're curious how it ships, it comes in a flat box roughly the size of a TV.\n\nPlaying It\nSo - the million dollar question - what's it like to play? Galaga isn't necessarily a physically crazy game like Street Fighter, but it can get pretty intense, especially at later levels.\nI can tell you that it plays absolutely perfectly. The joystick feels natural, if maybe a bit firm. But I'm used to different machines have a slightly different feel to their controls so after one game it felt right to me. The game itself is perfect (except for a few things I'll note below) and the speaker is nice and loud (and you can turn it off too which is nice). I've played maybe four games so far and it feels just like the arcade. I'm sure it isn't exactly perfect, but to me it's exactly what I wanted.\nI find myself walking into my office, looking over at it, and just smiling. That may seem silly, but finally having an arcade machine in my house, even if a smaller reproduction, makes me giddy. It's a roughly thirty year old dream of mine realized and I am completely happy with the purchase.\nThe Nits...\nOk, so wh",
		"tags":[
	        
		],
		"categories":[
            
                "video games"
            
		]

	},

	{
		"title": "Using NativeScript DataForm with Vue.js - Some Tips",
		"date":"Wed Nov 07 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1541548800,
		"url":"https://www.raymondcamden.com/2018/11/07/using-nativescript-dataform-with-vuejs-some-tips",
		"content":"As I've mentioned recently, I'm currently working on a NativeScript Vue application\nfor a client and as part of that work, I'm dealing with custom forms. My initial approach made use of custom components, which Vue made pretty trivial, but I've decided to give NativeScript UI a try. This is a set of free components covering the following features:\n\nCalendar\nChart\nListView\nDataForm\nSideDrawer\nGauge\nAutoComplete\n\nSpecifically, DataForm looked like it could be useful. While it's not too difficult to build forms with NativeScript, DataForm attempts to automate as much as possible of the process. As an example, consider the following data:\n\nNow imagine we tie this to a dataform control:\n\nAnd if we literally leave it at this - the control will automatically render a nice form for us:\n\nNotice how the control looked at my data properties and figured out what controls to use as well as how to create labels. yearBorn for example becomes Year Born. This all happens by default and is freaking cool, but you can control all of this as well if you don't care for their defaults.\nAll in all a neat little control, but I ran into some issues right away as soon as I started trying some of the more advanced features. Part of this is due to some poor docs (I've already sent reports in!) and I hope this post can help others avoid the issues I ran into.\nInstall with Vue Issues\nSo the docs tell you to install the relevant plugin, but right after that things go a bit awry.\nThe &quot;Getting Started&quot; for the Vue docs and DataForm, which isn't even labelled that (in the nav it's called &quot;Provide the Source&quot; tell you to do this:\n&quot;Add this to the main Javascript or Typescript file, usually called main.js or main.ts:&quot;\n\nOk, I did that. Then it says:\n&quot;Before proceeding, make sure that the nativescript-ui-dataform/vue module is required inside your application. This module handles the registration of the custom directives and elements required by nativescript-vue.\nAfter that simply add the RadDataForm tag to the HTML and set its source accordingly:\n&quot;\nSo that first paragraph didn't make sense. I mean, didn't I already do that? To make matters worse, the code sample below doesn't provide any additional help.\nI was only able to get things working by going to the NativeScript Playground, dragging a DataForm control on the page, and looking at what it did.\nBased on that, this is what worked for me:\n\n\nDo not add code to main.js/maint.js. From what I can see it wasn't necessary.\n\n\nIn your component, do require the dataform like so:\n\n\nEdit on 11/7/2018, a mere hour after posting... @bundyo reduced the original 5 lines of code I had to just one:\n\nLooking at that code, the paragraph I quoted above makes sense now, but I had no idea what code to even use. If the code sample on the page had included this, it would have saved me about two hours - I kid you not.\nWorking with Groups\nAlright - so the main reason I even looked at the dataform control was to make use of the &quot;groups&quot; feature. This lets you take a large form and create groups that can ben opened and collapsed. It isn't an &quot;accordion&quot; control per se, but it achieves the same purpose. (For folks curious, there is a NativeScript Accordion control but it has certain restrictions that made it unusable for my case.) Here are two screenshots I stole from the docs - first the Android version:\n\nAnd then iOS:\n\nSo, while cool, the docs on this were pretty slim, especially in regards to providing dynamic groups, by that I mean groups defined in data and not hard coded as tags on the page. I spent a heck of a lot of time trying to get this to work and finally gave up and asked for help on the NS Slack group. Thankfully @bundyo came to the rescue. What follows is his solution, not mine. My data is still hard coded but you can see where it could be modified to support data loaded from Ajax or some such.\n\nLet's break it down. First, look at the dataform:\n\nThere's two new attributes here - metadata and groups. So metadata is where you can do overrides on the default behaviors of the control. Don't like the label it selects for your property value? You can tweak it here. Want to use a custom drop down with specific values? You can set it here. We use this feature to specify the groups for each field. (And again, it's hard coded here but it could be dynamic.)\nThe next part is creating the groups. In this case we use an instance of PropertyGroup, one for each group, and ensure that the names match the names used in metadata.\nIf you want to see, and play with, a slimmer version, check out the Playground @bundyo made here: https://play.nativescript.org/?template=play-vue&amp;id=qWbsL5&amp;v=3 It really does a nice job of setting up the groups and fields all in one fell swoop. And because it's on the Playground, you can point the NativeScript Playground app at it and have it running on your device in 10 seconds.\n\nAnyway, I hope this helps. As I said, the docs her",
		"tags":[
	        
            "vuejs",
            
            "javascript",
            
            "nativescript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Azure Functions and the Marvel API to Visualize Character History",
		"date":"Sun Nov 04 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1541289600,
		"url":"https://www.raymondcamden.com/2018/11/04/using-azure-functions-and-the-marvel-api-to-visualize-character-history",
		"content":"I've been playing with the Marvel API for quite some time now (&quot;All My Friends are Superheroes&quot;, &quot;Building a Twitter bot to display random comic book covers&quot;, &quot;Using the Marvel API with IBM Watson&quot;, and &quot;Exampled of the Marvel API&quot;) and I find myself coming back to it from time to time to just see what cool stuff I can find in their database. Unfortunately it looks like Marvel isn't really doing anything new with their API lately, but at least it still works and I guess that's something. A few weeks ago I thought it would be an interesting experiment to see if you could automate visualizing the changes of a character over time. So what do I mean?\nConsider this first image of Spider-Man from way back in 1962...\n\nAnd now compare it to this awesome shot from 1988:\n\nAnd finally to a cover from this year:\n\nI just love seeing the progression of style over the decades especially with such an iconic character. I decided to try to figure out a way to automate this and display it to the user. Now, before I go any further, let me state that I'm not going to run this demo &quot;live&quot;. Why? First - I'm still not 100% sure how to stay &quot;safe&quot; in the free tier with Azure Functions. Last month I got a bill for 40 dollars because I made a wrong selection in a project and while that's my fault, I still feel a bit burned by it. Secondly, Marvel themselves have a limit on their API usage. It's a fair limit of course, but it's also something I just don't want to worry about. If either Microsoft or Marvel want to help me out here, just drop me a line! I won't hold my breath. ;) That being said, all of the code I'm about to show can be found at my GitHub repo here: https://github.com/cfjedimaster/marvelcharacterovertime\nThe Back End\nMy back end is built using Azure Functions. This was the first time I made use of Visual Studio Code integration and damn did it work well. I think it took maybe twenty minutes of setup or so but once done, it was one quick command to deploy to Azure when I had updates. It was also easy to run the code locally. From my limited experience so far, this is the best way to work with Azure Functions (obviously if you are a Code user) and it's what I plan on using in the future.\nMy application required only two specific features - the ability to search for characters and then the ability to find related covers over time. Let's start with the character search endpoint:\n\nThis one is the easiest as all it needs to do is use the characters endpoint with the nameStartsWith argument. This will let you enter a value, like 'spider', and get results. The stuff with the time and hash is simply part of Marvel's API security which frankly feels like overkill but there it is. I get the results and then map it down a bit to remove a lot of data I don't need. This makes the communication between Azure Functions and my web app a lot zippier as I'm not returning unnecessary data.\nCool! So far so good, I'm sure the next endpoint will be just as easy, right? Hah!\nMarvel doesn't have an API that returns covers with certain characters, but you can search comics for a character and I figured that would be close enough. In order to get my data, I thought I'd search for a year's worth of data for results including a character. Unfortunately, the character API doesn't return when a character was first seen. So in order to estimate that, I did a search with a date range from 1950 to 2090. Please feel free to come find me in 2090 and complain.\nI sort those results by the sale date and then use the first result as indicative of when the character's first appearance was. I didn't test this heavily but it seemed to work well with Spider-Man.\nOnce you have that - you can then ask for comics from every year from the initial year till the current year. And that's basically it. Here's the code:\n\nYou'll notice I'm using an array of Promises so I can quickly fire off a bunch of requests at once and wait for them all to complete. Marvel doesn't have a &quot;throttle&quot; limit so this may not always work well for other APIs. Finally, note I'm once again mapping the results back to limit the data being sent to the front end.\nThe Front End\nThe front end was a simple affair - prompt for the character, show results, then render comic covers over time. I built it using Vue.js and had quite a bit of design help from my buddy Garth. I really wish I could run this live for yall but as I said above, I just can't do it that for free and within the API limits.\nLet's start with the character search result screen:\n\nAfter you select a character, I then hit the back end, which frankly worked really freaking fast, especially considering how much data someone like Spider-Man has. Here are four screen shots from a heck of a long set of results:\n\n\n\n\nThe code is pretty simple. Here's the layout:\n\nAnd here's the corresponding Vue code:\n\nThere really isn't much more here than some Ajax calls. There's definitely",
		"tags":[
	        
            "azure",
            
            "vuejs"
            
		],
		"categories":[
            
                "serverless",
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Dynamic Components in Vue.js",
		"date":"Wed Oct 31 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1540944000,
		"url":"https://www.raymondcamden.com/2018/10/31/working-with-dynamic-components-in-vuejs",
		"content":"I'm currently deep into a project using NativeScript Vue and ran into an interesting problem that - thankfully - had an incredibly simple solution in Vue. While I certainly do not believe Vue is perfect, but the deeper I go the more I appreciate how powerful it is. The problem I ran into dealt with dynamic forms.\nThe application loads a set of data that represents an array of fields. Each field has a particular type, possible options, possible defaults and more. I knew I could build Vue components to represent these fields, but what I wasn't sure about was how to actually use them in my layout. Turns out there is a perfectly simple way to do this - Dymamic Components.\nBasically, instead of adding &lt;my-component&gt; to a layout, you would add &lt;component :is=&quot;my-component&quot;&gt;. And yeah, that's pretty much it. There's one important aspect though. Now that you know how to create a dynamic component, how would you also dynamically pass data to it?\nTurns out, the bind feature supports passing an object and expanding the key/value pairs out as attributes and values. It's as simple as: v-bind=&quot;object&quot;.\nSo this may make more sense with an example. Let's start off with a Vue application that has some hard coded data representing field data.\n\nIn the data block, I've got 4 fields defined. Each has a label, a type, and some have answers representing options. To keep things simple I kept out things like defaults. In the real app, I'll not only have defaults, but concepts like, &quot;default to the current time.&quot; Now let's look at the layout.\n\nNice and simple. I'm looping over each field, defining it's component based on the field.type value. I then pass in all the data of the field using the v-bind trick described above. As for how I implemented the components, I just did some basic HTML. Here are all three:\n\nThis is a pretty ugly implementation of the fields but it gets the job done. And of course in my 'real' app I'll be using both Single File Components and NativeScript components, but you get the idea.\nIf you want to see a live example of this, check out the CodePen below. Remember this is a super basic implementation for me to test the idea out, nothing more.\nSee the Pen pxXowY by Raymond Camden (@cfjedimaster) on CodePen.\n\nHeader photo by Mark Duffel on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "I Built a NativeScript/Vue.js App and You Won't Believe What Happened Next...",
		"date":"Thu Oct 25 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1540425600,
		"url":"https://www.raymondcamden.com/2018/10/25/i-built-a-nativescriptvuejs-app-and-you-wont-believe-what-happened-next",
		"content":"Please forgive the clickbait title. I was struggling with what to actually title this blog post\nand just decided to give up and go a bit over the top. With how little I'm blogging lately, I figured this\nwould at least put a smile on my reader's faces and that's worth something. ;) Speaking of my readers, those of you who have been around here for a while know I've been a fan of NativeScript since it's release, but I've also blogged very little about it. It's consistently been on my &quot;Things I'm going to do this year&quot; posts and I never get around to actually working with it. Well, the good news for me is that while I'm between jobs, I've got a client who wants to build a NativeScript app and I've got the time (while on the clock, and yes, I'm very lucky for that) to learn while I build out the project. Even more lucky for me is that there is a NativeScript Vue project that kicks major butt. I thought I'd share my experience playing with it over the past week as well a simple application I built with it.\nThe first thing I want to address it the development experience. I've been following the work the NativeScript team has done in that regards but I actually got to play with multiple variations of it and I have to say - they have done incredible work in this area.\nThe CLI\nSo yes, you have a command line. It's always felt a bit &quot;heavy&quot; to me in terms of installation and speed. That's not very scientific but it feels like the install process is a bit long and has a lot of moving parts. I did my testing in PowerShell as I didn't want to try getting the Android SDK running under WSL. The CLI can actually handle that for you, but in my case I already had Android installed. You can see more about this process at the CLI installation docs but I guess my point here is to not expect a quick npm i nativescript that will finish in a few seconds. I don't think there's anything that can be done about that, just consider this as a heads up.\nOnce you do get it installed, the CLI works ok, but in my testing, the first run of an Android project seemed incredibly slow. Much more than I've seen with Cordova. But that's a one time pain. You can run tns run android --bundle and it will automatically reload your application as you save files.\nAfter that initial load the process is - I'll say - &quot;reasonably&quot; fast. For my small-ish project it took maybe 3-4 seconds for each reload as I worked. In general this never bothered me until I started working on design type stuff and it got a bit frustrating when I screwed things up.\nThe command line will broad any console.log messages but I wish it would differentiate it a bit between it's own output as well. Here's a random example and while I know where my messages are, I'd like to see it called out more. (And yeah, it's way too small to even read. Sorry. But I haven't included a picture yet and it's way past due.)\n\nBefore I leave this section, a quick note. On multiple occasions I found that if I left the CLI running over night, in the morning it didn't seem to refresh well. I just CTRL-C the CLI and ran it again and everything would be fine. I'm assuming something just got lost between the terminal and the Android simulator. If I were a betting man, I'd totally blame Android.\nThe GUI app you think you don't need but you should try it anyway\nSo yes, I know we're all &quot;real&quot; developers and we have to use the CLI for everything, but you may want to check out the Sidekick application. This is a desktop GUI that wraps the CLI operations and lets you quickly generate new projects and test them. It also does a great job of rendering information about your project like installed plugins and other settings.\n\nEven more impressive, it can handle building to your iOS device... from Windows. In my testing this was a bit flakey. I know (or I'm pretty sure ;) it worked a few times, but I had trouble getting my last project working correctly. I'm going to assume it will work consistently though and that's pretty damn impressive.\nIf you want to learn more, you can watch this nice little video the NativeScript folks whipped up.\n\nOne oddity about the Sidekick is that while it has a &quot;logs&quot; output panel, you won't find console.log messages there. Instead, you want to ensure you select &quot;Start Debugger&quot;:\n\nThis pops open a new window and while still &quot;noisy&quot; like the CLI, it is a bit easier to read I think the terminal.\n\nThe Simplest Solution - the Playground\nSo the third option, and one of the easiest if you want to skip worrying about SDKs, is the Playground. This is a web-based IDE that lets you play with NativeScript without having to install anything on your machine. It even includes multiple walkthrough tutorials to help you learn. Even better, you can use the QR code feature (&quot;Yes!&quot; all the markerters yell) and a corresponding app on your mobile device to test out the code. Oddly - you need two apps on your device and their docs ",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Creating a Slide Show for Pinterest Boards in Vue.js",
		"date":"Tue Oct 09 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1539043200,
		"url":"https://www.raymondcamden.com/2018/10/09/creating-a-slide-show-for-pinterest-boards-in-vuejs",
		"content":"I've avoided Pinterest like the plague because I absolutely hated the way they made you sign up just to view basic content. But a good friendly recently got me interested in and I decided to sign up myself. I'm not doing anything terribly interesting with it but I've decided to give it a shot. The friend recently reached out to me to ask if I knew of any way to create a slide show from a Pinterest board.\nFor those who don't use Pinterest, &quot;boards&quot; are simply collections of items. Pictures, text, etc. I did some quick Googling and I couldn't find anything recent that was helpful. This friend was pretty smart, but also not technical, so I thought it might be cool to build something from scratch using Vue.js. All public Pinterest boards have a RSS feed, so all I needed to do was parse the RSS and then show one item at a time. If you just want to play with the tool and don't care about the code, go here:\nhttps://codepen.io/cfjedimaster/full/yRVYJa/\nThere isn't great error handling yet so - um - don't screw up? Ok, so how did I build this? I began with a simple Vue app that had an initial screen to prompt for your username and board name:\n\nAfter entering this information, the code parses the RSS found at:\nhttps://www.pinterest.com/USER/BOARD.rss/\nFor my RSS parsing, I used Feednami, a service I first reviewed way back in 2015. It still works well and was pretty much a no-brainer.\nOnce loaded, I then inject the HTML of each item in the view, wait six seconds, and then go to the next one.\n\nI could have added a bit of CSS, but I kept it simple. Let's begin by taking a quick look at the HTML.\n\nI assume there isn't much here interesting, but I can say the transition bit was difficult for me to get right. No matter how many times I use transitions in Vue I still struggle with it.\nThe JavaScript is rather short too:\n\nOnly real cool part (in my opinion) is the feednami integration, and it's interesting mainly due to how simple it is. Simple is good! You can find the complete CodePen below for your enjoyment. From what I know this was something my friend wanted for her kids so to me - it was time well spent!\nSee the Pen Pinterest to Slide Show with Vue by Raymond Camden (@cfjedimaster) on CodePen.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Storing (and Retrieving) Photos in IndexedDB",
		"date":"Fri Oct 05 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1538697600,
		"url":"https://www.raymondcamden.com/2018/10/05/storing-retrieving-photos-in-indexeddb",
		"content":"Hey folks - welcome to the first post in October! Yep, I'm still going rather slow when it comes to posting. Looking for a new job has thrown a monkey wrench into my creativity a bit so I apologize for the lack of content here. I worked on something a bit interesting today so I thought I'd share it here.\nI'm working with a client who has a Cordova application that makes use of the camera as well as the device file system for storage. I'm adding some code to handle storing form data, and associated pictures, in a queue for posting to a server while the device is offline. I decided to avoid, like the plague, any additional use of the file system and instead see if I could use IndexedDB (IDB) instead. IDB has pretty decent support now (thank you Apple, really, thanks) and also has good support for storing binary data. I decided to whip up a quick web demo so that I could test on my Android device and see how well it would work. What follows is my test, which is totally not production ready code (and this is why I fail the Google tests), but I hope it's of use to others. I'm going to share bits of the code base and explain them and then at the end I'll share the entire file. Again though - use with caution.\nAs a quick note, I wrote a good (imho) book on client-side storage as well as a video version. But they are both a bit old now. Instead of buying the book (although I won't stop you), I suggest reading the MDN guide: Using IndexedDB. As with everything on MDN, it kicks ass, and it's what I used today to refresh my memory.\nStoring Photos\nTo work with photos, I used a simple input field with the capture attribute:\n\nIf you've never seen this before, just a reminder that HTML is full of awesomeness and that you don't always need JavaScript to do cool stuff. I could have added some more to this tag to restrict the selection to images (which, of course, is not something your server should rely on since devtools can tweak that) but I was being lazy. I wrote about this more way back in 2016: Capturing camera/picture data without PhoneGap - An Update\nI added a change handler to this field so I'd notice as soon as a picture was selected:\n\nOk, so for my IndexedDB system, I set up the following code to initialize the database and objectstore. This is a bit &quot;mixed&quot; up a bit for simplicity and as a reminder, I'll share everything in one file below.\n\nIf you're new to IndexedDB I assume more of this makes sense, but feel free to ask me in a comment below if not. The last bit where I define the object store, I've told it to add an id field and auto number it for a primary key.\nAlright, so let's look at storage:\n\nThis is the change handler for the input field. Note that I don't notice a change from &quot;I picked a file&quot; to &quot;I cleared a file&quot;, but as I said, this is a quick test. I grab a handle to the file, create a FileReader, and then read the binary data. As you can see by the commented out line (which I normally remove from blog posts), I initially used readAsDataURL which returns Base64 string. In theory, binary data is smaller but I think you could use either. The only real difference would be in how you handle the data later. In my demo I re-display it on screen and that makes a difference. If you are storing it to the server via a POST operation, then your server-side code would need to handle it differently as well.\nWhen I've read in the binary data, I create an object with two fields, a created field and the binary data. In my real app, I'll have a bunch of form data too. I then open a transaction to the IndexedDB database and store my file. As I said, I'm a bit rusty with IDB but oh my god do I love the simplicity. (And if that still looks complex to you, there's multiple libraries out there like Dexie).\nOk, so as I said, my intent was to load and POST this data, but for my test I decided to just render it in the DOM. I added a small form and blank image:\n\nI added a click handler to that button with the idea that you would enter the PK of the data to load. I'm using Chrome and their DevTools for IDB are incredibly well done.\n\nNote that you have to return the binary data to base64 for rendering, that's the btoa part at the bottom there. That's one of those functions I never use until I find it some random StackOverflow question. I also totally read it in Maui's voice:\n\nAnd it works. I tested on the desktop and on mobile Chrome on my Android device.\n\nThat may be a bit hard to see, but in case you didn't know, Chrome can &quot;remote debug&quot; Android devices connected via USB. You can open URLs via the desktop, open dev tools, and even get a screen shot of the browser. It's damn handy and while not new, it's a great tool to have at your disposal.\nOops! I forgot to include the entire script. Here ya go!\n\nHeader photo by Samuel Zeller on Unsplash\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Quick Look at Stitch",
		"date":"Wed Sep 19 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1537315200,
		"url":"https://www.raymondcamden.com/2018/09/19/a-quick-look-at-stitch",
		"content":"As a developer, it can be quite overwhelming keeping up with all the cool platforms and technologies available to you. The flip side of that coin is that sometimes you discover really cool stuff and frankly you don't mind that you're a bit late. For example, a few days ago I was introduced to Stitch by MongoDB. This is a quite large product so I'm not going to over every detail, but it has some incredibly cool features that I'd like to share.\nAt a high level, Stitch covers three main functions:\n\n&quot;Stitch QueryAnywhere&quot;, which is a fancy way of saying a simple way to access your MongoDB from various development platforms. I was seriously impressed by how this worked and you'll see a demo in a moment. Given that a lot of my work in the past has been &quot;provide access to a backend database&quot;, anything that makes this simpler is a really good thing.\n&quot;Stitch Functions&quot;, a serverless function as a service feature. In some ways this is a basic FaaS option but they provide some neat integrations here that make it stand out from others.\n&quot;Stitch Triggers&quot;, which is basically a way to write functions that happen on certain events with your database.\n\nNow along with all of this, you may have missed that they launched a cloud-based MongoDB service called Atlas. I gave it a try as well and it worked well enough for me to consider not worrying about running MongoDB locally again. It has a good free teir as well so that's a plus. For the demo code I'm going to share below, I used Atlas to quickly create a MongoDB collection of cats, because cats:\n\nThe interface pretty much just worked as you would expect, but I liked that they had a &quot;Clone Document&quot; feature as it made it easier to create sample data.\nLet's first take a look at QueryAnywhere. This is a set of SDKs (JavaScript, React Native, Android, iOS, Node) that provides access to CRUD (read, write, update, delete, filter too of course) your data via a simple library. There is a full authentication system that handles anonymous users as well. Basically they make it easy to set your rules up (anyone can read, this type of user can add, etc) on their side such that your client side code is safe. Here is an example of the configuration I did for my Stitch app to allow anonymous users (default) to read my important cat data:\n\nOnce you have your data, and have setup access, their portal provides sample code for JavaScript, Android, and iOS (Swift) so you can quickly copy and paste in stuff to get going. Here is a simple Vue application I wrote:\n\nThe first two lines set up my initial connection and specify that I want to work with cats. In my created hook, I do an anonymous login, find all cats, and then simply copy them to my Vue data. The front end just lists this out and if you want to see it in action, check out the CodePen below:\nSee the Pen Sitch/Vue ex by Raymond Camden (@cfjedimaster) on CodePen.\n\nNote that I did have to include one additional script tag to load their library.\nOn the serverless functions side, what really impressed me here was the work done to make it easy to integrate with MongoDB. I guess that shouldn't come as a surprise, but given how nice it is to use a serverless function for easy access to a database, it's definitely appreciated. So for example (and I'm stealing this from their sample code):\n\nThey also make it easy to reach out to other serverless functions (ditto the stealing):\n\nThey have a decent online editor with support for running tests as well. (Hopefully this won't be too tiny!)\n\nThe only weird awkward thing I found here was exposing a function over HTTP. For that you need to add a HTTP Service and it wasn't immediately obvious to me that that was the way you would do it. For my cats I whipped up this quick service:\n\nAnd if your curious - you can see the result here: https://webhooks.mongodb-stitch.com/api/client/v2.0/app/app1-togyt/service/httptest/incoming_webhook/webhook0?arg=ray&amp;x=1. (Spoiler - it's cats.)\nAll in all, I'm really impressed by Stitch and I plan to play more with it. I'd love to hear from my readers that are using it so please drop me a comment below if you've given it a spin.\nHeader photo by Clem Onojeghuo on Unsplash\n",
		"tags":[
	        
            "stitch"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My Next Online Presentation - Serverless with Webtask",
		"date":"Wed Sep 12 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1536710400,
		"url":"https://www.raymondcamden.com/2018/09/12/my-next-online-presentation-serverless-with-webtask",
		"content":"Just a quick note to let folks know that I'll be doing another online presentation next month (and in November as well) covering how to get into serverless using Webtask. While Webtask is used for the platform, it's more meant to be a general introduction to serverless and I think you'll be able to take what you learn here and apply it to other platforms as well.\nThe next course will be October 8 at 11AM CST. You can find details here: Learn Serverless Application Development with Webtask\nThere will be a second one held on November 13th as well. Here is the outline, but note that it may change as I develop the course.\n\nOUTLINE\n\nSegment 1: Wrapping Your Head Around Serverless (30 minutes)\nDefining Serverless\n\tWhat it is\n\tWhat it isn’t\n\tWhere it makes sense\n\tWhere it *doesn’t* make sense\n\tOptions (IBM Cloud Functions, Google Cloud Functions, AF, Lambda, Webtask)\n\nSegment 2: Using Webtask (30 minutes)\n\tSigning up\n\tOnline editor, CLI\n\tBasic function form\n\tContext form\n\tHTTP control\n\tExercise 1 – Pig Latin app\n\tBreak – 10 minutes\n\nSegment 3: Deeper into Webtask (Context) (30 mins)\n\tContext object\n\t\tQuery param\n\t\tBody param\n\t\tHeaders\n\tSecrets\n\tMeta\n\tExercise 1 – Pig Latin (HTTP Post version)\n\nSegment 4: Storage (30 mins)\n\tThe storage object (when it makes sense and when it doesn’t)\n\tThe API\n\tVisual editor\n\tExercise 1 – Score reporter (records score, total, average)\n\tBreak – 10 minutes\n\nSegment 5: NPM Modules (20 mins)\n\tHow to add (CLI or visual editor)\n\tUsing request/request-promise\n\tExercise 1 – Building an API wrapper\n\nSegment 6: Middleware (30 mins)\n\tWhat it is and why you would use it\n\tAn example for security\n\tDescribing compilers and showing an example\n\tExercise – Add security to API wrapper\n\tBreak – 10 mins\n\nSegment 7: Couldn’t think of a title (30 mins)\n\tTesting locally (tip on skipping the func and using Node as a script)\n\tDebugging locally\n\tLogging (visual editor)\n\tResources (learning more about webtask, serverless in general)\n\tFinal Q/A\t\n\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My First Azure Function App - Twitter Image Displayer",
		"date":"Sun Sep 09 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1536451200,
		"url":"https://www.raymondcamden.com/2018/09/09/my-first-azure-function-app-twitter-image-displayer",
		"content":"So it's only taken me two months or so but I've finally built a real(ish) application using Azure Functions as a back end. I did warn it was going to take a little while and well - yeah - I was right. I've played around a bit more with things and I finally got to the point where I felt like I could build something. Before I could build my application, there were a few more things I had to figure out.\nAdding NPM Modules\nAdding NPM modules at the Azure Portal is pretty simple if you simply open the console. This is at the bottom of the screen:\n\nIt's a console like any other terminal and I simply typed in npm i X to install whatever package I needed. NPM complained about a missing package.json and I suppose I could have created that first, but it worked fine enough so I left it alone.\nMy assumption is that if you use the 'zip deploy' feature of the command line (as I described in this earlier post) then you would need to include node_modules in your zip or run the deployment in the command after deployed.\nI'm still figuring this out - but for now I know how to get NPM modules in and that's all that matters.\nWorking with Secrets\nMost serverless platforms (well, at least the ones I've worked with) have an idea of &quot;secrets&quot;, or a way to define at the function level a value you want to keep out of your code. API keys are a great example of this. Instead of directly pasting in some particular key into your code, a secret lets you reference a variable instead.\nFrom what I can see, the best way to use this feature in Azure Functions is via Application Settings. Now I've mentioned this before, but one of the things Azure Functions does out of the box is group your functions into an application. The more I use this, the more I like it. It feels like a good, logical way of grouping together serverless functions. One of the aspects of that with Azure is the ability to specify different application level variables. You can get there in the UI by clicking on the root note of your application and then simply scrolling down to the list of variables. You can add or edit as you see fit.\n\nIn the screen shot above, hopefully, you can make out a few variables that begin with twitter_, these are the custom values I made for my app. Once added, you can then reference them via process.env.X where &quot;X&quot; is the name of the setting.\nSetting Up CORS\nThe final bit I had to nail down was setting up CORS. I had already create an anonymous endpoint (see details on that in my blog post here) but CORS was done via &quot;Platform Settings&quot;:\n\nThis opens a panel where you can configure which hosts are allowed to make remote HTTP calls (via a browser anyway). In my case, I set it as * to make it as easy as possible.\n\nOk - finally - what the heck did I build?\nThe Application\nSo a bit over two years ago, I wrote up a blog post (&quot;Getting Images from a Twitter Account&quot;) where I described a Node.js application that let you view the images from a Twitter account. I follow (and have built) Twitter accounts that post random pictures from time to time. My current favorite is One Perfect Shot which shares stills from movies.\nI decided to take that vanilla Node.js function and convert the logic into an Azure Function. The code was relatively simple:\n\nI use the twit NPM library to do my searching. I look for an account value in the query string to determine which Twitter account to &quot;scrape&quot; for images. Also note the use of filter:media to ask for Tweets with images in them.\nOnce I get my results, I loop through them and add just the image URLs. That gets stuffed into an array that I then return.\nAnd that's it. The entire back end. Short and sweet.\nFor the front end, I whipped up a quick Vue.js project using Vuetify (which frankly felt like overkill). You can see the front end up on GitHub (https://github.com/cfjedimaster/webdemos/tree/master/tweetimages) but I'll share the app logic here:\n\nBasically I just run my serverless API based on a query string value and add the results to an array used by the display code. I quickly sent this online via Surge and you can see it here: http://black-and-white-frog.surge.sh/?account=oneperfectshot. Just change the variable at the end if you want to try another account. On the likely chance I overrun my API limits, here's a screenshot:\n\nWrap Up\nAnd I'm done. Well, no, not even close, there's still a heck of a lot more to Azure Functions, but I finally got to a point where I could build a simple demo and I'm pretty happy with the result. I do want to investigate the Visual Studio Code extension more soon as I'm hearing that may be the &quot;preferred&quot; way of working with Azure Functions and I'd also like to try integrating in with other Azure services as well. I do hope this was helpful and if you have any questions, just drop me a comment below!\n",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An Update",
		"date":"Wed Aug 29 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1535500800,
		"url":"https://www.raymondcamden.com/2018/08/29/an-update",
		"content":"A few months ago I posted about the loss of my wife. I thought I'd give folks an update and share some good news.\nThe last few months have been extremely difficult, but I am making progress. Thanks to the help of family and friends, I've been able to go from &quot;going through the motions enough for the kids&quot; to &quot;starting a new life&quot;. Early on I decided to see a therapist and that has been - easily - the best decision I've made in a long time. If you are going through any kind of grief, or other mental issue, I cannot recommend therapy enough. It isn't the type of thing where you go to a few sessions and are &quot;cured&quot;, but rather, my therapist helped me come to terms with my feelings and - most importantly - provided some direction and tools for me to move forward. I have to that on my own but just having a path forward is so damn critical.\nMy kids are happy and healthy - I've got my workspace back (thank God for school and daycare) - and in general I'm in a good place now. Not great, but good, and one thing I've learned to appreciate is just how damn good that can be.\nAfter some long thought, and talks with my therapist, I've decided that I'm going to return to travel. Not as much as I had before, but I'm going to give it a shot and see how well it works. I've got a great nanny (my niece) and my kids are in a good routine, so I feel like this is the right decision. I love speaking. I mean, I'm actually pretty darn shy and if you've spoken to me at a conference you know this. But I launched this blog years ago as a way to help others and I love sharing knowledge (and learning from others) at conferences.\nAnd.....\n\nI'm happy to say I've got not one but two upcoming speaking enagements coming up. I'll be speaking at a joint meetup between the Google Developer Group and Women Techmakers in Atlanta in October - &quot;An Introduction to Webtask&quot;. I'll then be speaking at Connect.Tech on PWAs (Progressive Web Apps). I am beyond excited to be able to do this and I hope it works out well.\nSpeaking of helping out - as you can tell my blogging has slowed down quite a bit. Frankly it's a bit difficult with my schedule these days, but I'm slowly adjusting and finding my creativity slowly returning so I hope to see an upswing in posts here.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "HTTP Stuff with Azure Functions (and more)",
		"date":"Mon Aug 20 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1534723200,
		"url":"https://www.raymondcamden.com/2018/08/20/http-stuff-with-azure-functions-and-more",
		"content":"It's been a few days since I wrote up some more about Azure Functions. My time to play with it has been somewhat limited with the kids starting school and me wanting nothing to do with my laptop at night - but despite that I've done some more digging and found some more cool stuff. As I stated last month, it was a rough start, but now that things are clicking I'm finding more and more coolness with the platform that I think is worth mentioning. As always, my goal here isn't to replicate the docs, but just point out what I think is neat!\nIn this entry I want to focus on the various ways you can execute your Azure Function over HTTP. In my first blog post I shared that you get an HTTP end point out of the box if you create a function from their quick start. This sets up a setting under the &quot;Integrate&quot; part of your function. You can see it here with the default settings.\n\nWhen you ask for your URL, you end up with something like so:\nhttps://rcamden-azurefunctions.azurewebsites.net/api/HttpTriggerJS1?code=Jf2lYDGQCXwOoplK52aNyEbsLrL4wku69PVP1RCwNsq1qiT60aOZ4Q==\nThe code value in this case is a key that has 'function' level access. In theory it would be fine (as far as I know) to share in a client-side application, but it probably isn't ideal. Luckily you can tweak this quickly enough.\n\nIn the screen shot above I've changed Allowed HTTP methods to &quot;Selected methods&quot; and then restricted the allowed methods to GET only. I've set the Authorization level to &quot;Anonymous&quot; so I no longer need to share a key. And finally, I added a ROute template value of &quot;cats&quot;. Now I can use this URL:\nhttps://rcamden-azurefunctions.azurewebsites.net/api/cats\nWhich, by the way, works, although I'm playing with the code so it may not work in the future. Go ahead and hit https://rcamden-azurefunctions.azurewebsites.net/api/cats?name=ray, you know you want to!\nSo... that's rather simple and direct. If you want a deeper look, you can read about it at: Create a serverless API. I suggest doing so because they bring up two related features that are pretty bad ass.\nProxies\nSo when I think &quot;proxy&quot; and &quot;serverless&quot;, I think about how I can use serverless to create a proxy to a remote API. This is great for when you need to transform an API's result. Or heck, when you just want to hide that you are using service X in case you need to change to Y in the future.\nWhile you can certainly do that with Azure Functions, in this case they are talking about a proxy from one Azure Function app to another. You can see the UI for this in my first screen shot:\n\nThe docs walk you through creating a proxy from one app to another and it's fairly boilerplate, but it's a cool feature to have. I like the idea of creating one proxy and having the freedom to swap out the back end Function app later.\nMock APIs\nThis was is a bit interesting too. If I had to build an API and didn't have access to the data, I may simply do something along these lines:\n\nand then simply replace that hard code logic at a later time. That let's me get back to my front end app (or whatever) and continue to work. Azure Functions actually let you make use of proxies to achieve something similar. Without touching your real code at all, you can set up what they call &quot;overrides&quot; to return static data. Here's an example I stole from that same doc:\n\nNifty! I still think I'd probably build a 'shell' function with hard coded logic but this is a great option as well.\nWhat's Next?\nOk - while there are still a lot of features I need to get into, the main thing I want to figure out next is how to use npm modules. After that, it's all details honestly. Once I get that figured out, I'll then build a &quot;real&quot; if simple application and talk about the process, pain points, etc. As always, let me know what you think!\n",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Node.js for the Non-Node.js Developer",
		"date":"Fri Aug 10 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1533859200,
		"url":"https://www.raymondcamden.com/2018/08/10/nodejs-for-the-nonnodejs-developer",
		"content":"Quick note: When I first wrote this article, it was entirely meant for ColdFusion developers who were interested in learning Node. After talking to my buddy Brian Rinaldi, he felt it would be useful for folks interested in learning Node in general. So I changed the title and URL, but kept the rest of the text as is. I think he is right and if you're doing web dev in any server-side language, I think this might be very useful helping you grok some of the peculiarities in how Node works!\nLast year at what is - most likely - the last cfObjective, I gave a presentation on Node.js specifically for ColdFusion developers. As a former (except for some minor side work from time to time) ColdFusion developer myself, I shared what I liked, what I didn't, and what confused the heck out of me when learning Node. My intent was not to provide an &quot;intro to Node&quot;, since so many of those resources exist, but rather to focus on the particular things that stood out to me when I was learning. I am still learning Node and probably have a good decade ahead of me before I consider myself an expert. But I'm certainly happy I've made the switch and I'd like to help others as well. I've been meaning to create a written version of my slide deck for some time, and when a reader emailed me a few days ago asking about Node from a ColdFusion perspective, I thought it was time to get off my rear and actually do it.\n\nWhat this isn't...\nTo be clear, this is not meant to be an attack on ColdFusion. ColdFusion provided an income for my family and I for many years. For a long time it was one of the most powerful, most practical, and most easy platforms to use. I have certain feelings about how Adobe is running the platform and I have certain feelings about whether it makes sense for people to start using ColdFusion, but that isn't the point of this. Feel free to hit me up privately, or heck, in the comments, and I'll be glad to share. But if you are making your clients happy and putting food on the table with ColdFusion, by all means, carry on!\nAlso note that JavaScript, and Node, is not a perfect language/platform. While I've had frustrations with CFML in the past, ColdFusion developers should be prepared to deal with the... idiosyncrasies of JavaScript as well. Go visit wtfjs.com sometime for a good example of how JavaScript can surprise you from time to time. Personally my favorite issue with JavaScript, and this isn't an oddity or bug at all, is forgetting when a value is a string and performing some arithmetic operation on it. That's easily correctible but something that trips me up still today.\nSo what is Node?\nOk, Node.js experts (and yes, I go back and forth between Node and Node.js, sue me), please do not get too angry here. I'm going to define Node in a way that made sense to me when I learned. There are better, deeper explanations, but I want to keep this simple.\nNode is - for all intents and purposes - using JavaScript on the server. It was created in 2009 (although Netscape had an earlier version no one seems to remember) and is powered by V8. Not the drink (although that would be awesome), but Chrome's JavaScript engine. It is open source (ahem, Adobe) and supported by a lot of large companies. Basically you don't have to worry about it going away or having a huge price jump.\nIt is the JavaScript you are used to, warts and all, although in a different environment. So doing things like $(&quot;someFormField&quot;).val() doesn't make sense. You aren't writing code that runs in a browser but code that runs in a server. On the flip side, you have access to the file system of the server and can do file system CRUD and database operations to a server.\nLike ColdFusion, the end result of calling a Node server is some kind of text or binary output. Ie, HTML, JSON, dynamic images, etc.\nSo yea! A lot like ColdFusion!\n\nExcept...\nNode is very bare bones. Out of the box, you don't get a web server. Don't get anything like &lt;cfquery&gt;. Not much of anything really related to web development, and that's fine. Node isn't just a web platform (more on that later). But the good news is that you can build anything you want. And people have. Lots of people.\nNPM, or Node Package Manager, makes it easy to install utilities. Hosted at npmjs.org, the NPM tool not only lets you search for code but also install it and any dependency. What's cool then is that if you install some utility Foo, and it needs Booger, and then later install Goo which also needs Booger, NPM will be smart enough to recognize this and not download it again. This ability has been a huge missing piece of ColdFusion since - well - day one. You have it now with CommandBox which is good, and frankly the people behind it (Ortus Solutions) are probably the best thing to happen to ColdFusion ever.\nUnfortunately, while having NPM is awesome, it can also be overwhelming. You search for something like &quot;rss&quot; to add RSS parsing and you may be over a hundred results. That's..",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript",
            
                "coldfusion"
            
		]

	},

	{
		"title": "Adding Your YouTube Videos to Your Static Site on Netlify",
		"date":"Wed Aug 08 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1533686400,
		"url":"https://www.raymondcamden.com/2018/08/08/adding-your-youtube-videos-to-your-static-site-on-netlify",
		"content":"Earlier this month I wrote up a blog post demonstrating how to use client-side JavaScript to render a list of your YouTube videos on your site (&quot;Adding Your YouTube Videos to Your Static Site with Vue.js&quot;). This works well and didn't use any &quot;fancy&quot; JavaScript that would be problematic for older browser (ok technically fetch is a bit modern but you could have replaced that with any other HTTP call) but I was curious to see if there was a way to do within my static site built with Jekyll.\nWhile it would be possible to build a plugin to do HTTP calls to generate output, I didn't want to pursue this route because, well, Ruby, and then I remembered something Phil Hawksworth had demonstrated a while back. Phil is a developer advocate for Netlify, the service I use to host and deploy this site. I've raved about them often on this blog (and on Twitter) so I'm somewhat of a fanboy. Phil wrote up a great article about how he added comments to his static site: Adding a Static Comments System to My Jekyll Build.\nHis idea was pretty brilliant. He wrote a Gulp script to get his comment data via a simple API, wrote it out to a Jekyll data file, and then when his site is built the data file is used to drive the content for his comments. Netlify lets you specify a build command when deploying and normally I just use jekyll, but by using a Gulp script, you could do a lot more.\nHere is a quick example of this in action. I did not do this for my own site as I was a bit chicken, so I simply generated a default new blog for Jekyll at the command line. Next, I wrote my Gulp script. (I haven't written Gulp scripts in years, so forgive any mistakes. Anything here good is from Phil's original script.)\n\nThe important bit here is the get:videos task. I use a RSS parser from npm called, wait for it, rss-parser, which worked well except it didn't seem to pick up one of the properties my own code did in the client-side version, specifically videoId which is used in the embed. Luckily I could get it manually as you see here:\n\nI especially like how easy it was to output YAML for Jekyll:\n\nOnce it's written out as a data file then it's easy to use in a template:\n\nThe final step is specify the gulp task in my Netlify settings for my site:\n\nAnd that's it! If you want to actually see this, you can view the live site here: https://fervent-beaver-e5dc28.netlify.com/videos/. The source code for this demo is here: https://github.com/cfjedimaster/jekyllnetlifydemo\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Testing Local Development with Azure Functions",
		"date":"Fri Aug 03 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1533254400,
		"url":"https://www.raymondcamden.com/2018/08/03/testing-local-development-with-azure-functions",
		"content":"Hey folks! So obviously I'm taking this Azure Functions thing a bit slowly. Not that folks care (probably ;) but I'm 10 days away from having my older kids in school and my youngest in day care which means 9 glorious hours of quiet time in my home office to really churn stuff out. In this post, I want to talk about how you can write Azure Function code locally and deploy to Azure for testing.\nTo be clear, this is not the same as doing the complete development locally. Azure supports this via another toolset called the Azure Functions Core Tools. I plan on trying that in the next few posts, but I wanted to try the approach of local code writing and Azure deployment first.\nI think an argument can be made that this may not be the best development process for Azure Functions. It's how I did stuff in OpenWhisk and Webtask so it's familiar to me, but I'm trying to keep an open mind about things here and recognize that maybe I need to adapt how I do serverless with Azure. I did get it running, but with a few issues that were (mainly) my fault.\nLet's get started...\nThe CLI\nFirst off, there is a Quickstart specifically for working with the CLI: Create your first function using the Azure CLI. However, this is bit misleading in my mind. They walk you through installing the CLI (more on that in a second), but the actual deployment comes from a Git repo. That's absolutely a valid way to deploy code, but while learning, I'd hate to have to push to Git and then deploy from Git every time I tweak something. I basically ignored this concept and filed it away as something that would be good in a CI (Continuous Integration) process later on.\nSo about that CLI... while following the directions (Install Azure CLI 2.0, I ran into issues that were entirely my fault. While I love WSL (Windows Subsystem for Linux), I'm really a shell newbie. I had changed my shell a while back to Fish and the directions given by in the CLI installation did not work for me. Again - that is my fault. Here is a specific example. The docs say to do this:\nAZ_REPO=$(lsb_release -cs)\nAnd for me, I had to run lsb_release -cs by itself, note the output (&quot;xenial&quot;), and then run set AZ_REPO xenial.\nOnce I got past that though I was mostly ok. The basic az login command didn't work well for me and I had to use az login --use-device-code instead.\nDeploying Locally\nOk - so can deploy local code? Yes! You want to use what is called Zip push deployment instead. This is covered later in the Azure Functions docs (Under the &quot;How-to guides&quot;) section and is probably a hint for me to maybe actually read all the docs before I write another line of code. But... yeah... I like to write code while I learn. ;)\nIn order to deploy this way, you have to create a zip file first. To be honest, it seems silly that the CLI can't do that zip for you, but it isn't too much of a hassle and as I'd make a shell script for this anyway, I can deal.\nYou also need to have a proper file configuration for your code. What do I mean by that? For OpenWhisk and Webtask, I write my function in some file, foo.js, and just deploy it. I only worry about other files when pushing npm modules and the such.\nAzure Functions actually have a more complex file structure. Not too complex, there's just a bit more going on. The docs do a great job of explaining the parts and showing an example:\nwwwroot\n| - host.json\n| - mynodefunction\n| | - function.json\n| | - index.js\n| | - node_modules\n| | | - ... packages ...\n| | - package.json\n| - mycsharpfunction\n| | - function.json\n| | - run.csx\nThere's a few things I want to point out here. First - Azure Functions are grouped into apps. This took me a while to realize but it makes perfect sense. Instead of deploying one particular function at a time, you work with apps instead. OpenWhisk didn't really have a concept of grouped functions. You did have packages, but in my mind, there were more appropriate for things you share, not really an &quot;app&quot; concept. I like this structure quite a bit.\nAlso note that each serverless function has a json file that represents the metadata of the function. While on one hand this means that each function has a minimum of two files versus one... I like this. Metadata in OpenWhisk is something you have to fetch via the CLI. Having it in a physical file makes it a bit easier to work with I think. I guess both approaches are good, but I like being able to quickly see (and check into source control) the metadata for my function.\nIf your curious what this looks like for a simple HTTP enabled example, here is one of them:\n\nI'm still learning Azure Functions (obviously) but I bet there is a heck of a lot more I can do in there.\nFinally - in order to make it easy to get this structure locally, don't forget you can export your function (like the one you made in the previous quick start) to a local zip.\n\nI downloaded my zip, copied one of the function folders as a quick way to make a new function, and decided to test the ",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Adding Your YouTube Videos to Your Static Site with Vue.js",
		"date":"Wed Aug 01 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1533081600,
		"url":"https://www.raymondcamden.com/2018/08/01/adding-your-youtube-videos-to-your-static-site-with-vuejs",
		"content":"Credit for this post goes to a discussion my buddy Todd Sharp and I were having. He's adding his YouTube videos to his site via server side code, and I thought it would be interesting to write up a quick JavaScript-only version of the code as well. While there are probably a bazallion libraries out there for this, I wanted to quickly mock up the idea with Vue.js for my own benefit. Also - while the JavaScript is pretty trivial, I've got a great follow-up to this post coming later in the week that shows a completely static (kinda!) way of doing this.\nGetting Your RSS URL\nSo the first step is getting your RSS url. My first attempt at Googling for the solution led to an older solution that was still helpful. If you go to your channel, for example, https://www.youtube.com/user/TheRaymondCamden, and then view source, just ctrl-f (Find) for rssUrl. You'll see something like this:\n&quot;rssUrl&quot;:&quot;https://www.youtube.com/feeds/videos.xml?channel_id=UC8KROrnEHSnnV3z5J_FoSIg&quot;\nAnd there's your URL. I would 100% bet that there is a simpler way of doing this, but this is what worked for me.\nParsing the RSS\nSo way back in 2015 (a long time ago...), I wrote up an article on parsing RSS with JavaScript: Parsing RSS Feeds in JavaScript - Options. My favorite option for this is still YQL. I'm shocked it is still around considering Yahoo seems to be - I don't know - not necessarily growing - but yep, it still works. However, the YQL I used for parsing RSS did not work for this RSS feed. Specifically:\nselect * from rss where url=&quot;https://www.youtube.com/feeds/videos.xml?channel_id=UC8KROrnEHSnnV3z5J_FoSIg&quot;\nI thought perhaps it was the equals sign, but escaping it didn't help. I tested quickly with my RSS to ensure the feature still worked in general, and it did, so I punted to just using simple XML instead. I don't mean XML parsing ala the first option in my earlier blog post, but grabbing the XML from YQL instead, which nicely parses the XML for you. The YQL for that is:\nselect * from xml where url = &quot;https://www.youtube.com/feeds/videos.xml?channel_id=UC8KROrnEHSnnV3z5J_FoSIg&quot;\nWorking with Vue.js\nOk, so now I know how to get and parse the XML from my video feed. For my first draft, I simply wanted to dump out each video as a raw object. I began with this layout:\n\nBasically this will print out a JSON version of each object. Now for the JavaScript:\n\nI've got a variable for my RSS feed and then one for the YQL URL. You can, in theory, simply change that first line to use your feed. I then do a quick fetch call to get the parsed XML. The result was slightly complex as you can see: res.query.results.feed. That was an array of objects with a key called entry. So to make my use a bit simpler, I make a new array of just that entry value. You can see the result below:\nSee the Pen Youtube video to HTML by Raymond Camden (@cfjedimaster) on CodePen.\n\nSweet!\nAdding the Pretty\nOk, so at this point, you have many options for how to display the videos. The result set includes thumbnails and links, so a non-interactive list of pictures would be fine. You could also embed a YouTube player for each video. The docs provide guidance on this, but the basic form is:\n\nDo note that they use autoplay=1 which you absolutely want to switch to 0 because autoplay is the devil, especially when displaying a bunch of videos! This is what I came up with to display the videos. It could definitely be better:\n\nNote that origin should change to your URL for additional security, but it worked fine as is on CodePen:\nSee the Pen Youtube video to HTML v2 by Raymond Camden (@cfjedimaster) on CodePen.\n\nLet me know what you think by leaving a comment below. As I said, I've got an interesting twist on this hopefully coming up later in the week!\nHeader photo by Noom Peerapong on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Stats Page for Jekyll Blogs",
		"date":"Sat Jul 21 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1532131200,
		"url":"https://www.raymondcamden.com/2018/07/21/building-a-stats-page-for-jekyll-blogs",
		"content":"Back when I ran this blog on Hugo, I built my own little stats script (A Simple Stats Script Hugo) to help me look at my blog at a high level. I converted to Jekyll a few months ago and recently I started work on rebuilding that support back into my site. What follows is my own little stab at creating a script to report on Jekyll-based blog content. All of the code I'll show below is up on my GitHub repo for this site and I'll share specific links at the end. This is just the beginning and I have some ideas for more stats I'd like to add, but I'd love to hear what you think so drop me a line below.\nFirst and foremost - I decided to did not want to use the same approach I had done for my Hugo script. In that one, I used a local Node script to generate a set of JSON data. I then copied that into a simple web app that used JavaScript to render the data. Instead, I wanted something that required zero manual work on my end. To accomplish that - I wrote two scripts.\nThe first one was a stats.json file that would use Liquid (Jekyll's template engine) to output the raw data. The second script was stats.md. This would use Vue.js to load in the JSON and then render it out. Let's first take a look at the JSON script.\nBefore I show the code, let me show the output:\n\nWhile it should be obvious from the names of the values, let me go over the stats:\n\nTotal Posts, total categories, and total tags. Of those three, only total posts is really interesting, but I do think it makes sense to keep track of how many tags and categories you have. Too many may reflect a lack of editorial focus.\nTotal words is 100% silly. Average words per post is definitely a bit more relevant.\nFirst and last post is interesting from a simple historical perspective.\nPosts per category and tags gives you an idea of where you focus your content.\nFinally - the list of dates. So this is where I ran into an issue with Liquid. I wanted to create an &quot;index&quot; that represented posts per year, month, day of week, etc. This turned out to be extraordinarily difficult in Liquid. Then I thought - what if I simply output all the dates and let the client handle it? I was worried about the size of the data but even on my blog with near six thousand entries the JSON only got to about 68K. I do have a lot of whitespace in my JSON (that I removed above) so there is room for improvement, but for now I'm satisfied with it.\n\nNow let's look at the script behind this:\n\nI begin by looping over every single post to gather up my word and data data. Once I have that, the rest of the content is pretty simple to generate. Do note that the first and last values for site.posts is reversed because site.posts is in reverse chronological order. (A big thank you to @mmistakes from the Jekyll forum.)\nSo that's the &quot;back end&quot; - although to be clear - when I publish my site this is run once and output as raw JSON. You can see the output here. Now for the &quot;front end&quot;:\n\n(Note - due to an issue of trying to render Liquid stuff to the browser in the source code, I renamed an endraw tag above to endrawx. It is correct in GitHub.) So this is a pretty trivial Vue app. I fetch my JSON and then just start assigning values. The only real work I do is to parse the dates. Right now I'm just rendering a &quot;per year&quot; stat, but I will probably add a &quot;per month&quot; and &quot;per dow&quot; table as well. You can view the output for my blog's stats here: https://www.raymondcamden.com/stats.\nIf you want the code yourself, you can grab both scripts here:\n\nhttps://github.com/cfjedimaster/raymondcamden2018/blob/master/stats.json\nhttps://github.com/cfjedimaster/raymondcamden2018/blob/master/stats.md\n\nSo, what do you think? Are there any stats you would add? Leave me a comment below!\nHeader photo by James Pond on Unsplash\n",
		"tags":[
	        
            "vuejs",
            
            "jekyll"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Quick Note on Azure Functions Returns and Results",
		"date":"Mon Jul 16 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1531699200,
		"url":"https://www.raymondcamden.com/2018/07/16/quick-note-on-azure-functions-returns-and-results",
		"content":"So technically I'm not really back at work. Last week I took the kids on a short vacation to Arkansas and this week (well early this week), my eldest is at orientation at the University of Alabama. His brother is doing a &quot;sibling program&quot; and the two year old and I are sitting in a hotel room slowly going stir crazy. Well he is. In my last post I had talked about the initial sign up experience with Azure Functions and creating my first serverless function with it via their tutorial. My plan was to continue along the tutorial which switches to the CLI but I found some interesting tidbits I thought I'd share.\nThe Return of XML\nSo I didn't notice this last time, and to be fair, the UI kind of mislead me. When I tested my function via the portal, this is what it displayed:\n\nWhen I tested the URL (and to be clear, I did test the URL last time, I just didn't notice) - the result was this (I added line breaks):\n\nYou can see this yourself here: https://rcamden-azurefunctions.azurewebsites.net/api/HttpTriggerJS1?code=Jf2lYDGQCXwOoplK52aNyEbsLrL4wku69PVP1RCwNsq1qiT60aOZ4Q==&amp;name=ray\nTurns out - the default result encoding for Azure Functions is XML. That is... surprising. To be clear, I'm not saying XML doesn't have it's place, but honestly, it feels like the only time I use XML now for new stuff is when calling some older service. I then immediately convert it to a more sensible format.\nBut ok - that's what Azure chose and I'm going to assume they have a good reason for it. Luckily, it's easy to work around.\nReturning JSON - Because it's 2018\nSo if you remember, my function from yesterday set the result value like so:\n\nTo specify a JSON response, you use the headers value in the result:\n\nYou can find a good reference guide for both the request and response values here: Azure Functions JavaScript Developer Guide.\nSo I took the initial function and made a slight modification. By default my code will return JSON, but you can request XML by using a query parameter. Technically I should be using the Accepts header and I could build support for that. But for a GET request, it's a heck of a lot nicer if I let my user simply pass a query parameter. In this case I'm choosing to make my user's life easier versus following REST specifications.\n\nThat error branch should also return an object but I'm keeping it simple for now. You can test this here: https://rcamden-azurefunctions.azurewebsites.net/api/HttpTriggerJS2?code=gQ9hykKhkaxVosuljfWQGC3p1UsshUzyhGG8OGEZTRC5kDXCUK0Wrw==&amp;name=weston. And if you really want XML, you can test that here: https://rcamden-azurefunctions.azurewebsites.net/api/HttpTriggerJS2?code=gQ9hykKhkaxVosuljfWQGC3p1UsshUzyhGG8OGEZTRC5kDXCUK0Wrw==&amp;name=weston&amp;format=xml\nA Bit More...\nSo hey - I randomly clicked on the Monitor aspect of my function to see what it showed. IBM Cloud Functions has a decent monitor page, but I did a lot of my own analysis of my functions to get the results I wanted. From the function, you get your last twenty results:\n\nThat's nice and simple. About the only thing missing here is an average duration value. If you click that link to open &quot;Application Insights&quot;, you get dropped into something that vaguely reminds me of the query browser in SQL Server:\n\nThat screen shot is probably too small but you can view a full size version here.\nThe query language is pretty dang sweet:\n\nI've never seen this before but it is pretty obvious how to modify it. You also get autocomplete while you type which makes it a bit easier to use. I found a good intro to the tool and from that I figured out how to get the average duration of my function:\n\nThere's probably a nicer way of doing that of course. I saved that query so I can use it again. And if your curious, the average (in ms) for my little test function was 169. Of course it really isn't doing anything so that isn't a real test. I'll be curious to see how Serverless Superman does there.\n",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My (Rough) Start with Azure Functions",
		"date":"Fri Jul 06 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1530835200,
		"url":"https://www.raymondcamden.com/2018/07/06/my-rough-start-with-azure-functions",
		"content":"As I mentioned a few days ago, I'm going to spend time this summer looking at Azure Functions. For my first look, I wanted to focus on what the sign up process was like. I already had an Azure account, but I wanted to start fresh just to see if things had changed or improved. One of the things I was concerned about specifically was the login process. While I've never sat down to document it precisely, I know I've had issues with logins on Microsoft sites before. Unfortunately, I ran into that again. It was frustrating, but I did get past it.\nOk - so to outline what I want to cover today - this post covers:\n\nStarting with Azure Functions\nSigning in with Azure\nMaking my first Function\n\nEverything here is documented so this will mainly be me sharing the URLs and screen shots of what I actually encountered.\nYou start off with Azure Functions at https://azure.microsoft.com/en-us/services/functions/.\n\nWhile I generally don't pay a lot of attention to the &quot;marketing home page&quot; for products, this one is rather nice and includes links to pretty much everything you may want to check out before committing to actually learning the tech. Two things in particular caught my eye here. First, they have a link to a cookbook that is free although it uses C#. I know squat about C# but as a cookbook, I think it would still be useful for just giving you an idea of the kinds of things you can build with the platform. At a bit over 300 pages, this is a great resource you should grab while your there.\nThe second thing you want to make note of is the link to pricing information. As expected, you get a really generous free tier. Currently that's one million executions and 400,000 GB-s of execution time. This all sounds good, but then you run into your first &quot;gotcha&quot; (although a minor one at that):\n\nNote—A storage account is created by default with each Functions app. The storage account is not included in the free grant. Standard storage rates and networking rates charged separately as applicable.\n\nThe storage account is - as far as I can tell - the space you need to actually store the files and resources for your functions. I didn't have to worry about this for OpenWhisk and for Webtask we simply limit you to 100K per function with no &quot;global&quot; limit. When I looked at the details for the storage plan I saw that they gave you a year of free use. I then used their calculator with the smallest possible numbers to estimate what I'd be charged as a developer just &quot;playing and learning&quot;, and from what I can see it would be about 10 cents a month.\nNow - that's way below trivial. I spend more on fancy beer. But it would be nice if Microsoft could simply eat this cost or have a free tier for like 100 megs of space. Basically - don't make me even think about it.\nBut yeah - that's a minor nit really. So now for the fun part - signing up for Azure.\nThe Quest for Azure\nTo ensure I was starting fresh with no cookies or other bits hanging around, I fired up Brave and headed to the home page at https://azure.microsoft.com/en-us/free/.\n\nAgain - a good page. I have a low tolerance for marketing but overall everything here was good. I will call out one thing I thought was a bit off. If you read over this page, you see this:\n\nCool. That's one thing IBM did well with their PaaS - nearly every single aspect of it had a free tier. It looks like Microsoft provides some free stuff which is ok (although I think IBM has the ideal standard here though). What bugged me though was this:\n\nMaybe I'm being picky - but &quot;free&quot; to me isn't &quot;free for a year&quot;. If you say something is free, the implication (to me anyway) is always free. Microsoft isn't hiding this at all, but it felt misleading. You scroll down past this block and then hit the really free stuff.\nIdeally what I want in a PaaS is the ability to play and use everything for free, within limits. If I go over those limits, shut my stuff off. Basically give me a sandbox. I don't think any PaaS actually does this but it would be nice.\nOk, so I start the registration process, and for the most part it's what you expect:\n\nYep, standard form. Then you begin handing over basic information.\n\nI'll ding Microsoft here for asking for my phone number earlier and not defaulting when asking for a verification. I'll ding me for looking at this form and having no idea what &quot;verification by card&quot; meant. I thought maybe it was a 'smart card' or something. Nope, a credit card. So yes,  you must give them a credit card to start using Azure. Again, I'll give credit to IBM here. They let you wait 30 days before doing that. I'm pretty sure Microsoft isn't going to steal from me so I didn't mind giving it here.\nExcept I couldn't. :\\ By default, Brave has some pretty tough ad blocking settings and it looks like no one at Microsoft tested their signup with Brave. (To be fair, as cool as Brave is I don't think the usage is very high.) I couldn't get to the credit card ",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Summer Plans - Looking at Azure Functions",
		"date":"Mon Jul 02 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1530489600,
		"url":"https://www.raymondcamden.com/2018/07/02/summer-plans-looking-at-azure-functions",
		"content":"I'm going to make two mistakes in this post. Well, mistake may be too strong a word. In general, I try to refrain from making &quot;plans&quot; as I almost always start off by promising to do more than I end up accomplishing. The second mistake is laying out a plan right before I go on vacation. I've already been somewhat slow here due to personal reasons, but I was thinking about this today and I thought it would be good to lay out some plans while giving myself a good amount of time (hey, &quot;summer&quot; could also mean Australian time too, right?) to get it done.\nWhen it comes to serverless, I started off spending my time with Apache OpenWhisk and IBM Cloud Functions. Now that I'm at Auth0, my primary focus is on serverless extensibility with Extend, powered by Webtask.io. I'm putting those blog posts up on our Extend blog: https://goextend.io/blog.\nSo over here on this dusty old blog, I thought I'd spend time looking at Azure Functions, Microsoft's serverless platform. I've only got some basic history with Azure as a platform as a whole and I have already played with Azure Functions a bit, but I thought I'd take a more systematic look at the platform. My questions/interests/etc. revolve around the following aspects:\n\nGetting started, specifically new accounts and how quickly you go from signing up to a &quot;hello world&quot; serverless function. I'm also curious what Microsoft/Azure demands up front in terms of signing up. I've got a few accounts already and one with access to stuff via friends at the company, but I'm going to use a new account for this testing. One thing IBM did kinda right, after a while, is allow you to sign up without a credit card. You had to provide one after thirty days, but you could at least get started without one. I'd love it if more cloud providers would make this a norm, and heck, simply toggle a setting of &quot;If anything I do is going to cost money, turn everything off&quot;.\nHow easily can I use my own editor and a CLI. I'm already using an editor that has tooling for Azure Functions but I really want to experience it &quot;bare&quot;. Webtask has an incredible online editor (ok, I'm biased), but in general I'm curious how the &quot;I have my own tools&quot; approach will work.\nOne thing IBM Cloud Functions did well is supporting other IBM Cloud services. I mean, at the end of the day, a service has a URL, username, password, and probably a Node library, but a platform can do a bit more to make easier to use services within it's own platform. So my question is - does Azure make it easier to use their own services within Functions.\nHow well does Azure Functions support non-HTTP executions. I know I focus mainly on building HTTP endpoints for client-side applications (and this is something Webtask also does incredibly well with the standard caveat of me being biased), but I'd like to see how easy it is to connect an Azure Function to some other source, either time based or related.\nLooking into reporting. Basically - how many times has my function run. What's the average execution time? Is one function creating a significant amount of errors? Can you pro-actively report that to me?\n\nSome concrete things I'd like to build to give me some &quot;exercises&quot; to accomplish:\n\nRebuilding Serverless Superman - a CRON based Twitter search and posting parody thing I built. (Which looks to be dead so I'll have to get that back soon.)\nRebuilding Random Comic Book which thankfully isn't dead.\nBuilding a simple form processor. Here is my post on doing it with Webtask.\nBuilding an image processor hahah no just kidding that's been done like ten thousand times.\n\nSo yeah - that's the idea. As I said, I'm giving myself plenty of time to work on this and as I always say, I'm open to suggestions so please feel free to give me some initial feedback below.\nHeader photo by Max Boettinger on Unsplash\n",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My First Stab at a Grammar Extension for VS Code",
		"date":"Mon Jun 18 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1529280000,
		"url":"https://www.raymondcamden.com/2018/06/18/my-first-stab-at-a-grammar-extension-for-vs-code",
		"content":"Let me start off by saying that this isn't necessarily the best Visual Studio Code extension out there and - frankly - it's probably near the bottom. But it's a beginning and probably the easiest experience I had building an extension yet. So what did I build?\nI've been using spell checking extensions in VS Code for sometime. (My current favorite is Spell Right). While the extension is good for catching spelling mistakes, it doesn't handle grammar. Grammarly has a pretty cool tool for checking grammar but doesn't have an API. When I asked about this on Twitter, the folks at LanguageTool chimed in to share that they did have an API and it was free to use. Their service is free (with a premium upgrade option) so I decided to give it a whirl.\nThe public API has a fairly simple endpoint. You pass in text, a language (or tell it to guess), and you get results. You can customize the results by turning on and off various rules though. Here's the entire extension, code-wise anyway:\n\nFrom top to bottom:\n\nI register a command, runLanguageTool, that the end user will run via F1.\nI attempt to get the text in the current document.\nI then do a bit of manipulation on the text with the assumption that it's probably Markdown and has front matter. I also render it to HTML and then remove that HTML in an attempt to get to the &quot;pure&quot; text. This has the side effect of not being able to report an issue and line it up with a line number, but I think that is ok.\nFinally, I render it using one of the newer VS Code extension APIs, the web view. This is a pretty powerful feature that supports communication back and forth, but for my usage I just render to HTML.\nAnd yeah, it's not terribly pretty.\n\n\nThat's probably a bit hard to read, so here is a copy and paste:\n\n\n1) Possible spelling mistake found\n...y I discovered the awesomeness of Slash Webtasks, an incredibly easy way to build your o...\nSuggestions: Web tasks\n\n\n2) Possible spelling mistake found\n...edibly easy way to build your own Slack integrations using Webtask. And while it truly is an...\nSuggestions: integration,integration s\n\n\n3) Possible spelling mistake found\n...build your own Slack integrations using Webtask. And while it truly is an awesome tool,...\nSuggestions: Web task\n\n\n4) Possible spelling mistake found\n...egration, especially when paired with a webtask, and I'd like to share a simple demo I ...\nSuggestions: web task\n\n\n5) Possible spelling mistake found\n...ining in a channel. We can use Slack's APIs to accomplish this with the following s...\nSuggestions: Axis,Apes,Apia,Avis,Apps,Apish,AIs,API,Pis,API s,ABIS,AFIS,AIS,AMIS,APII,APRS,APS,GPIS,PIS\n\n\n6) Possible spelling mistake found\n...llowing steps: Send every message to a serverless endpoint Take the text and analyze the ...\nSuggestions: server less\n\n\nIt could definitely use some improvement so I'd love folks to take a look at it. You can find the repo here: https://github.com/cfjedimaster/vscode-languagetool. And you can install it here: https://marketplace.visualstudio.com/items?itemName=raymondcamden.languagetool. Let me know what you think. As I said, this was - generally - a good experience working on the extension. I have not had the best experience with VS Code extension development. There isn't anything necessarily wrong with it, I just don't find it quite as simple as Brackets.\nOh - and I'm still trying out the whole async/await thing and I still freaking love it.\nHeader photo by Markus Spiske on Unsplash\n",
		"tags":[
	        
            "visual studio code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Transforming JSON Data into an API with Serverless",
		"date":"Fri Jun 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1529020800,
		"url":"https://www.raymondcamden.com/2018/06/15/transforming-json-data-into-an-api-with-serverless",
		"content":"This is something that has been sitting in my &quot;To Write&quot; Trello board for a while now and today I finally got around to building a demo. One of my favorite things to do with serverless is to build API wrappers. There are thousands of APIs out there, but many times you need to manipulate or change the data to make it more appropriate for your use. While you can do that on the client, it can be much more efficient to do so on the server. Of course, who wants to setup a server just to change an API when you can use a serverless function instead? Some examples of this are:\n\nTransformation: A few months back I used an API that was XML only. I used a serverless function to transform it into JSON since this isn't 1995.\nReduction: A while ago I made use of a music API that returned a lot of information. However, I only needed one small part of it. By building a serverless proxy that reduced the data returned, my client, a mobile app, received much less data and therefore was quicker for the end user.\nCombination: If one API doesn't cover what you need, two or more may, and you can use a serverless function to grab and combine those APIs. A good example of this is getting shipping information from multiple providers.\nProxy: And finally - just having your own end point means you can do things like server-side caching or even wholesale replacement of the 'real' API at any time in the future.\n\nFor my demo today, I'm doing something interesting I think. Sometimes an API isn't really an API, but just a data dump. It may update every now and then, but it doesn't support arguments. It's simply a URL that returns JSON. I thought it would be cool to show how you can build your own API in front of that JSON dump. I'll be using Webtask.io but obviously any serverless provider will do.\nFor my data, I'm using a JSON packet of Pokemon data here: https://raw.githubusercontent.com/Biuni/PokemonGO-Pokedex/master/pokedex.json. I found this dataset via jdorfman's awesome-json-datasets repo. The JSON data contains 151 different Pokemon. I have no idea how accurate that is. I've got kids who do but they aren't around me and I don't care enough to Google for it. ;)\nFor the first draft of my function, I simply returned the data. I also used a cache. As you know (or may know!), serverless is stateless. However, most serverless providers will keep your function &quot;warm&quot; (think active) for a short duration. That means repeated calls within a certain timeframe can make use of locally cached values for quicker results.\n\nThat's a fairly simple function. Basically return a cache or hit a URL and return that. No transformation, manipulation, or anything else. Again though I have some immediate benefits. If the server hosting the data is a bit slow, my cache can help with that. If the URL ever goes away, or if they decide to start charging for the data, I can potentially switch to another provider and my client's will never know. (And if the data changed, then I can change it back!)\nAlright - now let's add some filtering!\n\nI've added 2 possible filters here using the query string. (This is done via the Context object that all Webtasks have access to.) In both cases, I just do simple array based filtering. The only real &quot;work&quot; here is to lowercase stuff so you don't have to worry about matching the same case. My core API may be found here:\nhttps://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/eoApi\nAnd you can search by name, or type, or both. For example:\nhttps://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/eoApi?name=Ba\nOr:\nhttps://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/eoApi?type=ghost\nOr:\nhttps://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.sandbox.auth0-extend.com/eoApi?type=fire&amp;name=ca\nThat's it. Let me know if you have any questions by leaving me a comment below.\nHeader photo by Samule Sun on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Getting Notifications for New GitHub Project Releases",
		"date":"Wed Jun 13 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1528848000,
		"url":"https://www.raymondcamden.com/2018/06/13/getting-notifications-for-new-github-project-releases",
		"content":"This is one more post covering something that I assume most folks knew already. I just discovered this a few weeks ago myself. It definitely isn't new, and in fact, you can find a StackOverflow answer covering this from two years ago. But as I usually do - I share what I learn since I figure I can't be the only one who missed this.\nSo as the title says - how can you get notifications for new releases of a project at GitHub? Turns out every repo has an Atom feed available at:\nhttps://github.com/(org or user)/(repo)/releases.atom\nSo for example, here is the feed for one of my favorite new projects, VuePress:\nhttps://github.com/vuejs/vuepress/releases.atom\nAnd that's really it. Of course, the 'notifications' aspect involves reading the Atom feed and figuring out when something new has been added. Luckily, IFTTT has a service for this. If you've never used it, IFTTT is essentially an &quot;automation&quot; service for the web with hundreds of plugins/connections/etc for various services. One of the simplest solutions they have is a &quot;email me on new item in a RSS feed&quot; service. That works great for blogs that don't have a subscription feature and it works great here as well.\nSimply create a new applet and on the first step, select RSS Feed:\n\nThen select &quot;New feed item&quot;:\n\nAnd then paste in the feed URL:\n\nClick &quot;Create trigger&quot; and then you move onto the &quot;that&quot; aspect (i.e. what should happen when a new item appears in the feed). On the action service page, select email:\n\nThis only gives you one option (so I'll skip the screenshot), &quot;Send me an email&quot;. You can now customize the email. I suggest adding a prefix to the Subject to make it obvious what is going on:\n\nYou can customize more there too if you want. Finally click &quot;Create action&quot;. You'll get to the review page and you want to make sure you then hit the &quot;Finish&quot; button.\nAnd that's it. I hope this is helpful!\nHeader photo by Ankush Minda on Unsplash\n",
		"tags":[
	        
            "development"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My Vue.js Course is Coming Soon!",
		"date":"Mon Jun 11 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1528675200,
		"url":"https://www.raymondcamden.com/2018/06/11/my-vuejs-course-is-coming-soon",
		"content":"So as I announced back in April, I'm giving an online course on Vue.js. The May course was cancelled but the June one for the 22nd is still on.\nGetting Up to Speed with Vue.js\nWhile I'm biased, I definitely think this will be a great course for people who want an introduction to Vue. I've included the outline from the previous post below.\nSegment 1: Beginning Vue.js (65 min total)\n\nWhy/What/How Vue.js? (15 min)\nVue Basics (20 min)\nExercise 1 – Palindrome Checker (10 min)\nExercise 2 – Ajax Search (10 min)\nBreak+Q/A (10 min)\n\nSegment 2: Building Components (45 min total)\n\nWhat are components? (5 min)\nWorking with Properties and Events (10 min)\nWorking with Slots (10 min)\nExercise 1 – Music Component (10 min)\nBreak+Q/A (10 min)\n\nSegment 3: Routing (25 min)\n\nWorking with Routing (10 min)\nExercise 1 – Master/Detail (10 min)\nQ/A (5 min)\n\nSegment 4: State Management with Vuex (40 min)\n\nWhat's a store? What’s Vuex? (5 min)\nWorking with Stores (20 min)\nExercise 1 – Cat Store demo (10 min)\nBreak+Q/A (5 min)\n\nSegment 5: Building Apps with the CLI (30 min)\n\nWhat the CLI does + Installing (5 min)\nGenerating an App (10 min)\nResources (5 min)\nFinal Q/A (10 min)\n\nI hope to see you there.\nHeader photo by Geert Pieters on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Keeping Up with Browser Updates",
		"date":"Thu Jun 07 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1528329600,
		"url":"https://www.raymondcamden.com/2018/06/07/keeping-up-with-browser-updates",
		"content":"This is something I've been kicking around in my head for probably near two years now. I pitched it to a few publications and conferences and it never quite worked out, so I thought it would be nice to simply write up my thoughts here. As a web developer who has been in this biz since, pretty much, day one, I'm incredibly happy with how far the web has come and how great our platforms are now. That is certainly not to say that everything is ideal, but we've come a long way and I think that deserves to be celebrated.\nOne of the core ways our platforms have evolved is with our browsers. That's probably an obvious point to make, but specifically the speed of updates have been a tremendous benefit for both consumers and developers. I still remember the big, yearly or so, release of new browser versions as being a big deal. Now our browsers update (mostly) automatically and near constantly. For a good year or so now I couldn't honestly tell you what version of Chrome I was using. And that's with me trying to pay attention. Instead of a particular number now, when I think of browsers I tend to think more of their general support level and features and not a &quot;X.0 release&quot;.\nOf course - this presents a bit of a problem. Given that our browsers may be updating multiple times in a month, versus a year, how can we see what's changed?\nAnd to be clear, I'm not just talking about fancy new JavaScript APIs. Consumer-facing changes are also pretty crucial. For example, Chrome has been changing how it identifies secure versus non-secure sites in pretty significant ways. As another example, Firefox recently added a screenshot tool to their browser. Both of these things aren't code related at all but are certainly important.\nThe question is - how well do these browsers present this information? In my opinion, there are a few basic things browser vendors should do. As I said, this is my opinion, but you're on my blog so you should be expecting it!\n\nThe browser itself should lead you to this information. What I mean is - I shouldn't have to Google to find it. This is my first rule for a reason. If you &quot;hope&quot; your followers on Twitter see something than you have failed. Period.\nThe information should cover both consumer-facing changes as well as developer facing information.\nHigh level coverage is absolutely fine - remember these are release notes. It is also completely fine to link to blog posts that have deeper examples and docs.\n\nSo basically - don't make me hunt, include both dev/non dev, and give me a high level summary where I can choose what topics I care about.\nWith that out of the way, let's take a look at how the major browsers handle it.\nFirefox\nI'm starting with Firefox for two reasons. First - it has been my primary browser for a few months and secondly - it is the &quot;gold standard&quot; for me in terms of doing a good job.\nFirefox provides a link to it's update information from their About Firefox window:\n\nThat leads you here: https://www.mozilla.org/en-US/firefox/60.0.2/releasenotes/\n\nFirst off - from a design perspective - this is a very clean, not-overwhelming page. By default it's focused on consumer changes, which makes sense. Also note the tab bar on top that lets you see changes for their mobile versions as well.\nIt is a bit hard to see, and I think it should be more visible, but in the bottom right corner under &quot;Other Resources&quot; is the link to &quot;Developer Information&quot;. This is where you would find the other set of documentation you'll need. Note that they do a dang good job of grouping changes into various categories. This lets you skip stuff you may not care about:\n\nOn both the consumer and developer facing pages, they also do a good job of linking to earlier versions in case you need to dig deeper into the past. And I know I said it earlier, but again, I love the design and organization of these pages. The only possible change I'd make would be highlighting the developer information link a bit more back on the initial page.\nChrome\nAh, Chrome. For the past year or so I've given their developer advocates/engineers some trouble by constantly asking them to up their game here. I'm trying to stop as it's getting annoying (I'm sure). Chrome is the number one browser and has incredible market share, but I think they need to do a better job here.\nTo begin with, the &quot;About Chrome&quot; page is pretty sparse, and in my opinion, wasting a lot of opportunity here.\n\nThe one link on there related to help goes to consumer information, but not any release notes. Ok, so what else can we try? If you open up devtools and a new version was recently installed, you actually get an automatic additional panel with release notes. I don't have a screenshot of this, but it's fairly well done. In devtools you can also click the three dot menu, go to Help, and then get links to release notes:\n\nThis currently links to https://developers.google.com/web/updates/2018/04/devtools which is spec",
		"tags":[
	        
            "development"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An Update",
		"date":"Mon Jun 04 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1528070400,
		"url":"https://www.raymondcamden.com/2018/06/04/an-update",
		"content":"There is no real easy way to write about this, so I'll just put words down and let them lie where they fall. For those of you connected to me on Facebook or Twitter, you already know. On May 23rd, my best friend, my love, and my wife of twenty-two years, passed away suddenly.\nI am still in shock. I'm very lucky in some respects. This happened while I was home and not on a trip. My wife was one of eight children. Between my family and friends, I have been inundated with support. I've got support of people who love me, and many strangers who I've never met. I cannot thank everyone who has helped me, but I hope you know that it is appreciated.\nRight now I am trying my best to cover the basics - keeping my kids taken care of and preparing for the future. I've always been a bit forgetful, but over the past few weeks I've not been able to hold onto anything. (I'm working on that by basically writing everything down I can.) Parents at my eldest son's school set up a &quot;meal train&quot; that has covered meals for me and will continue to do so for all of June and a bit beyond. I'm also slowly preparing myself to learn some basic meal items outside of what I normally feed them - chicken nuggets and mac and cheese.\nSo...\nGoing forward, I'm not going to be travelling. This was a big part of my life as a developer advocate, and I loved being on the road. I absolutely missed my wife and kids while gone, but presenting was one of my favorite ways to try to help others. Heck, I even loved air travel. Of course having status helps make it better, but I truly enjoyed being on airplanes. (I've got a collection of near one hundred different pictures of commercial air craft.) Obviously it isn't the only way I can help others. I blog, I work on documentation and samples for work, and can present virtually. I'm going to double down on that, although to be honest I expect things to be a bit rough for a bit.\nI was originally scheduled to do a virtual presentation on Vue.js back on the 30th. Thankfully my rep at Pearson cancelled for me as I wasn't in the right place to make a decision about that. The second session, which is June 22nd, should be doable and I'll let folks know if things change.\nI want to share a small story about my wife and I. A few years ago we began what became one of our most favorite traditions - finding and watching scary movies on Netflix. We both had similar tastes (low to no gore) and we would love finding obscure or smaller budget scary movies to watch together. Most of these were pretty bad, but even the bad movies were fun to watch. And every now and then we'd find a true gem that was amazing.\nAs I said - this was a small thing. Just one part of why I loved her. She was an incredible woman, and frankly, every thing you see me do - every book - every presentation - I credit to her support and care. A week or so before she passed, I let her know that I credit so much in my life, both the big things, and small, to her, and it really touched her. I almost didn't share that as it seemed overly dramatic and silly, but I could tell it meant a lot to her. Again - I am lucky.\nPlease be patient with me. And again - to my friends who have reached out - I feel like I cannot thank you enough. To the hundreds who donated money to the fund set up by my good buddy... thank you feels so insignificant, but thank you.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "RIP ColdFusion Bloggers",
		"date":"Tue May 22 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1526947200,
		"url":"https://www.raymondcamden.com/2018/05/22/rip-coldfusion-bloggers",
		"content":"So today I killed ColdFusion Bloggers. This was rather abrupt and I apologize for that. A week or so ago it was reported to me that the data wasn't updating. I logged into the service providing hosting and discovered they were shutting down for good in a few weeks. I've had free credit with them for a few years now, but honestly, I didn't want to go find another cheap Node hosting solution.\nIf you go to the site now, you'll find a JSON, and HTML, export of the most recent list of supported blogs. It got down to about 120 or so I think. I also did an export of the entire Mongo DB with all the recorded entries. You could probably find a great deal of ColdFusion content there lost to the world.\nYou can find the source code for the current site here: https://github.com/cfjedimaster/nodecfbloggers. Anyone has my permission to use it, seed it with the database, and go to town. (Although you should visit my wishlist of course. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Text Sentiment Analysis IoT Demo",
		"date":"Mon May 21 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1526860800,
		"url":"https://www.raymondcamden.com/2018/05/21/text-sentiment-analysis-iot-demo",
		"content":"I spent the last week at a company offsite in Panama (which is quite beautiful, although I spent most of my time in a hotel). During that time I participated in a hackathon using multiple IoT devices. One of them was this nice little LCD panel:\n\nI've got no idea what this hardware is actually called (I mean what brand) and I was totally useless in terms of setting it up, but after my partners got it up and running and fired up a Node server on it, I built code that would sent data to it. For my part I decided to use Microsoft's Text Analytics API and Webtask. The idea was to build a &quot;sentiment analysis&quot; of tweets concerning a keyword (in this case Auth0) and provide a report on the average. You could imagine this the display giving a real-time(ish) status of how things are going. In the screen shot above you can see that things are going well. Awesome! We also built support for a more neutral response:\n\nAnd a &quot;oh crap, we must have done something really bad&quot; result:\n\nAgain - I didn't do any of the cool hardware part, I just built the &quot;get the data and send it part&quot;, but I thought it might be cool to share that code. About two weeks ago I wrote about doing something similar with Slack: Adding Serverless Cognitive Analysis to Slack. This meant most of my work was done for me. Let's look at the webtask.\n\nAlright, let's break it down bit by bit.\nI begin by initializing a Pusher object. This was my first time using it and I had a bit of trouble getting things working at first. I'll blame myself and not Pusher as I was trying to work quickly. Pusher makes it easy (somewhat) to connect different clients and send messages back and forth. My code sends messages and the Node app running on the device would listen for it and then display it.\nI then setup my Twitter library. I had keys from a previous app I created so I just reused it. The Twitter search is nicely aggregated in this call:\n\nI've only just begun using async and await and I probably barely understand it, but I freaking love it. searchForAuth0 simply handles calling the Twitter search API for my particular keyword, auth0.\nThen - I ask for it to be analyzed:\n\nThis just calls off to the Text Analysis API. And here I need to point out a major issue with my implementation. You'll notice I treat each tweet as a separate doc. To me, that makes sense as putting all the tweets together into one string would imply one particular author. However - keep in mind that even though I'm batching the call to the API, Microsoft will still &quot;charge&quot; you for 100 calls. To be clear, that's totally fair! But at the free tier of 5000 calls per month, I ran though 3.5K calls in about 30 minutes of testing. You'll notice the commented out line there that I used to short circuit calls to the API. It's also how I tested the different &quot;smiley faces.&quot; My coworker built support for that on the panel so I used that as a mean to test the different faces.\nYou can see that being setup in getEmotion, which translates the average score into one of three states represented by a string. My coworker looked for that string in the result and replaced it with the face.\nFinally it all comes down to:\n\nAnd that's it. I think it's pretty cool and I wish I understood the hardware aspect a bit more. I've setup my own RetroPie, but that was about three steps and didn't involve any real wiring. That being said, I hope the code above is helpful to folks!\nHeader photo by rawpixel on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Deploying a VuePress Site to Netlify",
		"date":"Wed May 16 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1526428800,
		"url":"https://www.raymondcamden.com/2018/05/16/deploying-a-vuepress-site-to-netlify",
		"content":"Before I begin, let me just state that what I'm covering today is already covered in the docs (Deploys - Netlify), but for me it wasn't quite detailed enough and I wanted to run through, and then document, the process myself. I don't know if this is helpful, and as always, I hope my readers will tell me, but I figured I'd share how it worked for me. Also note that VuePress is still early on in development, so what I describe today may not make sense in the far flung future of flying cars and jetpacks.\nFirst off - why Netlify? As my regular readers will know, I've been a user of them for raymondcamden.com for quite some time. They are - without a doubt - the gold standard for static site hosting services. Yes you can use Amazon S3, or Surge (a service I like as well and use for quick demos), but in terms of all the additional features you get, nothing comes close to Netlify. Period.\nThat being said - the feature I'm going to demonstrate setting up is their automatic build process for sites tied to GitHub. This is how my own blog works. I commit a new post to my repository, Netlify gets a ping, and then it begins a build process using Jekyll. So how does this look for VuePress?\nLet's begin with an incredible simple VuePress site. VuePress makes zero requirements on your default structure so to keep things simple, I built a site with just two pages. I want to be clear that this is not a good representation of all the cool stuff VuePress has. I just wanted a &quot;bare minimum&quot; site for the purpose of this demo.\nThe first page is index.md:\n\nAnd the second page is alpha.md:\n\nAnd that's it. Just a two page static site. So how do we get this to Netlify?\nStep One - GitHub\nThe first thing I did was create a GitHub repo for the site: https://github.com/cfjedimaster/netlify-vuepress-demo. That's nice and simple and no big deal.\nStep Two - Netlify Site\nNext - create a new Netlify site. You can do this via the CLI, but it's also pretty easy from the UI:\n\nSelect your repository, and in the next page, you need to provide build settings. Note, this is not going to work immediately, but we'll fix that.\nFor the build command, you want to use npm run docs:build.\nFor the publish directory, .vuepress/dist. The VuePress docs assume a docs subdirectory but our application's root is, well, the root folder itself.\n\nGo ahead and click Deploy Site, but as I said, expect it to fail.\nStep Three - Get it Netlify Ready\nIn order for the site to work correctly, we need to do a few things. First, we have to tell Netlify to install vuepress as part of the build process. To do this, create a package.json:\nnpm init --force\nYou don't have to use --force of course, I use that to be lazy. In the package.json, then add a new script. Here is my complete file:\n\nI'll be honest and say I'm still kinda new to using npm scripts. I really like them - but as I said - I'm new to them. Add this file to your repo, commit, and that's it. Like seriously. Here's a build history for my first test. You can see it failing before I figured out the package.json bits.\n\nYou can see this live site, I mean if you really want, here: https://tender-stonebraker-c8e749.netlify.com/. Cool part is - I edit my VuePress site and confirm it's cool locally and I can then simply commit my changes. Netlify will then pick up the change, run the build, and deploy the static site.\nAs I said - I hope this is helpful and if you have any questions, let me know in a comment below!\nHeader photo by rawpixel on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Welcome to RaymondCamden.com 2018",
		"date":"Tue May 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1526342400,
		"url":"https://www.raymondcamden.com/2018/05/15/welcome-to-raymondcamden-2018",
		"content":"Today marks the release of the latest version of my blog. I'm back to Jekyll and sporting a new theme by Just Good Themes. If you're curious, here's a bit of background into why I switched.\nThe last time I blogged on Jekyll, I was a bit upset with it. In fact, I said this: &quot;So yes, I’m officially done with Jekyll.&quot; But... things happen. At work, we use Jekyll for our docs and blog, and I actually had little to no difficulty getting both of those working in WSL on both my laptop and desktop. Given that experience, I began to think about migrating my blog back.\nWhat spurred that even more was me finally figuring out how to get Jekyll to exclude most of my content locally. Maybe this wasn't a feature last time I used it, but now it is rather trivial. Here's the config setting I use:\n\nWith this in play, my startup and reload time is about four seconds. Still a bit slow but acceptable. (And if I really cared, I could knock out various months in 2018 as well.) While Hugo definitely has Jekyll beat on speed, I cannot describe how much I disliked using it. Everything it did annoyed me. To be clear, I'm not saying it is a bad project. It is incredible fast, has lots of features, and served me here well for years. But as a developer, I really disliked using it. On the other hand, I enjoy hacking around with Jekyll.\nUsing it with Netlify was pretty simple. I followed this blog post which basically came down to adding 2 files and changing my build settings. Build times are pretty decent too:\n\nOne thing I really like about this theme, but which may be a bit annoying to regular readers, is that every post has a clear callout to the author (me). At first that seemed a bit silly since every single post here is from me (I have had a few guest posts, but not in years), but since most folks come in here via a Google search and probably have no idea who I am, I think it will be a nice change.\nSpeaking of &quot;regular readers&quot;, note that I'm no going to use FeedBurner to host my RSS feed. If you want to subscribe to my RSS, just use this URL: https://www.raymondcamden.com/feed.xml. I'm toying with the idea of setting up an email subscription list, but I'm not sure if that is worth the effort.\nThe only real &quot;bug&quot; I am aware of now is that my tag and categories archive are single pages. That means I've got (nearly) 6000 links on them which is pretty ridiculous. Jekyll can't generate new files so to fix this, I need to create files for each tag and category and have them run one simple template. That's pretty trivial work, but I just haven't done it yet. Once I do I'll update the links on the right. There are a few Markdown issues on some older posts, but I'll address those whenever I see activity (something I was doing on the old theme for even older posts).\nOutside of that - well - I hope you like it!\nHeader photo by Artem Bali on Unsplash\n",
		"tags":[
	        
            "development"
            
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding a Recent Content Component to VuePress",
		"date":"Wed May 09 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1525824000,
		"url":"https://www.raymondcamden.com/2018/05/09/adding-a-recent-content-component-to-vuepress",
		"content":"A few weeks back a new static site generator was launched, VuePress. Being based on Vue.js, I was immediately interested in it. I've only been using it for a few hours, mainly prototyping it for something I'd like to build at work, but I thought I'd share a little code snippet I wrote. You should assume this is probably the wrong way to do it. As I said, I'm pretty new to VuePress, but when I get something working I like to share it.\nImagine your building a blog, or a cookbook, and you want to show the latest content on your home page, or perhaps in side navigation. How would you do that?\nWell first off, VuePress provides access to the entire site via a - wait for it - $site variable. So in theory, you could do this:\n\nThat works, but breaks down when you need to add sorting and filtering. So for example, in a blog you probably only want the latest blog entries. You don't want to include the home page or contact page. In theory you could get crazy in your v-for there but why make your layout messy when a component will do instead?\nVuePress supports custom global components by simply dropping a file in the .vuepress/components folder. In my testing it appeared as if I needed to restart the server in order to get VuePress to recognize it, but it could have been another issue. For me, I created a file called RecentArticles.vue. This then let me drop this into the home page:\n\nNow let's look at the component.\n\nThe top portion simply handles the display, which in my case is a basic unordered list. The crucial bits is the computed property, recentFiles. In order for this to work, I have to make a few assumptions.\nFirst - I assume all of the blog entries are in a path called posts. VuePress returns the location of each page via the path property. I check for /posts/ in the path as a way of saying that this particular file is a post. You also get access to the frontmatter of each file and you could use a marker there too if you want.\nSecond - I then a sort. I'm assuming each post will have a published value in the front matter. (And I'm very happy that VuePress lets me use JSON for frontmatter and not just YAML.) I parse the published value and then sort.\nFinally - I return the top five. In theory I could make that an argument passed to the component and default it to 5. In fact, I'm pretty sure I'll do that. Heck, I may even be able to repurpose this to work for an RSS feed as well. (Someone wrote up a great article on how to build UI-less components but I'm having trouble finding that link. If I do, I'll post it as a comment below.)\nAnyway, I hope this is helpful. If I play more with VuePress I'll share more tips!\nHeader photo by Pineapple Supply Co on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Next Event - Modern Development with the JAMStack",
		"date":"Mon Apr 30 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1525046400,
		"url":"https://www.raymondcamden.com/2018/04/30/next-event-modern-development-with-the-jamstack",
		"content":"First off - my apologies for being a bit quiet here. I had two trips in a row, some big things (good things) happening at work, and some projects that aren't necessarily blog worthy yet, but hopefully I'll have some cool stuff to share soon.\nWith that out of the way, I'm happy to announce I'll be speaking at another Certified Fresh event next month, &quot;Modern Web Development with the JAMStack&quot;. This is a free online event on May 23rd at noon CST.\nNo idea what the JAMStack is? Well obviously it's this:\n\nHeh, if only. Imagine static site generators - but taken to the next level. Attend the free online session to get a much better introduction to the topic. I'll be speaking with Phil Hawksworth of Netlify (the fine folks who host this site) and covering some of the ways you can &quot;bring dynamic back&quot; to a static site. I hope to see you there!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Getting Up to Speed with Vue.js",
		"date":"Fri Apr 20 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1524182400,
		"url":"https://www.raymondcamden.com/2018/04/20/getting-up-to-speed-with-vuejs",
		"content":"One of my goals for 2018 was to become an &quot;expert on Vue&quot;. While I think I'm years away from considering myself an expert, I've spent a lot of time digging into it and trying to become better skilled with it, and all the various things in the &quot;Vue ecosphere&quot; like Vuex, Veutify, and more. I'm happy to announce that this May, I'll be giving a three hour course on learning Vue.js:\nGetting Up to Speed with Vue.js\nThis is an online course that covers everything from Vue basics to working with routing and the store. I really think it will be a great way to get introduced to Vue, see how it works, and start building applications right away. There will be a second class given in June but I don't yet see a way to register for that. (It's June 22nd so they may be waiting for the May class to end before opening up registration for the second.)\nHere's the current outline, although I may tweak it between now and then:\nSegment 1: Beginning Vue.js (65 min total)\n\nWhy/What/How Vue.js? (15 min)\nVue Basics (20 min)\nExercise 1 – Palindrome Checker (10 min)\nExercise 2 – Ajax Search (10 min)\nBreak+Q/A (10 min)\n\nSegment 2: Building Components (45 min total)\n\nWhat are components? (5 min)\nWorking with Properties and Events (10 min)\nWorking with Slots (10 min)\nExercise 1 – Music Component (10 min)\nBreak+Q/A (10 min)\n\nSegment 3: Routing (25 min)\n\nWorking with Routing (10 min)\nExercise 1 – Master/Detail (10 min)\nQ/A (5 min)\n\nSegment 4: State Management with Vuex (40 min)\n\nWhat's a store? What’s Vuex? (5 min)\nWorking with Stores (20 min)\nExercise 1 – Cat Store demo (10 min)\nBreak+Q/A (5 min)\n\nSegment 5: Building Apps with the CLI (30 min)\n\nWhat the CLI does + Installing (5 min)\nGenerating an App (10 min)\nResources (5 min)\nFinal Q/A (10 min)\n\nIf you want to learn Vue, and learn it with me (cat demos for the win!) then sign up and join me next month!\nHeader photo by Geert Pieters on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Example of the Async Clipboard API with Vue.js",
		"date":"Thu Apr 19 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1524096000,
		"url":"https://www.raymondcamden.com/2018/04/19/an-example-of-the-async-clipboard-api-with-vuejs",
		"content":"A few days ago Google shipped Chrome 66 and one of the new features enabled in that version was the Async Clipboard API. As you can guess, this provides access to the user's clipboard (both read and write) and is surprisingly easy to use.\nYou can read a good introduction to the API here, Unblocking Clipboard Access, but don't do what I did and stop reading as soon as you see the code. The example looks really simple:\n\nThat's writing to the clipboard in case it isn't obvious. When I tried this code it failed and the error was very vague (&quot;Undefined&quot;). Reading more on the article above, you'll see this is actually documented:\n\nAs with many new APIs, navigator.clipboard is only supported for pages served over HTTPS. To help prevent abuse, clipboard access is only allowed when a page is the active tab.\n\nAnd then a bit later...\n\nSince Chrome only allows clipboard access when a page is the current active tab, you'll find some of the examples here don't run quite right if pasted directly into DevTools, since DevTools itself is the active tab.\n\nI feel bad missing that, but it's not like this is the first time I saw code and stopped reading so I could play with it right away.\nSo - want to see an example using Vue.js? Of course you do! Imagine a scenario where we have generated a code for our user. We want to make it easier to use so when we can, we'll provide a button to copy it into their clipboard. First, the HTML:\n\nMake note of the button. It's checking a property to see if it should show up. Now let's look at the JavaScript.\n\nI begin by using the created hook to see if  navigator.clipboard exists. If so, I then enable the button by setting the supportsCB property to true. Note that I could make this a bit more secure by checking with the permissions API as well.\nNext - I define my copy method using the writeText call. When done, either successfully or with a failure, I edit a message to let the user know. That may be overkill, but I figured a confirmation would be nice. You can play with this below, but obviously you'll want to use Chrome 66.\nSee the Pen vue async clipboard by Raymond Camden (@cfjedimaster) on CodePen.\n  \nHeader photo by rawpixel.com on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "TIL - Pushing Node Apps to Azure with Visual Studio Code",
		"date":"Wed Apr 18 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1524009600,
		"url":"https://www.raymondcamden.com/2018/04/18/til-pushing-node-apps-to-azure-with-visual-studio-code",
		"content":"I've been playing, off and on, with Microsoft Azure for a while now. My main focus has been on the services areas (see my post comparing different visual recognition services) but I was also curious to see how well it worked as a PaaS for Node apps. About two or three months ago I tried to push a simple LoopBack app up and I was not successful. I put the blame on me for not reading the docs well, but it wasn't a good experience. About a week or so ago I was talking to a Microsoft employee about deployment in general and when I mentioned my last experience, he pointed me to this great tutorial:\nDeploy to Azure using App Service\nThis tutorial walks you through the process of installing the Azure App Service extension into Visual Studio Code and then using it to deploy a Node app to Azure.\nI'm not going to repeat what's in the tutorial as it generally just works fine as is. I will warn you about a few things you may run into while testing.\nFirst, when you begin the authentication process, it will ask you to open a URL and enter a code:\n\nDo NOT click! If you do, the little panel there will disappear, and if your memory is like mine, you won't remember the code. I had to quit VSC and restart it to get the prompt again. You can select text in the dialog and put it in your clipboard, or just jot it down.\nOops! So notice how in the screenshot above it says Copy. As in, um, Copy, like Ray, how could you miss that? Yep, that's all on me. I think maybe I was expecting it to pre-fill the form field with the code. Either way - just paste. Duh.\nSecondly, for me the first deployment was incredibly slow. I'd say about ten minutes. Maybe LoopBack is big (honestly I never really thought about it). Maybe it was provisioning things. But for whatever reason, that first push was definitely slow. However, after that it moved incredibly quick. I'd say maybe 30 seconds, or quicker, to get the app updated.\nFinally, the extension supports viewing logs from your application. That's cool, but it didn't always consistently work for me. That being said, last night when I was testing I was having a bit of trouble with it and this morning it seems to be working perfectly fine.\n\nFinally, and a bit off topic, but if you develop Visual Studio Code extensions, check out this button from the guide:\n\nClicking this will open Visual Studio Code right to the marketplace and the extension. It's just a properly formatted URL but I was surprised to see it work so well. (Obviously you would need VSC installed for it to work properly.) Like any good webdev I did a quick Inspect Element to see the URL: vscode:extension/ms-azuretools.vscode-azureappservice. I'm surprised I don't see that used more often.\nHeader photo by Lisheng Chang on Unsplash\n",
		"tags":[
	        
            "azure"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Missed VueConfUS? Watch (Parts) Online!",
		"date":"Mon Apr 16 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1523836800,
		"url":"https://www.raymondcamden.com/2018/04/16/missed-vueconfus-watch-parts-online",
		"content":"I wasn't able to make this year's VueConf US (even though it was in my backyard), but luckily, some of the videos from the conference are online:\nhttps://www.vuemastery.com/conferences/vueconf-2018/opening-keynote-evan-you\nRight now there's just 4 sessions, but they are incredibly good presentations including Rachel Nabor's on Vue's transition support for animations and Edd Yerburgh on testing Vue apps.\nWhile at the site, take note of the &quot;Notify me&quot; button on the right. You can sign up there to be notified when new videos are added. From what I know, all the videos will be available eventually. Plus, if you sign up, the fine folks at Vue Mastery will send you a Vue PDF cheat sheet that is really freaking nice:\n\nIt's two pages in length and I'm probably going to print it out for my home office. Actually disregard what I said above. If you don't feel like signing up for updates, you can still get the PDF here: https://www.vuemastery.com/pdf/Vue-Essentials-Cheat-Sheet.pdf. As I said, it is definitely worth downloading!\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Datalists with Vue.js",
		"date":"Thu Apr 12 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1523491200,
		"url":"https://www.raymondcamden.com/2018/04/12/datalists-with-vuejs",
		"content":"This isn't necessarily a very exciting post, but a few days back someone asked me about integrating Vue.js with datalist tags. The datalist tag is one of my favorite HTML tags and something I've blogged about a few times in the past. If you aren't familiar with it, it basically provides a &quot;autosuggest&quot; style experience to an input tag.\nThe HTML is pretty simple. Here is the example used in the MDN article I linked to above:\n\nBasically - you create a &lt;datalist&gt; element and supply options. You then take your input and add the list=&quot;id of the list&quot; attribute. Now when the user types, they will get suggestions based on the list and what they've typed in. It's pretty well supported (basically everyone but Safari and Mobile Safari, because of course) and fails gracefully (the user can still type anything they want). How would you combine this feature with Vue.js? Let's look at a static example. First, the HTML:\n\nYou can see the input field and the list. The option tag is tied to a variable called films. Now let's look at the JavaScript:\n\nNot too exciting, but it works rather well. You can test it below:\nSee the Pen Static Datalist by Raymond Camden (@cfjedimaster) on CodePen.\n\nHow would you make it dynamic? Simple - just change how the data is generated. Here's an example of that:\n\nAll I did was add in a created event handler and hit the Star Wars API for my data. You can test the result below:\nSee the Pen Dynamic Datalist by Raymond Camden (@cfjedimaster) on CodePen.\n\nI may be biased - but everything is better in Vue.\nHeader photo by Ilya Pavlov on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Serverless IoT for Enterprise Light Bulb Demos",
		"date":"Wed Apr 11 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1523404800,
		"url":"https://www.raymondcamden.com/2018/04/11/serverless-iot-for-enterprise-light-bulb-demos",
		"content":"Before I start, let me state a quick warning. No, this is not an Enterprise demo. Yes, it does involve a light bulb. This post was inspired by Burke Holland's post yesterday on his own light bulb/serverless demo (Displaying the Weather With Serverless and Colors). No, my post is not as cool as his, but yes, I'll share a picture from my office which I know is far cooler.\nA few months ago I was given a LB100 &quot;Smart Wi-Fi LED Light Bulb&quot; as a speaker gift. Out of the box it has great Alexa integration. As lame as it sounds, walking into my office and saying, &quot;Computer, office light on&quot;, is a small thrill every day. And yes, I say it in this guy's voice:\n\nWhile Alexa integration is nice and all, I was curious if there was a proper API for the device. While there seems to be no official, documented API (a strike against it imo), I found not one, but two npm packages for it:\n\ntplink-lightbulb\ntplink-smarthome-api\n\nI found the first one to be a bit easier so I stuck with it. It's both a CLI as well as a package you can use in your code. In order to use it, you need to figure out the IP address of your bulb. The mobile app that you use to setup the device reports the MAC address so I had to check my router to get the IP: 10.0.1.5.\nMy own particular bulb doesn't have many features. It doesn't support color for example. But it can be dimmed. So for my first test, I wrote this little script.\n\nThe second argument, 5000, simply refers to how long the bulb should take to change brightness. I ran it - and - voila - it got dark. I got a little bit excited about that. Just a little. But of course, this would be far cooler if it was serverless, right?\nI popped over to Webtask.io and created a new empty function. I added the npm module my original script used and came up with this little beauty:\n\nSo what's with the nesting and timeouts and so forth? While the light API has a callback, it represents the successful call out to the hardware. It does not represent the end of an operation, which in this case is 2000ms along with other network delays. Therefore I used the callback of the &quot;dim&quot; operation (setting brightness to 0) to set up a timeout for a bit more then my first duration to kick off another call to brighten the bulb up again.\nYes - this could be written better. I got some great support from the creator of the tplink-lightbulb package (David Konsumer) and he wrote it in a much sexier fashion (although reversed):\n\nSee folks, this is why I don't pass Google interviews! My version was almost ready, except I had to open a port. Yes, I opened a port on my network.\n\nBelieve it or not, it actually was this dramatic as I had to figure out the port and Apple's clunky interface for their router.\nBut it was so worth it. I copied the URL out of the webtask editor and set it as the webhook for one of my GitHub repos. For the hell of it, I also set it as a Netlify hook (my host) to run when a build is complete. Now, whenever one of those events occurs, my light will dim and get bright again as a nice, passive notification.\nI tried to take a video of it, but while it looked perfectly fine in real life, the video just didn't do it justice. Therefore I created a really horrible animated gif out of it:\n\nI apologize for that.\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Adding reCaptcha with a Serverless Form Processor",
		"date":"Fri Apr 06 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522972800,
		"url":"https://www.raymondcamden.com/2018/04/06/adding-recaptcha-with-a-serverless-form-processor",
		"content":"A few days ago I added Google's reCaptcha support to a ColdFusion site. It was pretty easy (some front end work, some back end work), so I thought I'd whip up a quick demo of how you could add it to a form using a serverless processor, in my case, Webtask. To get started, let's go over a quick demo of how such a processor could look before we add the captcha.\nBC (Before Captcha)\nFirst, here is the form.\n\nI've got three form fields and I'm using Vue.js to handle doing a POST via Ajax. I assume that all of this is pretty simple to understand, but as always, if you have any questions, add a comment. The end point is a webtask function. Here it is:\n\nIn this webtask, I simply grab the form data (it is in context.body, and you can read more about the Context object in the docs) and pass it to a function called checkForm. My form had three fields, but I only really care about two. If the validation fails (by returning anything in the array), I return a false status and the errors. Otherwise I return true and as the comment says, I'd probably email the form or store it somehow.\nAC (Air ConditioningAfter Captcha)\nWorking with Google's reCaptcha involves three main steps:\n\nFirst, you get a key. Google has made that quite a bit easier now.\nSecond, you add the front end code. You've got multiple options on how to do that.\nFinally, you validate the reCaptcha on the server side.\n\nTo get your key, start here: http://www.google.com/recaptcha/admin. Note that you actually get two keys.\n\nThe first key is used in the front end. The second key is used on the server side for validation.\nAdding the captcha is pretty simple. Drop in a script tag and then add a div:\n\nBy itself, this will create a hidden form field and when the user checks the captcha, it will fill in a key. If you are using a &quot;regular&quot; old server like ColdFusion, or even Node, then you would grab the value in the typical way you handle getting form values. However, in our case, we're using client-side code to POST to a serverless web hook, so we need to fetch the key manually. Here's the updated form (with a bit removed to cut down on size):\n\nOk, so a few things. First, when I added the script tag, note the onload bit:\n\nThis lets me listen for the load event for the captcha. I need this because I don't want users to submit the form until the captcha has had a chance to load. I added a new variable to my Vue instance that disables the submit button until that event fires. Basically onload just chains to app.enable() which toggles the value.\nThe next change is in my POST:\n\nYou can see I'm using a global object, grecaptcha to get the value from the UI. This will either be blank (the evil user ignored it) or a long string. Here's how it looks:\n\nNow let's look at the updated webtask:\n\nThe first major change is that checkForm is now asynchronous and returns a Promise. I did this because I knew I was going to be making a HTTP call to verify the key. I now pass that key, and the form, like so:\n\nWhat is context.secrets.recaptcha? Webtasks allow for secrets which are really useful for API keys. In my case, I simply set the key via the CLI: wt create form_resp2.js --secret recaptcha=mykeywashere. You can also set the key in the online editor.\nIn checkForm, you can see where I do a simple POST to Google's verify end point. If anything goes wrong, I return a generic error (I could make this more precise) and then finally we resolve the array of errors.\nYou can test this yourself here: https://cfjedimaster.github.io/Serverless-Examples/recaptcha/test2.html\nAnd the full source code for both versions may be found here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/recaptcha\nIf you have any questions about this, just leave me a comment below!\nHeader photo by Steven Wei on Unsplash\n",
		"tags":[
	        
            "webtask",
            
            "vuejs"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "TIL - Vue.js and Non-Prop Attributes",
		"date":"Tue Apr 03 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522713600,
		"url":"https://www.raymondcamden.com/2018/04/03/til-vuejs-and-non-prop-attributes",
		"content":"This weekend I worked on a PR for a Vue.js component (vue-static-map) that ended up being a complete waste of time. Almost. What I mean is - I added support for something that actually ended up being a baked-in feature of Vue. So sure - I wasted a bit of time, but I also learned something and as my readers know, every time I screw up - I blog it.\nOk, so what did I do? The project I filed a PR again, vue-static-map, is a wrapper for the Google Static Maps API. This is one of my favorite APIs as all it consists of is an image URL with specific parameters. If you don't need an interactive Google Map and just need a - you know - a map - then the Static Maps API is perfect.\nThe Vue component (made by Eduardo P. Rivero) wraps the API and makes it even simpler. However, I noticed something was missing from it - the ability to specify an alt or title tag for the image tag. So I modified his component to allow for alt and title:\n\nAnd that was that, right? Except I didn't realize (but luckily Eduadro did) that Vue already will pass in attributes that are not specifically defined in a component! And this is documented too: Non-Prop Attributes:\n\nA non-prop attribute is an attribute that is passed to a component, but does not have a corresponding prop defined.\n\nWhile explicitly defined props are preferred for passing information to a child component, authors of component libraries can’t always foresee the contexts in which their components might be used. That’s why components can accept arbitrary attributes, which are added to the component’s root element.\n\nWhich simply means that as his component uses img as the root element, it already supported accepting an alt and title tag. (His code did have a preset alt tag that he removed.) Vue is actually pretty dang smart about this too. So if your component has an existing class or style value and you pass in custom values, it will merge them instead of replacing them. You can read about this here. Finally, if you don't like this, you can disable the behavior in your component.\nAll of this was documented, but something I missed, and a pleasant surprise. Want to see an example? For my last Vue.js presentation, I built a simple Nicolas Cage component. (It just wraps PlaceCage.com). Initially I only built in support for height and width - both of which go into the URL to select the right picture. To support alt/title I don't have to do a thing. In the CodePen below, you can see where I'm using title and if you mouseover the result, it just plain works.\nSee the Pen nicolas cage (testing alt/title) by Raymond Camden (@cfjedimaster) on CodePen.\n\nHeader photo by Ilya Pavlov on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building Three Common Form Interfaces in Vue.js",
		"date":"Mon Apr 02 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522627200,
		"url":"https://www.raymondcamden.com/2018/04/02/building-three-common-form-interfaces-in-vuejs",
		"content":"Today I wanted to share three simple (mostly simple) Vue.js samples that demonstrate some common form UX patterns. In each case, I fully expect that there are probably existing Vue components I could have used instead, but as always, I'm a firm believer in building stuff yourself as a way to practice what you learn. So with that in mind, let's get started!\nDuplicating Fields\nFor the first demo, I'll show an example of a form that lets you &quot;duplicate&quot; a set of fields to enter additional data. That may not make much sense, so let's start with the demo first so you can see what I mean:\nSee the Pen Form - Duplicate Row by Raymond Camden (@cfjedimaster) on CodePen.\n\nThe form consists of two parts. On top is a set of basic, static fields. On bottom is a place where you can enter information about your friends. Since we don't know how many friends you may have, a field is used to add additional rows. Let's look at the markup for this.\n\nThe top portion is vanilla Vue binding. The bottom part is where the interesting bits are. First, I iterate over a list of friends. This is what &quot;grows&quot; when the button is clicked. Note the use of (f,n). This gives me access to each friend as well as a counter. It's a zero based number so when I render it, I add one to it. Also note how I properly use my label with a dynamic ID value: :id=&quot;'friend'+n&quot;. That was a bit weird to write at first, but it works well.\nThe JavaScript is pretty simple:\n\nThe only real interesting part here is defaulting friends with the first set of values so I get at least Friend 1 in the UI.\nShipping Same as Billing\nThe next UX I wanted to build was something you typically see in order checkouts, &quot;Shipping Same as Billing&quot; (or vice-versa). Basically, letting the user skip entering the same address twice. Here is the finished demo:\nSee the Pen Shipping same as Billing in Vue.js by Raymond Camden (@cfjedimaster) on CodePen.\n\nI thought this would be simple, and I suppose it was, but I wasn't necessarily sure how it should react once the checkbox was checked. What I mean is, if you say shipping is the same, should we always update? By that I mean, if you change the billing street, do we update the shipping street again? But what if you modified the shipping street? Should we disable shipping if you use the checkbox? But what if you wanted to use this feature to set most of the fields and then tweak one? Yeah, it gets messy. I decided to KISS and just do a copy (if you are checking it) and then don't worry about it. I'm sure there's an argument to be made that I'm totally wrong. Here's the markup:\n\nAnd here's the JavaScript:\n\nThe interesting bit is in copyBilling. I apologize for the sSame name - it kind of sucks.\nMove Left to Right\nFor the final demo, I built a &quot;thing&quot; where you have items on the left and items on the right and you click to move them back and forth. There's probably a better name for this and if you have it, leave a comment below. Here is the demo.\nSee the Pen Move Left to Right by Raymond Camden (@cfjedimaster) on CodePen.\n\nWhat was tricky about this one is that the select fields used to store data only require you to select items when you want to move them. So I needed to keep track of all the items in each box, as well as when you selected. Here's the markup.\n\nAnd here's the JavaScript. This time it's a bit more complex.\n\nBasically on button click, I look at all the items, and for each, see if it exists in the list of selected items, and if so, it gets shifted eithe rleft or right. I feel like this could be a bit slimmer (I will remind folks once again that I'm a proud failed Google interviewee) but it worked. Remember you can fork my CodePens so I'd love to see a slicker version of this.\nSo - what do you think? Leave me a comment below with your suggestions, modifications, or bug fixes!\nHeader photo by rawpixel.com on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Reminder on File Inputs, JavaScript, and Read Access",
		"date":"Thu Mar 29 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522281600,
		"url":"https://www.raymondcamden.com/2018/03/29/reminder-on-file-inputs-javascript-and-read-access",
		"content":"Let me begin by stating that what I'm covering today isn't actually new. It's stuff I've covered here before. But after a conversation with a reader via email I had to write up a quick test to confirm it myself. I don't believe this is a security issue, but I was kinda surprised and therefore I figured it was best to whip up a quick blog post.\nLet's begin with some basics. I assume you know that JavaScript running in the browser does not have access to your file system. That's a really, really good thing. Chrome used to support a file system API (and it may still support it, but it's definitely deprecated) that gave you access to a sandboxed file system, but it certainly was not allowed to touch the user's main file system. Now that binary support in IndexedDB is well supported, there isn't really a need for writing files to the disk.\nHowever, JavaScript can read files that the user selects via an file type input field. You can see a simple demo of this below:\nSee the Pen File Read demo by Raymond Camden (@cfjedimaster) on CodePen.\n\nBe sure to select a text file only, but you can also read binary data too. (The code would just need to adjust for it.) Also, I apologize for not using Vue. I feel bad. ;)\nSo here is where the interesting little tidbit came up. In one of my earlier demos, I showed selecting images and getting previews. It also supported multiple selections. So you could pick one image. Then pick another. And so on.\nWhat that demo showed, and what didn't really click with me, is that once a user selects a file, you have read access to it, even after they select another file. As I said, I can see why that works, and it isn't a security issue per se. I mean, the user did select the file. But it kinda surprised me that after I cleared my selection, I could still read it. This CodePen demonstrates this, a bit poorly (I'll explain why in a second):\nSee the Pen Testing multi file upload by Raymond Camden (@cfjedimaster) on CodePen.\n\nThis demo lets you pick a file, then some more, then more (etc.), and finally upload them all to Postman. Postman doesn't seem to handle the result very well, but from what I can see in DevTools, all files are definitely being uploaded.\nI guess that's all I have to say about it. Is anyone else surprised or is it just me?\nHeader photo by Kiwihug on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Links for Learning Webtask",
		"date":"Wed Mar 28 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522195200,
		"url":"https://www.raymondcamden.com/2018/03/28/links-for-learning-webtask",
		"content":"This will be a quick post. I wanted to share a few links for people who want to take a look at Auth0 Webtask, the serverless platform behind Extend. Some of these links will be obvious, some not, but hopefully they will be helpful for folks who want to learn more.\n\nFirst and foremost, start with the docs. I've been doing some edits here recently but in general, they are in good shape and will help you get started.\nDon't forget the online editor. I really, really love my editor and it is the primary tool I use for all my development, but the online editor is very well done and if you aren't sure you want to &quot;commit&quot; to installing the CLI, then I suggest simply working there to get started. The CLI has full parity with the editor so you can switch back and forth.\nThere is a public Webtask slack at webtask-chat.slack.com. You can signup here: https://skynet.run.webtask.io/webtask-signup. I didn't even know this existed until a few days ago as it is a bit hidden in the &quot;Contact Us&quot; modal. I'll be making this more visible soon.\nThe Auth0 Blog has some great content (they even covered LoopBack multiple times!) including good articles on webtask. You can find Webtask specific entries here: https://auth0.com/blog/tags/webtask/. Be sure to click the &quot;Older posts&quot; button at the bottom as the first page of results is pretty short.\nThere is a webtask workshop that you do by yourself to get introduced to different parts of the platform.\nIf you are already doing serverless with the Serverless framework, they support webtask too. You can read about that here: Auth0 Webtasks - Introduction. I've not blogged on the Serverless framework yet. I hope to have a few entries on that in the next couple weeks.\nFinally, my current posts here on webtask may be found at the tag: https://www.raymondcamden.com/tags/webtask. Not a lot yet, but it will grow. :) You will see more posts on the official blog, but I'll be covering it here as well.\n\nThat's all for now and I hope this is helpful!\nHeader photo by Valentin on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Customizable Weather App in Vue - 2",
		"date":"Tue Mar 27 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1522108800,
		"url":"https://www.raymondcamden.com/2018/03/27/building-a-customizable-weather-app-in-vue-2",
		"content":"A few days ago I shared the first draft of a customizable Vue.js weather app (Building a Customizable Weather App in Vue.js). Yesterday I had some time to work on an update and I thought I'd share my progress.\nAt the end of the first draft, my code would get your location and then use the Yahoo Weather API to fetch the forecast. What it didn't do was update the background to match the weather. To support this, I came up with the following procedure.\nFirst - I'm going to take the weather type, which Yahoo uses a code for, and &quot;collapse&quot; it down into a few specific types. Yahoo supports nearly fifty different types of weather, from tropical storms to dust. I figured it made sense to simplify the various types into a much smaller list. In theory, you could switch Yahoo out with another service and as long as you return the same core types, then the rest of the code would work fine.\nSecond - I wanted to make it so you (you being the person using my code for your own personalized app) could easily supply images. To support that, you can specify images by weather type like so:\n\nBy using an array, we can randomly select one to keep things a bit interesting so you don't see the same picture every time.\nFinally - to make it even easier for you - I support a &quot;catchall&quot; bucket of images that will be used if the code can't find images for a specific weather type. Or shoot, maybe you don't care about finding images for a particular type of weather and just want random pictures of your kids, weather be damned. Here's an example:\n\nI like this because it's simple and it allows you to be lazy too. Heck, you can even just use one image. Let's take a look at the updated code.\n\nThe important part above is getWeatherImage. You can see my logic to convert Yahoo's multiple weather types to a simpler list. Finally I just select a random image. The last change was to update the background:\n\nYou can see some commented out code there. I was using some CSS to darken the image and I was not able to dynamically update the URL. I ended punting on that. I'd love it if someone could help me figure that out.\nYou can find the code for this version here: https://github.com/cfjedimaster/webdemos/tree/master/vueweather/v2\nWhat's next? Well, in theory, I could add support for multiple locations. I was considering that, but the last post didn't get any comments so I'm not sure if anyone is actually enjoying this. I'm totally fine with that (I had fun writing it!) but I'm not sure I'll go deeper unless there is some interest. Let me know what you think.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Remotely Interesting",
		"date":"Fri Mar 23 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1521763200,
		"url":"https://www.raymondcamden.com/2018/03/23/remotely-interesting",
		"content":"A few days ago I remarked on Twitter about how interesting it was to be back in an office again. It was only for a day (I was in town for\na meeting at work), but just the actual feeling of being in an office is something that is rather unusual for me. I've been working from\nhome for roughly twenty years and have been - if I may say so - a bit successful at it. A few minutes after I tweeted, a follower asked me\nhow I've managed to do this. I thought I'd share my opinion on this out here for everyone to see and as always - I welcome people to chime in\nwith their own thoughts and opinions.\nWhen I think about working remotely, I tend to break it down into two main categories - the first being the core mechanics of making it work\n(i.e. things you need, should have, etc in your workspace) and the second being how to find a remote job and be successful at it.\nIn terms of what you need in your home, there is the &quot;ideal&quot; and then there is the practical reality of your situation. When I first\nstarted working from home, my &quot;office&quot; was a small desk in our bedroom. Later on, we moved into a slightly bigger house and I got my own office,\nand that is definitely the ideal situation, but I certainly don't expect everyone is going to have an extra bedroom around. I do think it's important you have some separation. So if your bedroom is your office, then during the day you may want to consider closing your door to create a more private space. You may want to invest in some nice noise canceling headphones to keep out noises from the rest of the house.\nTry to find some way to separate yourself from the rest of the house to help you focus.\nIf working at home is not an option, then you may want to consider a &quot;coworking&quot; space. These are buildings that let you 'rent' out space\n(normally not private though) a day or even a couple of hours at a time. This may be a great option for simply taking a break from working\nat your dinner table.\nYou can - within reason - also work at a coffee shop, but you're kind of pushing your luck there in terms of actually being able to find\na decent seat (with power) and not overstaying your welcome. And I don't know about you, but as much as I love coffee, the smell of it after\nthirty or so minutes really begins to bug me.\nWorking from home means I've much more available to do things like running a child to the doctor or picking up supplies for school, and that's great. While I have that flexibility, I still ensure that I follow a pretty strict schedule. I'm at my desk by 8 AM and I'm done around 5:30. (I take a good lunch and exercise break in there.) By having a schedule, I mentally switch over to &quot;work mode&quot; and I don't (typically ;) have issues paying attention and staying on task. I strongly recommend picking a schedule and sticking to it. Your schedule will also depend on when your coworkers are working which can be an issue if they are far away time-zone wise. As an American, this is not really an issue. I'm in CST. I have coworkers in PST and EST. Every now and then (although rarely) I'll have a meeting &quot;late&quot; for me (5 PM) but in general, we tend to be aware that the best times for meetings that covers all the bases. In the end, just be sure your manager knows your working hours and is ok with it, and be willing to be flexible when the need arises.\nI wasn't even going to mention it - but I assume high-speed Internet is an option. If not - obviously that impacts your ability to work at home. But if\nyou can afford it and haven't upgraded your connection, then I would definitely do so before transitioning to working at home.\nEverything else I think is pretty secondary. You can think about your desk and chair and stuff like that, but honestly I never really\nspent much time on that. I got a cheap chair and desk from Office Depot and it works fine for me. Some people have physical conditions where that may not be an option, so maybe think about that before setting up your workspace.\nSo - this all assumes that you've got the job. How do you actually find it? Many companies now are being very upfront about whether or not remote is an option. This saves you from having to ask up front. Obviously, if they do not, it is in your best interest to ask immediately. For someone new to the field, it's going to be difficult to start remotely. You don't have the history of work to prove your worth and your ability to get work done without supervision. You will probably want to go out of your way to ensure your manager knows what you are doing, how you are progressing, and generally, making sure that even though you aren't in the office your presence is felt. To be fair, that's true for people with years of experience as well. And heck, probably just as true for folks in the office too. If you can't document your achievements, in most cases it won't matter what you actually get done.\nIn terms of the tech industry, are there specific parts that are more open to r",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Slides and Assets from JazzCon PWA Talk",
		"date":"Thu Mar 22 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1521676800,
		"url":"https://www.raymondcamden.com/2018/03/22/slides-and-assets-from-jazzcon-pwa-talk",
		"content":"The title says it all. If you were in my JazzCon talk earlier on PWAs, here are my slides:\n    Building a PWA - For Everyone Who Is Scared To  from Raymond Camden \nYou can download the code here: demosjazzcon.zip. As an FYI, the reason the second demo failed was due to my using the PlaceCat service which is no longer around. My code to fetch and cache it failed and I didn't have code to handle that error. A good mistake to have and something I can learn from!\nHeader photo by William Recinos on Unsplash\n",
		"tags":[
	        
            "pwa"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Customizable Weather App in Vue.js",
		"date":"Mon Mar 19 2018 11:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1521457260,
		"url":"https://www.raymondcamden.com/2018/03/19/building-a-customizable-weather-app-in-vue",
		"content":"I am somewhat of a weather app collector. Even though they all basically give the exact same information, I just have a thing for beautiful renderings of the weather. My default weather app, Yahoo Weather, can be stunning at times.\n\nA few days ago I discovered my new favorite - Weather Kitty.\n\nAs you can guess, there is also a Weather Puppy, which is cute, but not cat cute. Sorry, nothing beats that. I was looking at the kitty app and realized that you can probably make a weather app on any topic and just churn out apps like crazy. Since &quot;like crazy&quot; is my main impetus for building things, I thought it would be fun (and educational!) to build a weather app in Vue. But not just any weather app. One that you could easily (with access to the code I mean) drop in your own photos. For example, one with your kids:\n\nOr even a Marvel comics one - because - why not?\n\nIn general, all of these apps tend to follow a similar pattern - get the weather and try to show an appropriate picture. In the case of the Yahoo app, it is a picture from the same geographical location. For others, it's just a picture that matches the current weather.\nMy end goal here then is to create an app where you can simply provide the pictures. You will need to categorize them (here are the sunny pics, here are the rainy pics), but then you're good to go. You can plop the code up on a web site and then run the app from your browser. (And sure, if you want, you could build a hybrid mobile app too if you want, but why?)\nFor the first iteration of the code, I focused on getting the weather and rendering it over a hard coded picture. In the next version (which may be a while - I have a week of travel ahead of me) I'll work on the &quot;It's raining, find the best picture&quot; logic. You can find the current code base here: https://github.com/cfjedimaster/webdemos/tree/master/vueweather/v1. I did not upload the picture so be sure to supply your own. Ok, let's take a look at the individual components. First, the HTML.\n\nThere's not much here. Basically I've got a loading div (that will go away when data has been received) and a few blocks for the forecast. Most weather apps support a way to add multiple cities and I have some ideas on how to support that, but I'm holding off for that till later. Also note that I've loaded Axios. Axios is a HTTP client and seems to be popular with Vue developers. I thought it would be nice to force myself to give it a try and for the most part, it looked like a good idea. But within five minutes I ran into a bug with one of the core features, so I probably won't use it again. (That sounds a bit mean perhaps, but if I run into an issue right away with a library, I don't take that as a good sign.)\nI don't normally share the CSS, but I'll do so here. It was a bit of a strugle to get the background picture right and text lined up right. I'm 100% confident this could be done better:\n\nNote the use of the gradient. This is done to slightly darken the background and make text a bit more clearer to read. Later, I need to make the background picture (which is used twice) something that I can edit via JavaScript. And speaking of JavaScript...\n\nI begin by defining my Vue instance and some basic variables I'll use in the app. The only one I think may be confusing is the images block, which will be fleshed out later to let you define images for your app.\nWhen the created event is fired, I do a Geolocation request. I don't properly handle the error state, but that could be added of course. Note that modern browsers require an https server to use this feature. While you definitely can test this on localhost, be aware of this restriction if you deploy to production. (And just freaking use https!)\nNext is the loadWeather function. I went back and forth between multiple different weather API providers, but ended up with the Yahoo Weather API. This is not a fan I'm a big fan of, but it is free and doesn't require a key. But look at the URL. Ick. (And I know it's ugly because it is using an embedded YQL string and YQL in general is pretty cool, but... ick!)\nOnce done - I simply upload my values and that's it. Want to test it? I pushed it up on Surge here: https://adhesive-flavor.surge.sh.\nAs I said above, the next step is to start working on image loading. What I'd like is the ability to provide multiple images per condition (so you don't always see the same thing) and a &quot;fallback&quot; option so that if you can't find pictures for every condition, you can at least show something. I'm definitely open to more suggestions as well - just let me know in a comment below!\nHeader photo by Nilo Isotalo on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Adding Referrer Protection to Webtasks",
		"date":"Mon Mar 19 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1521417600,
		"url":"https://www.raymondcamden.com/2018/03/16/adding-referrer-protection-to-webtasks",
		"content":"A few months ago I wrote up my experience of adding referrer style protection to an OpenWhisk action. Basically - checking the referer header to see if it is valid before executing a particular serverless action. I was thinking about that post and how I'd implement it with Auth0 Extend and webtasks and came up with a solution that helped me learn even more about the platform.\nWebtasks, and by extension, Auth0 Extend, have a feature called middleware. Unfortunately, this is not documented currently, but I plan on fixing that very soon. Middleware works just like it does in Express apps, but if you aren't aware of the feature, you can think of it like pipe. By specifying middleware for a particular serverless action, you're saying that the middleware should run first. What you do there is up to you. You can use it like I plan to below, a security check, or you can use it to load up and prepare some data for use later.  I first discovered this feature (because again, we have to document it!) via a great post by my coworker: Securing Webtasks Part 2: Using Middleware. I definitely recommend reading that post (and part one) for a much deeper look at webtasks and security. My post is fairly lightweight as I'm just doing one simple check.\nAlright, so how do we use middleware? You create a function that returns the logic of your middleware. Basically - a factory type function. (Functions that return functions always throw me a bit when I read them.) Bobby's post has a great minimal example of what this looks like:\n\nThe logic you employ here obviously will depend on whatever your middleware needs to do. Security-based ones would probably abort a request. Transformative ones would modify request values. You'll see my example in a moment.\nSo this part is simple. The part that isn't terribly simple is that you have to put your function up in a place where the source is available. So what do I mean by that? When I create a normal webtask and pass code to it, I'm giving a URL in response. But that URL executes the code. What you want is a URL that reveals the code itself. So for example, you could put the code up on a GitHub Gist if you want. For me, I'm using a method Bobby did - a webtask that spits out the code using ES6 template strings. Here's my logic:\n\nThis would then be pushed up to webtask.io using a regular wt call like so: wt create check.js. To be clear, the actual middleware is not the portion at the end. That's basically saying &quot;take this string and output it with the right content type&quot;. The logic is in the code on top. You can see this for yourself here: https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/check\nOh, and the logic is virtually the same as my previous demo. I've got a set of hard coded valid referrers that I can iterate over. I fetch the header via req.webtaqskContext.headers and check it. If I get a match, I immediately run next() to have the process carry on. If I get to the end, I return an error by calling next(error). If you've done this in Express then this shouldn't be new to you.\nSo the final bit involves setting up a task to use the middleware. I took an existing basic &quot;hello world&quot; task and recreated it like so:\nwt create hello.js --name securetest1 --middleware https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/check\nIf you're curious, the --name part there just gives a name to the webtask. By default it uses the filename minus the extension, but I already had hello up there so I wanted something new. The endpoint created is here:\nhttps://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/securetest1\nBut of course, you should get an error hitting it. To test, I built a quick HTML file that literally just had this block of JavaScript:\n\nI then plopped this up on surge and it ended up here: http://rhetorical-collar.surge.sh/test.html. If you go there with an open devtools, you'll see the endpoint executing successfully.\nAgain - this is just a little peek. Read Bobby's post for a deeper look, but I enjoyed rebuilding this check under webtask as it gave me a chance to learn about a feature I missed.\nHeader photo by Markus Voetter on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Text-Based Adventure in Vue.js (3)",
		"date":"Tue Mar 13 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1520899200,
		"url":"https://www.raymondcamden.com/2018/03/13/building-a-text-based-adventure-in-vuejs-3",
		"content":"It's been a while since I updated my little Vue-based game but I had a bit of time over the weekend to make some small tweaks. Primarily my work was on supporting what I call &quot;lookables&quot; - things that you can look at in a room to get more detail. I got it working, and I'm happy with how I did it, so let me quickly cover how I did it.\nUpdating the CLI\nUp until this point, my CLI was pretty basic. I had a basic list of commands that was used to verify input, but after that, the code always assumed your input was a movement. Getting &quot;look&quot; working was a bit complex. Since the command always requires an argument, I needed to update how I verify commands. I tried to keep it nice and generic. In my list of valid commands I added look * as a way of saying &quot;this is a valid commands but it must take an argument&quot;, but things got messy.\nSo I decided to punt. I thought to myself - in a real game engine, I think it's safe to assume that some commands are baked in and some are dynamic. I had already hard coded in support for movement and decided that &quot;look&quot; could also be baked in. I changed my earlier method, validInput to a new method, parseInput. This method would return a simple object containing a cmd and arg value. So movements would become {cmd:'movement', arg:'e'} (move east) and input like look cat would be: {cmd:'look', arg:'cat'}. Here's the updated code:\n\nLook Data\nThe next thing I did was decide on the &quot;look&quot; data structure. Every room would have an array called lookables. Every lookable item consisted of two parts:\n\naliases: This is what you're looking at and as the name implies, allows for a list of aliases. So for example, I'd want you to be able to look at a cat, a fat cat, and so on.\ndesc: This is simply the text description of what you're looking at.\n\nAll together then the look command is simple:\n\nWriting Lookables\nThe final part was creating an easy way to include lookables in room data. I decided on this format:\n\nEach lookable is defined on one line. The aliases and description are separated by an @ character. Aliases are separated by the | character. I then updated my utility script to parse this. It's boring text parsing but you can see it here if you want. The entire code base can be found here: https://github.com/cfjedimaster/webdemos/tree/master/vuetextbasedgame\nOh - and I also added support for a 'status' display to handle displaying the result of what you look at. I'm going to move the error conditions there and get rid of the alerts. You can &quot;play&quot; the game here: https://cfjedimaster.github.io/webdemos/vuetextbasedgame/. But note I only added lookables to the first room.\nWell... I think I'm almost done with this little experiment. I've got an idea for one last mod and then I think I'll move on.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Serverless Form Handler with Auth0 Webtasks - Express Style",
		"date":"Wed Mar 07 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1520380800,
		"url":"https://www.raymondcamden.com/2018/03/07/building-a-serverless-form-handler-with-auth0-webtasks-express-style",
		"content":"A few days ago I blogged about creating a basic &quot;form handling service&quot; using Auth0 Webtask. When I showed this to one of my coworkers, he had commented that this was something he would normally have used Express for. Auth0 Webtask does support deploying Express apps, and it's rather easy to do so, but using Express in a serverless world feels... weird to me. It just doesn't seem like something I'd want to do, which means it was a perfect thing for me to actually do so I could see what it felt like. I've done that and you'll see the code below. How do I feel about it now that I'm done? Keep reading and I'll share.\nLet's begin by taking a look at the code. I'm going to strip out part of it for space but the full version can be found in my GitHub repo that I'll link to at the end.\n\nSo let's discuss this. One of the first and most obvious changes is that I'm loading in Express and defining routes. Previously my handler had one main entry point, but now I've got - technically - three of them. I've got a route for the initial form display, one for processing, and one for thanking the user. Because I'm embedding the form and thank you page into to the app as a whole, I also scaled back on the &quot;dynamic-ness&quot; of the form processing. This isn't a &quot;generic&quot; form processing service now, but instead an app meant to display one form, process it, and thank the user.\nIn order to handle my two static views, I've embedded the HTML in strings at the bottom of the page. In the listing above I've removed them for space, but it's basically the same HTML I used in the previous demo. (The only change is the URL in the form action.) I'm not crazy about embedding the templates like that, but for now, it's the best option. (And in theory, you could get a bit fancy with this. You could have them as separate files and simply use a build script to generate the final file and deploy that to Webtask.io.)\nThe other big change is that I have to handle reading my form data myself - but Express makes that easy. I added in bodyParser and then refer to req.body in my post handler.\nFinally, the last bit that tricked me up was the redirect to thanks. I originally had this:\nres.redirect(/thanks);\nBut this sent me to my host Webtask.io domain without the prefix of the webtask name. In my case, I'd go from https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/form_handler_express to https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/thanks which gave me a 404. Thankfully I work with people far smarter than me and they helped me out with the req.originalUrl bit. (Thank you Olaf and Geoff!)\nMy deployment command is also slightly different:\nwt create form_handler_express.js --no-parse --no-merge --secret sg_key=mykeyismoresecretthanyours --dependency sendgrid\nSpecifically note the --no-parse --no-merge aspect. There's another way to handle this documented in the webtask &quot;custom program models&quot; page and that's an aspect I want to touch on more deeply later.\nIf you want to see the full source code, you can view it here: https://github.com/cfjedimaster/Serverless-Examples/blob/master/webtask/form_handler_express.js. Don't forget that the webtask CLI let's you create a task based on a URL. If you've got the CLI installed and configured with your credentials, you could make your own copy and then edit it in the online editor. You will need your own Sendgrid key of course.\nOk, so I promised up top I'd share what I thought. I'm... torn. :) While I still don't think I'd go for Express initially when working with serverless, I do like how I have my app &quot;tied&quot; together like this. In the scenario of needing to build a form and not needing a 'generic' service, I think Express worked really well here. Some code for the view - some code for the processing.\nHeader photo by Matt Jones on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Learn JavaScript Fundamentals at KnowJS",
		"date":"Tue Mar 06 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1520294400,
		"url":"https://www.raymondcamden.com/2018/03/06/learn-javascript-fundamentals-at-knowjs",
		"content":"My buddy Brian Rinaldi has been running free online developer meetings over the past few months at Certified Fresh Events. Last night he announced a new event, KnowJS. This is an all day online event featuring four of the best speakers in the JavaScript community - Kyle Simpson, Bianca Gandolfo, Brian Holt, and Kent Dodds. The event will be held on April 13 and tickets are limited to 50 seats. You'll be able to rewatch the talks at any time later. If you can't make the event, Brian will be selling access to the recorded content at a later date.\nSo if it isn't clear, I strongly recommend signing up for this event. I definitely think it's worth the cost and folks of any skill level should walk away with a huge amount of new knowledge!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Serverless Form Handler with Auth0 Webtasks",
		"date":"Fri Mar 02 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519948800,
		"url":"https://www.raymondcamden.com/2018/03/02/buidling-a-serverless-form-handler-with-webtask",
		"content":"Earlier this week I decided to build a rather simple application with Auth0 Webtask, a form handler. This was something I did many months ago with OpenWhisk and I was curious what the experience would be with Auth0 Webtask. As usual in such things, even though it was pretty simple, I did run into a few things that helped me understand Auth0 Webtask a bit more. Here is how I approached it.\nFirst, I built a form. I actually had a simple form I used last week in my (very last) OpenWhisk talk, so I just copied that over.\n\nI used Bootstrap to make it a bit nicer. If you want to see the final result, I'll be sharing the URL at the end. I created the first draft of my handler with the smallest code possible.\n\nForm data is available in the context argument so I've simply made a copy of it to address it a bit nicer. I log it and return it. To deploy this, I saved that code as form_handler_v1.js and used the following command line:\nwt create form_hander_v1.js --name form_handler\nWhat's with the --name part? By default the CLI will give your webtask a name based on the filename. I was planning on writing multiple different versions of this application so I used the name argument to specify exactly what I wanted. As I go on and add newer versions, I'll just keep using the same name.\nThe command line spit out the URL for me: https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/form_handler and I simply pasted that into my form. I filled out some data, hit submit, and got a nice, but not very helpful, JSON response:\n\nI also opened up a new tab in my terminal and ran wt logs so I could see the log output as the webtask was run:\n\nSweet. Practically done, right? I then switched to a new version, form_handler_v2.js:\n\nThis one kicks it up a notch by adding features and functionality from my original OpenWhisk demo.\nFirst - I have support for looking for a form variable called _from. If it exists, I'll use it as the sender for the email generated by the form. I also allow for a dynamic address as well, but restrict it to a hard coded list of valid values (RECIPS). As before, I use a form field with an underscore (_to).\nNext, I look for a _subject field to allow customization for the email.\nFinally, I look for _next as a way to know where to send the user after the form is processed. If nothing was specified for that, I simply send them back to where they came from. That's probably not a good idea, but I needed some default.\nAfter working with the &quot;special&quot; variables, I then gather up the form fields into a simple string. Note we skip any field that begins with an underscore. I wrote a mocked out sendEmail function and then did my redirect. I deployed this version by using wt create form_handler_v2.js --name form_handler and...\nIt broke. Why? I kept getting an error about form._form not existing. Turns out, when you use a webtask with this form: function(context, req, res), the expectation is that you are going to parse the request body to get what you want out of it. I had switched to this form so I could do the redirect at the end. This issue with the context value is documented, but needs to be cleaned up a bit (my job!). Luckily, there's an easy fix - add --parse-body to your CLI call. You can find this if you run wt create -h. So my new command was:\nwt create form_handler_v2.js --name form_hamdler --parse-body\nI didn't need to update my URL in the form as it already had the right value. I did change the email field to use _from though.\nOk, almost there! Now let's add in actual email support. I used sendgrid before so I figured I'd use it again. I already had a developer key so that saved me the trouble of getting a new one. Here's the updated version (form_handler_v3.js):\n\nSo notice I changed how I called sendEmail. Instead of &quot;fire and forget&quot;, I actually wait for it to finish. The code inside sendEmail is virtually the same from my previous demo and is just boilerplate email code. However, to get it to work, I need to add the sendgrid npm module. The visual editor has a slick UI for this, but as I'm using the CLI, I'll just use that. Luckily there's yet another argument for this, --dependency. Note, --dependancy will not work, because that's not how it's spelled. Learn to spell. (I'm talking to myself, by the way.)\nHere's the final version of the CLI call:\nwt create form_handler_v3.js --name form_handler --parse-body --dependency sendgrid --secret sg_key=mysecretsarebetterthanyours\nNotice I also added my sendgrid key as well. That was used here: context.secrets.sg_key. At this point, the CLI is a bit long, so I'd probably create a simple shell script to simplify it a bit. To finish testing, I added this to my form:\n\nThis provides a custom subject line for the email and an address to go to when done. You are more than welcome to test the form here - https://cfjedimaster.github.io/Serverless-Examples/webtask/test_form.html. Please note I won't actually be reading the emails. ;) You can find the ",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An Example of Ajax Searching with Vue.js",
		"date":"Thu Mar 01 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519862400,
		"url":"https://www.raymondcamden.com/2018/03/01/an-example-of-ajax-searching-with-vuejs",
		"content":"Last week I gave my very first presentation on Vue.js. In that talk I used the heck out of CodePen for my demos. I love how simple it is, I love that folks can edit and run my code. All in all, it is a just a great platform that works really well with Vue. But - for my presentation I really wanted a few demos that were entirely standalone. I really like CodePen, but I worry that it is a bit too &quot;abstract&quot; at times, or by itself. I'm not sure if that makes sense, but in the end, I just wanted to have a few demos that were file based, 100% complete, and so forth.\nI really liked one of the demos I built, so I thought it would be nice to share it individually as a post. It isn't anything that I haven't covered before per se, but like I said, I liked it. :) The demo covers a pretty common use case: Providing a search interface that uses a remote API and return the results.\nFor my demo, I used the iTunes API, which is surprisingly simple to use and doesn't require a key. Thank you, Apple. All the demo will do is provide a search interface and then render the results. Let's begin by looking at the front end:\n\nI'll focus on the Vue-specific parts in the template above. First, note the v-cloak declaration. Vue will automatically remove this style from the DOM after it has loaded. By adding a simple display:none definition for the style, we have a handy way of handling FOUC (Flash of Unstyled Content).\nNext I've set up a text field and button for my search. I could make this more complex per the specs of the API, but this is nice and simple. The text field is tied to a value called term and my button fires a click event to run a method search.\nBeneath that I've got a div that loops over the result. The only thing probably interesting here is the use of a simple filter for the date. You'll see how that is defined in a bit.\nFinally - look at the last two divs. These both are set to show up based on various flags. One for no results and one for the searching event.\nAlright - now let's look at the JavaScript:\n\nI begin by defining the filter, formatDate which just makes use of the Intl API. I've got an article on this idea coming out next week. My main app is relatively simple. I've got 4 pieces of data of which two are just flags. My real data is just the term and results. I've got one method, search, that hits the iTunes API and then renders the result.\nYou can test this version yourself here: https://cfjedimaster.github.io/webdemos/ajax-search/index.html. The full source code can be found here: https://github.com/cfjedimaster/webdemos/tree/master/ajax-search\nOk, not exactly rocket science, but hopefully a bit useful. For the heck of it, I wrote a second version. This version adds audio playback. First, I added this to the front end:\n\nThen I updated the JavaScript:\n\nI added an audio variable meant to represent the current piece of audio being played. Then my play method simply makes use of the Audio API to play it. Note that I have code to stop it on a new search or when selecting a new sample. Before I had this though it was kind of fun to click like crazy and hear all the samples playing at once.\nYou can try this version here: https://cfjedimaster.github.io/webdemos/ajax-search-2/. And the code may be found here: https://github.com/cfjedimaster/webdemos/tree/master/ajax-search-2.\nAs always, if you have any questions about this, let me know by leaving me a comment below.\nHeader photo by Annie Theby on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building an HTML5 Comic Book Reader - in 2018",
		"date":"Wed Feb 28 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519776000,
		"url":"https://www.raymondcamden.com/2018/02/28/building-an-html5-comic-book-reader-in-2018",
		"content":"Nearly six years ago I created a demo of a web-based comic book reader. For those of you who aren't comic book readers, you may not be aware that there are a few standard file formats for digital comics. Way back in 2012 I built a JavaScript-based parser to work with those formats, well one of them, and it actually worked well:\n\nComic books are typically stored in two file formats - CBR and CBZ. CBRs are simply RAR files and CBZs are zips. CBR seems to be much more common, but at the time I wrote the demo I was unable to find any JavaScript library that handled them.\nAnother thing I did back then was use the FileSystem API to handle storing images. This was only supported by Chrome and is now deprecated. If you want to store binary data, you should make use of IndexedDB which now supports binary data well.\nI decided to take a look into how I could update this code and was surprised to find an excellent library that handles both zips and rars. Heck, it even handles tar files. uncompress.js worked well for my demo despite a lack of documentation. The author does provide multiple examples though. By piecing together various examples and just generally messing around, I got the new version working.\nIt now works well in Firefox and Chrome, and is hella fast. I didn't update it to Vue, or upgrade the old jQuery or Bootstrap, but that could be done by someone wanting to file a PR.\nIf you want to test it out, point your browser here: http://cfjedimaster.github.io/HTML5ComicBookReader/index.html\nThe source code can be found here: https://github.com/cfjedimaster/HTML5ComicBookReader/\nI won't share all the code here as it is a lot of DOM manipulation (and again, Vue would make this much prettier), but here is the main method fired when a file is dropped onto the DOM.\n\nThe really crucial part is archiveOpenFile. That handles recognizing the type and figuring out the details. You get an archive object that contains data about all the entries. I filter that to files and then create image URLs for all the images.\nAnd honestly that's it. I'm sure a lot of improvements could be made still, but I was pretty shocked by how well this worked and how darn fast it was. Anyway, enjoy!\nHeader photo by Lena Orig on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "An Introduction to Webtask",
		"date":"Tue Feb 27 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519689600,
		"url":"https://www.raymondcamden.com/2018/02/27/an-introduction-to-webtask",
		"content":"Welcome to my first post on Webtask! Webtask is the serverless platform from Auth0. It powers extensibility behind our identity product as well as Extend. I'm assuming by now most of my readers have a basic grip on what serverless implies in general, so instead I'd like to focus on some of the coolest aspects of Webtask.\nOnline Editor\nFirst, while it has a CLI and you can use it to do everything, it also has a really nice online editor. As a dedicated koolaid drinking Visual Studio Code person I'll always default to my own editor, but this past weekend I needed to use the online editor (I had forgotten to commit a file to source control before leaving home) and found it really well done.\n\nIt isn't obvious from a static screenshot, but like any other modern editor you get intellisense, keyboard shortcuts, and more. You can also run tests directly from the editor as well.\nSource Options\nOk, this may not be something you would use very often, but on top of being able to source your webtask from a file locally, you can also source it from a URL. So at first blush that may not sound too interesting, it's just copying from a URL versus a local file. But you can set it up so that the source is read from the URL every time the webtask is executed. What this means is that you can use GitHub to host the code for the webtask and know that whenever you commit your code, the webtask will be updated as well.\nExpress Support\nSo I've seen Express run on serverless platforms before and I'm still not sold on why you would do that. But after seeing it working with webtask... I'm kinda intrigued. I can see why folks may want to consider doing that.\nStorage\nThis is easily my favorite feature. Webtask supports a lightweight storage system. This is absolutely not meant to replace a proper persistence system, but for small, lightweight data operations, you can make use of it to store data between executions. (I'll show an example in a bit.) So for example, maybe you just want to remember the last time a webtask ran and what the result was. This would support that very easily. It also supports handling write conflicts, both allowing you to handle them dynamically or letting you set a &quot;just write and screw conflicts&quot; mode.\nShow Me Some Darn Code\nThe basic structure of a webtask looks like so:\n\nThe context argument contains things like parameters and secret keys whereas the cb argument is your callback to return results. If you aren't using the online editor, you deploy it like so:\nwt create test1.js\nWebtask will automatically name your task based on the filename, but you can change that with a flag.\n\nYou can copy that URL and open it up in your browser of course: https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/test1\nReturning non-JSON results is also pretty simple:\n\nYou can try this one here: https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/test2\nAs an example of the storage support, here is a simple counter:\n\nAs I said above, remember that you can handle write conflicts manually instead of ignoring it as I did here. While not as exciting as the previous demo, you can test this one here: https://wt-c2bde7d7dfc8623f121b0eb5a7102930-0.run.webtask.io/counter\nWrap Up\nSo this was just a very quick look at Webtask and there is a heck of a lot more you can do. Take a look at the docs and let me know what you think. My plan is to migrate some of my OpenWhisk actions (specifically the Serverless Superman and RandomComicBook twitter bots) and I'll share how that process goes. I'll also be sharing more general How To stuff in the future.\nHeader photo by Christian Battaglia on Unsplash\n",
		"tags":[
	        
            "webtask"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Getting Happy with Vue.js",
		"date":"Mon Feb 26 2018 06:58:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519628289,
		"url":"https://www.raymondcamden.com/2018/02/26/getting-happy-with-vuejs",
		"content":"A happy day deserves another happy blog post, right? A few weeks ago I signed up for an interesting listserv called Data is Plural. This is a weekly newsletter of &quot;interesting&quot; datasets. I put that in quote not because it's dangerous or anything, but the datasets are incredibly varied from week to week. I want to thank my super-smart former coworker Erin McKean for sharing this resource.\n\nIn the last newsletter, one of the datasets caught my eye - HappyDB. HappyDB is a collection of one hundred thousand different &quot;moments&quot;. Simply put - 100K little messages of joy. So for example:\n\nMy Mother gave me a surprise visit at my home.\n\nAnd...\n\nI got engaged with my parents support.\n\nTo be clear, this isn't all deep or emotional stuff. Sometimes the data contains stuff like this:\n\nFrankly, I am constipated over the past 2 days and not able to defecate, today my bowel got cleared that moment really makes me feel happy and my bowel feels well that moment makes me feel happy really.\n\nSo yeah, there's that, but I'm going to focus on the fact that this is a giant pile of happiness. That's good, right? I thought it would be cool to turn this data into a little Vue.js application that randomly displayed one happy message at a time.\nNow - as always - I know this isn't a practical idea. But I also knew that I'd learn a bit while building it. The first issue I ran into was getting the data into a form that would be usable by the web. The data is in CSV, and while I know libraries exist for this, I also figured I could do a one time conversion to JSON locally. I also knew that 100K strings would be a bit large. I decided to take a &quot;slice&quot; of the original data since no one was going to sit there and watch my app go through all 100k messages. With that in mind, here's what I built to prepare my data.\n\nYeah, not terribly complex. I used a nice little csv parsing library, read the file, and worked with 5000 entries. The end result was a JSON file that &quot;weighed&quot; 482K which is pretty big, but I thought it was acceptable. In theory I could zip it and use a client-side JavaScript Zip parser (yes, of course one exists), but for a fun little demo I thought that was overkill. Another idea I considered and threw out was storing the data in IndexedDB. I actually think that would make sense, but again, KISS (Keep it Simple, Stupid).\nFor the front end, I decided to use a simple transition. I'd show a message - fade it out and then fade in the new one (selected by random). This turned out to be difficult for me. Vue has native support for transitions, but the docs really confused me. To be clear, there's nothing particularly wrong with them, but I had a hard time wrapping my head around them. Also, look at this:\n\nThe fact that a visual effect is in markup just didn't sit well in my head. I mean technically the v-if part of the paragraph tag represents something that can appear and disappear, but it doesn't bug me like the transition does. And again, I'm not saying Vue is doing it wrong. Just that mentally - my brain had a hard time with it. I'll also point out that Sarah Drasner has an article on the feature that is worth checking out as well: Intro to Vue.js: Animations .\nAlright, so with that said, let me share the code I wrote. First, the front end:\n\nThe app is really just one paragraph tag wrapped in a transition. Now let's look at the code.\n\nThe code handles loading in the JSON file, parsing it, and setting the local quotes array equal to the result. Then I select a random one and kick off an interval to load a new one every six seconds. Definitely not rocket science, but you can check out the live version here: https://cfjedimaster.github.io/webdemos/happy/.\nThis one was beautiful:\n\nI took my mom to a small hill at Lake Elsinore that was filled with a lot of flowers.\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Hello Auth0 Extend!",
		"date":"Mon Feb 26 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519603200,
		"url":"https://www.raymondcamden.com/2018/02/26/hello-auth0",
		"content":"A couple of weeks ago I announced I was leaving IBM. Today I'm happy to share details on where I'm going next (or to be clear, where I'm working right now). As you have hopefully figured out already, today is my first day at Auth0. I first discovered Auth0 nearly a year ago when working on a proof of concept with OpenWhisk (OpenWhisk, Serverless, and Security - a POC). At the time I was really impressed with their identity product. Not only did it really simplify something I hate doing myself (identity), it also did it really darn well.\nI started talking with folks there and was introduced to other parts of their platform, specifically Webtask and Extend.\n\nMy role at Auth0 is to be a developer advocate for Extend. At the highest level, you can think of Extend as a way for a service to provide serverless customization to their users. A great example of this is GitHub. GitHub lets you respond to events by use of webhooks. Basically, the user tells GitHub, &quot;Hey, hit this URL with details of the change and I'll do stuff.&quot; Webhooks work, but obviously require you to set up a server, or use a serverless platform to host the code.\nAuth0 Extend allows a user to actually write the code directly at the site and not worry about setting anything up at all. It makes customizations incredibly easy as there is nowhere else the user has to go. And because there is a more intimate connection between your service and the user's customization, you can provide more support out of the box then you would get in a traditional serverless platform.\nI admit - this may not make much sense at first and you are probably thinking that a blog post would clear things up. Good! I've actually got a demo built using LoopBack and Extend that really demonstrates things well. It needs to be cleaned up a bit but I should have that out this week. I'll also be spending time on the docs side of things to help make it easy for newcomers.\nAlong with Extend, I'll be spending time talking about Webtask too. It's a serverless platform and drives both Auth0 Extend and customizations for the identity side. It's a pretty cool platform and I've already begun to migrate some of my OpenWhisk code over to it. To be clear, I'm absolutely not saying anyone should stop using OpenWhisk. I think it is an incredible framework. But as a learning exercise, I'm finding it useful to migrate my stuff over so I can see how things are done with Webtask.\nWhile I plan on doing a lot of writing at Auth0, I'll still be blogging here of course. In general I try to update here when I've written something significant elsewhere, so don't forget you can subscribe for notifications on new posts. My speaking engagements page also shows where I'll be next so if you and I will be in the same place and you want to talk more about Auth0, just drop me a line.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Slides and Assets from My OpenWhisk Talk",
		"date":"Sat Feb 24 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519430400,
		"url":"https://www.raymondcamden.com/2018/02/24/slide-and-assets-from-my-openwhisk-talk",
		"content":"I apologize for simply not including this in my last post, but here are my assets for my (last ever!) talk on Apache OpenWhisk and IBM Cloud Functions.\n\n    Going Serverless with OpenWhisk  from Raymond Camden \nAnd here is a link to the demos: https://static.raymondcamden.com/enclosures/openwhiskpreso.zip\nHeader photo by Paul Bergmeir on Unsplash\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Slides and Assets from My Vue.js Talk",
		"date":"Fri Feb 23 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519344000,
		"url":"https://www.raymondcamden.com/2018/02/23/slide-and-assets-from-my-vuejs-talk",
		"content":"If you attended my Vue.js presentation at DevNexus, or even if you didn't, here are the slides and assets for my Vue.js presentation. I think it went well, but I'm looking forward to giving it a few more times to get a bit better at it.\nI haven't used SlideShare in a while, but what the heck:\n\n    Don&#x27;t Over-React - just use Vue!  from Raymond Camden \nAnd here is a link to the Codepen collection: https://codepen.io/collection/XzNgxR/\nAnd here is a link to download the zip file of examples I used in the session (which I may turn into separate blog posts): https://static.raymondcamden.com/enclosures/introtovueexamples.zip\nHeader photo by Paul Bergmeir on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "New PWA Series",
		"date":"Wed Feb 21 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1519171200,
		"url":"https://www.raymondcamden.com/2018/02/21/new-pwa-series",
		"content":"This is just an FYI, but I'm currently writing a series on PWAs (Progressive Web Apps) for Telerik. You can always find my list of external articles on my About Me page. So far I've got two articles in the series published and the third one is almost wrapped.\nPart one is: A Gentle and Practical Introduction to Progressive Web Apps.\nPart two is: A Gentle and Practical Introduction to Progressive Web Apps - Part 2\nYou get zero chances to guess what the third one will be called. ;) While I'm still kinda new to PWAs, I hope these articles can help others learn more about them. As always, I'm open to suggestions on improvements, but please post any comments about the articles over on the Telerik site.\nHeader photo by Michał Kubalczyk on Unsplash\n",
		"tags":[
	        
            "pwa"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Happy 15",
		"date":"Thu Feb 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518652800,
		"url":"https://www.raymondcamden.com/2018/02/15/happy-15",
		"content":"Fifteen years ago today (ok, fifteen years and a few days) I launched this little blog with the idea of trying to help others as well as myself. I've long believed that if I struggled with something then I needed to write it down to help myself remember what I figured out. That strategy has served me well I think and nearly 6000 posts later (currently at 5939 actually) I'm still enjoying this little mass of content I've created.\nThe initial version was entirely my own and written in ColdFusion. I based the design on... some other blog design that I honestly can't remember. It wasn't very pretty, but it worked:\n\nUgly or not, my third blog post was a link to download the source code. This was long before GitHub, even RIAForge, so I simply linked to the source code for folks to download and asked for feedback. The code, BlogCFC, turned into quite a project. It was in use by over 800 different blogs and grew quite powerful. I eventually &quot;retired&quot; from it but I'm very proud of what it became.\nThis site eventually moved to WordPress, which is a powerful engine, but also one that requires a bit of love to keep it happy. I then switched to simple static file hosting so I could simply deploy and not think about it.\nOver these past fifteen years, I have been very lucky with my readers. I've had a lot of comments (nearly 60K, thank you Disqus for not making that easy to find out) and very few trolls. I've gotten a lot of support and help. I've said this before and I'll say it again, thank you for being here, thank you for participating, and thank you for being awesome.\n\nI'll end with something I got a bit famous(infamous?) for - my Amazon Wishlist. If you've found this content helpful and want to show your support, please visit the list and bill your boss for it. I'll definitely appreciate it. :)\nHeader photo by Wang Xi on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Building a Text-Based Adventure in Vue.js (2)",
		"date":"Thu Feb 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518652800,
		"url":"https://www.raymondcamden.com/2018/02/15/building-a-text-based-adventure-in-vuejs-2",
		"content":"Yesterday I posted a proof of concept of a simple text-based adventure game built in Vue.js. While it was incredibly simple (and a bit broken, sorry), I made some progress in updating the engine today that I thought would be cool to share. Pretty much nothing visually changed, but I made some structural changes that I think will go a long way to improving the core game.\nOne of the first things I did was add support for command aliases. I already had this in terms of &quot;w&quot; being an alias for &quot;west&quot;, but I wanted a generic structure for this I could update more easily. With that in mind, I added a simple aliases.json file:\n\nTake a look at that last line. The use of pipe allows me to have multiple variations of an alias. I could have simply repeated them, but I liked this form. It felt more compact.\nNext, I added a file called commands.json. It's only purpose was to serve as a definition of legal commands:\n\nAgain, the last line is important here. My game doesn't support &quot;looking&quot; yet, but I used the form look * as a way of supporting the ability to look at anything. Eventually my code will map &quot;look X&quot; to a result that says, &quot;She wants to do command look with an arg X.&quot;\nI then combined my three data files into one JSON file called data.json and simply fetched that on start. I did this via a utility script I'm going to show in a bit. Here is the new startup routine:\n\nThe prepareAliases method simply handles taking those pipes and 'exploding' them out:\n\nAs the comment says, I want it to be easier for the author, or game developer. And that desire is what drove my final change. As I mentioned, I built a simple utility that would combine my JSON files into one. But I wanted to do something special for rooms. In the first version, rooms were defined in JSON. Here is an example:\n\nWhile that isn't bad, the description field doesn't really work well for long descriptions. I can't use line breaks or quotes without escaping and what I really want is the ability to just write. So I came up with a new format:\n\nI've got a simple description tag where I can put in anything I want. Exits are a line-delimited list of values where each line has a direction and destination. Finally, the name of the file is the &quot;key&quot; for the room. So this file would be initial.room.\nWith that in place, I built this utility:\n\nAnd then ran: node util.js &gt; data.json. And voila - done. You can view the code here (https://github.com/cfjedimaster/webdemos/tree/master/vuetextbasedgame) and actually play it here (https://cfjedimaster.github.io/webdemos/vuetextbasedgame/).\nHeader photo by Jeremy Bishop on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Text-Based Adventure in Vue.js",
		"date":"Wed Feb 14 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518566400,
		"url":"https://www.raymondcamden.com/2018/02/14/building-a-text-based-adventure-in-vuejs",
		"content":"Note - I found a bug with the room description that was fixed in a later build. Sorry about that! Happy Valentines Day! Today I'm showing my love for Vue.js by building something totally impractical and fun - a text-based adventure in Vue.js. As a child of 80s, I grew up playing text-based games from Infocom. In fact, to this day I still say that some of the most interesting games ever created were done by Infocom. My favorite? &quot;A Mind Forever Voyaging&quot;\n\nHeck, the first time I added RAM to a machine was just to support playing &quot;Wishbringer&quot;, another Infocom classic. I graduated from these games into MUDs while at college and had fun not only playing them but coding them as well. (See this nearly five year old post about the code I'm most proud of.) I thought it might be fun to take a stab at building a simple text-based game in Vue.\nNow - to be clear, a text parser is not a simple task. Infocom games were notorious for their complex parsers and their ability to take input and map it to a proper action in the game. I'm not going to pretend to have the coding chops to do that. I did think it would be interesting to try a few basic commands, like movement, and then see if I could build up from there. With that in mind, I'm happy to share my initial version.\nBefore I get into the code, note that you can find the complete code base here: https://github.com/cfjedimaster/webdemos/tree/master/vuetextbasedgame.\nAlright, so what did I build? For my initial version, I decided that I would only support basic navigation among a dataset of rooms. So given that a room has exits to the west and east, I'd support the user typing commands to move in those directions. If you moved to the west, I'd let the user know about the new room and then they would be able to move in whatever directions that particular room supported. I began by designing my game data. Right now this is a JSON file, but in the next version, I'm going to support the abilty to use individual files for rooms and use a Node script to handle converting that data into JSON. That will let me write more freely and not worry about escaping crap that JSON complains about. Anyway, here is the current version:\n\nEach room has a unique ID. This is used to allow one room to 'target' another for movement. And in theory, you could imagine items (magic potions?) that transport a user. Each room currently has 2 properties - a simple description and an array of exits. Now let's take a look at the front end.\n\nThe game's view layer is split into two states - one to use while stuff is loading (you could imagine the room data becoming quite large) and one to display the main &quot;game&quot; UI. Right now that supports two elements - a room description with exit data dynamically generated and then a &quot;CLI&quot; that's really just an input field. I applied all my CSS powers to generate this:\n\nAlright, now let's tackle the code. First, here is a filter I wrote to handle displaying exits. It simply handles converting &quot;x,y&quot; to &quot;X and Y&quot;, or &quot;x,y,z&quot; to &quot;X, Y, and Z&quot;. I could have done that in the view layer, but I also needed to support converting &quot;n&quot; to &quot;North.&quot;\n\nBy the way, dirMapping is external to the filter as it is used someplace else as well. Ok, now for the core logic.\n\nAlright, so that's a bit of code there, let me break it down bit by bit. The data block handles storing things like my current position and other flags.\nNext I use mounted to load the initial data. I previously had created but ran into an issue when I was trying to automatically focus the input field. First - refs can't be used in created, the DOM isn't rendered yet, and secondly, I had to use $nextTick() because this.loading = false; changes the DOM and actually makes that input visible. This one little part took me maybe twenty minutes, but I'm really glad I ran into it as I learned something new.\nThe cli method handles input and as the comments say, it is pretty simplistic at the moment. Right now it has no parser and just assumes everything is a movement. validInput is the beginning of the abstraction to handle verifying input, but obviously later I'll need some code to handle taking in input and mapping it to a proper action. As I said, this is just a beginning.\nThe only supported action now is movement, and you can see that in play in doMovement. First this converts your input to a shorthand value (ie, &quot;north&quot; to &quot;n&quot;), then verifies that it is valid for the room. If it is, I simply move you.\nFor errors I'm using alerts, but I really want to do something different. Like maybe have a div that is an active response to your input. It could handle both showing errors as well as responding to good commands (&quot;You move west.&quot;), but I wasn't sure how to handle that visually. Anyone have an idea?\nSo that's it. I've got some notes about what I want to do next. If you want to &quot;play&quot;, visit the",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Leaving Big Blue",
		"date":"Mon Feb 12 2018 05:43:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518414212,
		"url":"https://www.raymondcamden.com/2018/02/12/leaving-big-blue",
		"content":"A little over three years ago I was reached out to by an employee of IBM asking if I'd be interested in a developer relations role with them. I had been looking for a devrel role for some time (my position at Adobe had changed after I joined) and I was interested in what IBM had to offer. To be honest, outside of boring (yet I'm sure important) mainframes and Watson, I really didn't know what IBM had to offer for developers.\n\n\nI'm sure really important things are happening here...\n\nImagine my surprise when I discovered that they absolutely did have things relevant to me as a developer. First - I had no idea that Watson, the same Watson I had seen on TV and in the press, had a full set of APIs that allowed me to make use of it in my own applications. (By the way, now that I'm leaving, I can tell you that, secretly, Watson is known as Skynet internally. Haha, just joking. Maybe.)\nI then discovered the awesomeness of IBM Bluemix (now known as IBM Cloud). While certainly not the first Platform as a Service (PaaS) solution, it was easy to use and damn well designed.\nFrankly - I ended up kicking myself for not paying more attention to IBM.\nI've had a good time over the past few years talking about various IBM technologies, open source projects like LoopBack, and serverless with OpenWhisk. However, an opportunity came up that truly excited me and I thought it was too good to pass up.\nI've had the pleasure of working with some incredible talented people and the opportunty to learn a lot. My last day will next Friday, the 23rd, and I'll be sharing the news of my next adventure, soon after.\nHeader photo by Esaias Tan on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Installing Jekyll on Windows",
		"date":"Mon Feb 12 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518393600,
		"url":"https://www.raymondcamden.com/2018/02/12/installing-jekyll-on-windows",
		"content":"Before I begin, please note that this post is not a guide or anything like that. I'm literally just blogging this to help save others the hours of crap I went through trying to get Jekyll to run on Windows. Jekyll is - or I should say - was my favorite static site generator. But getting it to work under Windows, or even Windows Subsystem for Linux - has been a complete and utter crap fest. I did go through the directions here:\nJekyll on Windows\nBut nothing worked well for me. I've added a few posts to the Jekyll forum asking for help, but then I noticed this towards the bottom:\n\nOptionally you can use [Autoinstall Jekyll for Windows](https://github.com/KeJunMao/fastjekyll#autoinstall-jekyll-for-windows).\n\nThis led me to a repo that basically said, &quot;clone this and run the bat file.&quot;\nDoubts... I had them.\n\nAnd of course, as soon as I ran the BAT file, it failed. But I noticed that the failure seemed to be a simple typo (and I've already filed a bug report and PR) on it. I fixed it - ran it - and oh my freaking god it worked:\n\nAnd there you go. So yes, I'm officially done with Jekyll. It worked well for CFLib but I'm going to find me a new engine that is Node based. For folks curious, I use Hugo for this site, but I really don't like Hugo either. However, it processes incredibly quickly which is something I need for my nearly 6K pages.\nPhoto by Jeff Sheldon on Unsplash\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Building Table Sorting and Pagination in Vue.js",
		"date":"Thu Feb 08 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1518048000,
		"url":"https://www.raymondcamden.com/2018/02/08/building-table-sorting-and-pagination-in-vuejs",
		"content":"Earlier this week I was talking to a good friend of mine (who is also a recent convert to the School of Vue) and he was talking about the troubles he went through in adding table sorting and pagination to a table. He was making use of a particular Vue component that was - to be nice - &quot;undocumented&quot;. While I was reasonable certain that other solutions existed, I thought it would be fun to take a stab at writing my own support for table sorting and pagination. Not a generic solution, but just a solution for a particular set of data.\nI began with a Vue app that loaded in data via an Ajax call and rendered a table. This initial version has no sorting or pagination, it just loads data and dumps it in the view. Here's the layout:\n\nI've got a table with four columns: Name, Age, Breed, and Gender. And then I simply loop over my cats to render each row. The JavaScript is simple:\n\n(As an aside, I say it's simple, but as always, if anything here doesn't make sense, just ask me in the comments below!) While it isn't too exciting, you can see this running below.\nSee the Pen Vue - Sortable Table by Raymond Camden (@cfjedimaster) on CodePen.\n\nAlright, so for the first update, I decided to add sorting. I made two changes to the view. First, I added click handlers to my headers so I could do sorting. Secondly, I switched my loop to use sortedCats, which I'm going to set up as a Vue computed property. Here's the new HTML:\n\nOn the JavaScript side, I had to do a few things. First, I added properties to keep track of what I was sorting by and in what direction:\n\nNext, I added the sort method. It has to recognize when we are sorting by the same column and flip the direction:\n\nFinally, I added my computed property, sortedCats:\n\nI'm just using the sort method of my array with the property being dynamic. The modifier bit just handles reversing the numbers based on the direction of the sort. You can test this version below:\nSee the Pen Vue - Sortable Table (2) by Raymond Camden (@cfjedimaster) on CodePen.\n\nBy the way, you'll notice some debug data at the bottom of the view. In a real application I'd remove that, but I used that as a handy way to track values while I was clicking. I could have used Vue DevTools for that, although I'm not certain how well they work with CodePens.\nWoot! Almost there! For the final version I added pagination. I didn't want to add more cats to my JSON data set, so I used a relatively small &quot;page size&quot; of 3. I began by adding buttons to the front end for pagination:\n\nIn the JavaScript I made the following changes. First, I added values to track the page size and current page:\n\nNext, I added the prevPage and nextPage methods, which were pretty simple:\n\nFinally, I modified my computed property to check the page size and current page values when returning data. I did this via a filter call:\n\nNote the creation of a start and end value. I almost always screw this up so I created variables instead of a super complex if statement. While my code seems to work I'm still not 100% sure that math is right. And here is that final version:\nSee the Pen Vue - Sortable Table (3) by Raymond Camden (@cfjedimaster) on CodePen.\n\nSo that's it. There is definitely room for improvement. I'd like to add disabled to the buttons when they are at the 'edge' and I'd like to highlight, somehow, the table headers when sorting, but I'll leave that as an exercise to my readers. ;) (Don't forget you can fork my CodePens and add your own edits!)\nHeader Photo by Hannes Egler on Unsplash\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Pointing a Raygun at Your Site",
		"date":"Sat Feb 03 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1517616000,
		"url":"https://www.raymondcamden.com/2018/02/03/pointing-a-raygun-at-your-site",
		"content":"For a while now I've been talking about web site error reporting tools. Basically - services that will monitor your site for client-side errors that users get when trying to browse your site. You can read a review of some of these services I did for Telerik in my article here: \nWeb\nA Review of JavaScript Error Monitoring Services\n. That article is pretty old now and obviously the space has expanded since then. A month or so ago I was approached by the folks at Raygun asking if I'd like to take a look at their service. I did, and was pretty impressed. Before I get into it though I want to give a big thank you to Nick Harley from the Raygun team. With the holidays and some person stuff, I kept getting busy and putting off this blog post. I bugged him multiple times for extensions to my free trial and he never complained once. Thank you, Nick!\n\nRight away, one of the things you'll discover with Raygun is that it isn't just a &quot;error monitor&quot; service, but it also does a really good job of giving you feedback on your performance as well. Website performance is becoming more and more important lately, especially with the advent of PWAs. There are multiple different tools out there to help you diagnose and track performance issues, so having something tied in with your error handling is pretty compelling. I'll show examples of both of these with Raygun so you can get an idea of how it works. On top of error tracking and performance, they also do pretty cool user monitoring and tracking (including real time reports) and have features that tie into your deployments. Why? To help figure out if something got better or worse based on a new release of your site.\nSo before we go into some screen shots (all based on reports from this blog), you will want to take a look at their pricing information.\n\nThat is a bit high for a one person blog like raymondcamden.com, but also note that it covers the entire platform of features. If you just want the error reporting (crash reporting), the low end price is a really reasonable - 19 dollars at the time I wrote this.\nThe Dashboard\n\nThis is the main dashboard. It's clean and direct and really highlights the important data points. On the top bar two things stick out right away. First, my performance is poor (which was surprising!) and that for the most part, my site is error free. Seeing that 99% of people are not getting errors is great.\nAs a quick aside, the entire site renders really freaking well on mobile too, which is great if you need to look at an error report from your mobile device.\n\nCrash Reporting\nLet's look at the bug reporting:\n\nYou've got a chart showing you reports over time as well as multiple ways to filter your errors down to find particular items. Individual reports are very nicely designed too:\n\nI love the comment feature at the bottom. Even in a one person team, I could see using that to add notes about a bug. Like - maybe I don't have time to dig into it now, but I've got some ideas as to what the issue could be. I could use the comments as a way to jot down some notes until I get a chance to return to it.\nIn this particular report (and I realize it is probably too hard to read in the screen shot above), the error is coming from a Chrome extension, so while it's on my site, it isn't necessarily my issue. Raygun supports blocking errors from being reported again. There is also an &quot;Inbound Filters&quot; feature where I could add a block on any message containing chrome-extension:// if I wanted.\nThere's a nice report system for designing custom reports and the ability to export your errors into a zip file for your own processing. Speaking of processing, there are also a crap ton of integration features too:\n\nFinally, I'll share a screen shot of a typical email report. While this is a standard feature of such services, I just share yet another example of how much attention was given to design. Make note of the quick actions links on the bottom.\n\nReal Time Monitoring\nNow let's look at the real time reporting. There is a wide array of data here covering performance, browser and platforms, and location. Here's an example of it reporting on browser usage.\n\nBut the important report is the performance tab:\n\nFrankly, this is disappointing. I honestly thought my site was performing well. I went into one of the pages to get some details. In one report, I saw a breakdown like so:\n\nDNS: 3%\nLatency: 6%\nSSL: 0%\nServer: 7%\nTransfer: 1%\nRender: 22%\nChildren: 60%\n\nChildren? I wasn't sure what that was but it concerned me. I checked the performance docs and it defined children as &quot;Time for asynchronous assets to process - this refers to all requests loaded by the page up until onLoad (includes scripts, stylesheets, images and XHR requests)&quot;. I spent some time digging here and found some stuff right away that I had not noticed.\nFirst - I was loading my avatar picture twice. Why? The responsive design layout had it twice in the DOM. That wouldn't be a problem ",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Better Support for Scheduled Actions with OpenWhisk and IBM Cloud Functions",
		"date":"Thu Feb 01 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1517443200,
		"url":"https://www.raymondcamden.com/2018/02/01/better-support-for-scheduled-actions-with-openwhisk-and-ibm-cloud-functions",
		"content":"For a while now (certainly for as long as I've been using it), OpenWhisk/IBM Cloud Functions has had one main way to handle &quot;scheduled&quot; action invocations, the Alarm package. In order to this feature you would use the alarm feed (part of the alarm package, yes, both share the same name) and specify a CRON string for your schedule.\nThat works well. But CRON is... well, CRON is a very powerful and flexible system that looks like it was designed by the same people who created regular expressions. I know I can do anything I want with CRON but I absolutely hate doing everything with it.\nThe good news is that now there is a better way! The Alarm package has a new feed called interval. The interval feed takes (among others) a parameter that specifies the number of minutes to wait between calls. So if you want an action to run every hour? 60. Want it to run once a day? 1440. And that's it. The interval action also supports an optional startDate and stopDate parameter to let you specify a particular date range for the trigger.\nSo an example, consider this CRON based trigger:\nwsk trigger create periodic \n--feed /whisk.system/alarms/alarm \n--param cron &quot;*/2 * * * *&quot; \\\nThe interval version of this would be:\nwsk trigger create periodic \n--feed /whisk.system/alarms/interval \n--param minutes 2 \\\nI don't know about you, but that's a heck of a lot easier to understand. Of course, CRON does offer more complex support, so for example, running only on certain days, but frankly I'd rather write a line of JavaScript to exit early than spend an hour trying to get the CRON syntax right and hoping I didn't screw it up.\nAlso note there is another new feed in this package - once. This feed takes an argument named date and will only fire one time. Previously developers used the alarm feed and used the maxTriggers parameter with a value of 1. This is now deprecated (and that will be documented shortly) and once should be used instead.\nI don't think you need to update your existing OpenWhisk actions to change to either of these, but certainly going forward you may want to consider using interval instead.\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "A Multi-Step Form in Vue.js",
		"date":"Mon Jan 29 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1517184000,
		"url":"https://www.raymondcamden.com/2018/01/29/a-multi-step-form-in-vuejs",
		"content":"Last week I wrote up a demo explaining how to build a simple quiz using Vue.js. As part of that process, I demonstrated how to render one question of the quiz at a time and navigate through the set of questions. It occurred to me that it may make sense to also demonstrate how to build a simple &quot;multi-step&quot; form in Vue.js as well. Let's begin with a simple example. I'll show the HTML first.\n\nThis is a three step form that kind of mimics a typical conference registration. I've got three steps each set up in a div using v-if to control if the particular step is rendered. Note the buttons in each step. I call either next, prev, or submit based on what part of the process I'm in. The last part (where it says Debug) was simply a little, well, &quot;debugger&quot; so I could see data being entered as the process was completed.\nNow let's look at the code.\n\nI've got two interesting things going on here. First, I decided to put all the form data in a key called registration. This gave me a nice separation between the &quot;UI stuff&quot; and the actual form data. The prev and next methods simply change the step value. In theory I could add additional logic here to ensure I don't go to an invalid step, but as I'm writing the HTML myself I trusted myself to not screw that up. Finally, the submit action would do a simple AJAX post and then either redirect to a thank you page or show a server-side error. (I assumed folks were here for the multi-step form and not the AJAX, but if you want to see that, just ask!) Here is a complete embed of this demo:\nSee the Pen Multi-step Vue form by Raymond Camden (@cfjedimaster) on CodePen.\n\nYou may be curious - what if you didn't want to use AJAX for the form submission. I mean, there's a real form there, right? Well don't forget that v-if actually takes things in and out of the DOM. If you tried to submit this version, only the form fields from the last visible step would be posted. Here's a modified version that uses v-show instead:\n\nNote I also added an action and method to the form tag. In this case I'm using the Postman echo service so the result won't be terribly pretty, but you will see all the fields posted. Here's this version:\nSee the Pen Multi-step Vue form (2) by Raymond Camden (@cfjedimaster) on CodePen.\n\nWoot. Ok, so for a final version, I thought I'd try a quick Veutify version. Veutify is a material design UI skin for Vue and it's pretty bad ass. I want to be clear that I whipped this up pretty quickly so it is probably not the most ideal version, but it looks pretty cool.\nSee the Pen Multi-step form, Vuetify by Raymond Camden (@cfjedimaster) on CodePen.\n\nIn case you're curious, that control is called the stepper and has quite a few options for how to configure it. The default is a horizontal step process, but I noticed the labels in the header went away when the width was constrained. This version seemed to work better in the space I have here.\nAnyway - I hope this helps, and as always, remember there's going to be many different ways of doing what I demonstrated here. If you've been working with Vue and have some examples of this, please share below!\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Progressive Color Thief",
		"date":"Thu Jan 25 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516838400,
		"url":"https://www.raymondcamden.com/2018/01/25/building-a-progressive-color-thief",
		"content":"A little over five years ago (omg, really?) I wrote a PhoneGap demo that made use of a JavaScript library called Color Thief. Color Thief (by Lokesh Dhakar) is a library that can inspect an image for dominent colors. My PhoneGap demo was simple. It accessed the device camera to let the user take a picture and then used Dhakar's library to get the dominant colors. Here's a screen shot from the ancient demo:\n\nA little over a year later, I followed up that initial post with a new version that was 100% web standards based: Capturing camera/picture data without PhoneGap.\nThen, four years later, I updated it yet again using the MediaDevices API: New Camera Hotness from Chrome. Unfortunately that API still isn't available in iOS.\nI was thinking about this app last night and thought it would be a good use case for a progressive web app (PWA). It is fairly simple, but adding &quot;add to homescreen&quot; and offline support should be trivial, right? To be clear, there's more to PWA than that, but I thought those two features alone would be a great use case for the Color Thief app.\nI began by updating the code from years ago which meant I had a chance to add Vue. (Woot!) Here is the HTML:\n\nI've got the file input on top to allow for camera access. On desktop this will still work nicely by letting me select a file. I've got some intro text that goes away once the app runs with an image. Now let's look at the JavaScript:\n\nIt basically comes down to two functions. One to set an image source to the location of a file you select or picture you take with your device. THe second simply calls the Color Thief library. You can run this yourself here:\nhttps://cfjedimaster.github.io/webdemos/pwa_colorthief/\nIn case you don't want to - here's the initial view:\n\nand here it is with an image selected:\n\nI ran the Progressive Web App Ligthouse audit in Chrome devtools and got...\n\nOk. All those issues make sense and aren't a surprise. So let me walk through the changes I made to turn this into a simple PWA. (And again, I'm not doing everything, and I'm fine with that. I'm making improvements and that's always a good thing!)\nAdd to Homescreen\nIn order to improve the &quot;add to homescreen&quot; support, and allow for an automatic prompt (remember, that's two different things, ask me in the comments below if you don't know what I mean), I began by adding an app manifest. Here's the one I used:\n\nI initially started off with just the first 7 items you see above. The Lighthouse audit dinged me for not having background_color and theme_color so I added them as white just to shut it up. For the icons, I fired up paint.net, made an image with the right size, and typed. Not very artistic, but it worked:\n\nNotice how I put the text a bit off center. That's design.\nOn the HTML side, I had to add in a link to the manifest:\n\nI also did two other fixes. First, my viewport wasn't correct. Secondly, the Lighthouse audit wanted a theme-color meta tag. Not sure why as it's specified in the manifest, but there ya go. I should also point out that I did not add in the additional meta tags to support iOS. This won't prevent folks on iOS from adding to their home screen, they just won't get the nice icon. The good news is that iOS 11.3 is adding PWA support, and in general, folks upgrade on iOS very quickly. (In general - apparently that is going a bit slower than normal for iOS 11.)\nAt this point, I have what I need for a better &quot;add to homescreen&quot; experience. Android/Chrome Desktop users won't get an automatic prompt though because I don't have a service worker, so let's fix that.\nThe Service Worker\nMy service worker will be fairly simple. I want to cache, and use the cache, for the resources of my app. This will allow it to work offline. That's fairly boilerplate code and I was able to use the same service worker file I've used for my previous demos. All I changed was the list of files to cache:\n\nThis service worker will always use the cache if it exists. Caching strategies is a very complex topic, but just know that you have many different options in this area and can get very precise about how you would like your app to use the cache.\nThe final change to actually use the service worker. I added that to my main app.js:\n\nIf you can't see it - look in the mounted() method. Basically, if serviceworker exists as an API in the browser, register it. And that's basically it. You can run this version of the app here:\nhttps://cfjedimaster.github.io/webdemos/pwa_colorthief_2/\nIn Chrome, both desktop and mobile, you may be prompted to add it to your home screen. On desktop they call it &quot;app shelf&quot; for some reason, but the icon is added to your desktop:\n\nAnd the resulting Lighthouse audit:\n\nWOOT!!! 100!! I am a JavaScript GOD! Ok, no, not really, and as I've said multiple times already in this post, building a PWA will involve more than what I've done here, but in about 30 minutes of work I:\n\nDramatically improved the &quot;add to homescreen&quo",
		"tags":[
	        
            "pwa",
            
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "A Few Tips on Debugging OpenWhisk Functions with VS Code",
		"date":"Tue Jan 23 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516665600,
		"url":"https://www.raymondcamden.com/2018/01/23/a-few-tips-on-debugging-openwhisk-functions-with-vs-code",
		"content":"A few weeks ago fellow IBMer Niklas Heidloff wrote a great blog post on debugging OpenWhisk actions with Visual Studio Code. Please, please, please read his post first: Debugging Apache OpenWhisk Functions with VS Code While his post was great, I had a few problems understanding exactly how to make use of his code, in particular, how to work with an existing OpenWhisk project. You can see my conversation with him in his comments. I offered to help write up the process for integrating his work in existing code and the result is the guide below. Note that there are probably multiple different ways of doing this, but this is what worked for me. Ok, ready?\nLet's begin with a new OpenWhisk project in Visual Studio Code. In the screen shot below, you can see the project. I've got one action, hello.js, in an actions. folder. None of this is a requirement. You don't need a subdirectory and you can have any structure, file name, etc that makes sense to you.\n\nMy action is a pretty simple &quot;Hello World&quot; type example. It is not using an async response or a zip file, but I'll get to that in a minute. Also, I have not actually deployed the code to OpenWhisk. You don't have to do so in order to debug it first.\nStep Zero\nAlright - so to get started, you do not have to clone Niklas' repo. Having his files locally will help in the next few steps, but you can also copy code right from the GitHub site itself. You will only need to copy a few things so I'd probably skip cloning the repo.\nStep One\nThe debugger is going to use a local file called run.js. Make a new file in your project called run.js, and copy it from here: https://raw.githubusercontent.com/nheidloff/openwhisk-debug-nodejs/master/run.js. Note that you can save it anywhere in the project. In the screen shot below it is in root.\n\nStep Two\nNow you need to create a debug configuration. Click the debug icon in the left hand panel, and in the drop down that currently says &quot;No Configurations&quot;, click to open it and select &quot;Add Configuration...&quot;:\n\nIf you're prompted to select an ennvironment, just select Node. This opens a new JSON file (launch.json). In this file you need to define how Code will debug your file. The configuration will consist of both the file to test as well as another file of parameters. You can find samples of this configuration here: https://raw.githubusercontent.com/nheidloff/openwhisk-debug-nodejs/master/.vscode/launch.json. Let's add one:\n\nThere are three important parts here.\n\nIn args, the first argument is the action to run. The second argument is a JSON file containing parameters for your action. I'll show this in a moment. You need to ensure your paths are correct.\nThe second thing to note is name. This can be anything, but you probably want to name it something that makes sense.\nFinally, the program value should point to where you saved run.js.\n\nStep Three\nNow let's set up the payload. Payloads are simply your action arguments. What you have here will depend on your action and what you are trying to debug. As before, the use of a payloads subdirectory is arbitrary. Here is the one we will use:\n\nStep Four\nAlmost there, I promise! As a final step, you need to add one line to your action:\n\nAs the comment says, this is required for debugging. It is also required for zip-based actions. It is completely harmless in this action so we can just add this and forget it, even if we never debug again.\nOk, just to ensure we're on the same page, here's my Code window now:\n\nLet's Debug!\nNow we have everything we need to debug. To start, I'll simply ensure my new configuration is showing and then hit the green button:\n\nYou do not have to actually add any breakpoints if you just want to quickly run the action and see the output. But obviously adding breakpoints let you step through your code line by line and track down bugs and other nefarious issues.\nAnd yeah - that's it. Async actions? It works the same. Zip actions? It works the same. All you have to do is keep adding configurations to launch.json to add more ways to debug. You could have one per action, or many per action if you want to have multiple different payloads to test with.\nSummary\nSo just to quickly go over the above, here is what you need to do:\n\nEnsure your action has exports.main = main at the end.\nAdd a configuration that points to your action, a JSON file for arguments, and run.js.\nCopy run.js into your project (one time).\nMake a payload file for arguments.\nDebug like cray cray.\n\n",
		"tags":[
	        
            "openwhisk",
            
            "visual studio code"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Quiz with Vue.js",
		"date":"Mon Jan 22 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516579200,
		"url":"https://www.raymondcamden.com/2018/01/22/building-a-quiz-with-vuejs",
		"content":"For today's &quot;Can I build that with Vue.js?&quot; blog post, I'm sharing a simple quiz system I've built with Vue.js. The idea was to see if I could write Vue code that would handle a dynamic set of questions, present them to the user one at a time, and then report a grade at the end. I had fun building this, and it went through a few iterations, so let's get started!\nVersion One\nIn my initial design, I wanted to support the following:\n\nFirst, the quiz needed to have three different stages. An initial &quot;you are about to take a quiz&quot; view, a &quot;answer this question&quot; view that will cycle through all the questions, and a final &quot;you finished and here is your score&quot; view.\nFor the questions, I decided to just support two types: true/false and multiple choice with one answer. Of course, this could get much more complex. I built a dynamic survey system in ColdFusion a few years back (Soundings), and supporting every type of question took quite a bit of work.\nFinally, I wanted the actual quiz data to be loaded via JSON, so that any set of questions could be used. (Again though, as long as they matched the criteria defined above.)\n\nBefore getting into the code, let's take a look at the JSON structure of the quiz data first.\n\nMy JSON structure has 2 top level keys, title and questions. The title property simply gives the quiz a name. The questions property is an array of questions. Each question has a text value (the actual question text), a type (either &quot;tf&quot; for true/false or &quot;mc&quot; for multiple choice), and an answer property indicating the right answer. Questions of type mc also have an answers property which is an array of options for the multiple choices.\nTo host my quiz, I used myjson.com, which is a cool little service that acts like a pastebin for JSON. It also turns on CORS which makes it easy to use the JSON packets in client-side applications.\nOk, so how did I solve this with Vue? First, let's look at the HTML.\n\nI've got three main parts. Each of the three divs represent one &quot;stage&quot; of the quiz, either before, during, or after. I could have use if/else statements here, but I like the use of a simple if to toggle on each part. The second div is using a question component to render the current question. Now let's look at the code.\nFirst - the main Vue app:\n\nSo from the beginning, my data block handles created flags for each of the three stages as well as storing questions, answer data, and other parts of the quiz. The created block loads the JSON package of my quiz data and then begins the quiz by showing the initial view. Note we use the proper title of the quiz in the first view.\nAfter the user clicks the button to start the quiz, they can begin answering questions. This is where things get a bit complex. Let's look at the question component.\n\nI've got a template that handles rendering the question (both the question number and text) and then uses simple branching to handle the two types of questions. In theory, this is where you could start adding support for additional question types if you wanted. Make note of how the button fires an event handled by the component, but also then &quot;emitted&quot; out to the parent component. The parent component can then store the answer and update the current question number. This is how you advance throughout the quiz. Note that it also detects when you've answered the last question and fires handleResults to - well - handle the results. The code calculates how many questions you got correct, creates a percentage, and then sets the flag to render the final view.\nYou can take the quiz (and see all the code) below:\nSee the Pen Vue Quiz (v1) by Raymond Camden (@cfjedimaster) on CodePen.\n\nRound Two\nAfter getting my initial version working, I started to think about some improvements I could make to the code. The first one I thought of was simply moving the quiz itself into a component. This would better abstract out the logic and make it more usable. So one of the coolest parts of this update was how my front end code changed. Now it's this:\n\nThat's freaking cool. Here is the JavaScript. The main changes here is the creation of the quiz component.\n\nAnd while it doesn't look any different, you can see the complete app here:\nSee the Pen Vue Quiz (v2) by Raymond Camden (@cfjedimaster) on CodePen.\n\nVersion the Third\nFor the third version, I decided to add something that I think is really cool. Vue has a feature for components called slots. They allow you to pass markup to a component while actually inside a component. It's a bit complex, but imagine this. You've got a component that allows you to pass in a property for a &quot;thank you&quot; message. Ie, a simple string to use to thank the user. One option would be to pass it to the component:\n\nWhile that works, if the string gets large, as has markup in it, it can become unwieldy within a property. So Vue allows us to pass in the value inside the component like so:\n",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Example of Vue.js DevTools",
		"date":"Thu Jan 18 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516233600,
		"url":"https://www.raymondcamden.com/2018/01/18/an-example-of-vuejs-devtools",
		"content":"Yesterday I tweeted about the release of the latest version of the devtools extension for Vue.js and one of my followers had this to say:\nI never find vue tools useful, what is the use of it? I still use console.log to debug js errors&mdash; Muhammed Rashid  N.K (@rashidnk) January 17, 2018\n\nFirst off - I'm definitely in the &quot;console.log for debugging&quot; club myself. Yes, I know I can do step by step debugging with dev tools, but honestly, I'm typically quicker with just some logging. That being said, his tweet encouraged me to dig around a bit in the extension myself. I'm going to share what I've found below, but if you don't want to read, just scroll down to the end and I've got a video demonstration of everything covered here. Ok, ready?\nWhere to Get it:\nThe official home page for the Vue DevTools project is up on GitHub: https://github.com/vuejs/vue-devtools. You can find installation instructions, help for some problems, and more. Currently the extension is supported in Chrome and Firefox but apparently there is also a work around for Safari. Obviously you want to begin with installing the extension and don't forget to reload your page if you've got a Vue app already opened. Yes, I've made this mistake more than once.\nGetting Started\nLet's begin with a super simple Vue app. Here is the entire thing:\n\nAs you can see, I've got one input field bound to a model called name and then an unordered list that iterates over an array of cats. First thing you may notice in your devtools is the extension kind of &quot;announcing&quot; itself - you know - just in case you forget to notice the tab on the right.\n\nClicking on the Vue tab will expose the Vue-specific options. First up is components. In my app I just have one, a root app, and when you click it, it highlights the data available to it. Which could be cool if your view is only showing some of the data. Here I can see it all.\n\nThis is &quot;live&quot; so if I type in the input field it will be reflected immediately in the dev tools view. Even better, you can directly edit within devtools. Mousing over the items will give you controls for editing:\n\nThis also extends to the array - with options to completely remove or add items. To add an item, you need to enter valid JSON, but the extension will provide live feedback as you type.\n\nA little bit later...\n\nThe extension will also handle computed properties. Consider this version:\n\nAll I've done here is switch to a computed property called oldcats. The extension will now display this along with my data.\n\nYou can't edit those values (of course, it's computed!) but if you edit a cat in the data array such that one is older than ten, it will immediately show up below in the computed list.\n\nNeat!\nOk, so seeing data that I've got in my own file may not be terribly exciting. But what if we try a remote data source?\n\nIn this version I've just switched to use the Star Wars API for my data source. Once run, I can see the remote data in my devtools extension and I can even edit it.\n\nCustom Components\nSo what about custom components? Here is a script where I've defined a cat component. Frankly the fact that Vue doesn't ship with one by default is a terrible mistake.\n\nLook now how the devtools recognizes the new component:\n\nNotice how it also picked up on the properties sent to it. Now I'm going to skip over the Vuex tab and go right into Events. This was the only part of the extension that caused me trouble. The readme at the GitHub repo doesn't tell you this, but the Events tab is only for custom events emited by components. So when I had used a simple @click=&quot;doSomethingYo&quot; test and it failed to render, I thought it was broken at first. In the code sample above, you can see I've got a click event, but hitting that did nothing. I had to modify the code to emit a new event.\n\nWith this in play, you can now see events recorded. What's cool is that the extension will let you know an event was fired:\n\nClicking the tab and then the event lets you inspect what fired it and any additional information.\n\nWorking with Vuex\nGetting better and better, right? Now let's look at Vuex. Back in December I blogged an example application that made use of Vuex to build a simple stock game. This is where the Vue DevTools realy kick butt. You get insight into the data within your store as well as a running list of mutations.\n\nThe stuff on the left is &quot;live&quot; which is pretty cool in my stock app as it has a &quot;heartbeat&quot; that does mutations every few seconds. Clicking on them provide detail about the particular mutation - here is one for buying stock.\n\nEven cooler - you can actually reject or roll back your store state by just mousing over a particular mutation.\n\nYou can also use an export/import command to save/restore your Vuex state. This is incredibly useful for debugging issues.\nThe TV Version\nOk, if none of the above made any sense to you, hopefully the video version will make it more clear. As always, I'd",
		"tags":[
	        
            "vuejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using DevTools to Scrape Web Content",
		"date":"Wed Jan 17 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516147200,
		"url":"https://www.raymondcamden.com/2018/01/17/using-devtools-to-scrape-web-content",
		"content":"So yesterday I blogged a demo that was - by my own admission - somewhat silly and not really worth your time to read. However, I was thinking later that there was one particular aspect of how I built that demo that may be actually be useful.\nWhile I was creating the demo, I needed to get a list of all the songs the Cure recorded. I found this quickly enough on Wikipedia:\n\nSo there's only 67 songs there - in theory I could have typed that in about 5 minutes. But why do something by hand when you can use code?!?!?\nI began by right clicking on the first link and selecting &quot;Inspect Element.&quot; (As a quick FYI, I'm using Firefox for this, but everything I'm showing should work in every modern browser. And shoot - I just tested and it's not supported in Edge. Tsk tsk.)\n\nIt may be a bit hard to see in the screen shot, but I noticed two things here. First, the link used a title attribute with the name of the song. Second, I noticed there was a div named mw-category that appeared to &quot;wrap&quot; all the links. I figured this out by mousing over the div in the Inspector panel and noticing the highlight above.\n\nCool. So now I switched to the Console. For my first command, I wanted to grab all the links within that div:\nlinks = document.querySelectorAll('.mw-category a');\nWhen it was done, I tested to see if it seemed right by checking the length:\n\nNotice how I got 67 items and it matches what the Wikipedia page says as well. Cool! So, now I've got a NodeList of data that I can iterate over like an array. (It isn't an array, but I can use it as such.) So first I made a new array:\ntitles = [];\nAnd then I populated it:\nlinks.forEach((a) =&gt; titles.push(a.title));\nAnd when done, I took a quick look to ensure it seemed ok:\n\nCool! And for the final operation, I simply copied it to my clipboard using:\ncopy(titles)\nThis is the only part that is not supported by Edge. Hopefully they add that soon. The end result is a string version of the array I was able to drop right into my editor and go to town with.\nIf any of the following didn't make sense, I've created a quick video showing the process I went through.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Generating Random Cure Song Titles with Markov Chain",
		"date":"Tue Jan 16 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1516060800,
		"url":"https://www.raymondcamden.com/2018/01/16/generating-random-cure-song-titles",
		"content":"Before you go any further, please note that this blog post contains absolutely nothing of value. This was a stupid idea I had last night that I decided to quickly build this morning. It worked. It made me laugh. But there is nothing of value here. If your boss catches you reading this you'll probably be fired. You've been warned.\nSo - a Markov chain is - in my understanding - a way of determining what value would come after another based on a set of initial input. So given a set of data, let's say words, you can determine which word is most likely to come after another. You can find a great example of this to generate realistic Lifetime movie titles: &quot;Using Javascript and Markov Chains to Generate Text&quot;. Unfortunately the code samples in the blog are broken, but the examples are funny as hell.\nI did a quick search and found a great npm library that simplifies creating demos like this: titlegen. From the docs, here is a sample of how easy it is use:\n\nPretty cool, right? So I thought - what if I tried this with Cure songs? I scraped the content from Wikipedia, did a bit of cleanup, and created this demo:\nhttps://cfjedimaster.github.io/webdemos/generateCure/titlegen.html\nIf you don't want to click, here are some examples:\n\n\n\n\nThe demo is a stupid simple Vue app. The layout is just a few tags so I'll skip it, but here is the JavaScript. Note I've removed most of the Cure titles to keep it shorter:\n\nI don't think I understand even 1% of the math behind this and I don't know how realistic this is but my God did it bring it a smile to my face. If you want to look at all the code, you can find it here: https://github.com/cfjedimaster/webdemos/tree/master/generateCure\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Nuxt and Server-Side/Static Vue.js Sites",
		"date":"Mon Jan 15 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1515974400,
		"url":"https://www.raymondcamden.com/2018/01/15/nuxt-and-server-sidestatic-vuejs-sites",
		"content":"This isn't going to be a terribly deep post, more a general FYI for those of you smart enough to stay off Twitter, but I've been looking at Nuxt quite a bit over the past few days and I have to say I find it really interesting.\nNuxt describes it self with the following tag line: &quot;Universal Vue.js Applications&quot; Raise your hand if you read that and aren't sure what it means.\n\nBasically, it means rendering Vue apps on the server. Now, I read that, and the first thing I think is... why? I mean I love Vue (in case it wasn't obvious yet), but I wasn't convinced that I'd actually want to use Vue on the server. I started going through the guide and things began to click for me.\nThe first thing I found with Nuxt is that it generated a Node.js application for me, and in some ways, was like an alternative to Express. Express has been my Node framework of choice ever since I really started using Node. Heck, Express was the main reason I actually gave Node a chance. I've seen other frameworks out there and none of them clicked with me. Using Nuxt though was an entirely different experience. Maybe it's just that I'm in a Vue state of mind now, but I found working with it enjoyable.\nOne simple example of this is that I like that I don't have to build an explicit route to add a page to my application. So for example, if I want /about to work in Express, I'll first add the route to my JavaScript file, do some logic perhaps, and then tell it to render a file, probably using Handlebars on the server. In Nuxt, I can literally just make a page called about.vue and the route is made for me automatically. I love that. I mean, don't get me wrong, I greatly appreciate having fine grained control over routing. That's one of things I thought was cool when moving from ColdFusion, but having the simplicity back again, with Node and Vue, is compelling.\nI also like using .vue files, or as Vue calls them, single file components. Here is a rather ugly, poor example, I modified from the default source you get from the Nuxt starter template.\n\nI've got my template, my page component logic, and even custom CSS (that I didn't used) all easily used.\nNuxt also makes use of the Vue Router and Vuex all, and not just as &quot;it's here if you want it&quot;, but &quot;I'll make it even easier to make use of them&quot;, which I can definitely appreciate.\nYou can see a stupid small demo I made with routing up on Now: https://nuxt1-xccfuoonle.now.sh/. You can view the source code (but seriously, don't, I just screwed around) here: https://nuxt1-xccfuoonle.now.sh/_src.\nTo make things even cooler, while Nuxt generates a Node.js application, it can also generate static files! Now, it isn't necessarily a &quot;one step&quot; process. If you use dynamic routing of any sort, you have to do a bit of work to make the static aspect work correctly, but it isn't too bad from what I can see. I plan on covering an example of this later in my blog. I went ahead and threw the static version of the previous Node app up here: https://dist-unvgubjctc.now.sh/\nYou can also generate a SPA (single page application) if you want.\nAll in all... I'm pretty fascinated by Nuxt. As I said earlier, this is the first time since picking up Express that I've considered any other framework for my Node apps. And heck, this is the first JavaScript-based static site generator that has interested me as well. I'm going to do a few blog posts on this over the next few weeks, but as always, I'd love to hear from people already using it. Tell me your thoughts, good or bad, in the comments below.\nI'll leave you with a few links for further reading:\n\nThe Nuxt web site\nExamples of Nuxt\nNuxt on Twitter\nSarah Drasner on Nuxt\nSome more links shared to me by Brian Kimball on Twitter\n\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My New Lynda Course - Building APIs with LoopBack",
		"date":"Wed Jan 10 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1515542400,
		"url":"https://www.raymondcamden.com/2018/01/10/my-new-lynda-course-building-apis-with-loopback",
		"content":"I'm happy to announce the release of my latest Lynda.com course, Building APIs with LoopBack. This course introduces you to LoopBack and walks you through building APIs as well as a sample application using those APIs. I cover customizations, security, and more. It's near two hours in length and would be a great way to learn the framework. You do not need to be an expert in Node to use LoopBack, so I definitely recommend it. I hope my readers find this useful, and as always, leave me some feedback about the course either at the site or down in the comments below. (As a reminder, I also have a course on Ionic as well!)\nHere is the course outline:\n\nIntroduction\nIntroducing APIs with LoopBack\nModels\nData Sources\nSecurity\nCustomizing LoopBack\nFiltering and Validating Data\nDemo LoopBack App\nConclusion\n\n",
		"tags":[
	        
            "nodejs",
            
            "loopback"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building an OpenWhisk Activation Poll with Node, Vue.js and Vuetify",
		"date":"Tue Jan 09 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1515456000,
		"url":"https://www.raymondcamden.com/2018/01/09/building-an-openwhisk-activation-poll-with-node-vuejs-and-vuetify",
		"content":"I've had fun building my own tools to provide additional OpenWhisk reporting utilities and today I'm releasing another one. If you find it helpful, let me know in the comments below. Even if no one else uses it, it gave me a chance to play with Vue (and Vuetify) so I had fun writing it.\nThe goal of this particular project was to provide a web based interface for wsk activation poll. If you aren't familiar with this utility, it lets you run a constant poll in your terminal to watch for new activations. Remember that every time you execute an OpenWhisk resource, an activation is created. This activation contains information about the execution, including how fast it ran and the result. This is how it looks in terminal:\n\nNot bad, but I decided to whip up something with Vue to see if I could build something nicer. I decided to give the Vuetify library a try as well. This is a library that provides Material Design components for Vue. I had a bit of trouble at first, mainly due to me doing a shoddy job of reading the docs, but once I got into it, it worked really well. I also spent some time in their Discord chat server for support, and I was incredibly impressed by the help I got there. This is definitely an active project with lots of support behind it. I think I'll be using it again in the future. Let me share a few screen shots of the final result, and then I'll dig into the code.\nThe application lists activations on the left hand side. This list is updated in (near) real time.\n\nClicking on an individual activation loads the detail on the right:\n\nI'm using Vuetify's expansion panels to compress some of the details down so they don't take as much space. Each panel can be opened and inspected. At the end of the post I'll be sharing a quick video so you can see it in action as well.\nAlright, let's look at the code. First, the Node.js server.\n\nThe server makes use of Express and Node-Cache for simple RAM-based storage. I've got two end points. /activations is used to fetch activations and takes an optional argument to ask for activations after a certain timestamp. It stores the results in the cache and returns an abbreviated list of records including the name, duration, timesramp, and result of the activation.\nI'm using the OpenWhisk Node package to interact with OpenWhisk. In order for this code to work on your own system, you need to ensure you've set two environment variables - __OW_API_HOST and __OW_API_KEY. If you don't remember how to get them, simply run wsk property get. The key is the whisk auth value in the output.\nFinally, the other end point is /activation/:id. This simply returns the cached activation. I've got no handler for cases where an invalid id is passed so... yeah don't do that.\nOk, so now let's get to the front end. As I said, I'm using Vue and Vuetify. Let's look at the markup first.\n\nYou can see the Vuetify components in use. For the most part I assume this is pretty readable as is. The v-flex tags are how I'm doing the two column layouts. I'm using &quot;alert&quot; components for the list and expansion panels for the detail. Vuetify supports a heck of a lot more so be sure to check the docs. Now let's look at the JavaScript.\n\nAgain, this is pretty simple. I've got little data and just a few methods. The only thing I'll call out here is my use of unshift to add to the array of activations. The idea, of course, is to add new items to the top of the array. (I think I might be doing that in the wrong order though - anyone care to comment?) In theory this could cause the memory usage of this page to skyrocket. I should add something that caps the list to 200 or so.\nThe complete source code for the application may be found here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/activationpoll.\n",
		"tags":[
	        
            "javascript",
            
            "vuejs",
            
            "openwhisk",
            
            "nodejs"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "Another Example of Vue.js and Vuex - an API Wrapper",
		"date":"Fri Jan 05 2018 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1515110400,
		"url":"https://www.raymondcamden.com/2018/01/05/another-example-of-vuejs-and-vuex-an-api-wrapper",
		"content":"Hi! Welcome to my first post of 2018! If you are a new reader, please consider subscribing to my blog feed and leaving me a comment below! A few weeks back I blogged about my first experience using Vue.js and Vuex: &quot;An Example of Vuex and State Management for Vue.js&quot;. One of things I mentioned in that post was how my mental model for Vuex was Angular's Provider feature. It's definitely not the best mental model, but it's how I'm thinking about it for now. One of the biggest differences that I can see is that in Vuex you get the ability to automatically update your components with new data. Everything is kept in sync with little to no work on your part. That's a big difference compared to Providers, but I'm not an Angular expert and that may be something natively supported.\nFor this post, I wanted to look at using Vuex to wrap an API provider. Basically using the store as an abstraction over a remote API. For my demo I decided to use the Goodreads API. This is an OK API. The docs are good, but the actual implementation leaves a bit to be desired. One of the biggest flaws is not supporting JSON well, but I got around that. For my demo I decided to support 2 basic operations:\n\nSearch for Books: Given a term, find the first ten books that match it.\nSearch for Related Books: Given a book, ask the API for books related to it.\n\nThat second operation isn't actually a feature of the API. Instead, it is returned in one of the book detail APIs. However, I simply used Apache OpenWhisk (and IBM Cloud Functions) to build my own wrappers. I also converted the ugly XML into proper JSON results. This isn't a serverless post so I won't go into the details, but you can find both actions up on my GitHub repo: https://github.com/cfjedimaster/Serverless-Examples/tree/master/goodreads.\nI began by creating the demo without Vuex at all. Let's begin by looking at the HTML. My idea for the layout was a simple vertical list of results on the left side and related books displayed to the right.\n\nNothing too fancy there. You can see a few conditionals to handle displaying loading conditions, lists of books, etc. Now let's look at the JavaScript:\n\nAgain, not too complex. Make note of the data section and methods. My code handles updating various different data variables based on the current action. This isn't too bad at all I think. You can view the complete source, and run it as a demo, in the embed below. Note that I do not use a loading widget for loading related books and the APIs have been a bit slow today. Give it a minute when you click.\nSee the Pen Vue and GoodReads API by Raymond Camden (@cfjedimaster) on CodePen.\n\nAlright, so how can I modify this to use Vuex? Let me share the updated JavaScript (the markup didn't change at all, which is kick butt) and then I'll walk through the differences.\n\nOk, do me a favor and start from the bottom. Right away you can see that the main application code is quite a bit simpler. I've updated my data to use computed for stuff that the store is handling. (Note that I've called my store 'api'.) Now the books, related books, and searching state, all come from Vuex. My methods also are much simpler as they ask the store to do the heavy lifting.\nThe store itself consists of it's state values, mutations, and actions. Actions are how you handle asynch actions on a store and you can see the Ajax calls have been moved in there. The actions use commit calls to tell the store to save new values. In general, it should all be pretty obvious what's going on, but definitely ask me a question in the comments below.\nSo what's the net result? The Vuex version actually has twice the code as the original version. However - just looking at it, it feels like a much better architected version of the code. I like the Vue app is basically &quot;hands off&quot; in terms of how the API works and I like how my Vuex store is nicely organized by itself. I could easily use this store in other Vue apps whereas the previous version everything was mixed in together.\nYou can view the full source code, and demo, below:\nSee the Pen Vue and GoodReads API (with Vuex) by Raymond Camden (@cfjedimaster) on CodePen.\n\nLet me know what you think in the comments below, and as always, I invite Vue experts to come in and give me suggestions on how I could improve this!\n",
		"tags":[
	        
            "javascript",
            
            "vuejs",
            
            "openwhisk"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "Vue.js Version of My Random Comic Book Viewer",
		"date":"Fri Dec 29 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1514505600,
		"url":"https://www.raymondcamden.com/2017/12/29/vuejs-version-of-my-random-comic-book-viewer",
		"content":"A little over six months ago I blogged (&quot;Serverless Demo - Random Comic Book Character via Comic Vine API&quot;) an example of a simple front end to a serverless function I built that returned a random comic book character from the Comic Vine API. I still feel a bit sketchy about using the API. The forum is &quot;alive&quot; but no one official seems to be minding the store so to speak. That being said, I thought it would be fun to rebuild the demo in Vue.js.\nIf you don't want to go back and read the original article, let me quickly go over how it worked.\nFirst, I had a simple serverless function running in OpenWhisk to get the random comic character via the API. You can find the source code for that API here. The front end is what we care about. The HTML was essentially nothing as nearly all the displayed content was generated by JavaScript. I used jQuery to hit my API and then generated multiple different strings.\nI used template literals to make that code a bit nicer. So instead of a bunch of string manipulations, I had stuff like this:\n\nThere's more code involved then this, but this gives you the basic idea. I'm still generating strings by hand a bit (the first portion), but the template literals do make it nicer (second portion). However, with Vue, I should be able to do a lot of this in the HTML itself. Let's take a look at that.\nFirst, the HTML:\n\nNow let's look at the JavaScript:\n\nOk, let's talk about it. First off, the JavaScript is somewhat simpler than the first version. Now that Vue is handling the layout aspects, the amount of JavaScript I need is quite a bit smaller. Since I can do some basic logic in HTML as well, conditionals and looping, I don't need to do as much in JavaScript. There are still a few cases where it makes sense to do such things in JavaScript. If you look at the section handling description, you can see it is a bit complex. I could do that in Vue's template syntax but it would be too ugly. I also handle the image aspect there too since it lets me nicely handle falling back to a default image.\nI absolutely love though that my layout is now almost entirely an HTML concern, which feels intrinsically right to me. Once I had done the initial version, it became easier to make it even nicer than the original version. I included a simple link to do a reload and fetch another character. I could have done this before, but I was much more inclined to do so in the Vue version.\nIf you want to play with this yourself, you can do so here: https://cfjedimaster.github.io/webdemos/vuerandomcomicbook/\nNote that if too many people visit the demo at once (hah), I'll hit the API limits. If you want to see the full source code for this demo, you can find it here: https://github.com/cfjedimaster/webdemos/tree/master/vuerandomcomicbook\n",
		"tags":[
	        
            "javascript",
            
            "vuejs",
            
            "openwhisk"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "My 2017 and My Plans for 2018",
		"date":"Tue Dec 26 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1514246400,
		"url":"https://www.raymondcamden.com/2017/12/26/my-2017-and-my-plans-for-2018",
		"content":"So for the past few years I've done little writes up like this (&quot;My 2016 and my plans for 2017 &quot;). The idea is to look over what I accomplished, take stock, and plan for the next year. I always worry that this kind of comes off as bragging, but as I constantly tend to feel I'm failing, I like to allow myself to feel proud of what I've done. At least once a year. ;)\n2017\nThis year I continued in my role as a developer advocate for IBM's StrongLoop team, specifically the API area. Honestly I can't remember the exact name of our group and as those things seem to change a few times a year, I don't pay much attention to it. I basically spent my time focused on LoopBack, Node, and serverless.\nIn terms of presentations, my total count went down a bit this year. I gave 15 talks total compared to 21 last year. But the adoption really played a part in that as I couldn't really book a lot in the later half of the year. I'm doing much better at tracking my presentations now (&quot;Speaking Engagements&quot;) so it's easier now to see what I've done. This year I did multiple presentations on OpenWhisk and serverless technology as well as my first talk on voice-assisted technologies (specifically Alexa).\nIn terms of travel, I took 62 flights (some were personal, hello China) and nearly it 60K miles. My longest flight was 14 hours and 50 minutes (again, hello China) and I spent a grand total of 6 days, 18 hours, and 40 minutes in the sky. Thank you, JetItUp for the cool stats and visualizations. Here's a Earth-size view of my trips:\n\nAnd here is a US-focused one:\n\nI released two books this year. First was a book on static sites that I wrote with Brian Rinaldi. The second was a free book on serverless written for IBM and O'Reilly. I'm hoping to turn that short ebook into a &quot;real&quot; full size book in 2018.\nI also released my first course for Lynda.com, &quot;Learning Ionic Basics&quot; and I'll have my next one out in a few weeks. I really enjoy working for Lynda.com. They do an incredible job of supporting their creators.\nMy blog traffic declined this year. I got around 1.6 million page views in 2016 and only 1.3 in 2017. That's still good traffic, but I'm hoping I can see a rise in 2018. I published a bit over 150 entries with serverless being my top category. If you are really bored, you can view my stats yourself.\n2017 Goals\nHow did I do in my goals from the end of 2016?\n\nContinue to work with Node, API Connect, Ionic: I'm still doing Node stuff, but less 'full server apps' and almost entirely serverless. API Connect hasn't necessarily been something I cover a lot lately as I've focused more on OpenWhisk. Ionic is also something I've put on the back burner. Not for any reason - I still love the product and the team, but I've been focused on other areas. I'm hoping to turn my attention to it soon as they begin to support Vue.\n&quot;I've been saying this for a while now, but I really want to get better at NativeScript.&quot; Sigh I don't know what to say. I like NativeScript. A lot. And I'm great friends with the Progress folks. But once again I failed to spend much time looking at it. However, they too are looking to support Vue soon so once again I'm hoping this is the year I can spend time on it. I absolutely recommend it. Do not take my lack of attention to it as any kind of criticism.\n&quot;Learn and play a lot more with serverless&quot;: HELL YES, VICTORY, DING DING DING DING\nBecome a better presenter: Still working on that and have no plans on stopping my growth.\nDo deeper dives into static sites: I didn't really do this much, but as they work so well with serverless, I'm hoping to spend some time talking about both topics. I've got a few proposals out on that topic right now.\nLose 20 pounds: Oops. Next year? I'm going to blame the adoption again.\n\n2018 Goals\n\nBecome a Serverless &quot;expert&quot; - whatever that means. I think I'll be digging into this area for years to come.\nBecome an expert with Vue. I freaking love Vue. I've got my first talk on it in a few months.\nI've got a secret goal that I've been working on the past few months. I've already failed at this particular goal multiple times, but I'm not giving up. Certain friends are well aware of what I'm trying to do, and as soon as I succeed, I'll share here of course. Feel free to guess in the comments. (Nope, not at an adoption again, we're done with that.)\n&quot;Above all - try to be the best father, husband, and friend to everyone in my life. For the nameless people who read this blog and see me in person - try to educate and help as much as possible.&quot; This is a repeat from last year. Being a good father/husband/friend will always by my number one priority. I'm not saying I always succeed at it, but I always try to keep it in mind.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Favorite Media of 2017",
		"date":"Sun Dec 24 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1514073600,
		"url":"https://www.raymondcamden.com/2017/12/24/favorite-media-of-2017",
		"content":"For the past few years I've been doing a couple of &quot;end of year&quot; posts, one of which contains a list of the favorite books, music, movies, and video games I've enjoyed over the past year. This year feels like it went by in a whirl. Regular readers know I was out in the fall for my last adoption (and truly, this is it, seven kids is enough), but what folks may not know is that the adoption process is pretty lengthy. It feels like it consumed most of my year. Obviously it didn't, but it seemed like this year didn't leave a lot of time for &quot;media&quot;. And that's ok - I love that my family has grown again, but this recap is going to be a bit slim.\nMusic\nSo let's start with the slimmest of categories - music. I liked nothing this year. Zip.\nOk, no, that's a lie. But a few days ago I was thinking about the songs that seemed to be the best for the year, and discovered they were the exact same ones I mentioned last year. This isn't to say I didn't hear good new music. Once again my buddy Brian Rinaldi was my main source for that. (Check out his favorite tracks of 2017.) But nothing really stands out. I loved the Atomic Blonde soundtrack and I'll talk more about that in a minute, but in general, I'm just not terribly excited about anything.\nI will say that the lackluster Iron Fist did have some dang good tracks, my favorite being this one:\n\nAnd heck, while I heard this in 2016, not 2017, I'll include it as well - one of the best tracks from &quot;Luke Cage&quot;:\n\nBooks\nNow I have to have my head in shame. In 2017, I finished a grand total of....\n9 books. That's horrible. To be fair, I've got a two year old now who has rocked my world and some of the books I finished were 500+ doorstops, but I just don't know what in the heck happened here. Certainly there are greater priorities in life, but damn, I really want to do better.\nAlso - when I shared this list, someone pointed out that I didn't have one book by a woman author. It certainly wasn't intentional, but is something I want to address in 2018. As a start, I've added the &quot;Dragon Riders of Pern&quot; to my Amazon Wishlist. This is a classic I never got around to reading so I plan on fixing that next year. Here's my 2017 list:\n\nMy favorite, easily, was &quot;Empire Games&quot; by Charles Stross, but &quot;The Fireman&quot; by Joe Hill was darn close.\nVideo Games\nAs I've done the past couple years, most of my time was spent playing older games. &quot;Games with Gold&quot; has been an incredible source of value for me. I'd say over half my time has been playing games I got for free via the program. They're old, but still darn fun. As for new games, I'll focus on the two I've played most recently.\n\n     \nMy favorite game of the year is Wolfenstein 2. I enjoyed the heck out of the first game - both for it's genre (alt-history is my favorite) and just for plain being fun, and the sequel more than lived up to it's predecessor. It has a great (if somewhat silly at times) story and was just as fun as the previous game. I loved the very small RPG aspects of it and everything about it was great. I can't believe I got it for only 25 dollars. That was a steal. I can see by the link on the left that it's not that cheap anymore, but I can definitely recommend it at full price.\n\n\n     \nSigh. I enjoyed the last Battlefront, even though it had no single player, and even though I sucked at the MP. It was beautiful. Like, what I would think is perfect Star Wars MP. Well almost perfect. I felt like advancement was incredibly slow and after a while, I just gave up hope of ever reaching the top level. So when BF2 was announced with a single player (SP) story I was incredibly excited. Even better, it put you in the Empire, which I thought was even more exciting.\nSo yeah, I went into this game with really, really high hopes. And let's be clear, I love Star Wars, so you don't have to do a lot to make me happy if you've got a Star Wars license involved.\nThe SP story was... good. Not great, but good. This may be a spoiler, but the only thing that bugged me was the main character's eventual turn to the good side. I guess you should expect that (hence me not worrying too much about a spoiler warning), but I really wish she had stayed with the bad guys for the entire story. I think that could have been great. Overall though it is a great story and I love seeing even more of the post-RotJ timeline fleshed out. My biggest complaint though was how horrible some of the facial animations were. And then there's this:\n\nYeah, ok, Solo got a beard, but damn does it just look wrong in game. All of the faces honestly felt a bit off to me. But I could get over that. Honestly, I could, if it weren't for the load times. Really, really bad load times. Hell, load times to freaking leave a match and go to the menu can take up to a minute. To load. The damn. Main menu. Lord forbid you accidentally do something wrong and end up waiting for crap to load. I did that once and simply rage quit i",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "movies",
            
                "music",
            
                "video games"
            
		]

	},

	{
		"title": "Using JSON Web Tokens with Serverless OpenWhisk",
		"date":"Fri Dec 22 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1513900800,
		"url":"https://www.raymondcamden.com/2017/12/22/using-json-web-tokens-with-openwhisk",
		"content":"Hey folks, before I begin, let me preface the entry with a warning that this is an example of something I wanted to play with and should not be copied wholesale for your applications without a thorough security review. I got - well - attacked pretty harshly in comments a few months ago for making a few mistakes in terms of security so I want to ensure folks know that I'm putting this out as something to share, but you should use with caution. As always, if you have some constructive feedback in regards to how good/bad/etc this demo is, I'm always happy to read your comments. Whew, sorry for the long disclaimer. Let's get started, shall we?\nBack in April, I wrote up a quick demo of JSON Web Tokens (JWT) and Auth0 to lock down OpenWhisk (OpenWhisk, Serverless, and Security - a POC ). In that demo, I use Auth0 and social login. It worked pretty well. But it assumes you just want to verify a user, any user. What if you wanted to authenticate to a particular set of users? For example, maybe you're building an admin interface for a site and have one simple admin login. That's a common thing I did for a lot of the sites I built for clients. While there may be users for the front end, the admin was a simpler, separate user system. I decided to build a simple demo of what this could look like.\nI begin with the authentication action.\n\nIt begins by doing basic validation of the arguments. Then it simply checks against hard coded values for username and password. Note that I'm using a promise for the action even though everything is synchronous. In a real application, I assume you would check the credentials against a service, or database, and therefore would not be using an entirely synchronous solution. After the credentials are verified, I then create a JSON web token via the npm package I included in the beginning. You can read more about the package here - https://www.npmjs.com/package/jsonwebtoken. Note that I'm not using the option to add a timeout to the token. I think - typically - you would want a reasonable value there. That could be added like so: jwt.sign(args.username, creds.secret, {'expiresIn':'1h'}). Oh, and creds is just a JSON file containing a key to use for signing and verifying:\n\nOk, so that's authentication. What about verification?\n\nOnce again I'm using the jsonwebtoken package for the bulk of the work. I decode the token and if it fails, throw an error. If it succeeds, note that I pass on every argument sent to the function except token itself. Why? The plan here is to allow the verify action to be used in an OpenWhisk action. I can use it to lock down my actions and let the non-token arguments be passed along the sequence. How would that look? Consider this super simple 'helloWorld' action:\n\nGiven that my verification action was called verify and this is helloWorld, I can expose a locked down version like so:\nwsk action update --sequence safeHelloWorld verify,helloworld --web true\nJust to recap, let me go over what I built.\n\nI have an &quot;auth&quot; action that has a web API. It lets me login and get a JWT in response.\nI have a &quot;verify&quot; action that is meant to be used in a sequence with other OpenWhisk serverless actions.\nFinally, I built a demo action and tied it to the verification action in a sequence.\n\nPutting It Together\nIn order to test this out, I whipped up a quick Vue.js front end. I built it all in one file (which I'd not normally do), so let's check it out:\n\nAlright, so from the top, we've got a simple layout that uses v-if to dynamically show or hide a login screen. The second div handles a form that will interact with the &quot;helloWorld&quot; service created earlier.\nThe JavaScript is mostly taken up by two methods, one for login and one for helloWorld. In both cases I simply take the results and update my data and let Vue handle updating the front end. Make note that login() will remember the token value and helloWorld passes it to the method.\nYou can run this demo here: https://cfjedimaster.github.io/Serverless-Examples/jwtdemo/client.html\nRemember the username is admin and the password is letmein. You can find the complete source code for all the actions and front end here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/jwtdemo\nSo - what do you think? Leave me a comment below.\n",
		"tags":[
	        
            "javascript",
            
            "vuejs",
            
            "openwhisk"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "An Example of Vuex and State Management for Vue.js",
		"date":"Wed Dec 20 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1513728000,
		"url":"https://www.raymondcamden.com/2017/12/20/an-example-of-vuex-and-state-management-for-vuejs",
		"content":"When I first started learning Vue, I began hearing about Vuex and try as a I might, I couldn't wrap my head around what it actually did. The docs describe it like so:\n\nVuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.\n\nFrankly, I felt kind of stupid as I didn't quite grok what &quot;state management pattern&quot; was supposed to mean. I also found the docs and examples... difficult. It's hard to explain exactly why, and in general, the Vue docs are incredibly good, but the Vuex docs didn't make sense to me. Sarah Drasner has a good article on it (&quot;Intro to Vue.js:Vuex&quot;) but I still had difficulty wrapping my head around it.\nThis feels wrong, but the closest mental model I have is an Angular provider. One of the things I've been missing since learning Vue is an idea of a central &quot;data source&quot; for my components to use. Typically this is a wrapper for an API service of some sort, but I love having all those calls packaged up in a provider that my Angular bits can make use of. I don't think that's a fair comparison to Vuex, but it feels like something in the same neighborhood at least.\nFrom what I can tell, Vuex is especially handy when working with multiple components. It allows you to have one central place of &quot;truth&quot; for your data in each component, and know that if your data updates, any component using it will also get updated. With that said, I'm not sure how useful it would be in a Vue app that doesn't use custom components, but it may still serve as a nice separation of concerns. Plus - you may move from a &quot;simple&quot; Vue app to a more complex one using multiple components, and if you do, your data is ready to go.\nObviously I don't have a super great understanding of this yet. As always I try to be honest about what I know. But - I was able to build a demo. I've been thinking lately about text-based games. Not the old Infocom text adventures, but things like A Dark Room and Universal Paperclips. If you haven't seen those games, well, I'm sorry. Say goodbye to your productivity. My initial idea was to rebuild Taipan, but I thought I'd go simpler with a basic stock market simulation. Take a couple of stocks, have them change prices over time, and then let you buy and sell and try to become right.\nMy idea is that the data for the game: stocks, prices, cash on hand, etc, would be handled by my data store in Vuex. The Vue app (separate from Vuex) would handle UI interactions and retrieving data from the store, as well as pushing out updates. The code here is going to get a bit hairy, so let me try to make this as gentle as possible. Let's start with the front end. Before I even show the code, here is a screen shot:\n\nThe top portion of the screen represents the stocks. You see the current prices as well as how much you hold of each. Below that are two simple controls - one to buy stock and one to sell. I'm using the Bootstrap + Vue project to render the UI. What you can't see in the screen shot are the prices updating every 2 seconds. Here's the code.\n\nThat's probably a lot to digest, but what I want to point out is that at this layer, you aren't concerned with Vuex at all. All the data you see being used here, like with v-model and {{ cash | money }} are integrated with the Vue app. If you're curious, the | money thing is a Vue filter. This is the first time I've used one and it was as easy as most things are in Vue. Now let's look at the JavaScript. First the Vue app.\n\nThe first important change here is the passing in of the store object. I'll show that next, but this is how my Vue app knows how to work with the Vuex dta store.\nI've got a set of data that doesn't refer to game data, but more UI labels and controls and such. The &quot;meat&quot; of the data is in the store.\nNext you'll see the filter (love it) and a mounted function which handles updating my data. You'll see the first (real) use of Vuex here. My store.commit call basically asks the store to run a method to update itself. This method can take data too, but in this case, it's just an an event by itself.\nIn order to render data from the store, I use 3 computed values. Both stocks and cash are returned as is. holdingValue requires special logic so I'm using a 'getter' on my store. I suppose you could always use getters to be consistent, but I think in cases when you know you aren't performing any logic, it's ok to use them as is. (Remember, I'm new at this.)\nFinally we have two methods for buying and selling stock. I do a bit of validation and then call out to the store's buyStock or sellStock methods. Note that I don't actually check if I can buy or sell the stocks here. The store is responsible for that. If you look at the front end code, I've got hidden (by default) error states to report that. I wasn't able to figure out how to actu",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building an Image Placeholder Component for Vue.js",
		"date":"Mon Dec 18 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1513555200,
		"url":"https://www.raymondcamden.com/2017/12/18/building-an-image-placeholder-component-for-vuejs",
		"content":"One of the more interesting parts of Vue.js is the ability to build components. Components are a pretty big feature of Vue and I won't try to explain them completely here (the docs do a darn good job, and you should read Sarah Drasner's article on them as well), but at a basic level, they allow you to build a self-contained piece of UI/logic within a larger Vue application. So for example, you may be building an administrator for a product database and find yourself displaying product thumbnails in multiple places. You could componetize that product display into a Vue component to make it easier to reuse the code multiple times.\nThis is also how (from what I can tell!) most Vue libraries work. They ship as a component you can then simply drop into your code. When I added Bootstrap to my Vue demo (&quot;Last Update, Honest, to My Vue.js INeedIt Demo&quot;), this is what allowed me to write code like this:\n\nIn the snippet above, the b-tabs and b-tab tags are Vue components. All the logic behind rendering them was handled by the component. So yeah, a cool freaking feature, right? I wanted to build my own little demo of this feature and thought it would be cool to build a component to spit out placeholder images. Now - there's a bazillion of them are so, but the most stable, and serious (and as you know, I'm a serious blogger, remember, I work for IBM) is Placeholder.com. Their service lets you create placeholders of any size while providing options for color and text. As an example, this HTML:\n\nProduces:\n\nThis adds text and a custom bgcolor:\n\nand here is the result:\n\nNot too difficult, right? But I thought - what if I could create a simple Vue component for this. Here's what I came up with.\n\nAlright, so from the top, the very first argument simply specifies the name of the component. In this case it means I'll be able to do &amp;lt;placeholder&amp;gt; in my code. I've got an empty data function there I should probably remove as I didn't end up needing it. Skip down to the props section and you can see the various properties I've defined. This matches (for the most part) exactly with what the service provides. The meat of the component is comprised in the template and computed areas. The template is simple - just an image. But note that the URL value is somewhat complex. Hence the use of computed to deal with it. For the most part this began as just &quot;if you use this property, append the value&quot;, but I ran into an issue with the text color value. You can't specify text color without specifying a background as well. So my code &quot;fixes&quot; this by simply defaulting the background to what Placeholder.com uses. And that's it. Here's an example usage:\n\nAnd another:\n\nYou can see a demo of this via the embed below:\nSee the Pen vue placeholder component by Raymond Camden (@cfjedimaster) on CodePen.\n\nOf course, once you've done that, you can easily replace the boring (yet really good, honest!) placeholders with something more fun, like placebeer. Placebeer isn't nearly as full featured as Placeholder.com, so the component is quite a bit simpler:\n\nBut the result is awesome:\nSee the Pen vue placeholder component 2 by Raymond Camden (@cfjedimaster) on CodePen.\n\nAnyway, obviously there are more powerful ways to build Vue components, and better examples, but I hope this is interesting. As I've been blogging about, and learning Vue, I'm hoping my readers will come to me with suggestions and questions about what they would like to see. Just drop me a comment below!\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Review: The Last Jedi",
		"date":"Fri Dec 15 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1513296000,
		"url":"https://www.raymondcamden.com/2017/12/15/review-the-last-jedi",
		"content":"I normally save these &quot;off topic&quot; posts for the weekend, but as it is Friday afternoon and I'm literally unable to think of anything productive to do in my last hour of work, I figured I'd write up some quick thoughts on the latest Star Wars movie, &quot;The Last Jedi.&quot; As always, I remind people that I'm not even close to being a professional reviewer and I'm unashamedly biased towards loving anything in the franchise. With that out of the way, let's get started. I'm going to clearly mark the end of the review with a spoiler warning. Anything below that spoiler warning will be - err - well - spoilers. Also you should consider the comment section as spoiler-filled as well. You have been warned...\n\nI enjoyed the heck out of &quot;The Force Awakens&quot;, but I know some folks felt it played a bit too much too nostalgia. I get that and see their point, but it didn't diminish my appreciation for the movie. Especially after the disappointment of the prequels (which I do not hate per se, but that's another post), TFA was fun and I loved it.\nBut then I saw &quot;Rogue One&quot;, and my God, what a surprise. A new story, new characters (for the most part), and best of all, a new &quot;feel&quot;. Rogue One was like no other Star Wars film and that made me truly appreciate it.\n&quot;The Last Jedi&quot; has a good deal of that. While I think JJ played it safe with TFA, and delivered, TLJ feels much more unique and stands out much more. At times, it is shocking. I can remember at one point feeling like I needed to throw up my hands because I simply had no idea what was going to happen next. I don't say that as a complaint - but truly things really, really did not go as I expected. In fact, I can't honestly say I have a clue as to where the third and final movie will go. I mean I think it's obvious we know it will be a happy ending, but as to how it's actually going to come about, I'm not so sure.\nIn their second movie, it is really great to see Rey, Poe, and Finn develop. I think Finn had the least growth in this movie, but over all, the new heroes are really coming into their own. In the first movie, Kylo was well done, kind of balancing the line between petulant ass hat and menacing Sith lord. That sounds a bit like a contradiction, but for me, it worked. He continues to be amazing in TLJ, and makes me wish again that we had gotten a decent Anakin story.\nAnd Luke... I was worried. Not because of his acting skills, but... Luke is important. And damn... they did a great job with him. All I could ask for to be honest.\nAs expected, the visuals were amazing, and there was one scene in particular that was easily one of the coolest things I've seen on film - ever. (I'll detail it in the spoiler section, but I'm willing to bet folks who've seen it already know which scene I'm talking about.)\nThe story was... good. Really good. But I'm seeing quite a few people talk about the need for editing, and I have to agree. A good 30 minutes of material could have been trimmed and I think the film would have been better for it. To be clear, it wasn't necessarily anything bad, just... not really necessary. I'm sad to say that I could have done without Benicio del Toro. He tried... but honestly his entire part of the movie is what I'd cut. I don't know - I want to watch the movie again and really think about him. I can say (and maybe this is a tiny spoiler), it was nice to hear &quot;slicing&quot; mentioned for the first time in a Star Wars film.\nA lot of folks are talking about the diversity of this film, and the most recent films in general, and I can definitely agree that this is a great change as well. While most folks are focused on Rose Tico (who I thought was great), I want to know who the Asian First Order officer was. (And I hope they make an action figure out of him!) And I just found him - Orion Lee. I honestly didn't expect it to be that easy to find him on IMDB.\nSo... yeah. I enjoyed the hell out of it. I appreciated how different it felt. And despite it needing to lose some weight there in the middle, I'm want to see it again - like really soon. Oh - and how do I rank it? I don't necessarily like to &quot;rankings&quot; as it's so subjective and my feelings will change over time, but in general, I'd consider it my third favorite Star Wars movie, after Empire and Rogue One.\nSPOILERS AHEAD\n\nSPOILERS AHEAD \nOk, so random spoilers in no particular order.\n\n\nThe humor was, again, funny as hell. The opening bit with Poe and Hux... oh my god. I'm smiling now thinking about it.\n\n\nWhat happened between Rey and Kylo was incredible. As I said, I really didn't know what was going to happen. Heck, for a while I was convinced they would both change sides and we'd get a good Kylo and evil Rey.\n\n\nSnoke was great. I loved his attitude. His power. Everything. I'm disappointed we won't see more of them. And again - I did not expect that at all. I honestly thought he was on to what Kylo was doing. Oh, and what was the &quot;ele",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "New IBM Composer Feature - Additional Action Logging",
		"date":"Tue Dec 12 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1513036800,
		"url":"https://www.raymondcamden.com/2017/12/12/new-ibm-composer-feature-additional-action-logging",
		"content":"A pretty cool update landed in IBM Composer today, and I want to thank IBM engineer Kerry Chang for letting me know about it. When visualizing compositions, Composer will return information about the output of your actions, but do not provide that level of output for inine functions. Now typically these are very small bits of logic and you probably aren't too concerned about their output, but it can be something you miss if you're having trouble chasing down a bug. Another thing you don't get is the input to the composition. Now you can get both.\nFirst, be sure you update your fsh command line:\nnpm install -g @ibm-functions/shell\nIf you then run fsh app create, you'll see information about three new flags you can pass:\n\nYou can now request logging for your initial input (--log-input), output of inline actions (--log-inline), or enable both at once: --log-all. Note that -l is not a valid shorthand and will be removed from the CLI help shortly.\nAlso note that the same parameters exist for fsh app update as well.\nOk, so let's test this. I made a somewhat silly composition of 3 steps. The first was an action that returned a random name. It also allows for an optional title.\n\nThe second action is an inline action. I'll show that in a bit. The third action returns the &quot;cost&quot; of a string. This is just based on the length of the string.\n\nAlright, so let's put this together in a composition:\n\nYou can see the inline function simply takes the output from the first action and uppercases it. To test this, I created the composition like so:\nfsh app create fshlogtest app.js --log-all\nI then ran it:\nfsh app invoke fshlogtest --param title Lordy\nwhich gave me a result of 9000. Not that it matters, we just want to see the logging. I fired that up with:\nfsh session get --last\nThe first change you'll see is in Trace. It has additional items for the new logging events. In theory you can just ignore these, but don't be surprised when you see them:\n\nI switched to Session Flow and then confirmed the new items now show up. Here is the input:\n\nand the inline action output:\n\nHmm - I could probably make those graphics a bit easier to read. Let me know if you have trouble with em! Anyway, to disable this feature, simply update the composition and do not pass in the flag. This will remove the additional logging, and as the CLI says, there is a small performance penalty for adding this.\np.s. Don't forget there is an OpenWhisk slack and you can join the #composer channel there if you want to talk about IBM Composer.\n",
		"tags":[
	        
            "openwhisk",
            
            "javascript"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Adding Referrer Protection to OpenWhisk Actions",
		"date":"Mon Dec 11 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1512950400,
		"url":"https://www.raymondcamden.com/2017/12/11/adding-referrer-protection-to-openwhisk-actions",
		"content":"Today I was thinking about what it would take to add referrer-style protection to an OpenWhisk API. What I mean by that is the ability to say that a particular API can only be called from certain domains. To be clear, this is not secure in any &quot;real&quot; fashion. This Stack Overflow question does a great job of addressing why not: &quot;Does referrer header checking offer any real world security improvement?&quot; However, I do think it can help prevent some misuse, and perhaps even help prevent accidental versus malicious misuse. As long as you keep in mind that this is minimally protective, then I think you will be fine. Also note that you should look into the API Gateway feature for more ways to lock down your APIs. Ok, with that out of the way, let's look at how this could be implemented.\nFirst off - when an OpenWhisk action is enabled for web usage, you automatically get access to multiple different aspects of the request. The docs cover this in the &quot;HTTP Context&quot; section. The crucial bit is under __ow_headers where you get access to all of the HTTP headers used for the request. To help illustrate this, I built a simple &quot;echo&quot; action that looks like so:\n\nI then enabled it as a web action and built a web page to call it:\n\nBasically - on page load, call the API and dump the results into a div. I then used Surge.sh to host the static file. You can see this here: http://grieving-skate.surge.sh/temp.html Note - like most of my demos of this nature, I cannot promise the host/API will be up forever. So with that in mind, here is what the output looks like:\n\nAs you can see, under __ow_headers.referer I have access to the page where the API was used. Ok, so let's try using this in a semi-real action:\n\nOn top I've added a new array called allowedRef. This will contain a list of hostnames allowed to use the API. I then built a new function, safeCaller, that checks the current referrer value to see if it matches any of the domains. If yes, everything carries on. If not, we return false and the action throws an error. I put this up on Surge in another domain - http://level-glove.surge.sh/temp.html. If you run that, the output will be:\n\nWoot. Ok, but I don't like messing up my original action (as simple as it was) with unrelated logic, so let's break this apart. Now - at this point I went ahead and built a &quot;generic&quot; referrer checker that could be combined with an action sequence. My first demo used arguments to let you specify the domains that were allowed to run the sequence. You may think that specifying allowed domains in an parameter is risky, but default parameters specified for web actions are set as protected and can't be overridden.\nHowever, you can't specify default parameters for sequences. There is an open bug for this (https://github.com/apache/incubator-openwhisk/issues/2008) and there is a workaround for packages, but in that case, the parameter isn't protected. Unfortunately, we have to hard code the domains.\nFirst, I'll create the referrer checker.\n\nI've taken the code from the previous action and simply moved it into a new action. For the reasons I stated above I still have to include the allowed referrers in my code, but I can live with it. On success, I simply pass along the original arguments that were sent, and on failure, I then throw an error. I built this as a new action sequence:\nwsk action update safeToDelete/webecho2 --sequence safeToDelete/checkReferrer,safeToDelete/webecho --web true\nI updated both of my demos on Surge with the new URLs and confirmed they both still work correctly.\nSo all in all, rather simple, although remember the warnings up top - this isn't going to be very secure and should only be used for a 'casual' check of where the API is being used. But what do you think?\n",
		"tags":[
	        
            "openwhisk",
            
            "javascript"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An Example of Form Validation with Vue.js",
		"date":"Thu Dec 07 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1512604800,
		"url":"https://www.raymondcamden.com/2017/12/07/an-example-of-form-validation-with-vuejs",
		"content":"I was a bit torn about today's blog post. When it comes to form validation, I'm a huge fan of validating via HTML attributes. In fact, I just ran across a great post today on the subject by Dave Rupert, &quot;Happier HTML5 Form Validation&quot;. However, I know folks have issues with HTML validation, and it doesn't cover every use case, and finally, I thought it would just be plain good practice for me to write up a few quick examples. With that in mind, let's get started.\nFirst Example - Super Simple Form\nFor my first example, I wanted something as simple as possible. Given a form of three fields, make two required. I began with the HTML:\n\nLet's cover it from the top. The form tag has an ID that I'll be using for my Vue component. I've got a submit handler that you'll see in a bit, and my action is a fake URL that would point to something real on a server someplace (where you have backup server-side validation of course).\nBeneath that I've got a paragraph that shows or hides itself based on an error state. This is a personal preference of mine when building forms. I like a nice simple list of errors on top of the form. You may like error handling by the fields themselves. Use what works. Also note I'm going to fire my validation on submit rather than as every field is modified. Again, this is a personal preference.\nThe final thing to note is that each of the three fields has a corresponding v-model to connect them to values we will work with in the JavaScript. Now let's look at that!\n\nFairly short and simple. I default an array to hold my errors and set null values for my three form fields. My checkForm logic (which is run on submit remember) checks for name and age only as movie is optional. If they are empty I check each and set a specific error for each. And that's really it. You can run the demo below. Don't forget that on a successful submission it's going to POST to a non-existent URL.\nSee the Pen form validation 1 by Raymond Camden (@cfjedimaster) on CodePen.\n\nSecond Example - Custom Validation\nFor the second example, I switched the second text field (age) to email and decided to add custom validation. My code is taken from the StackOverflow question, How to validate email address in JavaScript?. This is an awesome question because it makes your most intense Facebook political/religious argument look like a slight disagreement over who makes the best beer. Seriously - it's insane. Here is the HTML, even though it's really close to the first example.\n\nWhile the change here is small, note the novalidate=&quot;true&quot; on top. This is important because the browser will attempt to validate the email address in the field when type=&quot;email&quot;. Frankly I'd be tempted to trust the browser in this case, but as I wanted an example with custom validation, I'm disabling it. Here's the updated JavaScript.\n\nAs you can see, I've added validEmail as a new method and I simply call it from checkForm. Nothing too crazy, but a good example I think. You can play with this example here:\nSee the Pen form validation 2 by Raymond Camden (@cfjedimaster) on CodePen.\n\n(And yes - before someone comments, I'm aware of the issues of validating emails and even a valid email format doesn't necessarily mean it is an email address that points to a person. Let's just chill, shall we?)\nExample Three - Another Custom Validation\nFor the third example, I built something you've probably seen in survey apps. I'm asking the user to spend a &quot;budget&quot; for a set of features for a new Star Destroyer model. The total must equal 100. First, the HTML.\n\nNote the set of inputs covering the five different features. Note the addition of .number to the v-model attibute. This tells Vue to cast the value to a number when you use it. However, there is a bug (imo) with this feature such that when the value is blank, it turns back into a string. You'll see my workaround below. To make it a bit easier for the user, I also added a current total right below so they can see, in real time, what their total is. Now let's look at the JavaScript.\n\nI set up the total value as a computed value, and outside of that bug I ran into, it was simple enough to setup. My checkForm method now just needs to see if the total is 100 and that's it. You can play with this here:\nSee the Pen form validation 3 by Raymond Camden (@cfjedimaster) on CodePen.\n\nFourth Example - Server-side validation\nIn my final examlpe, I built something that made use of Ajax to validate at the server. My form will ask you to name a new product and I want to ensure that the name is unique. I wrote a quick OpenWhisk serverless action to do the validation. While it isn't terribly important, here is the logic:\n\nBasically any name but &quot;vista&quot;, &quot;empire&quot;, and &quot;mbp&quot; are acceptable. Ok, so let's look at the form.\n\nThere isn't anything special here. So let's go on to the JavaScript.\n\nI start off with a variable representing the URL of the API I have running on OpenWh",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Example of Apache Cordova and Vue.js",
		"date":"Wed Dec 06 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1512518400,
		"url":"https://www.raymondcamden.com/2017/12/06/quick-example-of-apache-cordova-and-vuejs",
		"content":"Normally when building Cordova apps, I either use no JavaScript framework at all, or I use Angular with Ionic. But as you can tell by my recent posts, I've become infactuated with Vue.js lately. While the Ionic folks are working on making it easy to use Vue instead of Angular, I thought I'd demonstrate a quick example of how simple it is to use Vue with Cordova. I'm not going to worry about UI for this post, but rather show how to wait for the deviceready event in your Vue app.\nAs a reminder, the deviceready event is critical in Cordova applications. If you attempt to do anything &quot;special&quot; with the device via plugins before this event is fired, you'll get an error. Therefore most apps simply wait to do anything until that event has fired.\nI create a new Cordova project and then removed most of the code to start off a bit simpler. I modified the HTML to include a local copy of Vue (because my app could start offline) and then added a simple variable, status, to my display.\n\nMy plan here is to simply set status to true when deviceready has fired. Now let's look at the JavaScript.\n\nI've got a Vue instnace on top where I default that status value to false. I added an init method that I then simply use as my listener for the deviceready event. I normally think of my Vue methods as being helpers for stuff inside my app, but you can also call them from outside the app by using the instance. In this case it's a great way to &quot;turn on&quot; the app and do stuff after my deviceready event has fired.\nHere's an overly large example of this running in the Android emulator:\n\nTrivial, right? So for a more 'real' example, I added the Dialogs plugin and then modified my code a bit. First I added a button to the view:\n\nNote I've got two Vue related things going on. First, the button is disabled while a value, ready, is false. Secondly, I added a click handler to run a method called showDialog. Let's look at the JavaScript now.\n\nThis is virtually the same as before. I've renamed the variable I use to track the 'ready' status and I added showDialog. This just uses the alert method from the Dialogs pugin.\n\nAll in all - nothing special I guess, but simple is good. One big thing to keep in mind is that - normally - you always want to use a Single Page Application (SPA) for your Cordova apps. If your app has any kind of navigation, or view changes, you'll want to use the Vue Router. I wrote up an example of that here that you might find useful. If you're using Vue with Cordova, please let me know in a comment below. Also, if you want an early look at Vue and Ionic, check out this video by Paul Halliday.\n",
		"tags":[
	        
            "javascript",
            
            "vuejs",
            
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Building Related Selects with Vue.js",
		"date":"Tue Dec 05 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1512432000,
		"url":"https://www.raymondcamden.com/2017/12/05/building-related-selects-with-vuejs",
		"content":"My buddy Nic Raboy has been posting some great &quot;how to do X&quot; style posts on Vue.js lately and it's inspired me to do the same. With that in mind, I decided to work on what I thought would be a simple demo of &quot;related selects&quot; - this is a common UI interface where one drop down drives the content of another. While working on the demos though I ran into some interesting edge cases that helped me learn, so I hope what follows is useful. As always, remember that I'm learning and there are probably better ways of doing what I'm showing here. (In fact, once again my friend Ted Patrick shared an update to my code that I'll be including in the post.)\nFor my demo, I decided to try two basic examples. The first example would be static data. The second example would be Ajax driven such that every change of the initial drop down would require a quick Ajax call to load the contents of the second drop down. Let's look at the first example.\nI'll begin with the markup:\n\nThe first drop down is bound to a variable called selectedDrink and is driven by a list of data called drinks. You'll see all of this in a moment when we switch to the JavaScript. Note that my for loop is getting both the drink value as well as the numeric index. You'll see how that's used soon too.\nThe second drop down is looping over what will be a list of options for a specific drink. Note the use of v-if to only show up when we've selected a drink.\nFinally - there is a simple paragraph which states what you've selected. It is hidden as well until you've selected a particular value.\nNow let's look at the code.\n\nAs I mentioned, this first example is static, hard coded data. In this case, an array of simple objects where each drink has a label and an array of options for the values. The only real logic in play here is selectDrink. I'm resetting selectedOption back to blank. I do this to hide that final paragraph. All in all rather trivial. The issues I ran into were in v-if. Specifically the fact that when generating a set of options for a drop down, Vue will set the default value to undefined, and my conditionals were tripping up on that. This is expected but it tripped me up. You can play with the first version below:\nSee the Pen Related DDs by Raymond Camden (@cfjedimaster) on CodePen.\n\nSo I liked this, but I was kinda bugged by the amount of logic I had in the view (HTML) area. It isn't a lot, but it just felt like a bit too much. I worked on a second version to try to correct this. Here is the new HTML:\n\nThe main changes are in the second two blocks of layout and mainly in that I don't assume as much knowledge about the 'form' of drinks. In fact, I'd like to change options.length in the second block as well. The JavaScript is just a bit different:\n\nNote how the selectDrink method now does a bit more work. Again, I like this as I feel like more of the logic should be here versus the layout. You can view this below:\nSee the Pen Related DDs (p2) by Raymond Camden (@cfjedimaster) on CodePen.\n\nFinally, Ted Patrick shared a third version with me. Note that his is missing some of the logic of the second version. But check out the change:\n\nSpecifically note that the value of the drop down is the drink object itself. That's cool! I'm basically making a drop down where the value is some random JavaScript object of any shape. I really, really dig that! You can find his complete version below.\nSee the Pen Related DDs by Ted Patrick (@__ted__) on CodePen.\n\nOk, now for round two! For the second version, I wanted related drop downs where the related content was driven via a web service. In this case, I decided to build something using the Star Wars API. The Star Wars API has simple GET endpoints for different types of data, like films, people, etc. So I built a related select where the first drop down was the kind of data and the second was the actual data. (To keep it simple, I didn't worry about paging.) Here is the markup.\n\nFor the most part this isn't too much different from the initial version, except for the removal of the third paragraph. I added a &quot;loading&quot; widget as well. Now let's look at the JavaScript.\n\nI begin by defining a hard coded list of data types. I initialize items to an empty array. This will get populated when you select a type. You can see that logic in loadData. I run a fetch call to the end point and that's basically it. I have a little bit of logic to help keep my view simple, in this case creating a label property that is based on the best &quot;name&quot; for the data. As you can see, only films is a bit weird, using title instead of name. That's basically it. Here it is in action:\nSee the Pen Related DDs (p3) by Raymond Camden (@cfjedimaster) on CodePen.\n\nNote that I really should add a simple caching layer so that I don't refetch data I don't need to. Also, Ted again shared an updated version with some changes:\nI tend to use null as the initial model state as this denotes loading and allows easy hiding of ",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code 2017",
		"date":"Mon Dec 04 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1512345600,
		"url":"https://www.raymondcamden.com/2017/12/04/advent-of-code-2017",
		"content":"I'm a few days late, but this is just a quick reminder that Advent of Code has returned. This is a daily coding challenge where you are tasked to solve certain problems. No particular language is required, and heck, if you want to do it on paper you could give that a shot as well. This is my third year participating and it quickly goes from simple to &quot;woah, how in the heck do I solve that?!?&quot; Last year I didn't complete the entire set of puzzles, but I still enjoyed the heck out of it. I fully recommend &quot;cheating&quot; if you get stuck. The subreddit typically has solutions up way before I even get a chance to make an attempt.\nLast year I tried to blog about all my solutions but - yeah - that was a mistake. ;) I'm still posting my solutions to GitHub though: https://github.com/cfjedimaster/adventofcode. You're welcome to look at - and make fun of - my solutions.\nHere is an example of the type of challenges you can expect - in this case - the very first one:\n\n\nIt goes on to explain that you may only leave by solving a captcha to prove you're not a human. Apparently, you only get one millisecond to solve the captcha: too fast for a normal human, but it feels like hours to you.\n\n\nThe captcha requires you to review a sequence of digits (your puzzle input) and find the sum of all digits that match the next digit in the list. The list is circular, so the digit after the last digit is the first digit in the list.\n\n\nFor example:\n\n\n1122 produces a sum of 3 (1 + 2) because the first digit (1) matches the second digit and the third digit (2) matches the fourth digit.\n1111 produces 4 because each digit (all 1) matches the next.\n1234 produces 0 because no digit matches the next.\n91212129 produces 9 because the only digit that matches the next one is the last digit, 9.\n\n\nWhat is the solution to your captcha?\n\n\nThis one was pretty easy - but it quickly ramps up.\n",
		"tags":[
	        
            "advent of code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Last Update, Honest, to My Vue.js INeedIt Demo",
		"date":"Tue Nov 28 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511827200,
		"url":"https://www.raymondcamden.com/2017/11/28/last-update-honest-to-my-vuejs-ineedit-demo",
		"content":"Ok, I know I said my last post on my Vue.js INeedIt app was the last post, but I had an idea for just one more tweak and couldn't resist taking a stab at it. It didn't quite work out the way I wanted it too, but it was an interesting iteration to the app and I think now I can put it down and move on to the next thing I want to build. For this final version of the app, I decided to apply a bit of UI to make it look a bit nicer. I thought Bootstrap would be great for this, and I was excited to discover that there was actually a Bootstrap + Vue project that made this easy (somewhat).\n\nBootstrap-Vue lets you use Bootstrap in your project via components. I really like Bootstrap, but I can never remember the class names and such to use certain features. But the component version is much easier to use I think and it just feels a lot more natural.\nAs an example, check out how tabs look:\nSee the Pen bootstrap-vue tabs by Raymond Camden (@cfjedimaster) on CodePen.\n\nI really, really like that format. Actually using the library requires a few steps. This is covered in the Getting Started guide. Since I had a Webpack app, I followed the instructions there. I don't know about you, but it still weirds me out to &quot;install&quot; a client-side library via npm. Even more weird was when I used import statements to load the CSS. It worked, but it just felt... odd. This is how my main.js looks now:\n\nThe new lines are right on top. I import Bootstrap-vue and then the CSS. And literally that was it. So for example, here is the new initial page that lists the types of services available:\n\nHere is the template portion of the component:\n\nIt isn't that much different - mainly just the new b-list-group and b-list-group-item tags.\nThe service listing page makes use of the same component with the addition of a button to return back. Note the &quot;blue link&quot; border around it. I'm not sure why that's there (and it won't be in the next view), but as I was tired of fighting other issues (more on that later) I just didn't bother correcting it.\n\nHere's that template:\n\nFinally - I decided on a &quot;card&quot; interface for the result page. This is where I ran into the most trouble. First, I ran into issues with Google's API for Google Places. I set up a new key to use their Photo API and the Static Maps API. This key was restricted to localhost and my GitHub repo. Unfortunately, it never worked consistently. Sometimes both were blocked with 403 errors and sometimes just the place pictures were. I reported it to a friend at Google, but because of this, I can't share a public version of the app. I then ran into sizing issues using the card component. I switched to the carousel and had sizing issues there. I took my best stab at it and it's ok, but still not right. But I think it's pretty cool.\nAnyway - here is an example:\n\nScrolling down, the pictures are in a carousel you can browse.\n\nHere's the display code:\n\nPretty cool - even with the issues I ran into. You can browse the entire code base here: https://github.com/cfjedimaster/webdemos/tree/master/ineedit4\nI hope this progresive look at one app has been helpful to others. Now I'm ready to leave this demo behind and get to work om my next one!\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Yet Another Update to my INeedIt Vue.js App",
		"date":"Fri Nov 24 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511481600,
		"url":"https://www.raymondcamden.com/2017/11/24/yet-another-update-to-my-ineedit-vuejs-app",
		"content":"This is the last update to my INeedIt app - I promise. At least until I get another idea or two for a good update. But then that will be the last one - honest. (Ok, probably not. ;) Before I begin, be sure to read the first post about this demo and the update from a few days ago. The last update was relatively minor. This one is pretty radical.\nOne of the things I ran into when working on this app was that my app.js file was getting a bit large. To be clear, 260 lines isn't really large per se, but it gave me a slight code smell to have my components and main application setup all in one file. I especially didn't like the layout portions. While template literals make them a lot easier to write, having my HTML in JavaScript is something I'd like to avoid. Heck, just the lack of color coding is a bit annoying:\n\nThe solution is single file components. As you can guess, they let you use one file per component. Here is a trivial sample.\n\nEach single file component (SFC) may be comprised of a template (layout), script (logic) and style (design) block.\nSo this is cool - however - using this form requires a build process of some sort. I'm definitely not opposed to a build process, but I was a bit hesitant to move to one for Vue. I loved how simple Vue was to start with and I was concerned that moving to a more complex process wouldn't be as much fun. Plus, I found the docs for SFC to be a bit hard to follow. In general, I love the Vue docs, but I was a bit loss in this area as it assumes some basic familiarity with Webpack.\nWhen I first encountered this, I stopped what I was doing and tried to pick up a bit of Webpack. I ran across an incredibly good introduction on Smashing Magazine: Webpack - A Detailed Introduction. This gave me enough basic understanding to be a bit more familiar with how to use it with Vue. I still don't quite get everything, but I was able to build a new version of the app using SFCs and the Webpack template.\nTo begin working with the Webpack template, you need the Vue CLI. The CLI is a scaffolding and build tool. You can install it via npm: npm i -g vue-cli. Then you can scaffold a new app via vue init webpack appname. This will run you through a series of questions (do you want to lint, do you want to test, etc), and at the end, you've got a new project making use of SFCs.\nThe new project is a bit overwhelming at first. Maybe I just suck, but it was rather involved. You've got a built in web seerver, auto reload, and more, but in the end it felt much like working with Ionic. I'd edit a file and Webpack would handle all the work for me.\nSo how did I build INeedIt in this template? The template has a main App component that simply creates a layout and includes a router-view. As I learned earlier this month, that's how the Vue router knows where to inject the right component based on the current URL. I removed that image since I didn't have anything that was always visible.\n\nI then began the process of creating a SFC for each of my three views. For the most part, this was literally just copying and pasting code into new files. Here is ServiceList.vue:\n\nNote - as before I've removed 90% of the serviceTypes values to save space. Next I built TypeList.vue:\n\nAnd finally, here is Detail.vue:\n\nFinally, I modified router/index.js, which as you can guess handles routing logic for the app.\n\nAll I did here was import my components and set up the path.\nAnd that was it! Ok, I lie. When I made my app I accepted the defaults for ESLint and it was quite anal retentive about what it wanted, which is to be expected, but I disabled a lot of the rules just so I could get my initial code working. In a real app I would have kept the rules.\nI got to say... I freaking like it. I still feel like it's a big step up from &quot;just include vue in a script tag&quot;, but as I worked on the app it was a great experience. If you want to see the final code, you can find it here: https://github.com/cfjedimaster/webdemos/tree/master/ineedit3\nTake a look at the dist folder specifically. This is normally .gitignore'd, but I modified the settings so it would be included in the repo. You'll see that Webpack does an awesome job converting my code into a slim, optimized set of files. You can actually run the demo here: https://cfjedimaster.github.io/webdemos/ineedit3/dist/index.html\nFinally, take a look at Sarah Drasner's article on the Vue CLI: Intro to Vue.js: Vue-cli and Lifecycle Hooks Her entire series on Vue is definitely worth reading.\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Thank You!",
		"date":"Thu Nov 23 2017 07:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511423940,
		"url":"https://www.raymondcamden.com/2017/11/23/thank-you",
		"content":"For those of you outside America, today is Thanksgiving which is traditionally a day when we are extra thankful for the good things we have in life. I've got a lot to be thankful for in my life. I also want to share a quick thank you to all my readers who provide great feedback, awesome questions, and just make this worthwhile. Have a great day, love your friends and family, and please accept my thanks again!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Serverless Try/Catch/Finally with IBM Composer",
		"date":"Wed Nov 22 2017 08:38:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511339880,
		"url":"https://www.raymondcamden.com/2017/11/22/serverless-trycatchfinally-with-ibm-composer",
		"content":"It's been a few weeks since I blogged about IBM Composer, sorry about that, flying to China and getting a kid will put a kink into your blogging schedule. ;) Today I want to share a simple demo of how to wrap serverless functions with try/catch and try/catch/finally logic. Let's start off with a simple function.\n\nThis function simply takes an input value and then returns the result of dividing it into ten. If the input is 0, an error is thrown. I called this function tendividedby. Here's a sample result using an input of 2:\n\nAnd here is a result when the input is 0:\n\nIf you get the activation record for the last test, you can clearly see it reported as an error:\n\nAlright, so what if we don't want an error reported? This is where a try/catch composition will come in handy. I begin by creating a new file for my composition, app.js. Here's the code.\n\nThe composition consists of one command, composer.try. This command takes two arguments - the action to try running and an action to run on failure. In the example above an inline action is being used but you can definitely pass the name of an existing action instead. My inline action simply says to return a result with a string indicating the error. You could do other things of course, for example sending an email about the error so people could be notified.\nSo that's try/catch, nice and simple. How about try/catch/finally? While this isn't built into the composer function itself, you can &quot;fake&quot; it by simply using a sequence where the &quot;finally&quot; part comes after the try/catch. Here's how that could look:\n\nIn this case, I've set the &quot;finally&quot; part of my logic to be an action called final. All that does is add a timestamp:\n\nIn case you're curious, this could also be an inline function, but I wanted to demonstrate a mixture of both. To start working with this composition, first I create it:\nfsh app create trycatchdemo app.js\nThen I invoke it:\nfsh app invoke trycatchdemo --param input 5\nThis results in:\n\nAnd here is the error condition version:\n\nWhat's cool is that the Composer's graphical shell does a kick butt job of rendering these calls. So for example, here is the good test:\n\nAnd here is the bad run:\n\nIf you have any questions about these examples, let me know!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Update to my Vue.js INeedIt Demo",
		"date":"Tue Nov 21 2017 09:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511255460,
		"url":"https://www.raymondcamden.com/2017/11/21/update-to-my-vuejs-ineedit-demo",
		"content":"A few days ago I shared a simple Vue.js demo for an app called INeedIt. While not a terribly a complex application, it was something fun to build just to get some practice with Vue. Before I shared that post, I let my friend Ted Patrick take a look at the code and he had some feedback I thought would be nice to incorporate into a second version. I've made his suggested changes and I thought I'd quickly review them here. This is version two of the app and I've already got a third version of the app to share later. Anyway, here are the changes he suggested.\nFirst, and this is the simplest, was a change in the script tags I used. The first version had this:\n\nThis will load the most recent version of Vue, and the Vue Router, but also has some delays in terms of loading. How much of a delay? Half a second. While that doesn't sound like much, every delay adds up, and switching to a specific version removes that delay. Here is the simple update:\n\nThe second change was more important and fixes an obvious bug in the first version. My initial version used the created event to request the user's location. Unfortunately, this ran every time the route was loaded as the component was created each time the view was shown. Ted suggested a simple change to the code that addressed this.\nHe moved the data for the component (which included a few flags, the set of services, and the location) into the global scope. So the top of my app.js now has this:\n\nOnce again I removed a bunch of the serviceTypes values to save space. Next, the component's data function was changed to this:\n\nThis will point to the global variable and ensure that the values are not lost when navigating away and then back again. Finally, the created event was changed to mounted and now checks for GPS data.\n\nI don't like having the flags in the global scope and if I cared enough I could get around that, but this did work well and made the application run quicker, all good things, right?\nAnd that's it. As I said, not a lot of changes, but I wanted to separate them out so I could see a &quot;progression&quot; of the app. You can view the demo here: https://cfjedimaster.github.io/webdemos/ineedit2. The source for it may be found here: https://github.com/cfjedimaster/webdemos/tree/master/ineedit2\nAs always - feedback and comments are welcome. Tomorrow I'll be posting the third version which I'm pretty excited about! (Actually it's in GitHub now if you want to take a gander. ;)\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Two Quick OpenWhisk/IBM Cloud Functions Updates",
		"date":"Mon Nov 20 2017 08:16:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1511165760,
		"url":"https://www.raymondcamden.com/2017/11/20/two-quick-openwhiskibm-cloud-functions-updates",
		"content":"It's going to be a slow holiday week for me but I thought I'd share two interesting updates to OpenWhisk/IBM Cloud Functions that will be useful to developers. As a reminder, the wsk CLI does not prompt you to update when it is out of date. Read my guide for help on how to check and update your CLI. Ok, so what changed?\nCRON Details for Triggers\nThe first change isn't huge, but is real useful for me. Previously when you fetched details for CRON-based triggers, you did not get details about the CRON schedule back. So if you forgot, or weren't sure of, the schedule for your trigger, you were kind of out of luck. Now these details are returned. Here is a simple example of what you will see when running wsk trigger get X (I removed the rest of the content):\n\nYou can paste this into https://crontab.guru to &quot;translate&quot; this into English: &quot;At minute 0 past every 3rd hour.&quot;\nIBM Cloud Service Binding\nThe next update only applies to the bx CLI. I don't blog about the bx CLI much when discussing OpenWhisk as I don't want to assume all my readers are using OpenWhisk on IBM Cloud (which, by the way, is the new name for Bluemix). The bx CLI is used to interact with IBM Cloud and is pretty powerful. To work with OpenWhisk on IBM Cloud, you simply add the plugin and then you can do bx wsk as a - for the most part - mirror of the generic wsk CLI. However, there are some differences for things that are specific to IBM Cloud.\nOne such difference is pretty bad ass if you are using any services. So for example, Tone Analyzer. Whenever you create an IBM Cloud service, you get a set of credentials. You can easily use them in OpenWhisk actions by setting them up as parameters. You can make this even easier by setting them as default parameters so you don't have to pass them.\nBut now you can use &quot;service bindings&quot; instead. Basically set it up such that the credentials are passed to the action. So for example, given a service named 'conversation' and an action named 'enterpriseCatDemo', you could do this to have the credentials passed: bx wsk service bind conversation enterpriseCatDemo. The values will be passed as arguments named __bx_creds which is an object. It will contain the key conversation which includes the credentials. So if you bound another service named foo, then it would be in that key instead.\nI'd show a proper demo, but there's already a full blog post up on this demonstrating everything: Simplify binding your IBM Cloud services to serverless Functions.\nI'm adding a quick note here after publication - thank you to Carlos Santana for reminding me. Much like how wsk needs to be updated, so does the wsk plugin for bx. You can do that via the command: bx plugin update.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Another Vue.js Demo - INeedIt",
		"date":"Thu Nov 16 2017 08:40:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1510821600,
		"url":"https://www.raymondcamden.com/2017/11/16/another-vuejs-demo-ineedit",
		"content":"Ok, so ditto my previous warnings about being new to Vue.js and how my code is probably crap, blah blah blah. You get the idea. I'm learning. I'm sharing. Expect the code not to be perfect. Let's move on!\nI began with mobile development using Flex Mobile many, many years ago. I can remember getting a free phone at Adobe MAX (I think that was the year where we also got a Google TV) and downloading the Flex SDK that night in the hotel. The first app I built was something called &quot;INeedIt&quot;. The idea was simple - you are in a strange city and need to find... something. A bank. A restaurant. An ATM. The app presented a list of categories, you selected one, and then it would tell you the businesses matching that category that were nearby. You could then click for details.\nIf you're really curious about the old Flex Mobile version, you can read blog post from way back in 2011. I then rebuilt it in AngularJS in early 2014 and [again](https://www.raymondcamden.com/.../my-perspective-of-working-with-the- ionic-framework/) in Ionic. I thought it would be fun to build it - once again - in Vue.\nBefore getting into the code, let's look at some screens. This has no design whatsoever, it's just plain text. I've got some ideas about how to make it look nicer, but I thought it made sense to start with the functionality first, and then make it pretty later.\nOn the first screen, I do two things - load your location via Geolocation, and then present the list of categories.\n\nAfter selecting a category, the Google Places API is used to find businesses matching that categories within 2000 meters of your location.\n\nFinally you can click on a business for detail. In the screen shot below, you are seeing a small set of the data returned from the API. It's actually quite intensive.\n\nAnd there you go - that's it. Pretty simple, and no different than what you'd get if you Google for &quot;foo near me&quot;, but as an app, it's the perfect kind of thing for me to build to get some practice. Now let's look at the code. I'll be sharing a link both to the online demo and GitHub repo at the end. I'll also remind folks that this is &quot;rough&quot; code. I've already gotten some great feedback from Ted Patrick. His feedback was so darn good, I've decided to turn it into a follow up for later this week.\nLet's begin with the HTML, which is rather simple.\n\nBasically I just use an empty div and a router-view. After that I load up Vue, the Vue Router, and my own code. Everything shown on screen will be rendered via Vue components that are loaded based on the particular view of the app. All of my code is in app.js, but let me break it into smaller chunks for easier reading.\nFirst, let's look at the 'meta' code for the application, basically the routing and initial Vue setup.\n\nI've got 3 routers that map to my three screens. Each uses a particular component. Note the path in the typeList route. It's rather long. It includes a type id value, a type name, and the location values. I felt a bit weird passing so much in the URL, but then realized that this allowed for bookmarking and sharing, which is pretty cool.\nNow let's look at each component. Here is the initial view, ServiceList.\n\nThe component consists of a template and then various bits of code to support the functionality. The template has to handle three states:\n\ninitially getting the user's location\nhandling an error with that process\nrendering the list of services\n\nYou can see the logic to get the user's location in the created handler. It's just a vanilla Geolocation API call. The service list is a hard coded array of values set in my data function. I've trimmed a significant amount of data from the listing above. It's about 100 items or so. I felt bad embedding that big string in the code. It's ugly. But honestly it felt like the most practical way of handling it. It doesn't change often and making an Ajax call to load it seemed silly. I could move it to the top of my code as a simple constant (I did that for a few other values), but for now I'm just going to live with it.\nAfter you select a service type, the TypeList component is loaded.\n \nOnce again, I start off with a template, a rather simple one, but like before I made it handle a &quot;loading&quot; state so the user knows things are happening. Those &quot;things&quot; are a call to get the results for businesses in this particular category near the location requested. The Google Places API does not support CORS. But guess what? It took me five minutes to write a wrapper in OpenWhisk and IBM Cloud Function. I'm not going to share that code in the post, but it's in the GitHub repo. What's nice is that I not only get around the CORS issue but I can embed my API key there instead of in my code. Alright, now let's look at the final component, Detail.\n\nSo I've got a few things going on here. In the code, note that I make two manipulations of the data to enable some nice features in the display. First, I rewrite the price_level va",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with Routes in Vue.js",
		"date":"Sun Nov 12 2017 18:57:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1510513020,
		"url":"https://www.raymondcamden.com/2017/11/12/working-with-routes-in-vue",
		"content":"Warning - as I've made clear, I'm in the process of learning Vue.js. I want to share as I learn, but obviously, I'm still very new to it and you should not consider my code 'best practice', or heck, even 'experienced practice'. I full expect there are better ways of doing what I describe below, but I hope that sharing my learning process is helpful for others as well.\nOut of the box, Vue does a few things, and does them darn well. If you want more, you have to add additional functionality, one of those things is route management. I'm not even sure that's the best term for the feature, but basically it covers the ability to map a URL (route, like /cats or /cats/10) to a particular view of your site.\nThe official router for Vue.js is vue-router. It supports what I described above as well as more complex routing, for example, nested routes like /user/5/posts. The documentation goes into detail and I suggest reading it of course, but I thought I'd share a couple of examples. For my first example, I'm using one from the docs with a few modifications. Let's start with the markup.\n\nTwo things here to note. First, the router-view tag is simply a placeholder for where your content is loaded based on the current path. You could imagine that being the majority of your app's view with the rest being the header and footer for navigation. Speaking of navigation, you can see an example of that in the router-link tag. This maps to an anchor tag by default. The to attribute simply uses the path to link to, but you can also use more complex versions passing custom data to the next view.\nOne more quick note - I'm not including the rest of the HTML here, but note that the router takes a new JavaScript file as well. So the bottom of my HTML template includes this:\n\nHere's the JavaScript.\n\nThere's a few interesting things going on here. First note the definition of two components on top. This confused me. I've just started looking at components in Vue, and apparently you can &quot;fake them&quot; with simple JavaScript objects instead of using Vue.component. I guess it's one of those &quot;duck typing&quot; things. Anyway, two components are defined and then set in a new array of objects called routes. Note the mapping of a URL path to a component. This is then used to define a VueRouter which is then passed to a new Vue object.\nAnd that's it. Now when /foo is loaded, the Foo component will render in router-view, and ditto for /bar. While it isn't terribly exciting, you can see the full code and demo via the CodePen below:\nSee the Pen example of routing in vue by Raymond Camden (@cfjedimaster) on CodePen.\n\nOne thing you won't see though is the URL changing. It does do that so if you bookmark the Bar view, it loads fine.\nSo.... yeah. That's all rather simple. Like - I keep thinking more should be involved, and as I said above, the router does have multiple more complex options, but I absolutely love how simple it is to do basic routing.\nI decided to try my hand at a more complex example, the typical list/detail view. I made a simple demo using the Star Wars API. The initial view loads a list of movies that you can then click into for a detail. Here it is in action, and then I'll share the code.\nSee the Pen Kyqwgd by Raymond Camden (@cfjedimaster) on CodePen.\n\nHere's the initial HTML:\n\nAs you can see, I've basically just got a header and the router-view tag is doing the rendering. Now the JavaScript.\n\nAlright - so this one actually makes use of Vue.component. The first component, List, generates a simple ul list with data provided by the API. Note that I do a bit of manipulation on the results so I can get the ID in my display.\nNote how the link is made:\n\nInstead of just a path, I'm using an object that contains both a path name (film) and a set of parameters - in this case just the ID I made. (Technically I didn't need to do this - I could have passed the complete url value from the list to the detail, but... I don't know. I just felt like doing it. ;)\nThe next component, Detail, handles displaying the film. Note that I can use the passed in ID like so: this.$route.params.id. Again - simple. (Once again, technically, I don't need to do this. The Star Wars API returns all information for a film when you request all films. I already had all the data I needed. But I wanted to see how it would work if the initial &quot;list&quot; API had returned a slimmer set of data.)\nRandom Final Thoughts\nOk - so I just want to wrap up with a few random thoughts. As I said, I really like how simple this is. It's just pleasant.\nI dislike building my components in JavaScript. Template strings make it nicer of course, but I'd rather use something like Vue's Single File Component feature. I love how they look - but I'm also trying to hold off on going into a build system. I've got nothing against using one of course, and that's why I took a look at Webpack recently (see my suggestion for a great way to learn it), but I feel like once I start using the",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Great Tutorial for Webpack",
		"date":"Tue Nov 07 2017 11:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1510053060,
		"url":"https://www.raymondcamden.com/2017/11/07/a-great-tutorial-for-webpack",
		"content":"Every now and then I'll blog something that I've been discussing on Twitter as a way to help those who have somehow managed to avoid becoming a Twitter user. (And trust me, you're probably better off for it.) For some time now I've been hearing about Webpack, and I've had the opportunity to meet the creator (Sean Larkin) at a few events. However, I've not had much luck actually learning it. I tried reading the official docs once or twice and gave up quickly. To be clear, there wasn't anything wrong with the docs, they just didn't quite click for me, and I was having trouble seeing how Webpack would be useful for me.\nOne thing I like to make clear to people is that I don't do a lot of production work. I create content, both written and for conferences, and in general, my code is focused on illustrating how things work, explaining pain points, and the like. But I rarely do &quot;client work&quot; where I have to worry about process and the like. It's hard to admit all this at times but it's absolutely true as well.\nI'm in the process of learning Vue, and one of the topics I'm trying to wrap my head around is single file components. Part of the process for using these type of components is a build system of some sort. The recommended one is a webpack template and while it worked rather well for me, it bugged me that I had zero idea what was actually going on.\nBy random luck, I came across this excellent article by Joseph Zimmerman on Smashing Magazine: Webpack - A Detailed Introduction For whatever reason, this article really made sense to me and helped clear up why (and how) I could use Webpack in my development. If you've been meaning to learn Webpack, this is where I'd start.\nI do have one warning - this code sample early in the article will no longer work:\n\nInstead, change it to\n\nAs always - I hope this helps!\n",
		"tags":[
	        
            "webpack"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Read My (Free) Book on Apache OpenWhisk",
		"date":"Sun Nov 05 2017 12:51:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1509886260,
		"url":"https://www.raymondcamden.com/2017/11/05/read-my-free-book-on-apache-openwhisk",
		"content":"I am very happy to announce that my new book, &quot;Developing Serverless Applications - A Practical Introduction with Apache OpenWhisk.&quot; I believe that may win the prize for the most &quot;wordy&quot; title of any book I've written.\n\nThe best part is the price - 100% free. All you need to do is hop over to a signup form, register, and you'll get a copy of the PDF. It's a short book aimed at getting you up and running with serverless and OpenWhisk and will cover the basics of action creating, sequences, and making APIs.\nHere's the table of contents:\n\nIntroduction\nOpenWhisk Basics\nWorking with Actions\nUsing OpenWhisk Actions\nBuilding Sequences\nWorking with Packages\nUsing Triggers and Rules\nGoing Further with OpenWhisk\n\nI hope this is helpful to folks. OpenWhisk was my introduction to serverless, and it's obviously my favorite so far (although I do plan on learning more about Lambda and Azure Functions). Download the book and let me know what you think!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My NCDevCon Progressive Web App Talk",
		"date":"Tue Oct 31 2017 11:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1509451140,
		"url":"https://www.raymondcamden.com/2017/10/31/my-ncdevcon-progressive-web-app-talk",
		"content":"A few weeks ago I gave my first talk on progressive web apps (PWAs) at NCDevCon. I'm really happy they accepted my submission since it forced me to finally sit down and begin wrapping my head around the core concepts. While I certainly don't think I'm an expert yet, I think I gave a decent talk that focused on the practical aspects of PWAs and what's actually involved when creating them. You be the judge.\n\nYou can find a copy of the slide deck and demos here: https://static.raymondcamden.com/enclosures/ncdevconpwa.zip\nAs always, I would love to hear feedback on the talk. Let me know what you think by leaving a comment below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "OOO",
		"date":"Fri Oct 27 2017 15:15:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1509117300,
		"url":"https://www.raymondcamden.com/2017/10/27/ooo",
		"content":"Just a quick note that I'm going to be OOO (Out of Office) for an extended period. My response times to emails will be quite a bit slower and I probably won't be blogging. Things will return to normal in 2 weeks.\nNormally I let social media know about my blog posts to help drive traffic, but obviously there's no real reason to do that for this one. However, if you are a regular reader who comes here just to see what's new, do me a favor and say hi below. I'd appreciate a bit of validation that this blog is actually reaching people who care. ;)\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Calling Multiple Serverless Actions and Retaining Values with IBM Composer",
		"date":"Wed Oct 25 2017 17:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508950860,
		"url":"https://www.raymondcamden.com/2017/10/25/calling-multiple-serverless-actions-and-retaining-values-with-ibm-composer",
		"content":"Forgive the incredibly long title, but I wasn't quite happy with the shorter versions. As I've mentioned before, IBM Composer supports multiple different &quot;compositions&quot;, or &quot;logic doohickies&quot; (I work for IBM Marketing, honest), as a way to add conditional logic and branching to a serverless application. In this post, I'm going to share an example of the &quot;Retain&quot; composition.\nBefore I start though, I want to give a shout out to Olivier Tardieu. He basically wrote the composition for me and helped me wrap my head around the topic. Thanks Olivier!\nOk, so what does Retain do? The docs explain it like so:\n\ncomposer.retain(task[, flag]) runs task on the input parameter object producing an object with two fields params and result such that params is the input parameter object of the composition and result is the output parameter object of task.\n\nFor the most part that should be self-explanatory, but you may not know why you need this. Imagine the following sequence:\n\nGrab a keyword from a persistence system.\nSearch twitter for that keyword.\nEmail the results, and the keyword, to a user.\n\nThe crucial bit is in the last part. The keyword was used as input to the second part of the sequence. But if the Twitter action doesn't return the input that was sent to it, then how would you use it later in the sequence?\nRetain basically says - run this crap and at the end, combine the result (result) with the input (params).\nFor my demo, I had a somewhat more complex situation. I wanted to call two Watson services - in fact the two I blogged about yesterday, Watson Tone Analyzer and Watson Personality Insights. For both services I'd use the same textual input. So how would I do that?\nOne solution would be to enable a web action for both. I could then call both URLs in client-side code and simply use Promises to wait for them to both to complete. (If you don't know promises yet and want to see an example of that, just ask in the comments below!)\nWhile that would work, it would mean two different network calls.\nI could use a &quot;traditional&quot; OpenWhisk sequence, but right away I run into the issue of loosing results. I don't even care about the input, but if my sequence is:\n\nrun tone analyzer\nrun personality insights\n\nThen the second step's input is the output of the first one. That totally won't work. Luckily a composition using retain lets us do that. Here's the complete solution (with the assumption of course that the actions were already written):\n\nThat's a bit complex, but essentially I've got a call to watson/tone where I'm sure to preserve the input. Then a call to watson/pi where I first take the result of the previous action and pass in p.params as the input. The previous action in this case was the retain call and it stored input in p.params.\nFinally I combine into a new result with keyed names so it's easier to tell the tone analyzer result from the personality insights result.\nYeah, that's a mouthful, and to be honest, I couldn't have written this myself. It does make sense (mostly ;) to me now and it does work. I won't share the output as it's huge, but once I created it, it was a simple matter of using the fsh CLI to invoke the app.\nIn case it helps, here's the visualization of the app:\n\nYou can see the source code and sample output up on GitHub: https://github.com/cfjedimaster/Serverless-Examples/tree/master/retain\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "IBM Watson OpenWhisk Actions",
		"date":"Tue Oct 24 2017 14:58:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508857080,
		"url":"https://www.raymondcamden.com/2017/10/24/ibm-watson-openwhisk-actions",
		"content":"Many months ago I blogged about an OpenWhisk package I built for IBM Watson Tone Analyzer. The code wasn't terribly complex since most of the work was done by the npm package, but I thought it might be helpful to others looking to use the API with OpenWhisk. I've done some updates to that action and have added a new service today, Personality Insights.\nThe new Personality Insights action can be found in my Watson package. You can bind your own copy by using the full path: /rcamden@us.ibm.com_My Space/watson. Inside the package you'll find a tone and pi action. Since each service requires unique credentials, I did something kinda cool (well, cool to me) to make it easier to set your credentials.\nBoth actions support a username and password argument, but since they are unique, both actions also support an &quot;override&quot;. So for example, the Tone wrapper lets you pass tone.username and tone.password. For Personality Insights, it supports pi.username and pi.password. This means when you bind my package, you can supply all four arguments as defaults and then not worry about them later. This makes using the actions even easier - both support a text argument to easily pass your input.\nAs I said, the code is trivial, but here's the new PI action:\n\nIf you don't want to use my package, or you want to add to it or suggest a fix, you can find both in my main Serverless Examples repo.\n",
		"tags":[
	        
            "openwhisk",
            
            "watson"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Migrating a Static Site from Harp to Jekyll",
		"date":"Mon Oct 23 2017 14:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508769300,
		"url":"https://www.raymondcamden.com/2017/10/23/migrating-a-static-site-from-harp-to-jekyll",
		"content":"So a few weeks back I blogged about how I was working on an update to CFLib. Specifically - I was looking to migrate to a new static site generator to make it easier to update content. This past weekend I made a lot of progress with my update and I think I'm ready to release the new version. I thought folks might be interested in the details of the rebuild.\nThe Current Version\nThe current version of the site is built with Harp, the first static site generator (SSG) I used and the impetus for my introduction to the technology as a whole. I've got a soft spot for Harp. It's incredibly simple compared to most SSGs and a quick way to create a simple site. As much as I appreciate Harp, it hasn't been updated in a while and I'm not sure I could recommend it anymore. If you know you're building something really simple, maybe, but even then I worry the project isn't going to be around for much longer.\nWhen I was building CFLib, I had to find a way to support the one thousand plus UDFs in a way that would let me tweak the layout if I needed to modify something. Harp, like most SSGs, requires a physical file for each piece of content. (Jekyll actually has an interesting way around that, but that's not important right now.) Each UDF was a one line file where I simply included an EJS file to render the content. So for example:\n\nAnd here is the main template. EJS is kind of a icky templating language. It's flexible and it works, but it reminds me a lot of old ASP sites.\n\nNot pretty, right? But it works. However, you may be asking - where's the data? Most SSGs support something called &quot;front matter&quot;, which is basically a way to embed data on the top of a static page. This data is stripped out before rendering so it's not something the public sees, and it can be used across the site in other ways as well. So for example, your home page can show a list of titles from various blog posts where the title is embedded in front matter.\nSo yeah - Harp doesn't support that. Instead, you store all your data in JSON files. That's fine - I mean - JSON is easy to edit. So when I convert CFLib from dynamic to static, I simply wrote ColdFusion code to read from the database of UDFs and generate one giant JSON file of data. Giant. Like, huge.\nThis worked just fine until I actually needed to edit code. I had to escape code, craft a JSON block, insert it at the end of my giant JSON file, and then hope I didn't screw things up.\nIt got so bad that - frankly - I just stopped updating. While both traffic and submissions to CFLib has slowed considerably, I know it is still a resource for ColdFusion developers and I absolutely still want it to be available.\nWith that in mind - I began the conversion to Jekyll.\nThe New Version\nThe new version is built with Jekyll, my current favorite SSG. It doesn't power this site because as much as I like it, speed isn't it's best feature. (And I'll talk a bit more about speed in a bit.) Also, it uses Ruby, which I'm not a fan of, but I can get over that. The thing I like most about Jekyll is how flexible it is. Don't get me wrong - I think I can do the same stuff in Hugo that I can with Jekyll, but I swear Hugo seems to fight against me when I'm building something unique. Even the smallest thing in Hugo is awkward. (To me.) On the flip side, in Jekyll, I never worry about it. Assuming I can get past installation weirdnesses, once it's up and running I'm not concerned about being able to build what I need.\nThe biggest change in the Jekyll version revolves around UDFs. In Harp, a UDF is driven from:\n\none mostly empty physical file\na template file\ngetting data from a large JSON packet\n\nIn Jekyll, I've still got one physical file per UDF. However now the data is embedded in YAML. The file is still &quot;empty&quot;, but the data is much easier to get to. Here's one example.\n\nFor the most part, the only issue I ran into here was figuring out various YAML aspects. So for example, the args portion is an array of structs. In the sample above the array has one element, but I had to find out exactly how it was done. While testing I ran into a few more issues (like needing to escape colons), but for the most part, it worked well. I started off with hard coded UDFs, a few, and once I was convinced my layout was ok I automated it with a Node script.\nFor comparison's sake, here's the layout using Jekyll's templating language, Liquid.\n\nPersonally, I like this a heck of a lot better.\nThere's more to it of course, but you can see the entire source code up on GitHub: https://github.com/cfjedimaster/cflib2017\nThere's only one feature missing now I want to add back in and that's Disqus support. All I'm missing for that is getting an ID value into the front matter that I'll need to do via my generator script. (You can see that in the repo, named generate.js.) I went ahead and got rid of the RSS feed because honestly there aren't enough updates to the site to make it worthwhile.\nNow for the cool part - hosting. I've blogg",
		"tags":[
	        
            "jekyll"
            
		],
		"categories":[
            
                "jamstack",
            
                "jamstack"
            
		]

	},

	{
		"title": "Upgrading Serverless Superman to IBM Composer",
		"date":"Fri Oct 20 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508457600,
		"url":"https://www.raymondcamden.com/2017/10/20/upgrading-serverless-superman-to-ibm-composer",
		"content":"When IBM Composer was released, my plan was to try to slowly introduce readers to it with various different tutorials. My post earlier this week is an example. But I've been thinking a lot lately about a particular problem that Composer can fix for me, so I'm skipping ahead to a more complex topic for the post today. I guess this is a long winded way of saying - if you are still learning Composer this post may be a bit complex, but I'm definitely going to do more simpler posts later.\nThe Superman Problem\nAlright - so a few months ago I built one of my most complex applications on OpenWhisk, Serverless Superman. While I encourage you to read the blog post for details, the idea was basically to find random tweets about serverless and tweet them out with the word &quot;serverless&quot; replaced with &quot;Superman&quot;. (I can't believe I get paid to build stuff like this.)\nThe process was somewhat complex. Here is my attempt at building a flow chart to show how it worked. Note - I suck at flow charts.\n\nThe crucial part are the two branches there. If there are no tweets, the process needs to end. Also, sometimes I found tweets that were returned in the search but didn't actually include the word. Not quite sure what to think about that. I thought maybe it was in the hashtags but I didn't see it there either. So I've got a second thing to check as well before sending out the tweet.\nAll in all this works, but, you can't &quot;exit&quot; a sequence in OpenWhisk. You can only return an error. Now technically that isn't the case. I could use a combinator to try to get around this, but that felt over complex. I figured - who cares about the errors. I don't for sure.\nBut... it bothered me. Look at the error rate there:\n\nI loaded up the details:\n\nAnd began clicking. For almost every error, I saw what I expected - either &quot;No tweets&quot; or &quot;No serverless mentions&quot;. Ie, not real errors. But then I found this:\n\nThat was totally unexpected and - as far as I know - not something that should have happened. The issue though is that so many of my errors are &quot;noise&quot; and not real errors. This is where switching to a Composer app can help.\nThe Composer Version\nI know that Composer supports IF conditionals simple. I decided to rebuild the sequence as a Composer file. I'd take the opportunity to convert my &quot;simple&quot; actions into inline functions and then properly handle exiting when there are no tweets to create.\nSo as a reminder, my existing sequence consisted of custom &quot;set up and massage&quot; type actions as well as actions from my Twitter package. I've got a public Twitter package you can use to search and create tweets. I created a bound copy of the package with my app details so I can use it much easier.\nLet's look at the Composer file.\n\nLet's take it from the top.\nThe first thing I did was inline my &quot;setup&quot; function. All this does is prepare the data to be sent to my Twitter action.\nThen I run getTweets. That's a simple action call since all the work is packaged up. (And again, you can totally use my code too!)\nMy next inline action is a bit more complex. It handles doing additional filtering that Twitter's API cannot. I kinda think it's a bit too complex. Obviously what you inline and what you keep as an external action is up to you, and I'm willing to bet folks will go back and forth between what they feel comfortable doing, but for this update, I wanted to move everything inline just to see how it feels.\nI'm ok with this block of code - but I definitely think folks may disagree.\nThe next action is another inline function - and yeah - you may ask - why not simply &quot;combine&quot; the two. Well again, I'm not saying it makes absolute sense, but in the previous version I had them as separate actions so I thought it made sense to keep them separate here too. The logic here handles picking the random tweet and updating the text.\nHere too is where things begin to change. Previously that action returned an error if either of the two conditionals were false. In my case, I simply never set a value for newText.\nThat then lets the last part work very nicely - composer.if. If my status is not blank, run sendTweet. Otherwise do nothing.\nSo now the end result is either a tweet, or nothing. No errors. (Well, yes, I'll still have errors, but I'll have better errors now!) In case you're curious, check out how the Composer shell renders this:\n\nSlick rick. Anyway, once this was done I literally just had to update the rule associated with my CRON-based trigger to point to the new app. And it worked:\n.@thomasj is presenting multi-provider Superman apps. You can still join us. https://t.co/vU64L0xat5 pic.twitter.com/AF3GLx551v&mdash; Serverless Superman (@serverlesssuper) October 20, 2017\n\nRemember that there is currently a bug with wsk such that when you edit a rule, it disables it. Be sure to re-enable it right after the edit.\nAny questions?\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Copying to Clipboard with Windows Subsystem for Linux",
		"date":"Thu Oct 19 2017 14:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508424720,
		"url":"https://www.raymondcamden.com/2017/10/19/copying-to-clipboard-with-windows-subsystem-for-linux",
		"content":"This tip is 100% thanks to Ben Hillis, a developer working on the Windows Subsystem for Linux (WSL). Yesterday I needed to copy a file under WSL to the my system clipboard. If you Google for how to do this, you'll see a CLI called clip that works under Unbuntu, however, that doesn't work under WSL. If I had to guess I'd say because there's isn't a GUI involved with WSL but to be honest, I'd be guessing.\nWhen I asked on Twitter, Ben had a simple solution - use clip.exe. I keep forgetting that WSL provides complete access to Windows executables. I knew this - heck - it's how my tip on loading VSCode Insiders from WSL worked. But I didn't even think to check if Windows had a utility to copy to the clipboard.\nIn case you're curious, this is how you would copy a file under WSL to your Windows clipboard:\ncat report.txt | clip.exe\nAnd I'm sure there's numerous other ways too. Anyway, I'm mainly just blogging this so I don't forget.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building Your First Serverless Composition with IBM Cloud Functions",
		"date":"Wed Oct 18 2017 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508284800,
		"url":"https://www.raymondcamden.com/2017/10/18/building-your-first-serverless-composition-with-ibm-cloud-functions",
		"content":"A few days ago I blogged about the new Composer functionality for IBM Cloud Functions and OpenWhisk. This is a incredibly cool release and I'm going to try my best to demonstrate it over the next few weeks. In today's post I'm going to focus on what the process is like. By that I mean, how do I go from idea to actually using it and testing it. This won't be terribly different from the docs, but I figure it may still be helpful for folks to get an idea of how I'm using it. (And of course, I expect my usage to change over time.) Note that the code I'll be using for this post will be trivial to the max because I want to focus more on the process than the actual demo. Alright, with that out of the way, let's start.\nThe Demo\nAs I said, the demo is pretty trivial, but let's cover it anyway so we have context for what's being built. The demo will convert a random Ferangi Rule of Acquisition into pig latin. So the logic is:\n\nSelect a random rule\nTake the text and convert it to pig latin.\n\nBuilding a Serverless Application - Old Way\nI'll start off by describing what the process would be prior to the introduction of Composer. To be clear, &quot;old way&quot; isn't meant to be disparaging in anyway. OpenWhisk has always let me build really cool shit and Composer just makes it even better.\nFirst, I'll build the &quot;select a random rule&quot; action. Here is the code listing with the embedded very long list of rules removed. (You can see the full source code on GitHub - I'll share that link at the end.)\n\nI created this as an action called safeToDelete/rule. (As a reminder, I use a package called &quot;safeToDelete&quot; to store actions I build for blog posts and the such that do not need to stay alive.)\nwsk action create safeToDelete/rule rules.js\nI then tested to ensure it worked:\nwsk action invoke safeToDelete/rule -b -r\nAnd the result is:\n\nNext I created a Pig Latin rule, based on this repo from GitHub user montanaflynn:\n\nI then pushed it up:\nwsk action create safeToDelete/pig pig.js\nAnd tested:\nwsk action invoke safeToDelete/pig -b -r -p input &quot;My name is Ray&quot;\nWith this result:\n\nAlrighty. So to make the sequence, I have a problem. The output of the rule action is a variable named rule. The input for pig requires a parameter called input. In order to create a sequence, I'll need a &quot;joiner&quot; action. Here's the one I built:\n\nNote that actually this does two things. It maps the input as well as modifying the text to remove the number in front of the rule. I pushed this to OpenWhisk like so:\nwsk action create safeToDelete/pigrule pigrule.js\nAlright, so the final step is to create the sequence:\nwsk action create --sequence safeToDelete/ruleToPig safeToDelete/rule,safeToDelete/pigrule,safeToDelete/pig --web true\nThat's a long command but not too bad. Typically I'd make a shell/bat script so I could automate updating each individual rule and the sequence all in one quick call. I'll grab the URL like so:\nwsk action get safeToDelete/ruleToPig --url\nWhich gives me: https://openwhisk.ng.bluemix.net/api/v1/web/rcamden%40us.ibm.com_My%20Space/safeToDelete/ruleToPig\nTo test that, just add .json to the end. You can see that here.\nAnd finally, a sample result:\n\nI'll be honest, that's plain unreadable, but who cares. Let's move on.\nBuilding a Serverless Application - With Composer\nAlright, so I'm assuming you've followed the install instructions already and can safely run fsh in your terminal.\nThe first thing you'll run into is that Composer uses slightly different terminology. Instead of sequences, you'll create an app. To be fair, it isn't a 100% one to one correlation, but I think for now it's ok to mentally map the two.\nNext - you'll define your app in code, in a file. (You can use the graphical shell too but I don't.) So to start, I'll make a new file called - pigruleapp.js.\nThis file will contain the instructions that make up my composition. Here's what I started with:\n\nNotice I don't define composer. I don't have to as the system will handle that for me. All I do is define my logic. In this case, I'm using the sequence feature of composer and defining what to run. Essentially I've defined the exact same sequence I used before. (I'm going to make that better in a moment.)\nTo create the app, I'll use:\nfsh app create ruleToPigFsh ./pigruleapp.js\nIf I have to make any edits, I'd use fsp app update instead. Next I'll test it with:\nfsh app invoke ruleToPigFsh\nAnd - it works as expected:\n\nAlright, but let's kick it up a notch. First, I can visualize my app like so:\nfsh app preview pigruleapp.js\nWhich gives me this:\n\nYou can ignore the &quot;not yet deployed&quot; message on top. Basically the shell is letting you know you are viewing a local file and not a deployed app instance. So yes it is technically deployed. Anyway, what's not visible in the screen shot is that you can mouse over the blue boxes to get details. So for example, mousing over rule shows me action | safeToDelete\\rule. You can also double click a",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "DevTools Tips for Progressive Web Apps",
		"date":"Tue Oct 17 2017 14:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508251500,
		"url":"https://www.raymondcamden.com/2017/10/17/devtools-tips-for-pwas",
		"content":"As this month is apparently &quot;blog everything I can about PWA month&quot;, I wanted to share some quick notes on how I'm using DevTools, specifically Chrome (but I'll quickly look at Edge, Firefox, and Safari) to work with and debug progressive web apps (PWAs). To be clear, this will not cover every aspect of DevTools related to PWAs, but rather focus on what's been helpful to me so far.\nStarting Off\nFirst off - I generally stick to the &quot;mainline&quot; Chrome (released Chrome) as I prefer to use what regular people use. (Well, most devs.) That being said, Google Canary is making some improvements to DevTools in the PWA area. While I was testing last time, I found myself going back and forth a bit. Don't forget you can run both at the same time. At the time I'm writing this article, it looks like the biggest differences are that Canary lets you test background sync and push which is pretty good. I'm not using those features yet, but I can see being able to test them in the browser like that as being very helpful.\nNext - probably the biggest tip I can give is where you'll find PWA related tooling - under the Application tab.\n\nProbably obvious, but I remember having trouble finding it the first time. Also note that there is where you'll find tooling related to client-side storage, including Local Storage, Session Storage, IndexedDB, Web SQL, and Cookies.\nAlso note that Chrome lets you rearrange the tabs so your order may not match what you see in the screen shot above.\nApp Manifest\nThe first tool I think you'll find useful is the app manifest viewer. As you can probably guess, it will report on what settings you've specified in your app manifest along with providing a link to view it. Here's mine for my NMS Tracker PWA:\n\nFor the most part this should be pretty self-explanatory. I love that it displays the icons and that start_url is clickable. If you're doing anything special when a site is launched via the homescreen, this makes it easy to test.\nPay special attention to the &quot;Add to homescreen&quot; link. This lets you test Web App Install Banners which are an automatic prompt by Chrome to install your app to the homescreen. (Note, this also works on desktop too.) Clicking this link will have the browser check to see if you're app meets the criteria for this feature. It does not actually start the process. Instead, you'll need to reload the page in order to get the prompt, again, if you've &quot;passed the test.&quot;\nSpeaking of that, if you are missing something, you'll get a nice error in the console:\n\nSee how the error is clearly telling you what you are missing? This is really helpful. Note though that it only reports one issue at a time. If I fix the issue above and try again, I get a new error:\n\nBasically you have to correct one issue at a time (if you don't know what you need that is). When you do get it working, again, note that you have to reload the browser to get the prompt to trigger. Here's how it looks on desktop:\n\nAs a reminder, even if you &quot;fail&quot; the &quot;Add to Hoemscreen&quot; test, do not forget a user can still manually add your site to their homescreen.\nService Worker Debugging\nThe next useful tool will be the &quot;Service Workers&quot; section of the Application tab.\n\nIn the shot above, you can see the status of my service workers. Remember that your service worker has a &quot;life cycle&quot; which impacts when it actually impacts your page. To make things easier, you absolutely want to enable the &quot;Update on reload&quot; checkbox as I've done here. Just don't forget that for regular users, they will get the expected behavior in terms of the service worker life cycle.\nThis is also where I've seen errors in service workers reported - and also where I've had problems. I'll see random errors here sometimes that almost seem as if they are gremlins in the tooling as everything seems fine on the page. This is where I typically switch over to Canary as it seems to be more stable. I know that's a bit vague and I apologize, but there ya go.\nCaching\nThe next tool you'll want to check out is Cache, and specifically Cache Storage. This screen shot is from Canary which has made a few changes compared to mainline.\n\nNotice I've called out the cache name in the screen shot. If you've changed this and don't see it reflected, you can right click on the Cache Storage node and force a refresh. Also note that you can click on individual items in the cache to see what is there. Service workers give you 100% control over the cache so even if you see a URL there where you think you know the contents, it's entirely possible an older version, or even a modified version, is in the cache. You can also right click on individual items to delete.\nLighthouse\nOk, so I already covered this last week, but under the Audits screen you will find the Lighthouse tool:\n\nYou can select the following audits:\n\nProgressive Web App\nPerformance\nBest practices\nAccessibility\n\nI've run into issues sometime",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Review: Building Progressive Web Apps",
		"date":"Mon Oct 16 2017 16:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1508171040,
		"url":"https://www.raymondcamden.com/2017/10/16/review-building-progressive-web-apps",
		"content":"\n    \nIf you can't tell, I've been kind of on a PWA (Progressive Web Apps) kick the last few weeks. I want to thank NCDevCon for letting me speak on the topic which forced me to spend time learning it. I've been hearing about PWAs for a while now, and I've also sat in multiple presentations on the topic. But I have to be honest - I haven't attended many PWA talks that really made sense to me. (With the notable exception of my last two - one by Simon MacDonald and another by Tara Manicsic.) Part of the problem is that as it is my job to give presentations, it's hard sometimes to be objective when sitting in the audience. But even with that, I've really felt that a lot of the talks haven't really &quot;sold&quot; PWAs well. In fact, it felt like most PWA talks seem to be 99% around doing caching with service workers, and PWAs are far more than that.\nI'm not even sure how I found &quot;Building Progressive Web Apps&quot;. It was probably a random tweet. But I was incredibly impressed by the book. I can say it completely opened my eyes to what PWAs encompass and how they are built. I had felt a bit overwhelmed by the idea of learning PWAs, and even more so by the idea of actually creating one. After reading Tal Ater's well written book, I feel much more prepared to build PWAs. (And if you're a regular reader, then you can see my most recent posts as examples of this.)\nWhile I still find PWAs to be a &quot;big&quot; topic, it no longer feels overwhelming, and I have Tal to thank for that.\nHis book covers web manifests, service workers, caching, push messages, notifications, background sync, and more. He even spends time discussing IndexedDB, which isn't necessarily a new technology (I've got a book the topic myself), but has gained new importance as PWAs have evolved.\nI also appreciate the attention he spent to explaining why you would do certain things. For example, Tal goes into detail about the various caching strategies and why you would use them in your app. So you get more than just a random set of code samples. You get logical reasons for why you could actually use the code he shared. This dovetails well into the UX section at the end of the book. I love that it isn't just &quot;how to do X&quot; but rather &quot;here are things to think about if you want to do X&quot;.\nSo if it isn't obvious, I definitely recommend the book. (Note - the link above is an affiliate link - if you purchase through that I get a few cents.) In fact, this is only the second technical book I've read that I plan on purchasing a physical copy so I can keep it by my desk for easy reference.\nIn case it helps convince you, here is the table of contents:\n\nIntroducing Progressive Web Apps\nYour First Service Worker\nThe CacheStorage API\nService Worker Lifecycle and Cache Management\nEmbracing Offline-First\nStoring Data Locally with IndexedDB\nEnsuring Offline Functionality with Background Sync\nService Worker to Page Communication with Post Messages\nGrabbing Homescreen Real Estate with Installable Web Apps\nReach Out with Push Notifications\nProgressive Web App UX\nWhat's Next for PWAs\nService Workers: A Great Opportunity to Adopt ES2015\nFull-Page Interstitials or: How I Learned to Hate the Door Slam\nCORS versus NO-CORS\n\nI'd love to hear from anyone else who has read this book, and if you have others you would recommend, please share them in the comments below.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "books"
            
		]

	},

	{
		"title": "Some Progressive Web App Tips",
		"date":"Fri Oct 13 2017 14:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1507906500,
		"url":"https://www.raymondcamden.com/2017/10/13/some-pwa-tips",
		"content":"Yesterday I blogged about my experience turning a simple front-end application into a PWA. Since then I've had some things come up that I wanted to share related to PWA (Progessive Web Apps). This is a bit of a random collection of items but I hope it's helpful.\nWeb App Install Banner Stuff\nAs a reminder, the [Web App Install Banner] feature is the Chrome thing where the browser will prompt the user to install your web app. I mentioned yesterday I had trouble getting this to trigger in my app, even though I had (as far as I know) did everything I needed to.\nFirst off - even though I couldn't get that prompt to occur, if I manually added the site via &quot;Add to Home screen&quot;, the app was saved correctly and recognized as a PWA. I'd share a screen shot of this but I assume you can trust me on this.\nI asked some questions related to PWA on a private Google Developer expert channel and a Googler, Paul Kinlan, shared some details.\nFirst, if the app was ever installed before, of if it was ever declined before, than the user won't be prompted again. That didn't cover my case, but it is important to remember.\nSecond, there is a Chrome flag which bypasses this:\nchrome://flags/#bypass-app-banner-engagement-checks\nIn theory when you enable this, you can then use the &quot;Add to Homescreen&quot; link in DevTools to force it. However, it does not force the prompt, it just &quot;resets&quot; it so that when you refresh the app, it's as if you never declined it or installed it before. I'm having trouble with remote debug and Android adb on my desktop so I've not yet confirmed this, but as I said, on a manual home screen save, everything is kosher with my app so I think I'm good.\nEdit: So a few minutes after I released this blog post, I realized that I needed to set that Chrome flag on the device. Duh. I did that, restarted Chrome on my Android device, and instantly got the app installer banner. Woot!\nLighthouse Audits\nOne of the thing I keep repeating when I talk about PWAs is to not get too caught up in feeling like you have to implement everything immediately. That being said, there is a great way to test your PWA support and track your progress.\nLighthouse is a tool that checks various aspects of your site. It isn't just for PWAs but can cover performance, best practices, and accessibility as well. Best of all, it's included in DevTools now by default so you can run it directly in Chrome. Here is an example of it on my app:\n\nAs you can see, I failed a few things in regards to splash screen support, which is what I expected, but otherwise got a great score. Not shown in the screen shot above is a simple download button. This exports a JSON file. It wasn't obvious to me, but Paul Irish (another Googler) let me know that you can drag and drop the file directly into DevTools to load it again.\nYou can also find an online viewer here: https://googlechrome.github.io/lighthouse/viewer/. This lets you export HTML and print too.\nDon't forget about Sonar though, a Microsoft project which provides some damn nice tools for testing your site as well.\nLocalhost Warning\nSo.... yeah, this was a fun mistake, and apparently I'm not the only one to do it. While preparing for my PWA talk last week (I'll be sharing the video URL and assets as soon as I get it), I ran into an interesting problem. I had gotten offline caching working well with a service cache in one app. I then moved to another app and noticed that my new HTML wasn't loading. Instead, all of the content from the other app loaded instead. Why?\nI use httpster to quickly run web servers via the CLI. So I go into a folder, alpha, type httpster, and then open my browser to http://localhost:3333. When done, I kill it, go into another folder, and run it again.\nServer workers have a concept of 'scope' which means &quot;These are the domains and paths I control.&quot; When you first learn about this, you are encouraged to put your service worker JavaScript in the root directory of your web app as opposed to a &quot;js&quot; folder. That way the service worker can respond to HTML, CSS, and other requests for the entire site.\nSo - in my case - it worked as expected. I had built a PWA under a folder, told it to cache everything and not check the network if an asset was in cache, and when I switched to another folder, but used the same URL, it worked exactly as expected.\nTo fix it, I used the &quot;Unregister&quot; command in DevTools:\n\nThere's a few ways to avoid this. One, I could have installed my serviceworker at the same folder level and not root, so\nnavigator.serviceWorker.register('serviceworker.js')\ninstead of what I did:\nnavigator.serviceWorker.register('/serviceworker.js')\nAnother option, and this would make sense for &quot;real&quot; project work, would be to just DNS aliases in your local HOSTS file. However, be careful here. Chrome has certain rules for when https can be skipped when using certain features, service workers included. I thought the rule was basically htt",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Turning My No Man's Sky App Into a PWA",
		"date":"Thu Oct 12 2017 15:38:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1507822680,
		"url":"https://www.raymondcamden.com/2017/10/12/turning-my-no-mans-sky-app-into-a-pwa",
		"content":"A few weeks back I blogged about an app I built for one of my favorite games, &quot;No Man's Sky.&quot; The app was a simple single page app (SPA) that let me calculate items I needed to complete goals in the game. I've spent the last month or so researching PWAs (Progressive Web Apps) and while I'm far from being an expert, I feel comfortable enough now to look into how I can enhance my existing sites to support being PWAs, or at least moving them in that direction.\nCreating a PWA can seem daunting. It certainly did to me. Consider Google's &quot;PWA Checklist&quot; which has what seems to be an incredible amount of tasks laid out. To be fair, it isn't a huge list and it's includes instructions on both testing and fixing each item, which is awesome. But still - it's intimidating.\nWhat I've tried to do in my approach to PWAs is stress the importance of taking it one step at a time and not fixating on covering every single item. Some won't even make sense. For example, there is absolutely no reason to include push notifications in your app unless you actually need it.\nEvery single thing you do to improve your web site is a good thing for your users. If you don't ever make it to &quot;real PWA&quot;, don't worry about it. Your users will still appreciate the improvements!\nGetting Started\nOk, so let's get started. Let me begin by defining what I plan to add to the application. My app is already kinda responsive (although could be better) and served over HTTPS. I'm going to focus on two things:\n\nAdding an app manifest file to support &quot;save to device&quot; with a nice shortcut.\nAdding caching for offline support via a Service Worker so I can run it offline. To be fair, I only plan on using this app when playing the game and I'm pretty much always online at home, but there ya go.\n\nAdding a Manifest\nThe app manifest, technically the Web App Manifest is the easiest step. It's literally a simple JSON file that describes the app. You have quite a few options here in terms of app names (for example, you'll probably want a shorter name on the device home screen), icons, launch options and colors.\nThis is the manifest I went with - which supports some, but not all of the available options.\n\nThe short_name value is what will be on the device screen and as you can see, I've shortened &quot;No Man's Sky&quot; to NMS. Even this may be too long so I may end up removing &quot;Resource&quot;. I specified one icon, but could support multiple. I also slightly modified the URL to include a flag that Google Analytics can pick up. Finally, I told it to launch in &quot;standalone&quot; mode which basically means no URL and other browser chrome.\nAnd that's it. To add this to my site, I added this tag to the HTML file:\n\nChrome provides good support for parsing and showing you manifest details. Check it out below:\n\nWhat's not on that screenshot is the &quot;Add to homescreen&quot; link on the far right side. What this does (as far as I can tell) is simulate the new &quot;Web App Install Banner&quot; feature. This is where Chrome will actually prompt the user to add the site to the home screen. The feature has a certain set of requirements (you can't force it!) but this devtools feature lets you force it in testing. It also lets you see if you &quot;fail&quot; any of the tests, so for example, when I click it now I get:\n(index):1 Site cannot be installed: no matching service worker detected. You may need to reload the page, or check that the service worker for the current page also controls the start URL from the manifest\nIf I had forgotten an icon or did &quot;not enough&quot; in the manifest, it would have noted that first. Basically it reports the first error it finds and you may find yourself fixing one issue after another to make it happy.\nBut to be clear, this is ONLY for the new 'auto prompt' feature! Your user's can absolutely add them. Here is what that looks like. This the first prompt I get:\n\nNote the icon and short name in use. Oddly, you get a second prompt too. I honestly don't know why this one shows up.\n\nMaybe it's to let you know what the icon will be? Anyway, after all of this, it does end up on your device.\n\nIn case you may not see it, let's focus in a bit.\n\nWelcome to what I call the &quot;Not a PWA Mark of Shame&quot;. This is something Chrome added somewhat recently and I have very strong feelings about this. Basically it is a &quot;mark&quot; that the icon represents a web page that is not a &quot;real&quot; PWA. As I said, I have feelings. Negative feelings. But let's move on. Just be aware that the mark won't go away until you have added a proper service worker to site.\nNot that it's that much different, but here is the app running after opening it via the device homescreen. Note there is no URL banner on top.\n\nAdding Caching Via a Service Worker\nFor the next step, I'm going to add offline support via caching and a service worker. Service workers are a pretty big topic. I'd suggest reading the MDN docs on t",
		"tags":[
	        
            "javascript",
            
            "pwa"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Serverless Composition with IBM Cloud Functions",
		"date":"Mon Oct 09 2017 20:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1507581840,
		"url":"https://www.raymondcamden.com/2017/10/09/serverless-composition-with-ibm-cloud-functions",
		"content":"Today IBM announced a very important update to IBM Cloud Functions and OpenWhisk. This is a pretty huge update and is incredibly important for folks doing serverless with OpenWhisk on the IBM Cloud platform. I'm going to do my best to explain what these updates are and why I'm excited about them. As I said, this is a big update. So today I just want to give you my take on things and later on, I've got a set of examples to share that may help make things easier to understand.\nFirst and foremost - I want to be clear that this new feature is for IBM Cloud Functions, ie, OpenWhisk on IBM's platform. That's where I do all of my testing and playing around, but OpenWhisk has always been something you can run completely on your own. It's open source. It's free. You don't have to be involved with IBM at all. Obviously I think there's benefit to running it on our platform, and today's announcement is one example of that.\nThere are two new things announced today, but let's begin with the important one - compositions. Composer is a new way to construct serverless applications with multiple steps and complex logic. Previously, developers could chain together actions in sequences. (See my posts on the subject here and here). This worked well but had certain restrictions. For example, if you connected action A and action B, you had to ensure that the output of A matched the expected input of B. One simple way of doing that was creating a &quot;joiner&quot; action that literally just changed one set of data to another. That was simple to do, but did leave you with actions that were maybe just one or two lines of code.\nAnother issue you ran into were cases where data had to wrap &quot;around&quot; an action. Imagine a sequence of A, B, and C. A outputs a few values that B doesn't care about. How do you get that output to C without adding code to B? OpenWhisk has a package of combinators that helps with that (see this post) but they were (at least imo) a bit complex to use.\nComposer provides a solution to both these problems. It allows you to define, programatically, both the flow of your serverless application as well as some of the logic. So for example, I don't need to write a short action just to map data from one action to another. I can define it in the composer. I can also do multiple different types of branching logic as well data &quot;lifting&quot; around actions all within my composition.\nAnother cool use of this is simply &quot;leaving&quot; a sequence of actions early. When I built Serverless Superman, one of the issues I ran into was that there wasn't a nice way to simply exit the sequence when I didn't have a tweet to publish. So I ended up exiting with an error. Now when I examine my stats, this particular sequence has a high, but invalid, rate of errors.\nI have to admit - your first few attempts with this will be a bit difficult at first. At least for me, it took me a few tries to wrap my head around how things work. But the more I use it, the more I love it. As I've begun to build more complex applications, Composer is exactly the tool I want to use.\nSo what does it look like? Consider a simple sequence of two actions. The first, simply capitalizes text:\n\nThe next action reverses text:\n\nIf I wanted to join these two in a sequence, I'd have to map the result of the first one (output) to a new name, input, like so:\n\nThat's not the end of the world, and it's easy to do, but let's consider this:\n\nThis is a Composer script. You will use a new tool (more on that in a moment) to push this to IBM and create a new application. The sequence command works like a regular OpenWhisk sequence and can have any number of items inside. But notice the second one. That's a simple inline function that handles mapping output to input. No more &quot;joiner&quot; sequence!\nThe composer object has multiple methods supporting different types of logic. Here is a simple example of an &quot;If&quot; condition.\n\nIn the composition above, the first action returns a random number, and the second part of the sequence, the IF, will only run when the output is over 5.\nYou can read more about the different types of compositions at the docs and they have multiple examples as well. (And again, I plan on sharing some examples over the next few weeks.)\nNow I want to discuss the other part of the update, and this also is pretty freaking cool, even if you're just doing simple actions. In order to use Composer, you have to install a new CLI. Now - I have to say right away. That ticked me off. Like - seriously - why would I want to use two CLIs to work with OpenWhisk? I was wrong. Yes, it was something new to learn, but damn, damn is this new tool helpful.\nThe new tool is fsh, or as the docs say, &quot;the functions programming shell for the IBM Cloud&quot;. But yeah, let's just call it fsh. This isn't new technically, it was released before, but today it was updated in a pretty signifcant way.\nThe boring part is that the CLI will let you deploy your",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Facebook Chatbots with OpenWhisk",
		"date":"Mon Oct 09 2017 12:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1507553520,
		"url":"https://www.raymondcamden.com/2017/10/09/facebook-chatbots-with-openwhisk",
		"content":"This weekend I decided to take a quick look at running Facebook Chatbot, aka Facebook Messenger Platform, on the OpenWhisk platform. In general, it worked just fine and 90% of the issues I had were me not reading the docs correctly and making code screw ups. But I can say that I've got it working successfully now and it isn't difficult at all.\n\nIn this blog post I'm going to explain what you would need to do to get a Facebook Chatbot running on OpenWhisk. I will not cover the entire process of creating a bot. For that, you want to read Facebook's Getting Started guide which covers the things you need to setup and discusses the Node code. The code I'm going to share is a modified version of their Node code. It isn't very clean, but it should be enough to get you started if you want to use OpenWhisk for your bot.\nThe first thing you'll need to do is create a new OpenWhisk action. You are welcome to use my code to start off with, just remember what I said - it is a bit messy. When you create your action, you must use the web action flag so that Facebook can hit it. Given an action name of fbbot, you would enable web action support like so:\nwsk action update fbbot --web true\nNow you need the URL. You can get that like so:\nwsk action get fbbot --url\nWhen you follow Facebook's guide, this is the URL you use for the webhook. Do not add JSON to the end! I always do that as I'm used to building JSON-responding services, but Facebook requires a non-JSON response for verification. Just use the URL as is.\nNow for the code. I'm going to share the whole thing at the end, but first I want to share a few bits and pieces. Facebook will either send you a GET or POST request. The GET is always used for verification. You can handle this like so:\n\nNotice I sniff for the HTTP method first, get the values out of the args, and then check to ensure the token is correct. &quot;test_token&quot; was set on the Facebook side and should probably be a bit more secure. As the comment clearly says, I should add an error result there.\nThe next part of the code will handle responding back to the message. For the most part I followed Facebook's Node sample, but I modified things a bit. Facebook doesn't really how you respond to its HTTP call to your server. Your response means nothing essentially. Instead your code makes its own HTTP request to Facebook's API to respond.\nHowever - if you follow Facebook's lead and &quot;respond early&quot; while the response HTTP call is about to go out, that will not work on OpenWhisk. When OpenWhisk things you're done, it's going to shut down your serverless code. Therefore, the code I used basically waits till everything is done before creating a response.\nSince Facebook can, and will, batch responses, that meant using Promise.all as a way of listening for all the possible calls to finish. So here is the entire code snippet. Once again, please remember this leaves a lot of polish out.\n\nThe process function is where I did the logic of &quot;You sent:&quot; in response to your message. A real bot would do a bit more work obviously. callSendApi is basically just the API call to Facebook. I've modified it to use request-promise since I have to know when it's done.\nIn theory you can test this on the page I set up for it, BotTest1, but I'm not promising to keep that site up for long. Now that I've got this done, I'm going to look into integrating it with Watson for more advanced demos.\nLet me know if you have any questions!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Rebuilding a Flex Mobile App as an Alexa Skill",
		"date":"Wed Oct 04 2017 17:20:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1507137600,
		"url":"https://www.raymondcamden.com/2017/10/04/rebuilding-a-flex-mobile-app-as-an-alexa-skill",
		"content":"Many, many moons ago I created a ridiculous Flex Mobile app called TBS Horoscope. This was for the Nook platform, eventually moved to Amazon Android App store (where it still sits actually) and one of my few commercial apps. The app was fairly simple. It generated a fake horoscope when an astrological sign was requested and persisted it for the day. That way it would be a bit more &quot;realistic&quot;.\nI thought it would be fun to rebuild this for the Amazon Alexa. So I did it. Because I have a great job and I'm very lucky. Before I talk about how I built it, here's a video of it in action.\nNew Alexa skill - driven by @openwhisk of course. Previously a Flex Mobile app. pic.twitter.com/8uloEMRPi4&mdash; Raymond Camden (@raymondcamden) October 4, 2017\n\nOk, so how did I build this? I began by creating a service just for the horoscope generation itself. Initially this was going to be its own OpenWhisk action, but I ran into some issues where that wouldn't make sense. I'll explain why later. Here's the code.\n\nA horoscope is generated by combining a few random strings. I realized many horoscopes focused on money and love, hence the specific functions for them. To make things super random, I used a noun list with over 4500 words and an adjective list nearly 1000 words long. I had to convert these to JSON files as I didn't know how to read text files in an OpenWhisk action. (Now I do.) If you're curious, I actually copied the entire word list into my clipboard, pasted it into Chrome Dev tools with backticks before and after, then converted it into an array. Finally I did copy(s) to copy it back to my clipboard and then I saved it to the file system.\nOh - and rewriting ActionScript to JavaScript was simple as heck - all I did mainly was remove the types. That made me feel a bit sad and I thought about maybe using TypeScript for my code, but I'd have to transpile it before sending it to OpenWhisk and I didn't want to worry about that.\nSo that's that. The Alexa part is pretty simple. Once again I'll say I'm not happy with how I write my skills. It works. It isn't difficult. I just think I can do better.\n\nBasically this boils down to two things - help and the horoscope. You'll notice I don't actually persist the horoscope for the day. I was going to make this skill be a bit more advanced. It would ask you for your sign if you didn't give it, and heck, even let you give it a birthday and it would tell you your sign. I'd then persist the horoscope in Cloudant and everyone (of the same sign) would get the same horoscope.\nThen I remember horoscopes are bull shit and this is for fun and as a user, I'd rather have it just always be random.\nSo you might be wondering - where's the skill verification stuff? Back in August I released a package for that so you no longer have to write the code yourself.\nGiven that my Alexa skill action is called tbshoroscope/doHoroscope, I made my public API like so:\n\nI then grabbed the URL (wsk action get tbshoroscope/getHoroscope --url) and supplied that to Amazon. I'm currently waiting for verification from Amazon and when it goes live, you'll be able to find it via its skill name, &quot;TBS Horoscope&quot;. Note - the name may change as &quot;TBS&quot; is a television channel here. Also, if they find a bug, obviously the code may change a bit too. If the name changes, I'll add a comment below so please check it, and if the code changes, the GitHub repo will always have the latest. You can find this demo here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/tbshoroscope\n",
		"tags":[
	        
            "alexa",
            
            "openwhisk"
            
		],
		"categories":[
            
                "development",
            
                "serverless"
            
		]

	},

	{
		"title": "Reading a Text File on OpenWhisk",
		"date":"Mon Oct 02 2017 15:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1506958320,
		"url":"https://www.raymondcamden.com/2017/10/02/reading-a-text-file-on-openwhisk",
		"content":"Many months ago I wrote a quick post on OpenWhisk, serverless, and persistence (Serverless and Persistence). In that article I mentioned that while your serverless actions are stateless and you should not rely on the file system, you do have access to it and can make use of it. It isn't something I'd recommend normally and it should be considered a - if not red flag - at least yellow for &quot;are you sure&quot; before deploying, but there are cases where it might make sense.\nThis weekend I worked on an action that needed to use a four thousand line text file as a data source. I could have fired up a database or Reddis instance, but that seemed like overkill. I knew that I could easily write code to read the file and then store it in the action's variables scope. That way if the action was run while OpenWhisk had it warm, it wouldn't need to hit the file system again.\nI ran into some issues though and thankfully (once again), Rodric Rabbah at IBM helped me out. Let's look at a quick example and I'll describe what's necessary to make it work.\nFirst, the action:\n\nThis is incredibly simple, but the two parts to make note of are:\n\nIn order to read a file in the same directory as the action, you can not just use ./file but must use __dirname. This is what Rodric helped me out with.\nSecondly - you definitely want to cache to read as I'm doing here. I added a console message just to make sure it worked correctly.\n\nOk, so that's the code, but you have to do a bit more. In order to ship 2+ files with your action, you have to use a zipped action. That makes you also have to make a package.json. I made a quick one with npm init, just be sure it picks up your entry point matching your file name. In my case it was testtext.js and this is what the package.json looked like:\n\nYou then zip up the JavaScript file, the text file, and the package.json and deploy that. I use a script to make it easier:\n\nAnd that's pretty much it. When I couldn't figure this out over the weekend, I went into Chrome DevTools, pasted the entire 4k line file into it, and converted it into a JSON file I could require() in instead.\nAnyway - just to complete the post, here is a screen shot of it working and a very important, very wise quote:\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Calling a PowerShell Script from WSL",
		"date":"Mon Sep 25 2017 20:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1506369840,
		"url":"https://www.raymondcamden.com/2017/09/25/calling-a-powershell-script-from-wsl",
		"content":"Just a quick post to share a few things I learned this morning about PowerShell scripts and Windows Subsystem for Linux. I was trying to find a CLI way to set my screen resolution. I'm going to be recording some videos and wanted a quicker way to enter the right resolution, and then return.\nMy Googling turned up this blog post, Hey, Scripting Guy! How Can I Change My Desktop Monitor Resolution via Windows PowerShell?. While most of the post didn't really make sense to me, it led me to this this code listing, Set-ScreenResolution. I took the code and saved it as screenres.ps1 and tried to run it via PowerShell, but when I did, nothing happened.\nTurns out - the script was incomplete. It's basically (and this is my take on it) a function that is meant to be the top of a script file. The script needs to actually call the function before it will do anything.\nSo in other words, after saving his code and opening it up in my editor, I then added this to the bottom:\nSet-ScreenResolution -Width 1360 -Height 768\nI saved it as screenrespreso.ps1 and was good to go. I then edited the width and height for my normal resolution (3840x2160) and saved that as restorepreso.ps1.\nProbably obvious to anyone who has used PowerShell scripts before, but definitely confusing for me.\nAnd of course - you can run this from WSL. Just add -File to the command:\npowershell.exe -File &quot;c:\\users\\ray\\Desktop\\restorescreenres.ps1&quot;\nNote that you have to include the .exe at the end and the path is the &quot;real&quot; Windows path, not the WSL version of it under /mnt/c. I could make this easier with aliases of course.\n",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Note on CFLib",
		"date":"Thu Sep 21 2017 14:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1506002520,
		"url":"https://www.raymondcamden.com/2017/09/21/quick-note-on-cflib",
		"content":"I haven't blogged about ColdFusion, or CFLib in a while, but I wanted to give folks a quick update. I migrated CFLib to Node, and then static, years ago. Due to me not really working with ColdFusion anymore and a general slowdown in submissions, I thought it was best to convert it to a static site.\nMy intent wasn't to &quot;retire&quot; the site of course. While submissions slowed down, they still trickled in and folks found bugs from time to time. Unfortunately, when I setup the site to be static, the engine I chose (Harp) kind of limited how I designed the setup. The &quot;data&quot; for the site is one large JSON file. That includes all the code for each UDF escaped so it can be properly stored in JSON.\nAt the time I thought it wasn't a big deal. But it's made every update, tweak, etc a real pain in the rear. And as this site isn't my number one priority now, I've left some things slide a bit.\nEarlier this week I was thinking about this and did some quick tests with Jekyll, my favorite static site generator. I've found that I can build CFLib in it and it should be a lot easier to do edits and add new UDFs. I'll still probably rely on GitHub and PRs for stuff, but it will be a heck of a lot easier for folks to help out.\nSo - I don't have an ETA for this conversion. I've got a busy Fall coming up. But as things usually slow down after Thanksgiving, I'll most likely be able to get it out the door before Christmas.\nThank you for your patience. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "jamstack"
            
		]

	},

	{
		"title": "Run Visual Studio Code Insiders from WSL",
		"date":"Wed Sep 20 2017 03:19:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1505877540,
		"url":"https://www.raymondcamden.com/2017/09/19/run-visual-studio-code-insiders-from-wsl",
		"content":"A quick tip that is 100% thanks to the Visual Studio Code twitter account. For a while now I've been able to run Visual Studio Code from WSL (Windows Subsystem for Linux) by simply doing code . in the directory I'm working in. However, a few months ago I switched to the Insiders build. I was hesitant to do so as it seemed like it would be risky, but after one of Microsoft's engineers assured me it was safe I gave it a try.\nI think in the last few months I've seen one case where a bug was introduced that forced me back to the &quot;regular&quot; Code for a few days. Outside of that I've enjoyed getting the new features earlier and it is now my primary editor. (And to be clear, you can absolutely have both installed, and running, at the same time.)\nHowever, I noticed recently I wasn't able to run the Insiders editor from within WSL. I could still run code, but nothing worked for the Insiders build. After asking about this on Twitter, I got this:\nYou probably need to add the \\bin path to your .bash_rc or PATH&mdash; Visual Studio Code (@code) September 19, 2017\n\nDuh. I did a quick echo $PATH and saw that Code's path was available, but not Insiders. I hadn't even noticed it, but WSL automatically includes your Windows PATH setting into the Bash shell, so to fix this, I simply added it there. Another nice surprise (one I found a few months ago), is that Windows made editing your path a heck of a lot easier.\n\nSo - I added it - fired up a new Ubuntu shell - and bam, I could run code-insiders . and it worked right away. Note that if you do this under a Ubuntu directory, Insiders won't be able to view the files. But if you open it open /mnt/c (or lower) then Insiders correctly loads the proper Windows directory. I'll probably also make an alias so I can type fewer characters. I have a confession to make though. As much as I love using WSL I'm still very much new to Bash stuff.\n",
		"tags":[
	        
            "visual studio code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Microsoft's Edge Web Summit 2017",
		"date":"Sun Sep 17 2017 22:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1505687700,
		"url":"https://www.raymondcamden.com/2017/09/17/microsofts-edge-web-summit-2017",
		"content":"This past week I spent a few days in Seattle for my first Edge Web Summit. I was also invited to spend a day extra for some discussions with various teams at Microsoft involved with the web and engineering in general. I'd like to share some thoughts about the event and Microsoft's web efforts in general. If you are ignoring Edge as a browser, you are definitely making a mistake, and even outside of that, Microsoft's efforts here should be of interest to developers across the board.\nThe summit had 15 different sessions. Most - obviously were focused on Edge the browser, but also covered were ChakraCore and TypeScript. I don't cover TypeScript here a lot as I mainly only used it with Ionic 2, but I'm hoping to do more TypeScript in my non-mobile stuff going forward. While all the sessions were interesting in one way or another, some really stood out for me.\nMelanie Richards had an incredibly good talk on CSS Grid. I had the opportunity to use this for the first time about two weeks ago, and while I got things working relatively quickly, I wasn't exactly sure what was going on. Her talk really cleared things up for me and helped explain the features. I honestly don't do a lot with CSS, but this really feels like one of the best features I've seen yet. While it is not yet supported in Edge, it will be supported soon. You can see the status [here]https://developer.microsoft.com/en-us/microsoft-edge/platform/status/gridupdate/) and while there, make note of their entire web platform site providing status details, changelogs, and issues.\n\nThe other talk that I really enjoyed was Antón Molleda on Sonar. Sonar is a very cool linting tool for web sites and I'm in the process of adding a test setup for my own blog. (I'm not going to tell you how many issues it found, let's just say &quot;a bit more than one&quot;.) If you plan on trying out Sonar under WSL (&quot;Windows Subsystem for Linux&quot;), be sure to specify the jsdom connector.\n\nAll in all - some great sessions. Best of all - you can watch them now on Channel 9: Microsoft Web Edge Summit 2017.\nI'd also recommend the Web Payments and devtools sessions.\nOutside of pure technical knowledge, the one thing that struck me - again and again - was how deeply Microsoft seemed committed to responding to user feedback. In fact, one whole session was on how that process worked. I'm still primarily a a Chrome user, but I'm using Edge more and more to give it a fair shake. Microsoft also has strong plans for PWA support which is becoming more important for web developers. (I'll be giving my first talk on the topic at NCDevCon - wish me luck!)\nIn the past I've attended Google's Chrome Summits which are also really well done. I wasn't able to go this year but I can recommend them as well. I wish Apple had something similar. The best I can see related to this would be WWDC but looking at the videos from WWDC 2017 I only saw one session related to Webkit. (Oh, and don't bother trying to watch that video on anything but Edge or Safari.) Apple deserves credit for releasing developer builds of Safari but I still wish there were more active outside of their own conferences. I know they have an evangelist just for Safari but I'd love to actually see Apple at a web conference as much as I see Google and Microsoft.\nI definitely recommend making time to attend the summit next year. From a pure logistics perspective it was a well run event with the only real blip being no wifi (which wasn't intentional). Oh - and to be honest - I'd go to Seattle to attend an insurance conference. If you've never been, you need to visit. Outside of Louisiana, I think it's one of the most beautiful places I've seen. Oh, and it has Bigfoot souvenirs:\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Web Dev Tip Everyone Knows",
		"date":"Wed Sep 13 2017 15:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1505318340,
		"url":"https://www.raymondcamden.com/2017/09/13/quick-web-dev-tip-everyone-knows",
		"content":"This is something that's been on my mind to write up for a few weeks now. It is strongly in the &quot;I'm sure everyone knows this&quot; category, but I've always felt like the things everyone knows are precisely the kind of things that everyone doesn't know. This particular tip may be a bit too obvious, but here goes.\nImagine you're working on something involving forms. Let's say a basic Ajax-based search where you take user input and on a button click, you load in remote data to render on screen.\nThat isn't too difficult, but you may find yourself working a bit to get the layout just right, handle rendering the total number of results, and so forth.\nWhile working on this, your flow may be like this:\n\nFigure out what you need to fix, change.\nWrite some code.\nReload the page.\nType 'foo' and hit enter.\nLook at results and repeat.\n\nYou can automate that page reload thing if you want of course to make it a bit smoother, but you get the idea. Let's say that search field looks like so:\n\nOk, so here's my tip. You can save sometime by doing this:\n\nSo... right now some people are probably shaking their heads and wondering why I even bothered to blog this. But even today I'll find myself doing a &quot;write/reload/type/hit button/test&quot; cycle for a few minutes before I remember I can set a default to speed things up a bit. I'll normally include line breaks as I did above to remind myself to remove it later.\nI know there's browser extensions that can also pre-fill forms, but this is quicker and simpler to set up.\nSo there ya go. I hope this helps.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Very Cool Update for the OpenWhisk CLI",
		"date":"Mon Sep 11 2017 15:56:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1505145360,
		"url":"https://www.raymondcamden.com/2017/09/11/very-cool-update-for-the-wsk-cli",
		"content":"I was a bit behind on updating my WSK cli (see details on how to do that here) and was incredibly happy to see an important update. It may not be critical for everyone, but it was certainly important to me.\nFirst, you can browse commits to the CLI repo here - https://github.com/apache/incubator-openwhisk/commits/master/tools/cli - and I recommend checking it out as there are a few other things interesting there as well.\nThe change I'm most interested in is pull 2326 - support for name sorting. Why is this a big deal? The default sort for wsk action list is by the last time the action was updated. To me, this is a bad default. Well, not bad, but not one I'd use by default. Instead, I'm often trying to find the name of an action. I'll know what the name is in general, but perhaps not precisely.\nDid I call it randomComicBook or randomComic? Did I call it getTweets or getLatestTweets? By supporting the ability to sort by name, this becomes a lot easier for me now.\nCompre this:\n\nWith this (using wsk action list --name-sort):\n\nThis is nice enough that I'm going to make an alias like wska or some such so I can get to it quicker.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Another Vue Example - Image Recognition Service Tester",
		"date":"Tue Sep 05 2017 22:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1504648920,
		"url":"https://www.raymondcamden.com/2017/09/05/another-vue-example-image-recognition-service-tester",
		"content":"This weekend, I did some work updating my little Node-based image recognition service tester testing tool. The back-end is built in Node with a front end using vanilla JavaScript and Handlebars. I thought it would be interesting to see what it would be like to re-write the code in Vue. To be clear, nothing was broken so this was a completely arbitrary decision, but as I wanted an excuse to write some more Vue, I figured it was a good idea. As I've said though - keep in mind I'm still learning how to use Vue so what follows will probably not be &quot;ideal&quot; code.\nOld Version\nLet's start by quickly discussing the &quot;old&quot; version. I've got old in quotes there as this project is only a few months old. As I said, it was vanilla JavaScript and Handlebars, nothing too terribly complex. The front end HTML looked like so - with a lot cut out for space:\n\nIn the above code sample, you can see a &quot;main&quot; body template followed by a script tag used for the Google rendering template. I cut out the three other script blocks to save space as well as some of the Google one as well. But the basic &quot;form&quot; of the page was:\n\nI could have done that better. Handlebars lets you build your templates in their own files and then use the CLI to compile them into functions. This saves space both on the HTML and execution time of JavaScript as well as it can skip &quot;parsing&quot; your template. (You can view the entire version of the template here).\nOn the JavaScript side - it was mainly DOM manipulation. I'd upload the image, call the Node code, and then pass the data to the compiled Handlebars templates. There was a lot of &quot;hide this&quot;/&quot;show this&quot; going on, but Handlebars pretty much took the data as is. I did one small bit of manipulation for something with Microsoft's service, but that was the exception.\nHere's the source code for the original version:\n\nYou can see just how much is handling grabbing DOM elements and changing them.\nTo be clear, I'm not saying this is bad per se, and it wasn't difficult without jQuery at all. But let's consider the Vue version.\nThe New Vue\n(Can you guess how long I've been waiting to use that phrase?)\nI began by creating a JSON export of my Node code's results so I could test more quicker. I then began working on JavaScript. One of the first things I ran into was handling the form and the image preview. As a reminder - the code lets you select an image with a regular input/file field and then render a preview. Only after you upload does processing begin. I quickly discovered the v-model doesn't work with input/file fields as they are read only. This is what I ended up with.\n\nI've got a change handler on the file field to do the 'auto preview' thing and I've attached a submit handler to the form. The addition of .prevent means that Vue will handle preventing the default form submission thing for me. (Yet another reason I'm falling in love with Vue.) Here is the beginning of the JavaScript as well as the two handlers involved with the form.\n\nFor the most part, this is the same as before, except that I've attached the handlers to my Vue object. The actual rendering is done in another method, but as I don't call that method anywhere else, it should probably be folded into doForm, but I like the seperation a bit so I'm happy with it. Here's that method:\n\nSo in theory - I could have maybe just did something like - &quot;for x in data, this[x] = data.x&quot; - but the more specific checks felt... I don't know. Not better, but ok for now. If you remember, in my last update I made it easier to disable services so it's definitely possible .amazon or .ibm won't be there. And that's it for the JavaScript code. The entire file (found here) is now 69 lines, roughly half the lines of the previous version (136). Biggest thing missing are all (or most) the calls for DOM manipulation.\nThe front end HTML now has the templates &quot;inside&quot; the body as Vue is going to mark it up as is. Again I don't want to share the entire template as it is pretty long (although it did get smaller compared to the first version), so here is the same &quot;cut&quot; as before.\n\nThe full file may be found here.\nFor the most part, the conversion from Handlebars to Vue was straight forward. I screwed up a lot at first, but I was incredibly impressed by the error messages Vue spit out. In nearly every single case, it was obvious what I had done wrong and what I had to do to fix it. The only thing that threw me was an inline style:\n\nIt took me a bit to figure out that my CSS property had to go from background-color to backgroundColor. The error message told me I had invalid syntax, but I just couldn't figure out what was wrong with it.\nAll in all - I much prefer v-for over {{#each something}}. On the other hand, this form really wigs me out a bit:\n\nIt works - and I'll get used to it - but... yeah - wigs me out.\nAnyway - I hope this sample is helpful to others. The entire code base is up ",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Update to My Image Recognition Service Tester - Amazon Rekognition Support",
		"date":"Mon Sep 04 2017 20:22:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1504556520,
		"url":"https://www.raymondcamden.com/2017/09/04/update-to-my-image-recognition-service-tester-amazon-rekognition-support",
		"content":"A few months ago I wrote up a quick little demo to help me test multiple image recognition services at once (Testing Multiple Image Recognition Services at Once). This morning I was bored so I thought I'd quickly add a new test to the suite - Amazon Rekognition.\nAs before, I'm not trying to test out every aspect of the service, but rather hit the high points so it's easy to see - in comparison - how it handles the same input image as other services. Before I get into the Rekognition part, I did some other small tweaks ot my code as well.\nServices can now be disabled a bit easier by modifying this part of index.js:\n\nIt isn't necessarily rocket science, but if you comment out one of the above lines, the test for that service won't be run. The front end will correctly recognize this and simply not render the result. I had to do this as my Microsoft key timed out and I was too lazy to renew it. (Sorry Microsoft, I still love ya!)\nSo let's talk Rekognition. In general, the most difficult part of this API was setting up the right credentials. That's a common theme with me and AWS, but it feels like it's getting a bit easier now so I think in the future it will be simpler. The other issue I ran into was documentation. The links under the Rekognition Developers page leads you to the JavaScript SDK which is generic for all of AWS. I had trouble finding SDK docs specific for Rekognition until I came across the refernece link: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Rekognition.html. This is the link you want to bookmark.\nAnother thing to note is that the Rekognition service wants either an S3 path (no surprise there) or the actual bits of the image, up to 5 megs. Here's my complete Amazon tester module.\n\nAs I said above, I am not attempting to hit all aspects of the API. Instead I focused on 4 main parts:\n\nfinding general labels\nfinding faces\nfinding things that you may want to moderate (naughty bits)\nfinding celebrities\n\nI'm not going to share the 'render' code as it follows the same format as the others. (I do have some thoughts on the front end though - I'll share that at the end.) But let's consider some examples.\nFirst, consider the Captain:\n\nAnd here are the results:\n\nFaces\n\nNote, this report is not showing: BoundingBox, Landmarks (location), or Pose\n\nBrightness: 57.73588943481445\nSharpness: 99.98487854003906\n\nLandmarks found:\n\neyeLeft\neyeRight\nnose\nmouthLeft\nmouthRight\nLabels\n\nPeople (confidence: 99.27647399902344)\nPerson (confidence: 99.2764892578125)\nHuman (confidence: 99.27130126953125)\nArt (confidence: 54.227542877197266)\nChair (confidence: 50.68247985839844)\nFurniture (confidence: 50.68247985839844)\nFace (confidence: 50.529090881347656)\nSelfie (confidence: 50.529090881347656)\nModeration Labels\n\nNo moderation labels.\n\nCelebrities\n\nNote, this report includes uncecognized faces, but I believe it is the \nsame as the Face report so they will not be displayed below. Also, I'm \nhiding the same information (BoundingBox, etc) for celebs.\n\nName: Patrick Stewart\nwww.imdb.com/name/nm0001772\nBrightness: \nSharpness:\n\nLandmarks found:\n\nPretty good results if you ask me. I also tried this picture of him:\n\nAnd it still recognized him as Patrick Stewart. Be sure to note (as I say in the results) that I'm not displaying the specific face location data. That's definitely returned and would help you narrow in on the actual face image (as well as the 'landmarks').\nWhen using a picture of me (found here), it noticed I had a beard, but thought there was a 50% chance I had a cap or hat. It also didn't recognize me as a celebrity, which is technically correct unfortunately.\nGiven Sinistar...\n\nIt had some interesting results:\n\nLabels\n\nFlyer (confidence: 95.81148529052734)\nPoster (confidence: 95.81148529052734)\nLogo (confidence: 74.65271759033203)\nTrademark (confidence: 74.65271759033203)\nAmerican Flag (confidence: 73.34989166259766)\nEmblem (confidence: 73.34989166259766)\nFlag (confidence: 73.34989166259766)\nBrochure (confidence: 70.04080963134766)\nBadge (confidence: 69.28379821777344)\nGreeting Card (confidence: 63.669036865234375)\nMail (confidence: 63.669036865234375)\nArt (confidence: 61.1774787902832)\nModern Art (confidence: 61.1774787902832)\nText (confidence: 51.11092758178711)\nLabel (confidence: 50.73374938964844)\n\nI find the flag label interesting. It definitely has a flag-like aspect.\nAnyway - as before, you can find the source code up on GitHub: https://github.com/cfjedimaster/recogtester.\nOf course, the best news is that I think I can rewrite the front end in Vue - because that would be even more fun!\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Example of Sessions with Amazon Alexa Skills",
		"date":"Fri Sep 01 2017 16:42:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1504284120,
		"url":"https://www.raymondcamden.com/2017/09/01/an-example-of-sessions-with-amazon-alexa-skills",
		"content":"Just a quick post today. I worked on an Alexa skill a few weeks ago for a presentation that involved sessions. I had trouble finding a good example that I could wrap my head around, and while I don't think this code is very well done, I thought I'd at least share it in case others were having issues as well.\nSo first off, what are sessions in terms of Alexa? I've been working with sessions and server-side code for near twenty years so I just kind of assume everyone gets the idea, but basically it is a set of data associated with the current person and their current use of a system. That later part is crucial. If I have a conversation with an Alexa skill, I'm starting a session. If I stop talking and speak to it again later, it's another session. So this isn't data that persists forever, it's data just for the current conversation.\nSession data is passed as part of the request data to your skill. Here is how it looks:\n\nThe part you probably care about is attributes. Currently it is blank which represents an empty session. To set a value in the session, your response object will contain a sessionAttributes object with key value pairs. This is done at the top level of the response and at the same level as the response text. (I'll show a full example in a bit.) Basically like so:\n\nFinally, the other important part is to keep the session open. The response attribute of the response (yes, that sounds complex, but again, I'll show a full packet it in a bit) will simply set shouldEndSession to false when it wants to keep things open.\nSo - how about a demo? This isn't the one I built for my presentation (I'll share that later), but one I just whipped up for the blog post. The idea is to create a basic &quot;number guessing&quot; skill. The skill will generate a random number, let you guess, and also track how many times you tried to figure out the random number. I'll share the complete skill below, then call out various parts I think are important.\nI want to stress - I'm still figuring out the best way to work with Alexa. My code feels - awkward. It works, but I'm just not terribly happy with it. Also, I'm not using the verification bits just to keep things a bit slimmer.\n\nOk, so the first bits of code aren't really too important. Notice I attempt to wrap early if the user is asking the skill to stop or shut up. After that the real work begins. This block just sets some defaults for my response object.\n\nAll of this is pretty well documented on the Alexa side.\nNow consider this bit:\n\nAs the comment says, I'm copying the session data all at once. As I said above, you need to pass the session values back and forth with every request. Amazon doesn't store this for you. You may ask - doesn't this have a security implication for a game? Yes. I'm going to be storing the chosen number in the session and in theory - someone could sniff the bits (although it's over HTTPS so not sure how hackable that is). If you wanted, you could use the sessionId value Alexa passes and store the value in Redis or some other persistence system, but that seems like overkill.\nLooking at the &quot;start&quot; intent, you can see that setting my values is rather easy:\n\nAnd then I need to flag the session as staying open:\n\nMake note of the reprompt part - this is how Alexa &quot;waits&quot; for you to speak again. You won't need to say the skill name to &quot;wake&quot; Alexa, she will just wait.\n\nAnd then the guess intent is basically - compare what the user said to the store chosen number. Note the use of &quot;double equal&quot; instead of triple:\n\nThis was intentional as the value sent to the skill is cast to a string. I could have cast it myself, but, this felt ok.\nAll in all - not too difficult, and kind of cool that you can have these types of conversations with Alexa. Again, this was my first (ok, second) attempt, so don't take this as the &quot;best&quot;, but I hope this helps.\nI created a quick video showing it in action:\n\nAnd yes - I cheated. I looked at the OpenWhisk terminal output to see what my number was. I could have, and should have, added logic to say if you were too high or low. You'll notice I said the same answer at the end twice. When I said 34 the first time, Alexa heard 834. In general, her voice recognition is pretty darn good, but it isn't perfect.\n",
		"tags":[
	        
            "alexa"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a No Man's Sky Utility with Node and Vue.js",
		"date":"Thu Aug 31 2017 15:28:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1504193280,
		"url":"https://www.raymondcamden.com/2017/08/31/building-a-no-mans-sky-utility-with-node-and-vuejs",
		"content":"Nearly a year ago I blogged my review of &quot;No Man's Sky&quot;, a game that garnered near universal hate or love, with not many people in the middle. Since that time, the game has been updated a few times and it has become incredibly rich. I'm now on my second play and I see myself dedicating all of my game playing time on it for the next few months. (This is also the first game I bought for both console and PC.) For a good look at what's changed, I recommend Kotaku's article: &quot;No Man's Sky Is Good Now&quot;.\nOne of the biggest aspects of the game is crafting, and while that can sometimes be a chore, it can also be satisfying to build out your ship and base. I noticed that I had a hard time keeping track of what I needed to gather. What I mean is - consider that I want to build item X. It needs 50 iron and 50 platinum. That's easy enough to track, but consider item Y. It needs 50 iron and platinum too, but also a DooHicky. A DooHick needs 25 iron and 100 beers. (Ok, I'm kinda making this up as a go.) So the total needs for item Y are 75 iron, 50 platinum, and 100 beers. It gets more confusing when you add multiple items you need.\nI decided to attempt to build a tool that would let me say, given I want X and Y, just tell me the list of raw resources I need to build. I thought this would give me a chance to play with Vue.js again and actually create something I'd use. If you aren't interested in the tech and just want to check the tool out, you can find it here:\nhttps://cfjedimaster.github.io/nomanssky/client/index.html\nAlright, now let's dig into the tech!\nGetting the Data\nIn order to build my web app, I needed access to the data. Unfortunately, that data doesn't exist anywhere in raw form. If I wanted this information I was going to have to find a way to create it. The No Man's Sky wiki contains all of this information online in HTML format. It's maintained by the NMS community (passionate folks there) so it's always up to date. Turns out, the wiki software behind the site has an API. The API wasn't always terribly clear to me, but I was able to figure out a few things.\nFirst, I could ask the wiki for a list of pages for a category. I found four different categories on the site that included the data I needed. Here's an example of that API call:\nhttps://nomanssky.gamepedia.com/api.php?action=query&amp;list=categorymembers&amp;cmtitle=Category:Blueprints&amp;cmlimit=500&amp;format=json&amp;formatversion=2\nNext, I found out that you could get the &quot;raw&quot; data for a page by adding ?action=raw to a URL. So here is a page in regular form:\nhttps://nomanssky.gamepedia.com/Crafting\nAnd here is the raw form:\nhttps://nomanssky.gamepedia.com/Crafting?action=raw\nNotice the HTML is removed and it's a &quot;data-ish&quot; format suitable for parsing. When looking at a page for a blueprint, I saw this form was used to display crafting information:\n==Crafting==\n{{Craft|Carite Sheet,1;Platinum,15;Zinc,10|blueprint=yes}}\n{{Repair|Carite Sheet,1;Platinum,8;Zinc,5}}\n{{Dismantle|Carite Sheet,0;Platinum,7;Zinc,5}}\n\nThe first line there is for crafting, then you see repair costs and what you get if you dismantle.\nMy approach then was simple - get all the items that you can craft, and then suck down the &quot;raw&quot; form of the page and use a regex to get the data. I wrote my first script, getBuildable.js, to handle that.\n\nFor the most part, it's just &quot;suck down a bunch of crap and parse&quot;, not anything really special, and it mostly worked on my first few tries. To run it, I did: node getBuildable.js &gt; results.json and then simply removed the initial plain text value on top to have a valid JSON file. (Yeah, that's clunky, but I only ran it a few times.)\nThis gave me a JSON file of everything could be crafted and the parts necessary to do it. I thought I was done and moved on to the front end, but then I realized an issue. My goal was to generate a list of raw materials, therefore if a part of item X also has parts, I needed to recursively dig to get my list. I was going to do that on the front end, but then I thought - why do that every time? I decided to write another script that simply took the initial JSON and determined the &quot;raw resources&quot; necessary for every item.\nSadly, this took me like two hours. I hit a mental block with the recursion that I just struggled like hell to get past. My solution isn't even that nice, but at least I've got the &quot;ugly&quot; outside the web app and I can look at improving it later. Here is that script - don't laugh too loudly.\n\nSo on to the front end. This is the second app I've built with Vue, and I still freaking love it, although I ran into some frustrations I'll explain as I go through the code. The front end is pretty simple, just three columns for layout.\n\nThe Vue aspects here are pretty minimal, basically listing data. The idea is you click on a blueprint on the left side, this fills a &quot;shopping cart&quot; in the middle, and the right side ",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick OpenWhisk Utility - Activation Reporting",
		"date":"Tue Aug 29 2017 17:23:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1504027380,
		"url":"https://www.raymondcamden.com/2017/08/29/quick-openwhisk-utility-activation-reporting",
		"content":"I've already written a few things to help me get access to my reporting data (for example, see My Own OpenWhisk Stat Tool) and while debugging something today I ran into an issue with how the wsk CLI reports activations. Currently it just shows a name and ID. Consider this input: wsk activation list dotweet.\n\nTo get details about the activation, you then need to copy the ID and get details for just that particular activation. There is already an open enhancement request for making this report a bit deeper, but in the meantime, I've wrote a quick utility I could run to get more information. Here is an example of the output:\n\nAll I did was use the OpenWhisk npm package to fetch details and then add them to the result. I didn't build this as a &quot;real&quot; CLI, but I can run ./utils/activations.js for a default dump matching the wsk command line or supply a name and limit (currently you can't do a limit without a name) like so: ./utils/activation.js dotweet 50.\nThe code is rather simple:\n\nAnd if you want to use this yourself, you can grab the bits here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/util. I'm going to try to add more utilities under this folder as they come to mind. There are similar tools as well, like WITT, but unfortunately that isn't working for me right now. (And yep, I filed a bug report.)\nAnd as one more aside, I'm really tempted to build my own web UI like WITT just so I can play with Vue!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Dynamically Documenting OpenWhisk Packages",
		"date":"Fri Aug 25 2017 22:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1503700200,
		"url":"https://www.raymondcamden.com/2017/08/25/dynamically-documenting-openwhisk-packages",
		"content":"Earlier this week I was doing some work with the Cloudant package under OpenWhisk when I noticed the docs didn't include all the actions available. What I mean is, the docs currently mention the read and write actions, the changes trigger, and nothing else. Compare this to what you get when you run wsk package get /whisk.system/cloudant --summary:\n\nNormally this is the type of thing I'd trim, but I wanted to keep it all there so you can see the large set of other actions supported by the package as well. (There's already an open bug about this package.)\nOne of the cool features of OpenWhisk is that it supports annotations. You can apply metadata to actions, triggers, rules, and packages. That means it's possible to get information about packages, if the creator set up that metadata of course. Even without additional metadata, you can get information about what's inside a package and at least use that as a base to create documentation from. When annotations exist, you can then add them to the result for better output.\nWith that in mind, I built a little command line utility called &quot;packagedoc&quot;. Here is the script - it isn't necessarily a work of art:\n\nThe script makes use of the OpenWhisk npm package to integrate with the OpenWhisk system and fetch details on a package. Note you'll need to store your API key in an environment variable called __OW_API_KEY.\nAfter fetching the information, which is a large JSON packet, I do a bit of normalization to the data and then pass it to a Handlebars template for rendering. Basic usage looks like this:\nnode generate.js /whisk.system/cloudant output/cloudant.html\nHere is an example of the output: https://cfjedimaster.github.io/Serverless-Examples/packagedoc/samples/cloudant.html\nI used Bootstrap for the template and applied what I thought made sense in terms of display, ordering, etc. (For the life of me I'll never get why alpha sort seems to rarely be a default.)\nIf you want to check it out, you can get the bits from here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/packagedoc\nYou'll also see a samples folder with a few outputs from some of the OpenWhisk packages. What do you think - useful?\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Why I Hated (and Now Love) Arrow Functions",
		"date":"Fri Aug 25 2017 14:18:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1503670680,
		"url":"https://www.raymondcamden.com/2017/08/25/why-i-hated-and-now-love-arrow-functions",
		"content":"Earlier this week I gave a presentation (I'll share the links for that in a post later today) about the changing nature of JavaScript and as part of that, I quickly mentioned a few modern features that I really dig. One of them was arrow functions. A follower on Twitter asked if I could spend a bit more time on them so I thought I'd whip up a quick blog post. Let me start though with why I absolutely hated them.\nArrow functions have two main aspects. First, they are a new way of writing functions. Secondly, they handle &quot;This&quot; scope issues. The later is something I was fully behind. If you've ever seen that.x = this.x, then you'll recognize the issue. (And I'll share an example later.) The former though... filled me with deep and utter rage. Ok, I'm being a bit over the top, but at least to me, the syntax change in arrow functions was so radical that I had a mental block just trying to read them. It felt like one of those things developers do to be &quot;cute&quot;/&quot;cool&quot; without any real practical benefit.\nLet me share an example.\n\nIf you've never seen an arrow function before, that can be near incomprehensible. I'd see code using this form and my brain would come to an immediate stop while it attempted to &quot;rewrite&quot; it in the old school form. In order to really appreciate them, I had to start writing them, and every time I used one, I felt more and more comfortable with it and appreciated the terseness. I said earlier it felt like it was being cute for no real reason, but now it feels as natural as array shorthand (x = [1,2,]).\nTo understand arrow functions, let's consider an sample function and then rewrite it.\n\n(As an aside, I normally use template literals when building strings in JavaScript now, but I don't want to put too much new syntax in a sample at once.) The code sample above defines a function named hello that takes one argument and has one line of body code. Remember that a function can also be defined like so:\n\nOk, so the first thing you do when switching to an arrow function is dump the word function:\n\nNext, you add the arrow.\n\nAnd you're done. But arrow functions let you go even simpler. If you only have one argument, you can drop the parens:\n\nAnd if you only have one statement, you can remove the brackets:\n\nNotice I also removed the return. The execution of &quot;Hi, &quot;+name returns a string and a function will automatically return the last statement result.\nRemember though that the only required aspects of an arrow function are the removal of the word function and the addition of the arrow. If that last version looks too short to you, you can add the brackets back in. Also, maybe your function has one statement now but you're pretty confident it is going to grow. For example, you have a function that returns a boolean and for now you just want to return true.\nHere is an example of a function that takes two arguments - both before and after:\n \nHere is another example where the function is multiple lines:\n \nI'm also ignoring another modern feature, let, in the code above, because I don't want to put too much in front of you at once.\nArrow functions work really well, in my opinion, in call backs. The very first example I gave demonstrated this, but here it is again, along with the &quot;old&quot; version:\n\nIt isn't a huge change, but it feels more compact, and since callbacks like this are littered everywhere in most JavaScript files, the shorter syntax really feels like a boon.\nThe other benefit of arrow functions are how they correct issues with this. If you've ever written a call back and realized that this.something inside wasn't working correctly, this is something you'll appreciate. For an example, I'm going to &quot;borrow&quot; the one MDN uses (I'll be linking to it in a moment). Consider this code:\n\nIf you run this and inspect the value of p, you'll see that age never increases. That's because the this in the callback is it's own scope, not the one for the Person function. A common fix was to simply add that = this and refer to that.age. But that's smelly. Consider the arrow function version:\n\nBoom - just plain works. By the way, if the (() =&gt; { part confuses you, remember that the () portion is the argument list, which in this case is empty.\nSo yeah - I love these things now. I'll be honest - they still don't quite flow off the tongue (or off my fingers) as fast as &quot;old school&quot; functions, but they are getting more and more familiar to me. Alright, how about some resources?\n\nOnce again, MDN has the best documentation.\nNext, check out Dr. Axel's post: ECMAScript 6: arrow functions and method definitions. Note the age of that post - 2012. Yep, these may seem new, but they've been discussed for a while.\nFinally, read this great article by Eric Elliott - Familiarity Bias is Holding You Back: It’s Time to Embrace Arrow Functions\n\nI'd love to hear from my readers. Are you using this yet? If so, what do you think, and if not, why?\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Serverless iCal Parsing",
		"date":"Thu Aug 24 2017 16:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1503590400,
		"url":"https://www.raymondcamden.com/2017/08/24/serverless-ical-parsing",
		"content":"Today's post isn't necessarily too interesting code-wise, but it touches upon some greater, more broad, serverless topics that I'd like to bring up. A few weeks ago I discovered an interesting GitHub repository: https://github.com/gadael/icsdb.\nThis repository contains iCal files (think plain text calendar data) for non-working days for all 50 US states and various European countries. This could be useful in a number of ways, if, of course, you can parse the iCal data. I thought it might be interesting to build a simple service that would take a URL pointing to iCal data and return the information in JSON form.\nThere are multiple different ways of parsing iCal, but I felt ical.js by Mozilla was good enough. The ical.js library is pretty complex, letting you work with iCal data like a component, calling different methods to find data, but it can also just return a simple parsed version of the text. Here is the code I came up with:\n\nBasically - suck down the remote URL and parse using the library. As I said, there is a nice &quot;object based&quot; API that the library provides, but I found I could work with the initial data a bit easier. You can see where I just grab the third item in the array to get the actual events. I then &quot;flatten&quot; the data using flattenEvent. If your curious, the data is in a form called jCal, which has a specification: https://tools.ietf.org/html/draft-ietf-jcardcal-jcal-10. My flattenEvent function makes some assumptions that may not necessarily always work out well, but so far it's done ok.\nI tested it by asking for Louisiana holidays:\nwsk action invoke ical/get --param url\nhttps://raw.githubusercontent.com/gadael/icsdb/master/build/en-US/us-louisiana-nonworkingdays.ics\n-r\nAnd here are the top five results.\n\nSo... that's some data. Cool. What would I do next? When I first started thinking about this data, my first thought is that it could be a useful API for web apps. I'm a web developer so everything looks like a web-related source for me. Of course, why would I need serverless for that? I could run the iCal library in the browser - the only issue I'd run into is CORS - maybe. And I could always copy the iCal files to the same server as the app. But that's really limited thinking.\nBy creating this as a serverless action, I've opened it up to any event source, web or not. How about an example of something completely non-web based?\nImagine a support service where every day, my process gets a list of employees responsible for IT work in case of emergencies. I could imagine a process by which after getting a list of employees, I could check each one's home state against a list of holidays for that state and determine if that person is off today. It's the exact same code, but I'm simply using it in a different context.\nAnd this is something important to keep in mind here. Serverless isn't just about building NodeJS apps simpler. Instead I'm creating a resource that can be used in multiple situations, both direct (web app making a request) and indirect (a process run on a timed schedule).\nThis is something I've talked about before, but I feel like it's something that's possibly not quite as evident.\np.s. You can find the source code for this demo here: https://github.com/cfjedimaster/Serverless-Examples/blob/master/ical/get.js\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An OpenWhisk Package for Alexa Verification",
		"date":"Fri Aug 18 2017 13:39:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1503063540,
		"url":"https://www.raymondcamden.com/2017/08/18/an-openwhisk-package-for-alexa-verification",
		"content":"Edit on August 19 - I had an error in my code that broke Alexa sessions. I've updated the code below and in GitHub.\nYesterday I was working on a new Alexa skill (I really want the schwag Amazon is giving away this month for releasing a skill) and I had gotten to the point where I needed to lock down the service. I first talked about this back in March (Creating Alexa Skills with OpenWhisk - Part Two). Basically - Amazon requires you to secure your Alexa service and run a variety of checks to ensure the request is really coming from Alexa.\nLuckily there's a simple npm package for it (alexa-verifier) and using it with OpenWhisk is pretty trivial. Once you add the code I demonstrated in the blog post, you then simply ensure you've enabled &quot;raw&quot; support for the API. So instead of --web true you would use --web raw.\nBut as I was preparing to do this to the skill I wanted to release, I thought - why not see if I could simply put this logic in it's own action? Then a developer would simply need to use it as part of a sequence. So with that in mind, I quickly wrote the following.\n\nThis action takes in the request, digs out the relevant HTTP request information, and then runs the verification call.\nWhen done, it simply resolves the original request. That means your original code, the skill you were working on, testing, etc, can stay exactly the same, and that's freaking cool in my book.\nSo how do you use it?\nFirst, you have to make a new action, a sequence, that will combine my action with yours. Imagine your skill's action is nameCats. You could create a new, verified action like so:\nwsk action create --sequence alexa_namecats /rcamden@us.ibm.com_My Space/alexa/verifier,nameCats --web raw\nThe end result is an action called alexa_namecats, which isn't the best name, but meh, it works. Then you need the URL:\nwsk action get alexa_namecats --url\nTake that URL, add &quot;.json&quot; to the end, and update your Alexa skill URL:\n\nIf you do not want to use my shared package, you can grab the code yourself here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/alexa. As I think of more stuff that makes sense, I'll add to my Alexa package. (Or if you have ideas, gladly send me a pull request!)\n",
		"tags":[
	        
            "openwhisk",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Migrating from a Node App to Serverless",
		"date":"Mon Aug 14 2017 16:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1502727840,
		"url":"https://www.raymondcamden.com/2017/08/14/migrating-from-a-node-app-to-serverless",
		"content":"For a while now I've been thinking about how I would go about migrating a &quot;traditional&quot; Node application to a serverless one. All I've needed is a good example - and last week I found one. While going through the apps I had set up on Bluemix, I remembered that I had a Node server running to power my Twitter bot, https://twitter.com/randomcomicbook.\nI blogged about this project over a year ago (Building a Twitter bot to display random comic book covers) and while looking at the code again, I realized it would be a perfect candidate for rewriting using a serverless framework. Let's begin by reviewing the old application.\nVersion One - Traditional Node App\nI've already linked to the blog entry where I went into detail about the application, so I'll just cover the high points here. Let me start off by saying that this isn't necessarily the best Node app out there. Ok, honestly, it's probably pretty crappy. But it works - and I'm still learning - so I pretty much expect any code I look at that is a year old is going to have a few issues. You can find the entire code base on the Github repo, but let me share the main application file.\n\nThere's a few things to note here.\n\nFirst off, I still stuggle with &quot;how much code goes in my main app file versus includes&quot;, and you can see I've got a mismash of stuff here. I put the Marvel API logic in a module, but the Twitter stuff is not. Since this isn't a traditional web app and I don't have a lot of routes (more on that in a second), I'm kinda ok with it, but this could definitely be organized a bit nicer.\nI didn't even notice it till this week - but I'm using Express. I love Express. But the app has a grand total of one public route, and it's not even meant to be used - it's just a way for me to test. So I loaded an entire framework for no good reason. Hell I even set up a static directory that I never ended up using.\nAnd then the biggest thing to note here is - my code tweeted 4 times a day, but ran 24 hours a day. Cost wise that could have been a huge waste of money. (It really wasn't, but you get the idea.)\n\nVersion Two - Serverless Version\nIn designing my new version, I split up the job into the following actions.\n\nThe first action handles selecting a date.\nThe second action handles searching Marvel.\nThe third action simply selects the random comic.\nThe four action &quot;prepares&quot; the tweet.\nThe fifth and last action fires off the Tweet.\n\nLet's look at these components. I began with the date selection code.\n\nNothing too interesting here, but note the kinda cool logic to get the end of the month. If you use day 0 for a month, it really means day minus one. I found this trick on StackOverflow of course. The rest of the code is basically setting up parameters to use with the Marvel API. Speaking of - here is the action.\n\nThis is a new package I created specifically for the Marvel API. If you've read my blog for a while now you know I like to play around with comics, so I created a new package just for Marvel. Their API supports a lot of different end points and this just covers one, and I barely touched upon the supported arguments. But what's cool here is that I can now use this action in other applications in the future. You can too - I forgot to actually share the package, but just ask and I'll do so. (Yeah, that's a bit weird, but I'd like to know if anyone actually wants to use it before I make it public - and of course the code is up on Github.)\nAs a package I plan on making public, I created a bound copy of it with my Marvel credentials. This lets me use the action with no authentication required.\nThe next action handles selected a random comic book. (I named the file, &quot;selctCover&quot;, but technically it is selecting a comic. This bugs me, but not enough to rename the file.)\n\nI begin by filtering out comics without a thumbnail (or the default &quot;no picture available&quot;) and then just pick one by random. I also decided to remove a lot of extra data. I wrote this code last night, and looking at it now, that feels wrong to me. Yes, this action is specifically for this new application and yes, I know I don't need all that data, but I think I should have left the data as is. How about we pretend I didn't do that?\nThe next action then prepares information for the Tweet. Basically this is where I craft the text I want to use on each one. Here is an example of how a tweet looks:\n&quot;Avengers West Coast (1985) #56&quot; published March 1990https://t.co/vGzRiPQSzl pic.twitter.com/mU6y726Ep2&mdash; Random Comic Book (@randomcomicbook) August 14, 2017\n\nMy god - the neck on her is insane. Anyway, here is the code:\n\nFor the most part, I'm just digging into the comic data and finding the right values. Nothing special.\nAlright, so for the final part - I just need to send a Tweet. I built, and released, a Twitter package for OpenWhisk earlier this year: A Twitter Package for OpenWhisk. But at the time, I didn't support sending tweets. I add",
		"tags":[
	        
            "nodejs",
            
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Career Advice for a New Web Dev",
		"date":"Wed Aug 09 2017 17:38:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1502300280,
		"url":"https://www.raymondcamden.com/2017/08/09/career-advice-for-a-new-web-dev",
		"content":"So a few days ago, I sent out a simple tweet:\nMy DMs are open. I will help any women in tech (or really anyone who is marginalized) so far as I am able. Mentoring/counsel/whatever.&mdash; Raymond Camden (@raymondcamden) August 6, 2017\n\nThis was inspired by (and when I say &quot;inspired by&quot; I mean &quot;copied from&quot;) a tweet by Kent C. Dodds late last week.\nI've already gotten a few DMs and I'm trying my best to help out, but this one was an area I didn't have a lot of experience in and I thought I'd share it to see what people thought. It was definitely something I wanted to share publicly and in a place where I wasn't constrained by Tweet-size. Anyway, here is the question that was sent to me.\n\n\nI am a Master's graduate in Computer Science and working as a Web Developer at a startup. I currently live in REDACTED. My main area of interest is Web Development - mostly backend. I am looking for a job change and wanted to know how to prepare for interviews.\n\n\nI wanted some tips on resume writing (how I can improve my resume) and some ideas on personal projects I can work on.\n\n\nAlso, my main drawback is preparing for programming questions - the Data Structure and Algorithms ones. What is the best way to study for programming interview questions?\n\n\nThere's a couple of things here, so let me try to tackle them one by one, starting off with the ones I need the most help with from my readers.\nResume Writing\nSo yeah, I've only updated my resume a few times, and every time I've done it I've used a basic Microsoft Word template. The best advice I can give is one an old friend (Nathan Dintenfass) gave me - be sure you do not undersell yourself. I've run my resume by Nathan multiple times and every time he's had to push me to highlight things that he knew about me that I was completely minimizing.\nBut that's all I really have. Again, I'd love to hear some advice from my readers on this.\nPersonal Projects\nNow this is an area where I can definitely make some recommendations!\nFirst off, I'm a huge believer in creating applications that already exist. What do I mean by that? Whenever I learn a new language, I almost always build a blog. I'm not trying to build anything commercial or heck even anything I'm going to share. Instead, I'm practicing a language by building something that is already defined. I don't have to sit here and think of an idea. I already know what a blog does. This lets me focus on implementation rather than innovation (hopefully that makes sense). I can't speak for other hiring managers, but I'd gladly look at a code sample of something like that as part of your portfolio.\nWhile perhaps a bit harder to use as part of your portfolio, there are a lot of cool learning systems that have you practicing writing code. I wrote an article on this for Telerik: Tools to Learn JavaScript by Doing. My favorite of these is NodeSchool which has courses covering a variety of JavaScript topics. I'm not sure what exists like this for CSS or just basic HTML dev, but it's something to look out for.\nProgramming Questions\nSigh\nSo... yeah. I have &quot;feelings&quot; about this topic. I've pretty much decided I'll never work for a company that forces me to whiteboard code on the fly while someone watches. I failed the Google interview twice because of this and frankly I think it's bull shit. Somehow I don't believe my ability to help people learn is somehow tied to my ability to write a JavaScript Sudoku solver.\nBut...\nMy position in the industry is probably quite a bit farther along then the person who wrote me. (To be clear, I'm not saying I'm smarter, just that I've got a lot of history behind me. ;) The unfortunate truth is that you may not have much choice in this.\nEric Elliott has a great series of posts titled &quot;Master the JavaScript&quot; interview, and while (obviously) focused on JavaScript and not web-dev in general, I think it would be a great place to start. I don't see a way to link to that &quot;series&quot; on Medium, but start with this post and note the links to the other ones at the bottom: Master the JavaScript Interview: Soft Skills. You also want to check the rest of his posts as well. (Specifically, this one looks useful to: How to Land Your First Development Job in 5 Simple Steps)\nPlease Help!\nAs I said, this is not for me, but a woman in tech looking for help and while I was able to answer her a bit, I think more could be done. Readers - don't let me down!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick Example of Vue.js",
		"date":"Mon Aug 07 2017 14:36:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1502116560,
		"url":"https://www.raymondcamden.com/2017/08/07/quick-example-of-vuejs",
		"content":"Last week I was attending a conference and sat in a good session on Vue.js. I've seen Vue before, even attended another session, but I think I must have paid better attention to this one as I was really impressed with what I saw. In general, my go to JavaScript framework for building applications is Angular, and I really like it. (Despite the painful transition to Angular 2, no wait Angular 3, no wait 4, oh yeah it's just Angular now.) However, if I'm not building an &quot;app&quot; and just using a bit of JavaScript to embellish a page, it feels like overkill.\nI'm sure folks can (and do) use Angular in more limited ways, but for me, if I'm not building an entire application with it then it just doesn't feel like a proper fit. This is where Vue really seems to shine though. With Vue, I can do my &quot;non-app&quot; stuff in a much smaller way. You can build complete applications with Vue, but out of the box, it's very light weight. I can see Vue being much more appropriate for the typical thing I do with JavaScript - simple applications that have to work with the DOM and perform updates based on some form of logic.\nDon't take my word for it of course - I suggest going through the guide and play with the code there. I'm not trying to write a &quot;How To&quot; guide here since one already exists. What I do want to share though is a quick sample I wrote at the airport this weekend. You should absolutely not take this as a &quot;Best Practice&quot; example. Rather, I just wanted to write a simple application and then rewrite it in Vue.\nOk, so here is the initial, non-Vue code.\n\nThe code here is adding an event listener to an input field. On entering text, it performs a search against an OpenWhisk serverless API that simply returns names that match the input. When the result is returned, I then update the DOM with the result.\nYou can run this sample live right here: https://static.raymondcamden.com/demos/2017/8/7/search1.html\nNote that I'm using the Fetch API which is not supported in older browsers. Anything modern though will run it just fine.\nOk, now let's look at the Vue version (and once again, I'm not saying this is the best, or even the &quot;right&quot; implementation):\n\nOk, let's look at the changes. First off, I've created a new Vue app to handle my logic. I love how... I don't know... &quot;compact&quot; this is. I've basically set up the logic for my &quot;as you type/search/render&quot; thing and it's nicely contained on my site. (Of course, normally I'd have this in it's own JS file.)\nNext - look at how I assign the event listener to my search field. Using event handlers in HTML is something we moved away from years ago, but the way it is done here feels very nice. I like being able to see my control and note, &quot;Oh there is an event happening when 'input' is fired.&quot; Also note the use of v-model to handle a sync between the value of the field and data in my code.\nNow - look at searchNames. Outside of how I address stuff, this is pretty similar, but, I love that when I'm done, I simply update my local array, and then the rendering takes over.\nI love client-side templating (so much so that I wrote a book on it) and having it built-in here works really well.\nFinally - the last issue I had to clear up with the &quot;FOUC&quot; (flash of unstyled content) on load, which is done using a v-clock CSS declaration.\nAll in all, I think this is roughly the same amount of code, but it felt a heck of a lot easier to use and I especially liked being able to skip all the querySelector and DOM-writing tasks.\nYeah, I keep saying it &quot;feels&quot; good, which isn't very scientific, but if a framework is enjoyable to write, I'm probably going to use the hell out of it. On the flip side, I just don't feel that way about React, and I know React is an incredibly powerful framework, and the hottest thing on the planet now, but I just don't enjoy using it.\nAnyway, you can test the Vue version here: https://static.raymondcamden.com/demos/2017/8/7/search2.html\nIn both cases, try searching for &quot;abe&quot; to see some results. I plan on spending more time learning Vue and blogging on it, and maybe even giving a presentation on it too later this year. I'd love to hear my readers who are using Vue and what they think about it. I'd also love to hear from folks who looked at it and didn't like it. Let me know in the comments below!\n",
		"tags":[
	        
            "javascript",
            
            "vuejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with the Forwarder Action in OpenWhisk",
		"date":"Sat Aug 05 2017 13:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501938180,
		"url":"https://www.raymondcamden.com/2017/08/05/working-with-the-forwarder-action-in-openwhisk",
		"content":"One of the issues you run into when working with sequences in OpenWhisk is handling the flow of data from one action to another, especially when integrating actions from other packages where you have no control over the code. In the past, I've discussed how you can use &quot;intermediary&quot; actions to handle transforming the output of an earlier action into an appropriate form for an upcoming action.\nA recent StackOverflow question brought up another situation. Imagine a sequence where you've got a set of input arguments. The situation described in the SO post is roughly this: Given an email address, you want to see if it exists in Cloudant and if not, insert it. As part of his sequence, he runs an action that does a query for the email address. However, that action only outputs a result set. His original input, the email address, is &quot;lost&quot; because this action only outputs a result set.\nWhat he needs is the ability to say: &quot;Run this second action in the sequence and can you please forward along these arguments you don't need till later?&quot; Luckily, there's an default OpenWhisk action for that, /whisk.system/combinators/forwarder. The combinators aren't really documented right now but you can use the CLI to look at the API:\n\nThat may be a bit too obtuse. If you drop the --summary, you get more detail in JSON format. I'll rewrite it here in English format. First, here is a bit more information about the parameters:\n\n$actionName - Name of action to run to compute condition. Must return error to indicate false predicate.\n$actionArgs - Array of parameters names from input arguments to pass to action.\n$forward - Array of parameters names from input arguments to merge with result of action.\n\nAnd then there is a sample that describes how to use it with the default echo action. I've taken that and converted into a real CLI call:\n\nThat's a bit complete, so let's break it down bit by bit, starting with the parameters.\n\nFor $actionName, I'm telling forwarder what I want it to run.\nFor $actionArgs, I'm supplying a list of arguments that will be passed to the action.\nFor $forward, I'm supplying a list of arguments that will be passed to the next action, or just the output in general.\nFinally, everything after those arguments are my &quot;real&quot; arguments to my logic. In this case, x and y.\n\nSo in English: I want to run the echo command and pass X to it. When done, take the output of echo and merge it with Y, another argument, to give me a final result. Using the sample above, the output is:\n\nTo test this myself, and within a sequence, I built two actions. The first is doubler, it takes in a number and multiplies it by two.\n\nAnd then I built a second action called &quot;something&quot; (I was struggling to come up with a nice name) that is basically the same as echo:\n\nBoth of these were pushed up to OpenWhisk. I then created a sequence - but instead of making it with doubler+something, I made it with forwarder+something:\n\nBasically forwarder is going to replace doubler. To call this sequence, I built a JSON file as I didn't want to supply all the parameters at the CLI. In Windows Bash, $foo tends to break stuff (I escaped it above, but yesterday when I was researching I had forgotten how to do it). Here are my parameters:\n\nI'm saying - I want forwarder to run my doubler and pass in the number argument. When done, I want the result of doubler and I want parameters a and b to be merged. I ran the test like so:\n\nAnd the result was:\n\nI don't know about you, but I had a heck of a time understanding this until I got it working, so hopefully this will help others. I'll also point out that in the specific use case described by the user, I probably would have done things differently. Instead of using the pre-built Cloudant actions, I'm thinking I would have used the npm package for it myself and built my own. It would have been a bit more work perhaps, but maybe a bit easier to. I could see one action for &quot;insert x if it is new or throw an error&quot; versus 2+ actions doing the same thing. Remember, OpenWhisk is a pretty fluid system and there is usually different ways to solve the same problem.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Working with Action Metadata in OpenWhisk",
		"date":"Fri Aug 04 2017 16:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501865100,
		"url":"https://www.raymondcamden.com/2017/08/04/working-with-action-metadata-in-openwhisk",
		"content":"Yesterday I was giving a presentation at KCDC and one of the attendees asked a great question:\n\nCan an action know if it is being executed within a sequence?\n\nNow, immediately I said that doing this was a bad idea. The whole point of serverless functions is for them to do one thing only in a stateless manner. If you find yourself writing code that cares whether or not it's being executed in a sequence than you are probably doing something wrong.\nThat being said... I thought it was an interesting question anyway. And yes, I can say that you should &quot;Never do X&quot;, but I don't want to say that there may never be a time when we have to do X, and maybe learning how to do it can help us learn more about OpenWhisk in general, right?\nLet's begin with the docs. Under the section on creating actions, there is this: Accessing action metadata within the action body. It details that you have access to various environment variables for the currently executing action. They are:\n\n__OW_API_HOST\n__OW_API_KEY\n__OW_NAMESPACE\n__OW_ACTION_NAME (ding ding, we have a winner!)\n__OW_ACTIVATION_ID\n__OW_DEADLINE\n\nIf any of these don't make sense based on the name itself, just check the docs. Accessing those environment variables will change depending on the language you are using for your action. Here is a simple JavaScript action that echoes them all back.\n\nAnd here is an example of the output:\n\nOk, so, the first thing I thought was - would the action name change if an action was being run in a sequence? Remember that a sequence is also an action. You create it by passing a flag and telling it the list of actions to run, but when you are done, you invoke it as any other action. I made a new action, let's call it meta2, and I placed it in a sequence called metaSeq. When running either action, the name inside meta2 was still meta2.\nThat's expected I think, and desired, so that's good.\nOn a whim though - I tried something else. I looked at the activation IDs being used. And here I saw something interesting. When I looked at __OW_ACTIVATION_ID inside the action (and by look I mean console.log) and then fetched it via the CLI, I saw this in the sequence call:\n\nNote the &quot;causedBy&quot; key. This is - obviously - not there when I run meta2 outside the sequence. So I tried actually fetching it in my code:\n\nUnfortunately, this runs into a chicken/egg problem. At the time this code runs, even though I have an activation ID, I can't actually fetch it yet via the API (the Node module is simply using the REST API). Maybe if I added a slight delay of a second or so it would work, but now we're going too far down the rabbit hole to even be slightly sensible. (And yes, I did try waiting, because why not, and nope, it doesn't work, the action has to be complete before you can fetch the activation from it itself.)\nSo despite this being rooted in something I wouldn't do in the first place, I hope this was useful in terms of showing the kind of information an action can know about itself. Leave me a comment below if you've got any suggestions or questions!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Serverless BASIC",
		"date":"Tue Aug 01 2017 16:40:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501605600,
		"url":"https://www.raymondcamden.com/2017/08/01/serverless-basic",
		"content":"I tend to tease myself a bit about the &quot;useless demos&quot; I like to build, but almost consistently I end up learning something new. It may not be an earth shattering realization of something incredibly deep, but generally, if I learn something, and if I can share it, I consider it a win. Case in point - running BASIC programs in a serverless environment.\nI have quite the soft spot in my heart for BASIC. I learned to code with Applesoft BASIC on a 2e (or 2+, not sure now) and I can still remember the joy of getting my first program to run. (After an incredibly stupid error that happened because I didn't read the docs. Thankfully that never occurred again.) I recently came across a great little on line BASIC interpreter at http://calormen.com/jsbasic/ and when I noticed it was open source, I thought it would be cool to get this up and running in OpenWhisk.\nNow - let me be clear. This is a bad idea for (at least) three reasons.\n\nThe code is already 100% client-side. If my use-case is a client-side application, then putting it on OpenWhisk doesn't gain me anything. In fact, it slows things down as my code would have to make a HTTP call to the server to run the code.\nBASIC is an interactive language. It can prompt you for input which doesn't necessarily make sense in a \"run and return the output\" context.\nFinally, Applesoft BASIC in particular has a graphics mode. (Two actually.) In theory I could setup OpenWhisk to return images (and it would be fun to get that working), it doesn't necessarily make sense for my demo.\n\nOf course, why should I let that stop me? I began by working on the action code. I ran into a problem right away as the documentation for the library is a bit lacking. But when I filed a bug report on it I got a response very quickly. The biggest issue is that I have to tell the library what to do on input and output. For input, I do nothing (I'm just not going to support program input) and for output, I just store it up into a string. Here is the action I built:\n\nBasically I initialize the code with a string input (the BASIC code) and then &quot;run&quot; the program via the driver function until it is complete. This will totally fail if you write code expecting input, or if you use graphics modes, but it lets basic stuff work just fine. (Wow, I'm typing &quot;basic&quot; a lot.) Now let's look at the front end. I wrote it all in one quick file so forgive the mix of HTML, CSS, and JS.\n\nIt's a relatively simple web page. I use a text area for input (with some sample code in there already), a button to run it, and a div to display the output. Here's where I ran into two things that tripped me up.\nTo send data to my action, I wanted to use a POST instead of a GET. With the Fetch() API, this isn't too hard, but all the demos I saw used a FormData object. Doing this sends the data as a multipart form. From what I can tell, this is not support by OpenWhisk. To be clear, OpenWhisk ran just fine on this request, but it didn't take the form fields and automatically turn them into arguments. I could have handled that myself, but I wanted to keep the code as is.\nIn order to send a urlencoded fetch call, I first tried just adding the header you see above. But apparently - if you send a FormData() object, that will override the urlencoded value and keep it as a multipart post instead. So I had to manually urlencode my form post. Since it was just one value though it wasn't too hard. I'm still new at Fetch so if I missed something obvious, let me know.\nIf you want to run this yourself, you can do so here: https://cfjedimaster.github.io/Serverless-Examples/basic/test.html\nAnd yes, you can write an infinite loop. I can remember doing that on machines at Sears back in the old days. (Never anything naughty of course.) OpenWhisk will automatically kill the process after 60 seconds so I'm not too concerned about you doing that, but, please, don't. ;)\nOh, and the code for the client and action may be found here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/basic\nEnjoy!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Geolocation Emulation in Chrome (and others)",
		"date":"Mon Jul 31 2017 22:47:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501541220,
		"url":"https://www.raymondcamden.com/2017/07/31/geolocation-emulation-in-chrome-and-others",
		"content":"This isn't necessarily new, but as I ran into it recently I thought I'd share as it was pretty cool. Last week I blogged about a demo I had built (&quot;Serverless for Vampires&quot;, you read it, right?) and as part of that demo I had some simple front-end code making use of the Geolocation API. It's a simple enough API, but I specifically needed to test with a few different locations.\nI knew that Chrome supported emulating a location, but I was pleasantly surprised to see some nice updates to the tooling. First off, how do you actually find the feature?\nIf you open up Dev Tools, you probably won't see it:\n\nClick the three dots in the upper right hand corner to open the menu, then &quot;More tools&quot;, and then &quot;Sensors&quot;:\n\nThis will pop up a... I don't know... &quot;sub panel&quot; of the main dev tools where you can find emulation tools for geolocation, orientation, and touch.\n\nOk, so far so good. But what surprised me is what I saw in the dropdown:\n\nWhile I'm not sure why they don't have the sprawling metropolis of Lafayette, LA, this is a dang handy little feature and incredibly useful. So plus one for Chrome. How about the others?\n\nMicrosoft Edge has emulation for a particular device, UA strings and browser profiles, orientation, resolution, and geolocation. No handy &quot;city dropdown&quot; but you can emulate at least.\nFirefox has nothing like this... outside of orientation support when in responsive mode. Oh - and touch emulation too.\nSafari has nothing. Ok, just joking, let me go to my Mac and check.... ok as far as I can see - no support. I also checked the Technology Preview version.\n\nAnyway - I try to check my devtools for new stuff every now and then, and Chrome is actually being a lot more proactive about sharing new updates. You may not know this, but also under the &quot;three dots&quot;/&quot;More tools&quot; menu is a &quot;What's New&quot; screen. It seems kinda odd to put it under More Tools, but I'm just happy they added it. (I've long had a beef with how Chrome documents their updates, and while this isn't as good as what Firefox does, it is a move in the right direction.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Online Presentation - Developing in JavaScript in 2017",
		"date":"Mon Jul 31 2017 14:48:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501512480,
		"url":"https://www.raymondcamden.com/2017/07/31/online-presentation-developing-in-javascript-in-2017",
		"content":"Late next month I'll be giving an online, and free presentation on JavaScript and how it has changed (and is changing) over the past few years. Basically - if you've been happily writing JavaScript and not been paying attention to some of the rapid changes recently, you may be feeling a bit behind. I'll try my best to explain how things are changing, what ES6/ES7/ES8/ES2017/etc means (in a practical sense), and how to start learning and employing these new features in your day to day code. As I think my readers know, I am certainly not a JavaScript Ninja. So this will be very much a &quot;How I see it&quot; style presentation focused on what I've found to be useful and where I turn to for help.\nYou can register right now: http://certifiedfreshevents.com/events/javascript-2017/\nI'm actually part of a full hour presentation where TJ VanToll will be following me with a discussion on TypeScript.\nIf this presentation doesn't seem up your alley, then check out Certified Fresh Events in general - there's already more planned. This new site is run by a good buddy of mine, Brian Rinaldi, and I think you'll see many more cool events coming in the future.\n",
		"tags":[
	        
            "development"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Some Notes on Windows 10 and Ubuntu",
		"date":"Fri Jul 28 2017 15:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501254120,
		"url":"https://www.raymondcamden.com/2017/07/28/some-notes-on-windows-10-and-ubuntu",
		"content":"I've got a few notes to share about Ubuntu on Windows. This will be a bit random, but hopefully it is helpful. First off - why am I writing about this now? As of a few weeks ago, Ubuntu is now available in the Windows Store, if, and only if, you are using the Insiders Build. I'm using it on my laptop, but not my desktop. However, what I ran into will apply to anyone who is currently using Bash and wants to upgrade to the &quot;real&quot; release. Ok, ready?\nFirst and foremost, you want to read the blog post by Scott Hanselman: Ubuntu now in the Windows Store: Updates to Linux on Windows 10 and Important Tips. If you've already installed Bash, scroll down to: TODOS IF YOU HAVE WSL AND BASH FROM EARLIER BETAS. His tips worked just fine for me. Note that when you copy your home directory to the desktop, it may take a while. Be sure to clean any trash you may have beforehand. Even with me doing that it took about five minutes for everything to copy.\nOk, so here's what threw me a bit. First - how do you use the new Ubuntu app? You can either run it from the Start menu, or just type unbuntu in a regular command prompt.\nHow do you use it with Hyper? Previously I had configured Hyper to use c:\\windows\\system32\\bash.exe. But I had a heck of a time actually finding ubuntu.exe. You can find it in a special &quot;WindowsStore&quot; folder, but when I tried that path, Hyper would fail on startup. Again, Scott Hanselman helped me out. Configure Hyper.js like so:\n\nTo configure Visual Studio Code's integrated terminal, you can just use ubuntu.exe:\n\nAnd at that point, you're done, except for everything you had installed of course. For me, this was mainly Node and Node-related items. I used NVM (this guide helped) to get Node installed.\nThat's basically it. I still have the &quot;old Bash&quot; around and I'm going to keep it there until I'm sure everything is running ok.\nAs an aside - I've found Bash on Windows to be excellent. My only real complaint is that heavy file operations, like npm installs or Git calls on large projects, seem to be slower when compared to OSX. This seems to be more of an issue for installing big CLIs versus installs I'll do for a Node project. It isn't enough to really bug me, but I've definitely noticed it. I raised this when speaking to an engineer at MS Build, so hopefully it's something that can be improved in the future, but it certainly isn't enough to stop me from using it.\n",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Serverless for Vampires",
		"date":"Thu Jul 27 2017 20:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1501187040,
		"url":"https://www.raymondcamden.com/2017/07/27/serverless-for-vampires",
		"content":"Ok, as a quick warning, this is a pretty stupid demo. I mean, I know that's my thing - I'm the person who makes demos with cats. But this really pushes it a bit too far. I was bored a week or so ago and found an interesting API, the Sunrise Sunset API. This is a free API that returns sunrise and sunset times for a specific location. So obviously I saw that and thought it would be the perfect kind of service for vampires.\nI mean - imagine you're nice and comfy in your incredibly gothic tomb, you've woken up from a nice nap, and aren't really sure what time it is.\n\nPro Tip: Don't google for 'vampire crypt' at work. Source\nOr perhaps you know the time, but aren't sure if the sun has risen yet. This is where the API comes in hand. Working with the API is pretty simple - for basic options you simply pass in a longitude and latitude. I began by building this OpenWhisk action:\n\nFrom top to bottom - I do a bit of basic parameter validation and then just pass off a request to the API. Once I've got the result, I compare the current time to the sunrise and sunset times for the location. If the current time is after before sunrise or after sunset then I return true. I named this action isVampireSafe, so true means yes, it is perfectly for vampires to come out, have a good time, etc.\nI put this up on OpenWhisk and enabled it as a web action and with that, I was completely done with my vampire-safety API. This basically took me ten minutes or so and I've already done a public service for monsters everywhere.\nOn the client side, I wrote up a quick application with a bit of minimal HTML and JavaScript. Here's the HTML:\n\nAs you can see, just a simple H1 label and an empty div. Forgive me for the inline style. And here is the JavaScript:\n\nThis just boils down to a quick Geolocation call and then a Fetch off to my API. Once I get my results I update the HTML. Here's a screen shot of it in action (note, it is currently 1:49PM my time and about 200 degrees).\n\nOf course, if I wanted to get really precise, I could do a weather check as well and allow vampires to exit if it is cloudy/stormy outside, but that would be a silly waste of time of course.\nWant to try it yourself? You can visit the online location here: https://cfjedimaster.github.io/Serverless-Examples/vampire/client.html\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Using Postman with OpenWhisk",
		"date":"Mon Jul 24 2017 17:33:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1500917580,
		"url":"https://www.raymondcamden.com/2017/07/24/using-postman-with-openwhisk",
		"content":"For a while now I've been a huge fan of Postman. If you've never heard of it, it's an incredibly powerful tool for working with APIs. I know a lot of folks like to use Curl at the command line for doing HTTP calls, but I much prefer a visual tool instead. Plus, Postman makes it much easier to save and organize API tests for use later. It's free, supported everywhere, and I strongly recommend it. I've been using Postman lately with OpenWhisk and I thought it would be useful to share a basic explanation on how you could use them together. To be clear, there is nothing &quot;special&quot; about OpenWhisk and it's APIs. If you already know Postman well, then you don't have to do anything special to use it, but for folks who may be new to Postman, I thought a quick overview would be useful. Alright, ready? Let's go!\nFirst and foremost, you want to begin by installing the Postman client.\n\nGrab the bits for your operating system, install, run, and you'll be ready to go. Remember that this is an actual application. I'm on Windows but always grab the Linux bits for command line stuff since I'm using WSL (Windows Subsystem for Linux). In this case, though, I'd want the Windows client.\nMy focus on this post will be related around using Postman for OpenWhisk, so if you want to learn everything about the tool, you should check out the docs. The first thing I'd suggest though is creating a &quot;Collection&quot; for your tests. A collection is like a folder, and generally you should use one per project, or set of tests. For this blog entry I made a new one, &quot;OpenWhisk+Postman&quot;.\n\nAlright - so let's build a quick a OpenWhisk action. This one is trivially simple, but it serves our purposes for testing.\n\nAs you can see, this will simply echo back &quot;Hello, X&quot; where X is a name. By default it will be &quot;Nameless&quot;, but you can pass a parameter called name to change this. I stored this on OpenWhisk as the action name, postmantest.\n\nRemember that there are three main ways of calling your OpenWhisk actions via HTTP:\n\nVia the authenticated REST API - this is what the CLI uses.\nVia a Web Action\nVia a Managed API\n\nLet's cover these one by one.\nAuthenticated REST API\nUsing the REST API does not require any special modifications to your actions. In order to call the API via Postman, we'll need two things - the URL (of course) and the authorization information. Luckily this is pretty easy to figure out. The URL to invoke your action will - generally - look like so:\nhttps://openwhisk.ng.bluemix.net/api/v1/namespaces/_/actions/postmantest\nThe underscore there represents the &quot;default&quot; package. If I had put my action in the &quot;foo&quot; package, you would see foo/actions instead. But if you forget this URL 'pattern', there is a quick way to find it. When using the CLI to work with OpenWhisk, you can always add the -v flag to enable verbose mode. This will share a lot of additional information, including the URL and authorization headers passed. Here's an example:\n\nThere's a few things to notice here. First off, notice that the URL has two additional arguments, blocking=true and result=true. These are critical to ensure you actually get a result from the call and not just an activation ID. The result one though you may want to edit, especially if you want to keep see the metadata of your call (for example, how long it took to run).\nThe next thing to note is the authorization header. The authenticated REST API is - well - authenticated. That should be obvious. You can handle that authentication in one of two ways via Postman. You can either set a header called Authorization and pass the string exactly as you see in the command line output. Or you can tell Postman to use Basic authentication and supply the username and password. This is not the username and password for Bluemix. Rather, this is information that the OpenWhisk system sets up for you automatically and that you setup with the CLI. This is how the CLI is able to use your credentials to work on your actions. In theory you could use either with Postman, but it &quot;feels&quot; more proper to use the username and password values instead. So how do you get them? Just run wsk property get:\n\nIn the screen shot above, you can see where the information is displayed. The username is the ugly set of random digits before the colon and the password is everything after it. Now we have the bits be we need to add this to Postman.\nYou should have an empty tab available where you can add in the information required to call the API. First, copy the URL and change the method from GET to POST. Then in the &quot;Authorization&quot; tab, change the type to Basic Auth. Copy and paste the values from the CLI call above for your username and password.\n\nFinally, click &quot;Save&quot;. To be clear, you do not have to save Postman items. Postman will remember them even if you close the app, but if you plan on using the API again, it makes sense to do so. You can also ",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Great Reminder of Persistence in Serverless",
		"date":"Tue Jul 18 2017 16:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1500394260,
		"url":"https://www.raymondcamden.com/2017/07/18/great-reminder-of-persistence-in-serverless",
		"content":"Today I ran into a great bug. By &quot;great&quot; I mean it totally confused me but as soon as I realized what I was doing wrong, I knew it would be a great thing to blog about as a reminder to others. I don't think this applies just to OpenWhisk, but to serverless in general.\nOk, so what did I do? I was working on a sample action that simply returned an array of cats. As an added feature, you can filter the cats by passing an argument. Here's the code:\n\nPretty simple, right? Either return cats as is, or filter based on a quick indexOf check. (On reflection, I should probably lcase the filter and the cat name to make the filter a bit easier to use, but whatevs.)\nSo I fired this up on OpenWhisk, web-enabled it, and ran a quick test. First, I hit:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/arraybug.json\nAnd I got:\n\nWoot. Ok, now filter it like so:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/arraybug.json?filter=Lu\nAnd the result is perfect:\n\nNow let's try a filter returning nothing:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/arraybug.json?filter=x\n\n\nAlright, so final test, let's return everything again with the original URL:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/arraybug.json\n\nUm. Err. What the heck, right? So I added a console.log. Reran my code and it was fixed.\nWoot!\nAnd then I filtered again.\nAnd it broke again.\nWhatever is the opposite of woot!\nAnd then this is where I remember a blog post I did back in February: Serverless and Persistence. Specifically this part:\nFirst off - when your action is fired up, OpenWhisk does not kill it immediately when done. Rather, it keeps the container used for your code around to see if the action will be fired again soon. \nAnd then I looked back at my code, specifically the filter. Realization dawned.\n\nIn case it wasn't obvious the issue was two fold:\n\nOpenWhisk was keeping my code 'warm', in other words, up and ready for repeated calls.\nMy code was modifying, and destroying, the original array when a filter was used.\n\nIncredibly obvious when you think about it. My fix was to just make a copy of array (and I set the original to a const for good measure).\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Creating a Serverless Meetup API Wrapper",
		"date":"Mon Jul 17 2017 17:58:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1500314280,
		"url":"https://www.raymondcamden.com/2017/07/17/creating-a-serverless-meetup-api-wrapper",
		"content":"Yesterday I was doing some research into serverless meetups when I encountered something that bugged me about the Meetup.com web site. Specifically, this:\n\nI couldn't search for meetups until I logged in. Now, don't get me wrong, I've got a Meetup.com login, and I could have logged in in about 2 seconds, but this really bugged me. (As an aside, you can just go to https://www.meetup.com/find/events/ to skip being forced to log in.)\nEven after logging in, I didn't like that I had to explicitly tell the form to not care about distance in order to find groups across the country:\n\nIn this case, their defaults I think make sense. As an evangelist, I'm looking for meetups I can speak at (hey, by the way, I'd love to speak at yours!), so my use case is not the norm. But I was also a bit bugged by the fact that I couldn't limit my searches to America.\nTherefore - I decided to do what any good developer would do - spend 10x as much time writing my own solution versus just dealing with what was given to me!\nI knew Meetup had an API and I blogged on it a few years ago (Using the Meetup API in Client-Side Applications), so I figured this would be a great example of how I could use serverless, and OpenWhisk in particular, to build my own API wrapper around their data to build my own tool.\nFor my wrapper, I decided to build an interface to their Find Groups end point. (By the way, since I complained a bit about their UI I want to point out one thing they do very nicely - not forcing me to login to read API docs!)\nHere is the action I created to wrap their API.\n\nFor the most part, this is simply building a URL based on arguments. I'm not doing any validation since the API will do that for me. I'm also not doing any pagination since in my testing, I got over 100 results. I couldn't find docs on how many max results they would return and I did do a bit of &quot;prep work&quot; for adding support in the future, but for now, it will return at least 170 or so results in my testing.\nNote that the action expects an argument for the key. I set that as a default action parameter so I don't have to include it in my own calls.\nThe only real interesting part is the manipulation I do for &quot;country&quot;. While the Meetup API has a country argument, it seems to ignore it when you set the radius argument to global. So basically, I can't say &quot;Don't care about distance from my home but keep it to America.&quot; Therefore I do my own filtering on the results after fetching them.\nThis is a great example (imo) of where serverless wrappers can be so useful. I took an existing API and built my own to address the shortcomings (or at least my perceived shortcomings) of it.\nAnd that was it - literally. I &quot;web&quot; enabled it and my API was done. I then built the front end. I'm not going to bore you with my HTML and CSS. You can run the demo yourself here: https://cfjedimaster.github.io/Serverless-Examples/meetup/client/\nIn case you don't want to, here's an example of the output:\n\nYeah, not necessarily the prettiest demo in the world, but it did give me a chance to finally try Flexbox. I have an idea for a nicer version I'm going to try to get out the door this week, but we'll see. The JavaScript code behind this is relatively simple. It's 99% DOM manipulation to be honest.\n\nYou'll notice my URL constant on top sets up a bunch of defaults and when the actual search is performed, I'm just adding a keyword.\nSo what do you think? If you want to see the code for yourself, you can find it here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/meetup\nThe &quot;action&quot; folder contains my action code (one file) and &quot;client&quot; contains the client-side application that uses the API on OpenWhisk.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Handling SMS with OpenWhisk, IBM Watson and Twilio - an Update",
		"date":"Fri Jul 07 2017 16:49:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1499446140,
		"url":"https://www.raymondcamden.com/2017/07/07/handling-sms-with-openwhisk-ibm-watson-and-twilio-an-update",
		"content":"Last week I blogged about a sample application I built using OpenWhisk, Twilio, and IBM Watson. The idea was - I could send a picture to a SMS number set up with Twilio, Twilio would send the data to OpenWhisk, OpenWhisk would get the picture and send it to Watson for identification, and finally the result would be sent back to the user. This morning a coworker pointed out a few issues and I found a way for the code to be much simpler. Normally I'd just update the post, but I thought a followup would be better.\nFirst off - the code for the action that listens for a POST from Twilio had this line:\n\nI totally forgot to say that those two values come from Twilio and should be set as default parameters for your OpenWhisk action. Like so:\nwsk action update smsidentify/gotMessage --param accountSid X --param authToken Y\nWhere X and Y are the two values. You could hard code them too I suppose, but it's better to have them as parameters.\nAs someone who tries his best to teach well, I look out for stuff like this - when the writer makes assumptions - and I try my best to not do that myself. Sorry!\nThe next issue was a bit more subtle. I had noticed an error in the Twilio logs in regards to the response type. Here is an example:\n\nHowever, since the error didn't seem to break the app, I forgot about it. I should not have done so. Turns out, you need to return an XML response in reply to the HTTP POST Twilio sends to your action. My original code in sendResults.js looked like so:\n\nBasically - use the Twilio library to message back and just return 1 since I don't really care about what I return. However, this is what caused the problem - I wasn't doing a proper XML response. I initially tried to find out how, &quot;How do I send an empty XML back to just make Twilio happy&quot; when I discovered something else - I can actually send my result in the XML! Not only does this make it easier to send the result, but it solves other issues as well.\nIn the original post, I talked about how the &quot;identify&quot; action had to &quot;carry over&quot; the phone numbers that the first action received. This was so that the third and final action could properly &quot;call back&quot;. But now that I'm simply returning XML, that's no longer an issue!\nSo my corrections involved the following. I edited identify.js to remove the &quot;carry over additional args&quot; hack. (You can see the source here). Then, I edited sendResults.js to be much more simpler:\n\nFinally, and this is critical - I edited the Web Hook URL configuration in Twilio to remove &quot;.json&quot; from the end and change it to &quot;.http&quot;. This tells OpenWhisk that I'm going to be defining my own response type with headers and the like. (As much as XML is kinda meh, I think I'd like it if OpenWhisk Web Actions supported .xml in the URL. I'm going to file a request for that.)\nAll in all, I'm really happy that this bug helped flesh out (a bit) my knowledge of the Twilio API. As a reminder, all three actions can be found on GitHub here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/smsidentify.\n",
		"tags":[
	        
            "openwhisk",
            
            "watson"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Google Analytics and RSS Report - Version 2",
		"date":"Thu Jul 06 2017 15:20:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1499354400,
		"url":"https://www.raymondcamden.com/2017/07/06/google-analytics-and-rss-report-version-2",
		"content":"Over two years ago I blogged about a demo I had built involving RSS and Google Analytics. The reason for that demo was simple. As an active blogger, I want to have an idea about how my recent content is doing. Google Analytics, however, doesn't have a way to recognize what my most recent content is. By combining a RSS feed and a Google Analytics API, I could create a mashup that reported exactly what I wanted - how my latest entries were doing.\nIn the time since I wrote that initial demo, a lot has changed. First off, the Google Feeds service I used was discontinued. Luckily there's multiple options around that (see my post, &quot;Parsing RSS Feeds in JavaScript - Options&quot;).\nThe other change was to how I integrated with Google. There were multiple updates to the libraries I used - both in terms of authentication and working with Google Analytics. The Reporting API is now at version 4, and while still pretty complex, it's pretty powerful as well. Google has a really good JavaScript quick start that I used as a fresh start for the project. What I especially like is that they have very clear docs on the permissions you have to enable before using the code.\nI'll be sharing my code, but note that I will not be sharing an online copy. My code will point the values you need to tweak in order to host this yourself. Before I share the code, a screen shot so you can see what I built:\n\nAs you can see, it's not much. Just a table with my latest entries as discovered via RSS and than a report on the page views for each. Ok, now the code. First, the HTML.\n\nFor the most part, this is ripped from the demo. Note the meta tags which allow the authentication to work. You don't see that in the screen shot above as I've got code to hide the login button after a successful login. What's cool is - the entirety of the authentication is basically that one button and the meta tags. Google handles the entire process. I love how easy that is. Note that the client id metatag would need to change for your own version of this. Now the JavaScript:\n\nSo in general, the proces is - get the RSS and then ask Google to analyze each URL. I used Yahoo's YQL feature to parse the RSS. Once I have the entries, I then switch to Google's Reporting API. This is where things get a bit weird.\nThe JavaScript library has one method and one method only - batchGet. This method lets you run up to 5 reports each. So while the library has one method, the reports are where you specify what you want to search for. Since a RSS feed has - normally - ten entries - my code is hard coded to work with 2 sets of 5 links. I could make this a bit more generic, but I don't want to. (I'm allowed to say that, right?) The batchGet method returns a promise, so I can chain them together with an &quot;all&quot; call to wait for both to be done and then just update my table.\nAnd that's basically it. If this is helpful and you end up using the tool, please leave me a comment below.\nNotes\nI could have used my JSONFeed source instead of my RSS. See my post from May about that. But I thought an RSS version may be more useful for other people since JSONFeed is still rather new.\nFinally - damn - it's kind of depressing how low my page view counts are for my most recent content. My blog still gets very good traffic, a bit over 100K page views per month, but it's really kind of ego crushing to see how little traffic my latest content receives.\n",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Important Note for OpenWhisk Developers",
		"date":"Wed Jul 05 2017 15:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1499269920,
		"url":"https://www.raymondcamden.com/2017/07/05/important-note-for-openwhisk-developers",
		"content":"Yesterday while working on a new book (yep - I'm writing a book for OpenWhisk!) I discovered something pretty important. I generally use the OpenWhisk docs on IBM Bluemix as my reference: https://console.bluemix.net/docs/openwhisk/index.html. I knew that the docs were sourced from the GitHub repo, but what I did not know is how far behind the Bluemix docs were.\nThis is probably just a random fluke, but it bit me in the rear (and I'll explain why next) so I'm now updating my own bookmarks to the GitHub docs: https://github.com/apache/incubator-openwhisk/tree/master/docs.\nOk, so why was this a big deal to me? In the past when I've blogged about Web Actions, I've mentioned that you need to manually add a CORS header to use it in client-side applications. In fact, about a month ago I shared a generic solution for this: Using a Generic CORS Enabler in OpenWhisk\nWhile working on my book though, I discovered that I no longer needed to do this! In fact, the CORS header is now automatically added. This is documented along with a new annotation you can add if you want more control over this behavior. This is a great new addition and one that makes a lot of sense I think.\nIn the docs you'll also see a note about another new feature, Vanity URLs. All this does is make your API urls a bit nicer looking, so it isn't a huge big deal, but it's nice to know about. You may be curious though how you can actually use it since it requires a namespace with pretty strict restrictions.\n\nThe namespaces must match the regular expression [a-zA-Z0-9-]+ (and should be 63 characters or fewer) ...\n\nMy namespace fails this right away: /rcamden@us.ibm.com_My Space/. However, I forgot that in the Bluemix you can rename both your org (the &quot;rcamden@us.ibm.com&quot;) part above and the space (&quot;My Space&quot;). However... currently the OpenWhisk UI on Bluemix does not handle the rename correctly. Until the bug is fixed, I'd avoid renaming your org/space on Bluemix if you have existing OpenWhisk actions.\nI said this feature &quot;just&quot; adds a nicer URL, but is does include a way to automatically load an HTML resource as well, and that could be handy for quick documentation purposes.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Designing an OpenWhisk Action for Web Action Support - Take Two",
		"date":"Mon Jul 03 2017 20:25:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1499113500,
		"url":"https://www.raymondcamden.com/2017/07/03/designing-an-openwhisk-action-for-web-action-support-take-two",
		"content":"Back a few months ago I wrote up a quick blog post on how an OpenWhisk action could support running in both &quot;regular&quot; mode and &quot;Web Action&quot; mode: Designing an OpenWhisk Action for Web Action Support. I started off that blog post with a warning stating that what I was covering was bleeding edge and likely to break. Turns out I was right - it did.\nI want to share a quick update to that blog post and demonstrate how it can be done now. Of course, things may still change in the future. In general, OpenWhisk is very good about not breaking things, and the main reason my code broke was because I was doing something not documented in the first place. However I still think this is something folks may want to do so I hope this is helpful.\nI will not be rewriting the entire post, therefore, be sure you read the post so you have some context as to what I'm doing.\nLet me start off by pointing out some of the simpler differences.\nFirst and foremost, note that the way to enable a web action has changed a bit. Now you use the web annotation, not web-export. So to &quot;Web Action-ify&quot; your code, you do:\n\nwsk action update nameOfAction --web true\n\nSecondly, the URLs for Web Actions no longer have &quot;experimental&quot; in them. Here is a URL for the &quot;caas&quot; action in the &quot;default&quot; package:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/default/caas\nNow, let's talk about the code. Originally, my code used this method to determine a &quot;regular&quot; versus Web Action call:\n\nOriginally all the &quot;meta&quot; stuff was in __ow_meta_something. Now the meta part is gone and the verb one is just method. So an updated version of this would be:\n\nThis change also has to be done to getRequestType:\n\nAlso note I added a final json result for cases where no content-type is explicitly provided. And that's it. You can test the HTML version here:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/default/caas.html\nThe image-only version still works, but Postman isn't rendering the preview for me. I can see the right header so I don't think it is an issue.\nFinally, the old URLs still can work and the older __ow values can still be sent. If you are in production with an action and have clients pointing at the old URLs, the args you get will differ from folks using the new URLs. Thanks to @rr for reminding me of that on Slack (and for helping out with this in general).\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Handling SMS with OpenWhisk, IBM Watson, and Twilio",
		"date":"Thu Jun 29 2017 16:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1498752180,
		"url":"https://www.raymondcamden.com/2017/06/29/handling-sms-with-openwhisk-ibm-watson-and-twilio",
		"content":"I have made some important corrections to this guide - please see my followup here: https://www.raymondcamden.com/2017/07/07/handling-sms-with-openwhisk-ibm-watson-and-twilio-an-update\nEarlier this week I got a chance to play a bit with Twilio's API (An OpenWhisk Monitor/Alert POC) and I have to admit I was shocked at how easy it was to use. It got me thinking about what else I could do with it (as an excuse to learn of course) and I whipped up a pretty cool demo I'd like to share.\nThe idea is simple: Take a picture with your phone, text it to a phone number, and then let Watson's Visual Recognition service try to determine what it is. Finally, respond with a description based on that data. Here is a sample from a picture of one of my cats:\n\nAnd another test with a dollaction figure:\n\nAll in all, the results are kind of hit or miss, mainly because of how I tried to &quot;Plain English&quot; the results, but pretty impressive (I think) for approximately one hour of work. So how was this built?\nI had already signed up for Twilio earlier this week for my last test, and as part of that work I had provisioned a number. As you can tell in the screenshots above, I haven't yet left the trial account but I was able to do everything I needed to without paying a dime. (My phone number will cost me one dollar a month, but I'll probably let it expire as I'm only &quot;playing&quot; now.) While I had a number provisioned, the only thing I had to do was setup the web hook. This lets me say, &quot;When you get a SMS, run this code.&quot; I did that in the phone number configuration screen:\n\nYou can't see the full URL there, but it's the &quot;Web Action&quot; url for the sequence I'm about to show you. As I've said before, OpenWhisk still doesn't make this quite easy to get, but once you figure it out, you're good to go. In my case, it is:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/smsidentify/handleSMS.json\nSo what's the code behind this? I built this with three actions:\n\nThe first action is responsible for looking at the SMS message sent by the user. If they didn't send a picture, respond with help text. Otherwise respond with a &quot;we're working on it&quot; message.\nThe second action is responsible for looking at the image by using the Watson Visual Recognition service.\nThe final action takes the results from Watson, tries to make a simple English sentence from it, and sends it to the user.\n\nHere's the first action:\n\nFor the most part, the Twilio npm package makes this trivial. When it pings my webhook, it passes a lot of data. The parts I care about are:\n\nFrom (who sent it)\nTo (technically this is already known to me, I've got one number, but I should make this dynamic)\nNumMedia - a value representing the number of images sent in the message.\n\nUsing that last value, I simply see if they sent me just plain text, and if so, I respond with help text. If they sent me an image, I pass that, along with the From/To, to the next action.\nNext is my identify logic. It works well, but it bugs me a bit. Let me share the code and then I'll explain what bothered me.\n\nSo, the Watson npm package makes identification easy. But I ran into a problem with the OpenWhisk version. OpenWhisk has a slightly older version of the package. That meant the sample code didn't work exactly as I would like, specifically there wasn't a constant for version_date in the package. It was easy to fix, but kinda threw me a bit.\nThe next problem I had was more a philosophical one then anything else. I wrote this action first. Outside of that issue I just described, it was simple and worked well. But when I put this action inside a greater sequence, I realized I couldn't leave it &quot;pure&quot;. What I mean is this...\nMy first action has information about the SMS message that I need my last action to know, specifically, who to send the message to and from what phone number. In order for action 3 to have this, I have to pass it to action 2, and action 2 has to pass it on as well. I felt like I had ... I don't know, &quot;sully&quot; my pure action a bit by having it handle stuff that wasn't it's responsibility. Unfortunately, there really isn't a clean way of doing this. I mean, I could store data in a Cloudant record and clean it up when done, but that felt way over-engineered. In the end, I grimaced, and got over it.\nNow for the final action:\n\nAs you can see, the main thing I do is try to create a plain English version of the results. This not very intelligent. For example, I don't use &quot;an&quot; instead of &quot;a&quot; when the tag begins with a vowel. I could also make it a bit random so it seems more human. But in the end, it worked ok.\nAll in all, everything worked very well. I did see some lag at times with Twilio. For example, I had 3 test messages show up a few hours late. I'm not sure why and I guess I could investigate more on why. That's probably the cell network more than anything else.\nI can say that the das",
		"tags":[
	        
            "openwhisk",
            
            "watson"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An OpenWhisk Monitor/Alert POC",
		"date":"Tue Jun 27 2017 21:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1498600440,
		"url":"https://www.raymondcamden.com/2017/06/27/an-openwhisk-monitoralert-poc",
		"content":"A few weeks back I posted (Monitoring OpenWhisk Activity) about how you can monitor your OpenWhisk activity. One of the things I made note of is that it would be nice to have an &quot;alert&quot; system such that I could specify that if a certain action began performing poorly, I could get an alert. Now &quot;poor&quot; is a fairly nebulous term, but today I worked on a little demo that I'd like to share. It uses OpenWhisk itself to monitor your OpenWhisk actions, but in general, I'm more concerned about my code failing, or third party APIs failing, then OpenWhisk itself.\nI began by creating a basic action that would return true if the action was acting up. Let me share the code and then I'll explain how it wors.\n\nAlrighty - so the action expects the following arguments:\n\nFirst and foremost, the action to check. Note that the code uses activations to track action health and the last time I checked there was a bug with getting activations for an action in a package. Obviously if that is still a bug it's going to be fixed, but keep that in mind when using this code.\nThe from argument is the number of milliseconds from epoch to use as a filter for fetching data.\nThe max argument is the total number of previous activations to check. In general, I'd expect people to use this over from, also, the value you use here will totally depend on your usage. So for example, if I have an action that runs hourly and if I want to check health every day in a CRON trigger, than I'd set max to 24.\nThe rate argument is a percentage value that represents healthy. It defaults to 100% which is probably too high, but again, it depends on your particular usage.\n\nI use the OpenWhisk npm package. Note that I do not have to specify the authentication information since I'm instantiating it in the main function itself. This means it will pick up the authentication values for the account. I then fetch activations using the filters as described above. Note that there is a undocumented limit of 200 activations. You could get more (I document this here) but since you have control over how often this check runs, you can specify a schedule that will ensure you don't run into that problem.\nOnce I have my activations, I loop over, count up the good ones, and then use a bit of math to get the percentage. If you are too low, then we are in error. I called this action, isBad, and I know it's kinda bad to use a negative like this. In this case, the true status of this action implies we've got a problem and that kinda makes sense to me, but I could also see building this as isGood and reversing the results. The &quot;Clean Code&quot; part of me felt bad about this but the &quot;Get er Done&quot; part of me told it to shut the front door.\nNote that I return a status and the action requested. I could have also returned the total and total good values as well.\nSo in theory... that's mainly it. A person using this would combine this with another action to handle the result and do something. So for my first example, I combined it with a simple email action:\n\nIn this action, I just use SendGrid to fire off an email. The template is incredibly simple. And here it is when executed (I fixed the typo with its/it's later):\n\nAnd then for the hell of it, I wrote up a Twilio action so I could send a SMS:\n\nIt requires your Twilio app auth info and a number to send to as well as a number to send from. And here is in action:\n\nAnd that's basically it. To make this &quot;live&quot; I'd set up a CRON trigger and figure out how I want to be notified, but since my code is perfect, I don't have to worry about this. Woot! You can find the source for all three of these actions here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/alert\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Another Form Processor Option - LiveForm",
		"date":"Fri Jun 23 2017 15:41:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1498232460,
		"url":"https://www.raymondcamden.com/2017/06/23/another-form-processor-option-liveform",
		"content":"As a proponent of static site generators, one of the things I keep an eye out for are services that work well with them and provide functionality you lose after going static. Probably the most important of these are form handlers. Just being able to build a simple contact form can be incredibly helpful. On my site I use Formspree and in the past I've used FormKeep. Today I'm going to quickly review another new service, LiveForm.\n\nLiveForm has a pretty impressive set of features:\n\nreCAPTCHA support\nWebhooks\nThe ability to notify multiple people at once (ie, don't just email X)\nAskismet\nA read-only API\nCSV export\n\nAnd best of all - file uploads.\nAbout the only thing really missing (compared to Formspree/FormKeep) is the ability to specify a custom subject for the message.\nTo begin using it, you have to register first, but then you simply create a new form. This is done by just giving it a name.\n\nYou're then dropped into a simple admin for the form that begins with boilerplate text you can copy for a new form or just the form tag itself if you have an existing form.\n\nAt that point, things pretty much work as expected. You can specify a redirect URL by using a hidden form field, otherwise you end up on a generic LiveForm page. The UI to view messages is nice and provides a bit of metadata about the submission (browser, IP, and time).\n\nThe email reports are nicely formatted too:\n\nNotice that this submission had a file attached to it, and LiveForm treats images differently (it displays them). If you upload something else, like a .exe (yes, I tried), you get a link to the file on S3.\nAnd that's basically it. Like I said, I think the killer feature here is file uploads. The API is real cool as well. Their blog has a great example of using it along with image uploads.\nPricing seems pretty reasonable, and best of all, nothing is removed on the lower plans. All you get is fewer forms. The highest tier, at 19 bucks a month, gives you 50 of them. The lowest tier ($3), gives you 5. But in all cases, there are no limits on the number of submissions or your use of the features. That's really darn compelling.\nAnyway - check it out - and as always - if you have any experience with the product, let me know in a comment below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Update on My Random Comic Book Character API",
		"date":"Wed Jun 21 2017 19:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1498073700,
		"url":"https://www.raymondcamden.com/2017/06/21/update-on-my-random-comic-book-character-api",
		"content":"So today's post isn't necessarily that interesting - but I try to live by the rule of blogging everything that causes me trouble. Earlier this week I blogged about creating a &quot;Random Comic Book Character&quot; API. In the post, I ended with a simple HTML demo that displayed a random character on page load. Initially my plan had been to send an email every day with the character but I decided against that as I figured it would be too much trouble. Today I obviously decided I must like trouble as I went ahead and did it. And yes, it went as well as you can imagine.\nSo first, a quick recap. The core API is based one OpenWhisk action. It handles making multiple calls to the Comic Vine API and massaging them down into one set of data. So in theory, all I needed to do was take some of the &quot;character JSON into HTML&quot; logic from the desktop demo and just use that as an email. Easy!\n\nSo I know HTML email is a complete and utter shit show but I knew if I kept things simple and used inline styles I'd probably be ok. I had used a Google Font for my desktop demo so I figured I'd try that too and it wouldn't hurt it if failed. So here's the action I built:\n\nThat's a big chunk of code, but honestly, most of it is the same from the desktop demo. I expect a character to be passed in, I render it, and then use SendGrid to email it. I created a sequence that joined my actions together and fired it off and... it worked! Mostly...\n\nIt's not the prettiest email, but outside of the image not loading, it worked. Clicking the &quot;load image&quot; thing worked fine so I thought - ok - let me figure out why the image isn't loading by default.\nEverything ended happily ever after.\nOk, no. So GMail has a setting for external images but I already had it on:\n\nI clicked the &quot;Learn more&quot; link because I always want to learn more, and discovered this gem:\nNote: If Gmail thinks a sender or message is suspicious, you won’t see images automatically. Instead, you'll be asked if you want to see the image.\nErr... ok. I did a tiny bit of research into determining if there was something I could do to make GMail think email from me via SendGrid was super duper safe, but it seemed like a lot of work. I then decided - why not try encoding the image via base64 and data URLs. That should be easy, right?\nOf course not - it took me a good hour just to get the data right. Mainly because I missed a little tip in the Request docs that say you have to disable encoding if you're working with binary data. That didn't frustrate me at all.\nEventually I got the base64 text right (verified via a regular HTML file) and I did my test. And nothing. No error, but the image just didn't show up. I went into dev tools and GMail had simply removed it. I have no idea why. From what I saw from Googling (everything comes back to the big G), GMail does support data URLs, but it never worked for me. Hell, I even tried the weird CID/attachment thing and that didn't work either. If folks want to see what I tried before I reverted, you can find the ocde here: https://github.com/cfjedimaster/Serverless-Examples/blob/master/comicvine/dailyEmail.fail.js\nOn a whim, I decided, why not take a look at the email on my phone.\nAND OF COURSE, ON THE MOBILE DEVICE, IT LOADED THE EXTERNAL IMAGE AUTOMATICALLY.\nsigh\nBut just for fun, it decided to not support the remote font.\n\nI removed the font styles from the text and it ended up ok.\n\nThe final version of the code is up here - https://github.com/cfjedimaster/Serverless-Examples/blob/master/comicvine/dailyEmail.js. The final bit was to make a cron trigger, a rule, and then see how well I did my cron when, and if, I get the email tomorrow morning.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Enabling API Management for Serverless with OpenWhisk",
		"date":"Tue Jun 20 2017 18:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497984840,
		"url":"https://www.raymondcamden.com/2017/06/20/enabling-api-management-for-serverless-with-openwhisk",
		"content":"I've shared more than a few examples of OpenWhisk APIs that I've shared with API management but I haven't actually detailed what that process is like. Today I'm going to review what exactly it is, how you enable it, and how you make use of it. As with most things I say here about OpenWhisk, remember that this is still fairly new and some of the details will probably change in the future.\nWhat is API Management?\nLet's start with the definition first, and as always, I'm going with my definition, not necessarily the marketing verbiage. To me, API Management involves everything &quot;on top of&quot; just exposing your code via a HTTP interface. This involves things like:\n\n\nRobust statistics involving everything from how often your API is run to what arguments are passed to what status it returned. I'd also add memory usage, throughput, and pretty much every single aspect of every single invocation of the API. I want to be able to see how well my API is performing over time and judge where I need to spend time optimizing, or perhaps what new features I need to add. For example, if I see people using a cat API to search for female cats of a certain breed, I may want to add simpler ways to get that data.\n\n\nSecurity and usage settings that let me lock down my APIs to require a key, or other login, and set usage limits. This ties back to the stats area as well as I'd like to be able to see who my biggest consumers are and how they make use of my API.\n\n\nWhile the first two points are the main things I care about, I'd also add support for, in some way, documentation. That could be automatic documentation and testing tools like what LoopBack provides. In the best of worlds, my API management generates docs or publishes docs I've already written, but always allow me to customize and add to what is provided out of the box.\n\n\nSo obviously IBM has some experience with this - and you can see this in full effect with IBM API Connect. While you can definitely make use of API Connect with OpenWhisk, there's a new built-in option to for folks using OpenWhisk under Bluemix called &quot;Bluemix Native API Management.&quot; To keep it simple, I'll refer to this as BNAME.\nAPI Management and OpenWhisk\nOpenWhisk can expose your code in one of three ways:\n\nVia a secured REST API - this is automatic and is what the CLI uses to work with your actions.\nVia Web Actions. This allows for anonymous access only and doesn't provide any built in tracking. Although you can write your own security layer and get analytics by looking at your activation history and using the monitor UI.\nAnd finally, via the API Gateway.\n\nThe gateway feature is how we can manage our OpenWhisk APIs and do some of the stuff I described above. You can work with the gateway via the CLI or the UI and in the rest of the post I'm going to walk you through the process of doing that.\nOur Action\nBefore we go any further, let's build a simple action we can then manage with the API gateway. This action simply does pig latin:\n\nIts got a tiny bit of error handling but mainly just wraps a piglatin function. I pushed this to OpenWhisk as the pig action and tested it from the CLI to ensure it worked:\n\nWoot. Nice and simple and totally a real world example. Totally.\nAPI Management via the CLI\nLet's start by showing how to enable API management via the CLI. The CLI lets you create new managed APIs, but doesn't let you specify the security settings that you can via the UI. Before you create a managed API, you must first enable &quot;Web Action&quot; support. I wish the &quot;Make an API&quot; CLI call would do this for you automatically but for today, you have to do it first:\nwsk action update pig --web true\nTo create the managed API, the CLI uses this form:\nwsk api create BASE_PATH API_PATH VERB ACTION\nAlright, let's break this down.\n\nBASE_PATH is like a group for your API. Imagine you have a few APIs related to cats: get, delete, create, search. I'd want all four of them to be grouped under a BASE_PATH of cats. That means my APIs will end up looking like this: (stuff)/cats/get, (stuff)/cats/delete, and so on. Also, this &quot;group&quot; is where you will apply security and rate limiting. All four will be rate limited/locked down or not - you can't set unique values for each end point. For all intents and purposes, our API is &quot;cats&quot; and those individual end points are simply parts of the great API.\nAPI_PATH is just the end point, as described above. So &quot;get&quot;, &quot;delete&quot;, etc.\nThe VERB will be one of the &quot;regular&quot; verbs used with REST APIs: get, put, post, etc. You &quot;should&quot; follow convention here and use GET for read actions, post or put for create/update, but the REST Police will not be stopping by your cubicle to slap you on the hand if you don't follow the norm.\nFinally, the ACTION is just the action you want to expose.\n\nThe CLI supports more options of course, just run wsk api -h for details. Alright, so let's expose the pig latin API",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Serverless Demo - Random Comic Book Character via Comic Vine API",
		"date":"Mon Jun 19 2017 18:39:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497897540,
		"url":"https://www.raymondcamden.com/2017/06/19/serverless-demo-random-comic-book-character-via-comic-vine-api",
		"content":"For today's demo, I'm going to be using the Comic Vine API, but let me warn folks that I think it is bad idea to use this API in production. I started looking at it over the weekend and while I was &quot;successful&quot;, I found numerous documentation issues and lots of forum posts that have gone unanswered. My gut tells me that this is not something I'd ever use for a &quot;real&quot; app, but since I don't build real apps it doesn't matter, right?\nOn the flip side, I do want to call out something I think the Comic Vine API does very well. I love the status report of your usage:\n\nSpecifically (and I called it out with the arrow) - I love the &quot;Your request rate is fine&quot; comment. It's a small thing, but it's a plain English summary of my status that doesn't make me look at raw numbers and figure out how much I'm abusing the rate limits.\nAlright, so with that out of the way - what did I build? I've previously shown how to get a random comic book character from the Marvel API (All My Friends are Superheros) as well as random comic book covers (Building a Twitter bot to display random comic book covers). In both cases, my thinking was that Marvel's history was so rich, I wanted to be surprised by what data existed out there and by how deep the history went. I was a comic book reader growing up, gave up in college, and started reading again about ten years ago.\nThis was also about the time of Marvel's cinema rise to power and DC's... well, DC's trying for sure. &quot;Wonder Woman&quot; was incredible and I'm a big fan of all their TV shows. The net result of all this new video media is that I've gotten more interested in reading comics. After I caught up with the Flash for example, I picked up a few TPBs. I then did the same for Green Arrow. My appreciation for comic books as a whole is growing and I think that's a great thing.\nI thought it would be interesting to recreate what I built with the Marvel API using the Comic Vine API instead. Specifically I was looking at getting a random comic book character. As much as I complained about the API above, once you have an API key it's pretty easy to figure out how to get a random one.\nHere is the root URL to fetch all characters, I say &quot;all&quot;, but it is paged:\nhttps://www.comicvine.com/api/characters?api_key={{key}}&amp;format=json\nThe {{key}} part is dynamic of course. Calling this gives you one page of results in no particular order, but the important part is the metadata:\n\nSee the number_of_total_results there? Because I know how many characters exist in their database and because I can both offset and limit my results, getting a random character is actually fairly simple:\n\nLet's start in the main function. I begin by making a note of totalChars. You'll notice I've hard coded it to 100k. Even though I know Comic Vine has over that number, I made my system just use that as an upper hit on the first call to the action. When I get my result back, I store that &quot;real&quot; number. There's multiple issues with this.\nFirst - the number will only persist as long as my action does, and if no one is using it, it won't persist at all. Second, it is possible that the total number of characters in the Comic Vine database could shrink dramatically.\nThe &quot;best&quot; solution would be to make one call and ignore the character results but make note of the total and then make my random call again. That would double the HTTP calls and make my code more complex. So I made what I thought was a good compromise.\nI get a random number, use that as the offset, and then just make my call. I use field_list to reduce the amount of data a bit which was a somewhat arbitrary decision. I then just resolve the result when done.\nThis worked well! So of course I decided to kick it up a notch. As I started looking at my data, I noticed a few things. There is another API for the detail for the character that returns a bit more information. I thought - why not return that data as well. I decided to add that second call like so:\n\nNotice I'm returning a promise in my then block so I can use yet another then to chain it. You can also see in the field list the kinds of interesting data you get in the result. I just append this data to a detail key in the result and resolve it.\nThe next thing I wanted was the name of the first comic book that this character appeared in. Here's where the API failed me. The initial character result contains an API URL that should point to that end point, but it didn't work for me. So instead I use the issues API to fetch it. This was yet another promise chained in resulting in a grand total of 3 HTTP calls to get one random character. Here's the entirety of the action now:\n\nBy the way, while I'm pretty comfortable with Promises now, I want to share a Gist snippet that my friend Shannon Hicks shared with me. It's based on an earlier version of the code, but it uses the new await functionality from ES6 and frankly - it's freaking awesome. I decided",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Monitoring OpenWhisk Activity",
		"date":"Fri Jun 16 2017 15:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497627840,
		"url":"https://www.raymondcamden.com/2017/06/16/monitoring-openwhisk-activity",
		"content":"I've mentioned a few times already that I was going to discuss what monitoring is like with OpenWhisk and I thought today would be a good day to finally write down my thoughts. In general, this is an area where I think OpenWhisk could use some improvement, and I'll point out where and how, but let's go over the details first.\nBroadly, there's two ways to monitor your OpenWhisk activity - via the UI or via the CLI. The UI, available at https://console.bluemix.net/openwhisk/dashboard, loads a basic status screen that looks like so:\n\nThat's pretty big, so let's break it down a bit into each component. First and foremost, you probably want to consider filtering.\n\nThe right most option is self-explanatory, but the drop downs may be a bit confusing. The &quot;Time Frame&quot; drop down lets you select the most recent 50, 100, or 200 activations. The &quot;Limit to&quot; drop down selects an action. However - the actions you see are based on the Time Frame limit. This is important and really confused me at first. That means if your action isn't in the past 50 activations, you won't see it. And if it isn't the past 200, you won't see it at all. It's likely that triggers will be the most &quot;noisy&quot; of your activations so using that filter may be a way to resolve the problem, but if your account is &quot;busy&quot;, you may not be able to get a report at all for your action. (However the CLI has a way around this.)\nNow let's look at the &quot;Activity Summary&quot;:\n\nNote that the labels you see will be based on the data. That's... kinda obvious. What I mean is - notice how the &quot;pathTest&quot; bare makes it clear that the numbers at the end are averages. I didn't notice that, and if all my bars had been long, I would not have known. I would have guessed that, but I also would have thought that maybe it was the &quot;last invocation&quot; duration. I've filed a bug report on this to make it more obvious somehow. Speaking of labels, the red items on the left represent errors, which is sensible, but you won't always see numbers. If you mouseover and a wait a sec, a label will show up giving you the exact value.\nNote that &quot;dotweet&quot; is a problem. I can see right away that it's something I may need to look into. It's also my most complex OpenWhisk usage. (It drives Serverless Superman.\nLet's switch to the &quot;Activity Log&quot;:\n\nThis gives you a more &quot;log&quot; like view of your recent activity along with a snippet of the result. The activation IDs in small text are clickable, and if you click, you can see the full activation report. I've shared that before, but as a reminder, activation records include things like when the it occurred, how long it took, logs, and the result.\nThe last bit is the &quot;Activity Timeline&quot;, atime based graph at the bottom:\n\nThis is pretty much the same as the &quot;Activity Summary&quot;, but organized by time, not by action name.\nNow that I've talked a bit about the UI, what about the CLI? The CLI lets you get activations by using the activation list or activation poll command. The list command supports limiting and skipping items so you can do basic paging by hand. Both support filtering by activation name, but do not currently support filtering by package (I believe). So for example, if I had an action called doIt in my homeSecurity package, and one called doIt in my skyNet package, I can't filter to just the homeSecurity/doIt actions. (And again, I believe - I could be wrong on this.) Right now activation lists are pretty bare:\n\nBut there is an open request to add a bit more data to this, like the time it ran and if it was successful or not. As a reminder, I wrote my own tool to add some of this to the CLI. It works on one action at a time but gives you a summary of the last 2000 activations.\n\nI also want to point out a very cool new project launched by a fellow IBMer, Whisk Information Timeline Tool. This project, by Kerry Chang, is open source and looks pretty freaking amazing. Definitely check it out.\nSo - all in all - I really think this is an area we need to improve. It should be much quicker to get an idea of how well my action is running and 200 activations is not nearly enough history for something running on a CRON schedule. The REST-based API is helpful, and it's how I built my tool and how Kerry built hers, but it's missing the ability to get total number of activations which makes paging a bit difficult. I think one the official UI gets some improvements and the API has this feature, we'll be covered - both by people who are happy with what's available up on Bluemix and by being able to build their own tooling.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Testing Multiple Image Recognition Services at Once",
		"date":"Thu Jun 15 2017 15:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497539040,
		"url":"https://www.raymondcamden.com/2017/06/15/testing-multiple-image-recognition-services-at-once",
		"content":"I'm a big fan of image recognition APIs - by that I mean services that let you send in a picture and have them scanned to determine what's actually in the picture. When they work well, it's cool as heck. When they fail, it's typically pretty funny. All in all that's a win-win for me. For a while now I've been wanting to build something that would let me compare multiple services like this at the same time. This week - I did that.\nI call it &quot;RecogTester&quot;, which isn't very imaginative, but it provides a reports via Google's Cloud Vision API, IBM Bluemix's Visual Recognition API, and Microsoft's Computer Vision API. The UI of my app isn't spectactuclar, but here's how it looks.\nInitially, it is just a form prompting you to select a picture:\n\nAs soon as you do, I use a bit of code to create a preview:\n\nAs soon as you upload, it sends the image to my Node server and then fires off requests to the 3 services. When done, it renders:\n\nThat may be a bit hard to read in a screenshot so I've created a PDF export of the page here: https://static.raymondcamden.com/images/2017/6/samplereport.pdf.\nI'm not hosting this application live on the Internet as I'd be charged for usage, but you can find the complete source code for it on my GitHub here: https://github.com/cfjedimaster/recogtester\nAlright, first I'm going to dig into the code, and then I'll talk about the services and how easy/hard it was to make use of them. Disclaimer - I work for IBM, so my opinion may be biased.\nThe Server\nThe server component consists of one main application file and then a service file for each image API. Here's my index.js:\n\nThe only thing here really interesting is the use of Promises to fire off calls to my API testers. I can then use Promise.all to wait for them all to finish. I know I've talked about Promises a lot, but I just want to remind folks about how darn useful they are for situations like this.\nGoogle's Cloud Vision API\nThe first service I built was for Google. In the past, I've found Google's authentication/authorization stuff for APIs to be a bit obtuse. I could always get it working, but I had to struggle a bit to remember what I had to do. It felt like that if I needed one hour to build an API wrapper, a good 15 minutes of that was just trying to enable the API and let me server hit it.\nGoogle has made some good improvements in this area, and you can see it in the Quickstart:\n\nThose buttons lead you right to the correct place and help you setup what you'll need for access. Then it's simply a matter of using the API. Google provides a NPM package so after including it, the code is super easy. However, authentication/authorization was a problem again. Instead of just copying a key value into your code, you need to get a JSON file exported that includes your authorization info. This is documented here but you have to dig a bit to grok that you copy the values of the keyfile into code. For my project I use one main creds.json file and I simply copied the contents of keyfile into it. You won't find that file in GitHub, obviously, but I do describe the &quot;shape&quot; of it in the readme.\nOnce you've gotten past that, the API is hella easy to use. Here's the entirety of my service wrapper:\n\nThe only thing special here is me specifying what type of analysis I care about. In this case, all of them. In a more real-world scenario you'll probably use a bit less. All in all, the coolest aspect of the API is the &quot;similar&quot; results. In most cases it returned exact copies (which could be useful for hunting down people stealing your work), but when it truly found different, but similar results, it was neat as heck. As an example, I uploaded this:\n\nAnd Google returned:\n\nIBM Watson Visual Recognition\nNext I built my wrapper for Watson Visual Recognition. I've used this service a few times already so it's easy for me, but again, I'm biased. Using it requires signing up for Bluemix (you can do so for free here) and then simply adding a new instance of the service. When you do, you can then quickly get the credentials via the UI:\n\nThe Watson team provides a npm module (watson-developer-cloud) that makes working with the service relatively easy. I ran into one problem though - the face detection API wasn't documented. (I filed a bug report for that.) Here is the code for my service wrapper.\n\nFairly simple, and I use promises again to handle the 2 calls. The results can be pretty freaking insane at times. While I found the amount of data returned to be much smaller than Google or Microsoft, the classifiers (or tags), were insane accurate at times. Consider this picture:\n\nWatson's first classification was that it was an aircraft, which is spot on, but number 4 was that it was a jumbojet, which is spot on if I know my planes.\nMicrosoft Computer Vision\nThis one was the most difficult to setup. Mainly because I assumed I needed to set my authentication up under Azure and get the Azure npm package. That was problematic because the",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using a Generic CORS Enabler in OpenWhisk",
		"date":"Mon Jun 12 2017 21:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497303840,
		"url":"https://www.raymondcamden.com/2017/06/12/using-a-generic-cors-enabler-in-openwhisk",
		"content":"Today's post (well, the code and the idea, the writing, warts and all, are all me) comes from a coworker, Stephen Fink. Stephen and I were chatting in Slack about generic utilities, and the idea for a &quot;simply CORS enable an action&quot; utility came up.\nAs a reminder, there's two ways to expose your OpenWhisk code as anonymous API - either via a web action or via Bluemix Native API Management (BNAME). The later is a bit complex so a web action is where I tend to go to for simple demos, but there's one catch with it. Typically you have to modify your action in order to use in a client-side application.\nSpecifically you need to return the CORS header as well as base64 your JSON. Once again - sequences really help out with this. By building a generic &quot;CORS Enabler&quot; action, you can then use sequences to quickly expose actions. Let's look at a simple example.\nI'll begin with the action - isGoodCat. Here's the code.\n\nAs you can see, the action simply returns whether or not a cat is good. This code is perfect and does not need cats. It is the most right program created. Ever.\nNow let's look at the generic CORS enabler Stephen wrote:\n\nThere's nothing out of the ordinary here. This action will basically just echo what it was given, but note it outputs the CORS header and handles the buffer crap. (Yes, I called it crap. I hope we can get rid of this little hack soon!) The only real logic it applies if it sees a domain parameter it will use that to lock down the CORS header a bit.\nSo given you've deployed this to OpenWhisk as corsenabler, to &quot;expose&quot; our cat API, all we do is this:\nwsk action create --sequence isGoodCatAPI isGoodCat,enablecors --web true\nAnd that's it - you're done. You can see mine here (although there's no promise it will be up forever): https://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/default/isGoodCatAPI\nAnd here is a sample output:\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Uploading Files to an OpenWhisk Action",
		"date":"Fri Jun 09 2017 20:49:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1497041340,
		"url":"https://www.raymondcamden.com/2017/06/09/uploading-files-to-an-openwhisk-action",
		"content":"This post starts with not one, not two, but three disclaimers. Read carefully!\nFirst off, if you want to upload files to an OpenWhisk action, you can, but your limited to file sizes less than one meg. That's pretty small, but don't forget you can use a third-party cloud storage provider to serve your file. (IBM has a Cloud Object Storage service that would be useful here, and obviously Amazon S3 would work too.) Don't forget you can detect the size of a file in JavaScript before you attempt to POST it.\nSecondly, while I was able to get a solution working, I really feel like this is something OpenWhisk can handle better at the platform level. I will go into detail about the particular parts that were difficult and what I had to do, but as I said, I think this is something that can, and should, improve in the future. I'll do my best to try and come back to this post if that happens, but... I'm old. I forget stuff. So just keep in mind the date of this post.\nThird item, and this is more a general warning then anything else, while I love Postman, it failed to work correctly for me when I was testing. I'll 100% put the blame on me, but when I switched from Postman to a &quot;Plain old HTML form&quot; for testing, I made a lot of progress.\nAlright, ready?\nFor my action, all I wanted to see in was it processing a form that included a file. OpenWhisk will already parse FORM data of the &quot;regular&quot; kind, your text fields and such. What I did with the file wasn't necessarily important (I tried a few things just to ensure it really worked), but pretty much anything should work.\nYou would think this would be easy. There's a bunch of npm packages that make processing a file upload easy. My favorite is formidable. However, they all suffer from one core problem in OpenWhisk - they want an instance of a HttpRequest object.\nWhen working with Express, you have this baked in. But under OpenWhisk, this is all handled behind the scenes. You do get passed 100% (afaik) of the request data, but it isn't a proper HttpRequest object itself. I tried faking it, but nothing worked well. (In fact, one solution that another project used was to mock the object with SuperTest.)\nI asked on Twitter and got two good tips - one from Wes Bos and another from Cesidio Di Benedetto. My solution does the following:\n\n Get the raw body.\nConvert that string into a stream using string-to-stream.\nPass that steam to parted, which seemed like the most low level, simplest multipart form parser. \n\nThis was all pretty frustrating and it seemed like there must be some library that would just let me pass a giant multipart string to it (and there probably is!), but I couldn't find a &quot;one shot&quot; solution.\nOk - so the code. First off, the action has to be web enabled, and raw body enabled. That's done like so:\nwsk action update nameOfMyAction --web raw\nNow for the code.\n\nI begin by getting the raw body. Remember you have to tell OpenWhisk to make this available with the annotation I mention before. I then make a fake stream using string-to-stream. I then make use of parted - and I pretty much just copied and pasted their sample. The end result is a parts object that contains all my form fields where the files are paths to the temporary file system. All in all pretty simple, but the line where I created decoded took me like an hour of trying random crap until I got it right.\nAnd basically - that's it. When parted is done (see the end event), I essentially &quot;echo&quot; the file back to the user. My code assumes a file field named file and assumed it was a png. It would be trivial not to do that though, but for the demo, I just wanted something quick and dirty.\nThe front end is just an HTML form:\n\nHere is a completely unnecessary animated gif showing it in action.\n\nYou can find the source code for this demo here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/file_upload\nAnd again - consider this whole example covered in a fine layer of &quot;Use with Caution&quot; and sprinkled with a &quot;Are You Kidding Me&quot;. It worked - but hopefully I can share a nicer solution in the future.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Recording and Assets for OpenWhisk Serverless Presentation",
		"date":"Thu Jun 08 2017 15:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1496935260,
		"url":"https://www.raymondcamden.com/2017/06/08/recording-and-assets-for-openwhisk-serverless-presentation",
		"content":"Thank you to everyone who attended my webinar yesterday. For those who missed it, you can view the recording by going here: https://engage.vevent.com/index.jsp?eid=556&amp;seid=90389. The UI seems to imply you will be registering for a new event, but ignore that. Once you get into the system you will be watching the recording of my session. For those who wanted the demos and slides, you can get them via the link below. I used a PDF export of the slides.\nhttps://static.raymondcamden.com/enclosures/webinar_openwhisk.zip\nAs always, if you had any followup questions, please add them below and I'll try to answer them as soon as possible!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Quick OpenWhisk Debugging Example",
		"date":"Mon Jun 05 2017 17:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1496685240,
		"url":"https://www.raymondcamden.com/2017/06/05/quick-openwhisk-debugging-example",
		"content":"Let me start off by saying that this is not a post about debugging serverless with OpenWhisk. I want to write something up on that at a good high/broad level. Rather I want to share a quick example since something I wrote broke recently and I thought it would be a good &quot;real world&quot; use case of something going wrong, how I looked into it, and how I corrected it. I think debugging/monitoring is one of the most crucial aspects of a serverless platform, and something OpenWhisk itself needs some help on, but again, I'll save that for a later post.\nA few days ago I blogged about a new serverless project called Serverless Superman (&quot;Building the Serverless Superman&quot;) This was a &quot;kinda&quot; simple little project where on a timed basis I'd look for tweets with the word &quot;serverless&quot;, pick one by random, and then tweet it from the @serverlesssuper account after replacing the word &quot;serverless&quot; with &quot;superman&quot;. I think it's going great so far:\nSuperman computing takes another step forward https://t.co/sKBnOJa7Q5&mdash; Serverless Superman (@serverlesssuper) June 5, 2017\n\nIf by &quot;great&quot; we mean silly and pointless of course. So, what went wrong?\nFirst - I noticed something was wrong when I didn't see Serverless Superman in my &quot;serverless&quot; search column in my Twitter client. I had set it up to tweet once every ten minutes (assuming it could find a tweet in the past ten minutes) and after looking at the account's Twitter page, I noted it had been quiet for a few days. Unfortunately, OpenWhisk doesn't (currently) support any way to let you know if an action is having problems, and I have some thoughts on that, but as I said in the beginning, I'll discuss that more later.\nSo at this point, I need to had to figure out what went wrong. My app only used two services - OpenWhisk and Twitter. It is absolutely reasonable to think that Twitter could be down (it happens), but as I was looking at a few days of downtime (and I could visually verify Twitter was working) I knew that wasn't the case.\nI then thought about how my app worked. Without going into too much detail (again, you can read the post on how I built it), I know I had:\n\nA trigger to handle doing &quot;stuff&quot; on a CRON-basis\nA rule to handle associating the trigger with an action\nA sequence that was my action for the rule\nAll the component aspects of that sequence: Get tweets about serverless, find one, change it, then tweet it. (And while I'm only listing three things there, it's actually a set of 5 actions total.)\n\nI began with the trigger. I fetched the activations for the trigger like so:\nwsk activation list serverless_superman_trigger\nThis returned my most recent activations filtered by the name:\n\nFirst thing you notice - there isn't a lot of information here. There's already an open bug to add a bit more detail here, most importantly, the date associated with each activation. I knew that it was sorted with the most recent on top, so I copied and pasted that into the CLI:\nwsk activation get 0d4ba8daaeeb4e609cb28f042c06defa\nWhich returns:\n\nRight away I see the trigger was a success, but I wasn't really concerned about it being successful. Literally all it has to do is run to be a success. What I needed was the time it ran. Unfortunately this is returned in a &quot;seconds since epoch&quot; format. I hopped over to EpochConverter to get a real value. As a quick aside, if you actually do that for the sample above it will report a time from today, well &quot;today&quot; being relative to when I wrote this blog entry. Obviously the activations I was worried about were back when the thing died, and was dead. The important thing to note is that it was &quot;current&quot;, in other words, some value in the past ten minutes.\nOk, so the trigger is fine. To be honest, I hate working with CRON as the syntax confuses the heck out of me, and that's why I wanted to ensure it was still firing.\nAlright - so next I checked activations on the rule. Again, I didn't expect anything but successes here and I wanted to confirm it. I ran:\nwsk activation list serverless_superman_rule\nI then copied and pasted the activation ID for the most recent instance. Here is the result - and again - I didn't really expect much here:\n\nSo - next was to look for activations of the action tied to the trigger/rule. Again, I used the CLI:\nwsk activation list dotweet\nAs another quick aside, dotweet is actually in a package, but it is kind of a pain right now to filter by package/action. I got the most recent activation, and voila - my issue!\n\nBoom! My issue. (Well, the first clue.) In case you're curious, that's just a subset of the activation record. The CLI supports filtering, and I just discovered that while writing this blog post. I got that result above by doing:\nwsk activation get 9e72e78649c4458d8aa1e10a85a38077 response\nAs you can see above, Twitter was blocking my account from Tweeting. I logged onto the site, checked the ",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Integrating OpenWhisk with Your Node Application",
		"date":"Fri Jun 02 2017 15:31:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1496417460,
		"url":"https://www.raymondcamden.com/2017/06/02/integrating-openwhisk-with-your-node-application",
		"content":"In most of my posts on OpenWhisk, I either show running the actions via the CLI, or demonstrate them with the anonymous REST API end point. However, there is another way of using actions as well. Every time you use the CLI, it is making authenticated REST calls on your behalf. You can find the documentation for that API here: Using the OpenWhisk REST APIs\nWhile you make use of this via the CLI automatically, there may be times when you choose to use the authenticated APIs in your code as well. For example, imagine a simple Node application that performs a set of functions. It's possible you may migrate one, or more, of those functions into serverless.\nNow - I know what you're thinking - why would we still use a Node server if we're moving to serverless? Like most transitions, not everything is done at once. You may have ten different things going on with your server and only some have migrated to serverless. Embracing serverless isn't about destroying every single server you have left, and there are some things that simply don't make sense in serverless. So I thought I'd build a simple Node app that demonstrated how this could look.\nI began with a simple Node/Express app that consisted of three different routes. A home page, a cat listing, and a dog listing. Here is my main index.js:\n\nI won't share the home view (it's all in GitHub), but notice my /cats and /dogs routes both work with a dataService module to get their respective list of animals. Let's look at that.\n\nI assume this is all pretty standard stuff, but let me know if not. In both cases, I'm faking an asynchronous response by using promises and static data. And just to be sure I'm not crazy, here is a screen shot of the only view that matters - the cats:\n\nAlright - so this is the core, initial application. It's static, but you could imagine that dataService module using Mongo or some other persistence system. Now let's see about migrating part of this to serverless.\nFor my demo, I'll focus on the Cats data since, well, that's obviously the most important one. I'll begin by creating a new action that handles this logic.\n\nNote that I've modified the list of cats a bit just to make it obvious to me in the UI that I've switched to it. After saving this file, I used the wsk command line to deploy it to Bluemix:\nwsk action create safeToDelete/getcats getcats.js\nAnd then obviously ran a quick test to ensure it worked:\nwsk action invoke safeToDelete/getcats -b -r\nThe result is what I expect:\n\nWoot. Ok, so how do I update my Node app to use it? As I said, there is a REST API, and I could just hit that by making a HTTP request. Instead, I'm going to use the openwhisk npm module. It makes using OpenWhisk actions a heck of a lot easier. How easy? Here is my new dataService.js:\n\nI begin by adding the openwhisk module to my code, specifying my options, and then creating a new ow object. Note that you can, and probably should, specify your credentials with environment variables. If you do, you can skip the entire options part and just go to town.\nNext - look at getCats. My index.js file was already expecting a promise, and the OpenWhisk library uses promises in everything it does. So I run my action and ask it to be blocking and get just the result. (This works exactly like the CLI.)\nThat's almost enough. Unfortunately, my initial code returned an array, and my OpenWhisk action returns a JavaScript object with the array in the cats property. I could just tweak index.js, but that feels wrong to me. Instead, I made this change:\n\nI simply wrap the OpenWhisk call with my own Promise, and when OpenWhisk is done, I resolve just the cats.\nAnd that's it. Later if I migrate dogs to OpenWhisk as well, I could update that too. Or I may not. And that's the point. Moving to serverless can be done as much, or as little, as it makes sense for your project. Let me know if you have any questions, or comments, about this example.\nFull source code: https://github.com/cfjedimaster/Serverless-Examples/tree/master/nodeexample\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "TIL about Datalist and Display Limits",
		"date":"Thu Jun 01 2017 15:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1496329740,
		"url":"https://www.raymondcamden.com/2017/06/01/til-about-datalist-and-display-limits",
		"content":"I've blogged about the &lt;datalist&gt; element a few times before (I'll link to them at the end of this article), and while support is ok (if you ignore Safari and iOS), it's one of my favorite HTML tags. It's pretty rarely used (as far as I can tell) but has a real good practical effect much like the &lt;details&gt; tag. As a spec, it is a bit in flux. When I brought it up last time on Twitter, Šime Vidas brought up how there are different implementations/specs in play. That's the focus of this article.\nA few days ago, a reader reached out with the following problem:\n\n\ni found your helpful example on datalist usage many mounts ago, in :\n\n\nhttps://www.raymondcamden.com/2012/06/14/example-of-a-dynamic-html5-datalist-control/\n\n\nbut now i have a question:\n\n\ni get about 100 or more items from an ajax call and append (innerHTML) them into a datalist on my form. but when i searching my desire text, only 20 items show in dropdown. is there a way to increase number of datalist options more than 20?\n\n\nI quickly built a demo with 20+ datalist items to see if I could recreate this. I was pretty sure it wasn't an issue with the Ajax call, but just the number of items. Here is the demo I created:\n\nVery exciting, right? I set this up on jsbin.com and you can run the demo yourself here: https://jsbin.com/cikuzab\nSo what did I see? I tested first in Chrome and it worked as expected. Just double clicking or typing A showed more than 20 results. I did see something odd when devtools was open - the UI goes &quot;over&quot; the window bounds:\n\nBut this looks to be an issue with responsive view in dev tools in general. Consider this date input:\n\nAs the UI for these controls are using (I believe) underlying OS controls, I don't think there is much that can be done about it - and frankly I'm a bit off topic now, but again, it works - 20+ items are shown.\nI then tested in Microsoft Edge, and it worked just as well.\nFinally, I tested in Firefox and here is where things get interesting:\n\nFirst notice that it contracts the list UI a bit. I like this as it feels like a nicer design. My form only has one element, but in a real form, I think this would work a lot better. However...\n\nNotice that I'm only seeing AA20. It looks like Firefox limits the list items to 20. This seems a bit weird since they are already keeping the UI nice and small and providing a scrollbar, but, yep, that's exactly what the reader ran into. If I type AA2, I can finally see all the matching items:\n\nSo yeah... there ya go. And as Šime had warned me on Twitter, this is definitely a feature that is a bit up in the air right now. Would I avoid datalist? No - I still think it is useful and it degrades nicely in my opinion, but definitely keep this in mind. If you were using Ajax along with the control, you could wait till you have enough input that has 20 or fewer matches, or even add a warning dynamically next to the control.\nAre any of my readers using datalist? Here are some other articles I've written on the topic:\n\nExample of a dynamic HTML5 datalist control\nDatalist version of a Country Dropdown\n\n",
		"tags":[
	        
            "html5"
            
		],
		"categories":[
            
                "html5"
            
		]

	},

	{
		"title": "Updating NodeJS on Windows - Some Tips",
		"date":"Wed May 31 2017 15:15:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1496243700,
		"url":"https://www.raymondcamden.com/2017/05/31/updating-nodejs-on-windows-some-tips",
		"content":"I love Node, but I swear every time I go to update it I end up running into one problem or another. And this has been true for both Windows and OSX. I went through an update yesterday for the release of Node 8 and I thought I'd share what I ran into and how I corrected it. Please, please do not take this as the best recommendation. I simply want to point out a bug I ran into, how I got around it, and what I used to get me back up and running.\nI'm going to divide these tips into two sections, one for the &quot;main&quot; Windows OS and one for WSL, the Windows Subsystem for Linux. I spend most of my time in WSL and I really recommend it for most devs, but I use the main Windows terminal for Cordova-related testing so I wanted it updated as well.\nWindows Subsystem for Linux (WSL)\nI'm going to start with the WSL because, as I said, it is my preferred environment. For my first attempt at upgrading (I've got two Windows machines), I downloaded the Linux Binary (x86/x64) and copied it into my shell via the Terminal. I extracted (using tax xf thefile.xz) and then sudo copied the node and npm executable from the extracted file. I then tested with node -v and npm -v and while Node worked, npm pooped the bed. I then went into that bin folder, ran npm from there, and did: sudo npm uninstall -g npm followed by sudo npm install -g npm. The first time I ran the install it threw up an error so I did it again because, why not, and it worked.\nAll in all this felt like an all around bad idea even though it seemed to work. When I got to my second machine, I came across this good blog post: My Bash on Windows Dev Environment where the author suggested using a tool called n. The command he suggested specifically did not work for me because I had Node already installed, but when I went to the project page for n, they suggested simply installing via npm and that worked fine.\nOnce n is installed, it's pretty easy to update: sudo n latest. And of course, like nvm it allows you to install other versions as well if you need to switch back and forth.\n\nWindows Terminal\nSo as I said, I spend most of my time in WSL, but I still wanted Node at v8 for my &quot;main&quot; Windows, ie what I get via cmd.exe and PowerShell. To upgrade I decided to take the easy route and just get the installer. I figured that would be a no-brainer. But then I ran into an odd issue. After installing, my Node was at v8, but my npm was at v4.5.0.\nWhat the heck???\nI uninstalled, reinstalled, uninstalled again and ensured the folder was gone and my recycle bin was empty, and it just didn't work. So I filed a bug (13311) thinking that perhaps it was a simple installer issue.\nLet me just state that as a first time bug-poster on the Node project, I was really impressed by the support I got. I had multiple folks offering to help, and coworkers as well. (I know we have IBMers helping develop Node, but it didn't even occur to me to ask on our local Slack.) It was their help that fleshed out the issue.\nUser @refack suggested I run where to find any npm commands that may still be around. When I tried where in Powershell, it didn't work, so I just assumed it didn't work, but running it in cmd.exe revealed the issue right away:\n\nSee the npm in C:\\Users\\ray\\AppData\\Roaming\\npm? Sometime in the past I must have updated npm by using npm install -g npm, which copied the file there and left it after uninstalling. (I'm going to report that.) I corrected this by simply doing npm uninstall -g npm and that left the &quot;new&quot; npm to be the only one found on my system.\n\nAlso note that you can run nvm for Windows: nvm-windows. I didn't bother with this since WSL is my main environment and I don't see updating the core Windows version of Node very often.\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Handling Errors in OpenWhisk Sequences with a Combinator",
		"date":"Fri May 26 2017 16:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495814640,
		"url":"https://www.raymondcamden.com/2017/05/26/handling-errors-in-openwhisk-sequences-with-a-combinator",
		"content":"As I build more powerful sequences in OpenWhisk, one of the issues I've run into is how to handle &quot;routing&quot; in sequences. Basically, given a sequence of A=&gt;B=&gt;C=&gt;, there may be times when:\n\nB may throw an error\nB may be all I need to do and we can skip C\nA decides it wants to skip B\nB may throw an error but I want to keep trying for a while\n\nAll of this is technically feasible, but not necessarily simple to set up. Remember that there is a Node package for working with OpenWhisk. In theory, I could build an action that makes use of this package and does all of the above directly in code. But what I'm more interested is how this could be set up at a &quot;meta&quot; level. Basically, I want to do all of this in an abstract manner and leave my actions as pure code.\nUnfortunately what I've described above isn't quite possible yet, but you can get close to it with the Combinators package. At the time I'm writing this, the combinators package isn't documented in the reference, but you can get information about the package using:\nwsk package get --summary /whisk.system/combinators\nInstead of copying and pasting the result in, I'll format it a bit. Here are the actions:\n\n/whisk.system/combinators/eca Event-Condition-Action: run condition action and if result is successful, run action.    (parameters: $actionName, $conditionName)\n/whisk.system/combinators/retry Retry invoking an action until success or attempt count is exhausted, which ever comes first. (parameters: $actionName, $attempts)\n/whisk.system/combinators/forwarder Forward parameters around another action. (parameters: $actionArgs, $actionName, $forward)\n/whisk.system/combinators/trycatch Wraps an action with a try-catch. If the action fails, invokes a second action to handle the error. (parameters: $catchName, $tryName)\n\nFor today, I'm going to focus on the trycatch action. As it says in the description, it wraps an action and lets you call an action on an error occurring. So imagine this action:\n\nAs you can see it just takes one argument, error, and if it is true, it will throw an error. Let's run it:\nwsk action invoke safeToDelete/divide -b\nAnd the result. (By the way, ignore the name 'divide' - I was thinking about doing something else.)\n\nAnd now let's try with an error:\nwsk action invoke safeToDelete/divide -b --param error true\n\nAlright - pretty much exactly as expected. So now let's try with the trycatch action. It takes two arguments - the name of the action to try and the name of the action to run on error. Here's how you would call it:\nwsk action invoke /whisk.system/combinators/trycatch --param '$tryName' //safeToDelete/divide --param '$catchName' //safeToDelete/error -b\nMake note of a few things here. The use of /_/ is an alias to my namespace. The combinator action isn't running in my local namespace so this helps let it know where the actions exist. I could make a bound copy of the action to have my own copy, but that's not really necessary. Next, make note of the single quotes around the params. When I forgot them, my shell had issues with the dollar signs in front of the params. Thanks to Carlos Santana (once again) for the help. When run, the output is the same (technically not the exact same, the metadata is different, but the result is the same).\n\nAnd here is an example where we force an error:\nwsk action invoke /whisk.system/combinators/trycatch --param '$tryName' //safeToDelete/divide --param '$catchName' //safeToDelete/error -b --param error true\n\nPay special attention to the status. The result is now marked as successful. My error came from the action I built to handle it:\n\nObviously I could do a bit more here but you get the basic idea. This could also be added as part of a greater sequence. Unfortunately, there isn't a nice way to handle &quot;A, B and maybe stop or go to C&quot;. The eca action comes close, but requires you to throw an error in B, which is wrong in my opinion. The good news is that there is some improvement to this space coming soon. Watch my blog for updates. :)\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "OpenWhisk Webinar and Presentations for June",
		"date":"Tue May 23 2017 21:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495573800,
		"url":"https://www.raymondcamden.com/2017/05/23/openwhisk-webinar-and-presentations-for-june",
		"content":"In June, I'll be giving two presentations on Apache OpenWhisk. The first will be online (and free) on June 7 at 12PM CST. You can register for the event here: https://engage.vevent.com/index.jsp?eid=556&amp;seid=90389\nAnd if by some chance you happen to be in the great state of Louisiana and want to see me give the talk live, I'll be giving the presentation to the Acadiana Software Group on June 14 at 6:30 PM. I'll try to bring some schwag for that one. Here's a Facebook event link if that's your thing: https://www.facebook.com/events/1920692501553535/?acontext=%7B%22ref%22%3A%224%22%2C%22action_history%22%3A%22null%22%7D\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building the Serverless Superman",
		"date":"Fri May 19 2017 21:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495229400,
		"url":"https://www.raymondcamden.com/2017/05/19/building-the-serverless-superman",
		"content":"So yes - I built something stupid again. Recently I discovered the awesomeness that is @Big Data Batman. This is a twitter account that simply copies tweets with &quot;Big Data&quot; in them and replaces it with &quot;Batman.&quot; It works as well as you may think - either lame or incredibly funny. (At least to me.) Here are a few choice samples.\nOpen any business publication and you’ll probably find an article about Batman. https://t.co/RTPF0PIvNs pic.twitter.com/PpsvDz2bFC&mdash; Big Data Batman (@BigDataBatman) May 19, 2017\n\nIoT Guide: Difference between Batman and Internet of Things https://t.co/09S56zqWij&mdash; Big Data Batman (@BigDataBatman) May 19, 2017\n\nSenior Developer – C#, .NET, IoT, Batman https://t.co/ulcL5zwotr Job Essex&mdash; Big Data Batman (@BigDataBatman) May 19, 2017\n\n\nThat last one is a bit subtle.\n\nI thought it would be fun to build something similar for serverless, and obviously, I had to name it Serverless Superman.\n\n\n\nAlright, so how did I build this? My basic idea was this:\n\nOn a schedule, look for tweets about serverless from the last X minutes, X being the same as my schedule, find a random one, replace the word \"serverless\" with Superman, and tweet.\n\nAs with everything complex I've done with OpenWhisk, my solution involved a sequence of actions. I began by plotting out my actions in text form.\nseq1:\naction1:\nset search: serverless\nsince: today\ntwitter/getTweets\naction3:\nremove RTs or older than X minutes\naction4:\npick one and use Superman\ntwitter/sendTweet\nI named it &quot;seq1&quot; as I thought maybe I'd end up with multiple sequences, but one was enough. Let's break this down action by action.\nAction 1: setupsearch\nThe purpose of this action was to serve as the input provider for the next one that will perform the Twitter search. Here is the code:\n\nThe only real complex part here is since. The Twitter API lets you filter by date. Unfortunately you can't filter by time. That's going to be a problem later on but I'll address that in the third action. Notice I'm using two keys related to my Twitter account. I got these by logging into the developer portal with my Serverless Superman account.\nAction 2: twitter/getTweets\nThis is an action I built as part of a public package. You can find the complete source code here: https://github.com/cfjedimaster/twitter-openwhisk. I'm not going to share the code since I blogged on it a while back, but I did have to update the package action to support the &quot;since&quot; argument.\nAction 3: filterresults\nThe purpose of this action is multifold. It's main role is to filter the Tweets, but I also flatten the data quite a bit as well. I filter out retweets, replies, and items older than X minutes, where X is 10.\nFinally I return an array of results where I just carry over the id, text, created_at, and hashtags value of the tweets.\n\nOne thing that kind of bugs me is the TOO_OLD value. Right now I have to ensure it matches my cron job (more on that later) and if I forget then I'll have a issue with my data. It's not that too bad of an issue and so I just got over it.\nAction 4: makeresult\nYeah, that's a pretty dumb name. The idea for this action is - given an input of tweets, pick one by random and replace the word &quot;serverless&quot;. Here is where things get a bit wonky. Sometimes I found tweets where &quot;serverless&quot; wasn't in the text. When I looked online, I saw them in the hashtags. Ok, so I updated my code in action 3 to include the hashtags. This is where I then discovered that the Twitter API seemed to not include all the hashtags I could see in the Tweet.\nSo... I shrugged my shoulders and got over it. As you can see, I wrote a note that it would be good to not give up and select another Tweet, but I thought maybe Serverless Superman could just STFU for a bit and wait.\n\nNote that if I don't have any Tweets or if I can't find the word &quot;serverless&quot;, I reject the sequence. This is not the right thing to do. OpenWhisk does support conditional sequences but it's a bit... complex right now. There is an open issue to make it a bit simpler and when that happens, I'll consider updating the post then, but for now I dealt with it. It does mean, however, that my action is going to report errors when an error really didn't occur.\nAction 5: twitter/sendTweet\nFinally - I send my Tweet. This is a new action in my Twitter package so I'll share the code here.\n\nNothing real complex here, but note I'm only allowing for text based Tweets. The API supports a lot more than that.\nFinally, you may have noticed that my sendTweet action requires multiple authentication tokens. How did I pass them? I didn't. I simply used the OpenWhisk &quot;bind&quot; feature and made a copy of my package with all my tokens attached to it. Bam - done.\nPutting it Together\nThe final bits included actually setting up the scheduled task. The first part required making a CRON based Trigger. Here's the command I used for that:\nwsk trigger create serverless_superman_",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My First Lynda Course - Learning Ionic",
		"date":"Fri May 19 2017 16:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495209720,
		"url":"https://www.raymondcamden.com/2017/05/19/my-first-lynda-course-learning-ionic",
		"content":"So this kinda snuck up on me (in terms of how quickly it got produced I mean ;), but I'm happy to announce my first course for Lynda.com (AKA LinkedIn Learning):\nLearning Ionic\nIt's a short course meant to introduce folks to Ionic 2 (but will work just fine with Ionic 3/X) and I think it's a rather 'gentle' introduction for folks who have only seen Ionic 1 so far and are a bit worried about the jump to Ionic/Angular 2. Here are topics covered:\n\nMaking a default application\nExploring application files\nAdding navigation and UI components\nTheming\nUsing a hard-coded detail view\nAdding the SW provider via CLI\nMaking a list and a details page\nAdding a loading widget\nUsing native and market Ionic resources\n\nAnyway, I think it's good. But obviously I'm biased. ;) As always, check it out, let me know what you think (positive or negative), and enjoy. I know I've been laser focused on serverless lately, and that certainly isn't because I'm done with Ionic!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Creating a JSON Feed for Hugo",
		"date":"Thu May 18 2017 18:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495132320,
		"url":"https://www.raymondcamden.com/2017/05/18/creating-a-json-feed-for-hugo",
		"content":"Edit on 3/26/2018 to fix tags, thanks to commenter Carl Recently a new specification was launched to recreate RSS in JSON, JSON Feed. For folks who may not be aware, RSS is an XML spec (well, multiple ones) for sharing content between sites. Blogs, primarily, and content-heavy sites typically make use of this. I'm not sure how many people outside of developers actually use RSS, but it's still definitely a &quot;thing&quot; even if you don't necessarily think of it when thinking about APIs.\nThe idea behind JSON Feed is to simply recreate the same, or similar, functionality in JSON as opposed to XML. You can read more about the launch on their announcement post and read the full spec as well. (And actually, this is one of the better written specs I've seen. You won't need a PhD in CompSci to grok it.)\nI thought it might be fun to build a JSON feed for my Hugo site. I ran into some trouble getting the JSON output working right and want to thank @bep for helping me out on the forums. I based my solution on a new Hugo feature, Output Formats, but obviously I may have done this in a completely stupid manner as well. As always, I encourage folks to leave me a comment and let me know what could be improved.\nALright, so to begin, I created a file in /content called jsonfeed.md. This file is empty and just serves as a way to tell Hugo where to create the feed.\n\nNote that I'm specifying json as my output and I'm saying my layout will be the feed template. That's where the real work takes place:\n\nMost of this should make sense I think - but let me call out some particular aspects. First, I've made description and author both optional based on whether or not the user has this in their Hugo settings.\nIn the range call, I had to use slightly weird syntax to support &quot;include a comma except for the last iteration.&quot; This syntax makes zero sense to me, but I copied it from another support post by @bep located here. Even though the internal if with the comma is at the top, it renders at the bottom. Yeah, ok.\nAs for each item, for the most part it just plain works as shown. Hugo summaries are plain text only so I used content_text instead of content_html. I run it through a pipe to make it safe for JSON. For tags, I was a bit torn. Hugo supports both categories and tags, and in theory you could use both I guess. Or just categories. I used tags just to keep it simple, but that's definitely something you would want to look into for your own Hugo site.\nFinally, the date one rendered a bit weird on my blog. Let's look at the output to see the full response (I removed a bunch of items to make it shorter):\n\nNotice the double -0700 -0700? I'm pretty sure that's just an issue on my blog due to how I specify dates in my posts. I probably did it wrong, but with near 6000 posts, I'm not changing it. For my fix, I switched up the above code to:\n\nAnd that's it. As I said, there's probably a better way of doing this, and I honestly don't know if anyone is going to even make use of JSON Feed, but if folks want mine, they can find it at https://www.raymondcamden.com/jsonfeed/index.json\n",
		"tags":[
	        
            "hugo"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Using URL Paths in OpenWhisk Web Actions",
		"date":"Wed May 17 2017 15:42:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1495035720,
		"url":"https://www.raymondcamden.com/2017/05/17/using-url-paths-in-openwhisk-web-actions",
		"content":"Time for another quick OpenWhisk tip. As you know (or may know!), when you create an OpenWhisk web action, you can pass parameters via the query string or via a form post. So consider the following trivial action:\n\nAll this action does is say hello to a name that comes from the arguments passed to the function. After creating the action (and enabling web support), you can then hit it at your URL like so:\n\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/pathTest\nAnd pass a name like so:\n\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/pathTest?name=Raymond+Camden\nCool. But what if you want to use the URL path instead of query parameters? Perhaps something like so:\n\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/pathTest/name/Raymond+Camden\nThe good news is that this is pretty simple to support. OpenWhish will pass this information to your action as args.__ow_path. OpenWhisk actually passes a bunch of things you can read about here, but for our purposes, the __ow_path value is all we need. So consider this new version:\n\nAll I've done is look for the path, see if it has length, and then I parse it. Now - in my particular case I'm assuming only one valid path: /name/X. Obviously you could write the code to be a bit more generic, perhaps in the form of: /name/value/name/value etc. But to keep it simple I just look for /name/X and if that matches, set args.name to it. The result works perfectly:\n\nHeh, oops. Almost. So you may have noticed I used decodeURIComponent above, and it works correctly if you encode spaces with %20:\n\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/safeToDelete/pathTest/name/Raymond%20Camden\n\nSo from what I've seen in my research, the plus sign is not meant to be decoded, and it may actually be part of the original string. So what you do here is up to you really. In this particular use case where I'm working with names, it would probably be safe to go ahead and replace plus signs with spaces:\n\nI hope this helps! As a quick aside, the URL in my tests includes safeToDelete. That has nothing to do with the post. I'm just trying to use that package as a way to flag to myself actions I can safely delete later. (As you can imagine, I've got a bunch of crap up now on OpenWhisk and I'm starting to feel like I need to clean up a bit!)\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "My Own OpenWhisk Stat Tool",
		"date":"Mon May 15 2017 16:22:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1494865320,
		"url":"https://www.raymondcamden.com/2017/05/15/my-own-openwhisk-stat-tool",
		"content":"While waiting at the airport this past weekend, I worked on a little utility to help me retrieve information about my OpenWhisk actions. As you know (hopefully know!), Bluemix provides a &quot;stats&quot; page for your OpenWhisk stuff but it is a bit limited in terms of how far it goes back and doesn't yet provide good aggregate data about your action. So for example, I really wanted to see how well my action was responding in a simple tabular fashion. With that in mind, I built a command line tool that provides a report:\n\nYou'll notice in the screen shot above that the action I tested has exactly 2000 activations. That's because I put an upper limit on the total number of activations returned by the code. This was somewhat arbitrary and could be tweaked of course. This particular action, getTraffic, has been running for months and probably has around 20k activations. Currently there isn't way to get the total number of activations though. (I've filed a bug report on that though.)\nThe code in question is relatively simple, although a bit ugly in terms of how I progressively load the data. I tried a Promise-based approach but had difficulties figuring out how to make that work with the API. Note that the code expects your credentials in a file called creds.json. This needs to include two values, apihost and api_key, both of which you can get from the OpenWhisk CLI.\n\nYou can find this code (and make improvements!) here in my GitHub repo:\nhttps://github.com/cfjedimaster/Serverless-Examples/blob/master/stats2/getstats.js\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Some thoughts on HoloLens (and the new Surface Laptop)",
		"date":"Fri May 12 2017 17:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1494608460,
		"url":"https://www.raymondcamden.com/2017/05/12/some-thoughts-on-hololens-and-the-new-surface-laptop",
		"content":"I'm currently at the tail end of my first Microsoft Build conference and had the chance to spend time with the\nHoloLens. I thought I'd share what it was like to use it in person since the videos don't really do it justice. I've also had the chance to get hands on the new Surface Laptop and I'll talk about that at the very end.\nI want to begin with a bit of context about what exactly the HoloLens is. HoloLens is augmented reality, not virtual reality. Microsoft refers to this as &quot;mixed reality&quot;, but I figure mot people have heard of AR and can understand that better. If you don't know what that means, the difference is that instead of a virtual environment that surrounds you completely (VR), AR adds items to the reality around you. When wearing the HoloLens device, you can still see everything around you in the real world world, but the hardware will 'draw' various things on it to augment what you see.\n\nSpeaking of the hardware, it's pretty slick, but definitely a 'process' to put on. The first time I used it I had trouble setting iot up right, but by the second and third demo it was pretty trivial. It isn't heavy, and it works with glasses, but it did feel a bit heavy on the frame of my glasses. The key thing to remember is that the hardware actually contains everything. You aren't tethered to a machine for it to work correctly. You can walk around and interact without having a cable tripping you up.\nThe next thing you discover - rather quickly - is that the area where the AR works is a small window of your total view. Essentially - look forward, and imagine a rectangle on the right hand side. This box is where the virtual stuff will be displayed. This sounds limiting, and sometimes it is, but it really comes down to the use-case/demo you're participating in. In some cases it was pretty obvious, and in others it wasn't noticable at all.\nAnd it's the demos where I quickly got a feel for where HoloLens worked really where, and also where it simply just didn't make sense. The first demo, a Mars exploration, was really cool, but in the end, didn't really work as a great example of the hardware. Imagine being in a room, and as you look around, you'll see the surface of Mars. It is impressive, but really more suited for a more full experience with VR. I'm guessing Microsoft starts with that as the later demos or a bit more serious, but honestly, it wasn't even close to the best thing I saw.\nIt was the later demos where things really began to click. There were multiple examples of &quot;product configurators&quot; that I think we're great. So for example, configuraring a car, seeing it in 3D sitting on a table, and then having a full size version to look at. That was impressive. On paper, it sounds boring compared to Mars, but in practical usage, it worked really well. A later example used a motocycle that was modified in various forms. The AR displayed the changes over the 'shell' of a bike and again, I really thought it was great.\nAnother demo involved construction. First, it showed taking plans off a screen and throwing it on a table. This worked well for giving me feedback on how the plans would work in real life. Basically imagine being able to build a model from 3d plans instantly. And even better, you could adjust the 3d model and it applied back to the original plans. Cool, but it got better. The demo then actually 'blows up' the plans around us to demonstrate how a particular door isn't going to work due to a load bearing girder. The demo than shows a proposed alternate placement for the door. Again, all of this sounds rather dry, but imagining this out in the real world it's going to work really freaking well. Oh - and in that same demo, I could &quot;look&quot; at a wall and see the stuff inside the wall. Neat!\nBut there was one demo that truly sold me on this as a product. I have an unnatural fear of being electrocuted by wiring. I know it isn't that big of a deal, but I absolutely hate even the thought of doing minor home repairs that involve electricity. Hell, I get a bit nervous if I have to plug something in and my hand strays near the metal plugs. There's no real good reason for this, it just makes me nervous.\nOne of the demos involves Skype integration, and basically it begins as a video call in the HoloLens. That's actually kinda cool by itself. But the real demo involes replacing an electrical switch. Probably a pretty trivial job, and it was completely fake in terms of 'real' wiring, but I was definitely nervous when I began. Here's where it gets cool. My partner on Skype had me look at my tools and was able to actually draw an arrow to what I needed to use first. When I picked up the voltmeter, she told me how to use it. She then walked me through attaching the switch, again using arrows, lines, etc, to clearly tell me what to do. On her side, she could see everything I did to help direct me and keep me from killing myself. (Ok, yes, I know it wasn't really dangerous, but like I sai",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Enabling CORS for an OpenWhisk Action",
		"date":"Tue May 09 2017 17:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1494352440,
		"url":"https://www.raymondcamden.com/2017/05/09/enabling-cors-for-an-openwhisk-action",
		"content":"A few weeks ago I blogged about how to enable CORS for OpenWhisk Web Actions. In case you aren't aware, CORS is the standard way to allow client-side web applications to access your APIs. (JSON/P, an older method, still works, but CORS is really what you should use.) If you read the post, then you know it isn't too difficult at all, but I wanted to share another way of doing that's even easier.\nFairly recently, we released new API management support for OpenWhisk actions. This is a huge feature set and something I want to talk a lot more about, but right now I'm between trips so it may be a few weeks before I cover it here. The incredibly simplistic summary of this feature is that it allows you to expose your OpenWhisk actions in a managed API. You can lock down who has access while also adding rate limiting. You also get basic stats about usage to help you track your API traffic. If you want a quick overview, watch this video.\n\nWhile the OpenWhisk management console has tools to help you manage your APIs, you can also use the CLI to do some of the same actions. One of the cooler things you can do is point to an action and simply say, &quot;OpenWhisk, give me an API and tailor it for GET requests returning JSON.&quot; Alright, you don't actually say that, but let's check it out.\nFirst, here is my action. It is a simplified version of the code I used in the previous demo, but this one is not returning headers and the like - just raw data.\n\nFirst I push this up as corstest2 like so:\nwsk action update corstest/corstest2 corstest2.js --web true\nNotice I'm enabling web action support, but my code isn't bothering to return the proper headers and body, it's still rather simple. Now I enable an API:\nwsk api create /corstest /test2 get corstest/corstest2 --response-type json\nThis is pretty similar to the older api-experimental CLI examples you will see on my blog, but that has been deprecated now in favor of the new management system. The first argument, corstest, sets up a base path while the second, test2, is the path for this particular action. I tell it the method (get) and the action (corstest/corstest2) and finally the response type we want, json. And literally - that's it. The CLI spits out the URL (which I took a screen shot of but it was kind of ridiculous long) but here is what it looks like (spaces added for wrapping):\nhttps://service.us.apiconnect.ibmcloud.com/gws/apigateway/api/37871051d18d0b2115da90f2924\n58913e22e5d182c8a965fadcfbf6b5fcc96c6/corstest/test2\nOnce I had that, I updated my client-side demo to use that URL.\n\nNot really that interesting, but super simple. If you want to run this demo, you can do so here:\nhttps://cfjedimaster.github.io/Serverless-Examples/corsdemo/test2.html\nBe sure to open your console though as I don't spit out anything in the DOM.\nAnyway - as I said, there is a lot more to this particular feature than what I'm showing here, but I thought it was a cool update to the problem of enabling CORS support.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Launching Today - Serverless Weekly",
		"date":"Fri May 05 2017 16:56:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1494003360,
		"url":"https://www.raymondcamden.com/2017/05/05/launching-today-serverless-weekly",
		"content":"The title says it all. I've been a fan of the Cooperpress weeklies for sometime now and I'm happy to say I'm helping them launch a new one - Serverless Status. (Yeah, I called it &quot;Serverless Weekly&quot; above, but you get the idea. ;) As with the other weeklies, this a free newsletter providing interesting links concerning serverless stuff. This will include blog posts, demos, product announcements and more. I'll be contributing to it, so feel free to DM or @ me suggestions.\nYou can sample the content (and sign up) now at https://serverless.email.\n",
		"tags":[
	        
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "New Book: Mobile App Development with Ionic 2",
		"date":"Thu May 04 2017 16:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493915520,
		"url":"https://www.raymondcamden.com/2017/05/04/new-book-mobile-app-development-with-ionic-2",
		"content":"Guess what I got my grubby little hands on?\n\n\nYep, a brand-spanking new book on the latest version of Ionic by Chris Griffith. I had the distinct pleasure of being one of the technical reviewers for this book and I enjoyed it quite a lot. I felt like I &quot;knew&quot; Ionic 2 going in, but obviously didn't know everything, and I definitely discovered some great new tips while editing. I also really like Chris' use of one main sample application throughout the book. The reader really gets a chance to see it grow and expand with new features. Pick it up by clicking the link below (and yep, that's an affiliate link so if you buy, I get a few cents, thanks!)\nMobile App Development with Ionic 2: Cross-Platform Apps with Ionic, Angular, and Cordova\nYou can read a sample here and I've included the table of contents below:\n\nChapter 1 - Hybrid Mobile Apps\nChapter 2 - Setting Up Our Development Environment\nChapter 3 - Understanding the Ionic Command-Line Interface\nChapter 4 - Just Enough Angular and TypeScript\nChapter 5 - Apache Cordova Basics\nChapter 6 - Understanding Ionic\nChapter 7 - Building Our Ionic2Do App\nChapter 8 - Building a Tab-Based App\nChapter 9 - Building a Weather Application\nChapter 10 - Debugging and Testing Your Ionic Application\nChapter 11 - Deploying Your Application\nChapter 12 - Exploring the Ionic Cloud\nChapter 13 - Progressive Web Apps\nChapter 14 - Conclusion\nAppendix - Migrating Ionic 1 to Ionic 2\nAppendix - Understanding the Config.xml File\n\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Building Your Own Serverless Search Engine with OpenWhisk",
		"date":"Tue May 02 2017 20:43:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493757780,
		"url":"https://www.raymondcamden.com/2017/05/02/building-your-own-serverless-search-engine-with-openwhisk",
		"content":"This is a demo I've been working on for some time. It isn't necessarily that complex (or cool), but it's just taken me a while to get the parts together. As you know, I'm a huge proponent of static site generators. My own site is run on one and I recently released released a book on the topic with Brian Rinaldi.\nOne of the things I cover in the book is how to &quot;bring dynamic back&quot; to a static site. That includes things like forms, comments, and search. In the book I recommend Google's Custom Search Engine feature. It's what I use for search here and it works well.\nBut I got to thinking - how difficult would it be to set up a similar system with OpenWhisk? All I would need is two parts:\n\nA way to index my content.\nA way to search my content via an API.\n\nTurns out, there's a pretty cool service that does this already - Tapir. Tapir lets you specify a site and it will begin indexing it for your automatically. You then simply use an API end point in your code to perform searches. This is a cool service, but I can't recommend it. While it still &quot;runs&quot;, the folks behind it no longer support it so it's not something I'd suggest. But it serves as a good basis for what I'd like to build in OpenWhisk, so that's how I got started!\nIndexing\nTo handle indexing, I needed a few components. First, I needed a way to parse RSS entries. That's easy enough. I built an action for this a few months ago. You can see it here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/rss. Here's the code:\n\nPretty trivial. I'm using xml2js to handle the parsing and then filtering down the result to just the items. Done.\nThe next thing I needed was a way to work with ElasticSearch. IBM Bluemix lets you provision a new instance in seconds, so I did that. Once I had it provisioned, I made use of a package I created to work with ElasticSearch. You can find the code here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/elasticsearch\nIt's also shared on OpenWhisk itself so you can bind your own copy at &quot;/rcamden@us.ibm.com_My Space/elasticsearch&quot;. The package has actions for adding items to your ElasticSearch instance, performing bulk operations, and doing searches. Obviously ElasticSearch supports more, but I built only what I needed. For my usage, I decided to use the bulk operation. I figured I'd take the RSS items and insert them all at once.\nTo make this work, I made a new action to sit between them. I touched on this in my previous post about using sequences as a way to massage input/output for actions. In that post I was focused on input/output, but obviously a sequence can be created between two &quot;pure&quot; actions and one in the middle that massages the data from one to the other. In my case, the action was called flattenRSSEntriesForSearch. Here's the code.\n\nFor the most part, this is just &quot;convert one array to another&quot;, and frankly, I just read the docs on bulk inserts and followed their direction. One cool thing about this setup is that ElasticSearch is smart enough to take my input and update existing items I already created. Notice I'm using the URL as the ID. Since URLs are unique, they work great as a primary key for my ElasticSearch data.\nSo I took my RSS action, my 'joiner' action above, and my bulk action, and made a sequence called rssToES. Here's how I'd call it from the command line:\nwsk action invoke -b -r rssToES --param rssurl http://feeds.feedburner.com/raymondcamden\nThen all I needed to do was make a trigger to call this once a day. Bam. Done. (Ok, I lie. I didn't bother making the scheduled task because I'd probably forget about it and it's just a demo so there's no need for it, but I could. Honestly.)\nSearch\nOk, so how do we handle search? First, we need to support a search string, obviously. ElasticSearch has a hella-long list of ways to search, but they also support a simple &quot;just give me a damn string&quot; style search which is what I'll use now. It's supported by the search action of my package, so that's good to go. But - that's not what I want to expose to the web.\nSo once again, I rely on the technique in the last post of using a sequence to massage my data. First, I created an &quot;input&quot; action called rssSearchEntry:\n\nMy ElasticSearch search action needs the search term as well as the index and type. I set index and type to hard coded values and then just pass on the search string. Search isn't too exciting but you can check out the code in the repo here.\nOnce the search action is done, I've got a result that includes metadata as well as matched documents. So I built a third action, rssSearchExit, to massage that into a simple array.\n\nNote that I also replace the body of the match, which includes the full HTML of the blog entry, with a shorter 'context' value that has HTML removed. This seemed like a good idea to me, but obviously you could leave that up to the client-side code if you wanted.\nThe last part of the action simp",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "OpenWhisk Sequences as Input/Output Providers",
		"date":"Mon May 01 2017 17:39:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493660340,
		"url":"https://www.raymondcamden.com/2017/05/01/sequences-as-inputoutput-providers",
		"content":"I've been thinking a lot lately about sequences and OpenWhisk. A few weeks ago I blogged about how well sequences work to let you &quot;mix and match&quot; different actions into new creations. Today I'm writing about another powerful feature of sequences, that as &quot;Input/Output Providers&quot;. I'm not necessarily sold on that name, but hopefully my blog post will make it clear what I'm talking about.\nBack when I first blogged on OpenWhisk (&quot;Going Serverless with OpenWhisk&quot;), I mentioned how the platform has support for automatically recognizing some of the services you add to Bluemix. In that post, I mentioned how when I added a Cloudant service, OpenWhisk automatically provisioned an instance of the Cloudant package with my credentials pre-set. This meant I could do full CRUD with Cloudant on OpenWhisk and not have to specify any authentication.\nCool!\nBut - while these actions worked well, they weren't necessarily set up, by themselves, to be called as part of an application. In the blog post, I got around this by writing an action that used the OpenWhisk NPM module to call my Cloudant action. I massaged the results a bit and then returned them. While that worked, it's actually much easier to use a sequence. Here's an example.\nImagine I've got a Cloudant package connected to a service with a simple database of blog entries. (I do, actually, and I'll be blogging about that demo later this week.) My goal is to create an API that returns all the objects from the database. That can be done with the list-documents action. The docs for the Cloudant package don't go over every action, but don't forget you can browse the package from the CLI like so:\nwsk package get &quot;/rcamden@us.ibm.com_My Space/Bluemix_CloudantForDemos_Credentials-1&quot; --summary\nThe list-documents action takes 2 params, dbname and params. For dbname, I can use blog, and for params, I need to tell Cloudant to return the full record so I'll use &quot;include_docs&quot;:true.\nOk, so given that, how do I make this easy with a sequence? First, I created a new action called getall_entry. In this case, &quot;entry&quot; refers to the entry point, or input, to the final sequence I'll create. Here's the code.\n\nYep, no logic at all. This simply outputs the parameters that will drive list-documents. I then created another action, getall_exit, and obviously, &quot;exit&quot; refers to the exit point, or output, for my sequence.\n\nWhen Cloudant returns data, it includes additional information and other things I don't need. I can use the map function on the result to massage the result into a simple array.\nSo the last thing I need to do is just create the sequence! I make a new sequence like so:\nwsk action create --sequence getall getall_entry,Bluemix_CloudantForDemos_Credentials-1/list-documents,getall_exit\nThe end result is a new action called getall that handles calling Cloudant for me and returning a nice array of data. What's cool is that I could completely switch database engines by just updating the action in the middle. No one using my action would ever need to know.\nIn fact, I did something a bit like that. I didn't switch engines, but rather switched actions. I'm still pretty rough with Cloudant, but I found that I couldn't sort my results when using list-documents. So instead I built a view that indexed my data by date. The Cloudant package offers the exec-query-view action for running views. I had to tweak my entry point a bit so I could tell the view to sort:\n\nI then updated my sequence to use exec-query-view instead. Net result? Same data, but sorted by date descending, which is a bit more appropriate for the demo I'm building. I could update my entry point to allow for the sort to be a parameter to make this a bit more realistic perhaps. Here is the output:\n\nAny questions, or suggestions, about this approach? Leave me a comment below!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Bound Packages, OpenWhisk, and Web Actions",
		"date":"Fri Apr 28 2017 16:08:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493395680,
		"url":"https://www.raymondcamden.com/2017/04/28/bound-packages-openwhisk-and-web-actions",
		"content":"Hey folks, this is just a warning to other users in case they run into the same issue I did. As you (may) know, OpenWhisk supports the idea of packages. Packages let you organize actions into a cohesive unit, much like packages in other languages/platforms. Packages can also have default parameters that apply to every action in the package.\nPackages can also be shared, which makes them callable by other users. And even cool, you can then &quot;bind&quot; a package locally to yourself. Why would you do this? First, it would let you make an simpler alias of someone else's package. Second, and I think this is the big part, it lets you specify your own default parameters.\nSo I've got a package for ElasticSearch (you can see the code here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/elasticsearch). I put this in a package called, wait for it, elasticsearch, and I then shared it publicly.\nFor my use, I bound it as myelasticsearch and set default parameters for my ElasticSearch authentication. Ok, so here's where things went a bit heywire. I could easily test myelasticsearch/search via my authenticated CLI calls, but I wanted to expose it as an anonymous API.\nTurns out - you can't. The basic issue is that my bound copy is - essentially - an alias. While I'm allowed to overwrite default parameters, I'm not allowed to overrule the &quot;Is this a web action&quot; setting. I think that makes sense, but I've forgotten this twice now so that's part of the reason I'm blogging it.\nSo what's the fix? Rodric Rabbah (fellow IBMer) suggested a simple fix - making a sequence. A sequence is a chain of actions that acts like one action. I can then web enable that one. I could actually make a new sequence of one action and web enable it, but for the specific demo I'm doing (hopefully out on a blog post today), there is a bit of stuff I can do there to make using the API simple.\nLet me know in the comments below if any of this doesn't make sense!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An Example of How I Dig Into Your Code",
		"date":"Thu Apr 27 2017 14:49:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493304540,
		"url":"https://www.raymondcamden.com/2017/04/27/an-example-of-how-i-dig-into-your-code",
		"content":"Like most web developers (or some of you I hope), from time to time I'll open up dev tools on a page and take a look at what's going on behind the scenes. This morning I was drinking my coffee, waking my sick butt up (allergies - never really been a problem before, but this year decided to go all epic on me), when I got an email about an update to a bug I had filed for ColdFusion. The detail page it led me to had some interesting issues and I thought it might be interesting to share some of the things I found.\nTo be clear, I'm not picking on the developer here. In fact, one of things I found is the same mistake I made early on in my career. But I thought it might be interesting to my readers for me to share how I dig into a site and poke around. This isn't a &quot;Full Investigation and Performance Report&quot; type thing. No, this is &quot;Let me poke around for 10 minutes with dev tools and see what I'd find.&quot;\nOk, so how did I start? First, this is the link I was sent regarding an update to a bug:\nhttps://tracker.adobe.com/#/view/CF-4198322\nGo ahead and click that link - it will open in a new tab.\nI remembered the bug, but I couldn't remember if I reported it or had just voted for it - or whatever. I then noticed something odd. Nowhere in the bug report does it say who made the bug. Hmm. Ok, let's open dev tools.\n\nLooking at those network requests, I assumed that the POST to /search was returning the bug data. Let's dig into that. Chrome's dev tools do a great job rendering JSON into a nice, easy to browse view. I dug into and found some interesting stuff:\n\nSo right away - I can see that maybe this is my bug. A custom field includes my name, and a unique ID that probably represents my user account. That's probably safe. If it is my bug, I have to wonder why they don't show it in the UI, but return it in the JSON. In general (stress - in general) - if you aren't using data, you don't want to return it. Every extra bit of data you don't display just makes your app slower. (Remember that tip, it's going to come back.)\nI then noticed the comments field. It mentions a length of two. I glanced back up at the actual view and saw there was no comments. Eh? Let's up that one.\n\nSo to me, those look like internal comments. We can see a changelist for the fix, even see what Java file was patched. We also see the name of the person who did it. If I had to guess, I'd bet the front end checks that visibility field to see if it should be displayed.\nAgain, hopefully nothing 'secret' is being returned in comments. But as I said - if you aren't using the data, why return it?\nSo I looked back in the list of network requests and noticed two things:\nhttps://tracker.adobe.com/api/search/created?id=5FBC41E943BD265C992015D5&amp;email=raymondcamden@gmail.com&amp;name=Raymond%20Camden&amp;startAt=0&amp;orderBy=createdDate&amp;order=DESC\nhttps://tracker.adobe.com/api/search/voted?id=5FBC41E943BD265C992015D5&amp;email=raymondcamden@gmail.com&amp;name=Raymond%20Camden&amp;startAt=0\nLooking at the URLs, it looks like this returns bugs I created and voted for. In my case, both of these are kinda big. Together they total around 200k. From what I can tell, nothing in this page uses it. If you click home, a full redirect is not done, so this data is not lost, so that's good, but I'd probably delay loading that on the issue view page. There is no guarantee I'm going to click around, and in fact, I'm probably here just to look at the bug report and thats it.\nIn both cases, the full issue is loaded, which could be loaded later. Why not return just the IDs, or the IDs and a title for viewing, but wait to load the details till later?\nAgain - this is exactly the kind of thing I did when I was learning to work with Ajax apps. I'd be willing to bet when then person who wrote this test, she or he had maybe 10-20 bugs assigned to them so the results were much smaller.\nOh - and the ID used in those values? You can absolutely use other IDs to load other people's reports. How do you find those ids? Either in the creator field (yes, I confirmed I was the creator of that bug), or in the comments. (As an aside, it looks like that creator field is only returned for stuff I made.) I picked an ID from one of the comments (sorry Jay Kirk) and confirmed I saw their data. Votes are public, as are bugs you create, so this isn't necessarily a security issue either. Just another example of the kind of things I try. ;)\nFinally - as I'm writing this - Adam Cameron is also digging around and noticed a lot of this info is dumped straight to the console itself - so there's no need to even dig into the network requests.\nI actually went a bit further and played with sending requests via Postman. I was able to trigger a stack trace:\n\nAn unhandled exception is also a &quot;No No&quot; for production sites, but this looks to be a Jira issue, not code written by the Adobe CF team as far as I can tell.\nAnyway - is this useful?\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using Device Motion on the Web",
		"date":"Tue Apr 25 2017 23:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493163000,
		"url":"https://www.raymondcamden.com/2017/04/25/using-device-motion-on-the-web",
		"content":"I'm currently working on an article for TDN that looks at how web standards have advanced in comparison to the default list of plugins supported for Apache Cordova. In my research, I looked at the Device Orientation API. Specifically, I was interested in device motion. For Cordova, motion and orientation are split into two plugins, but spec-wise, they are covered in - well - one spec.  In general, it is a fairly simply API to use. Here is an example from the Mozilla Developer Network page on device motion:\n\nFairly simple, right? Unlike the Cordova plugin which lets you get the current value or listen for the values at an interval, the web standards API is just an event. When it fires, it fires.\nI did some basic testing where I just listened for the event and logged the values. I found that in many cases, it fired all the time, whether or not the device was moving. I added a bit of logic to my code to limit my logging to cases where X, Y, or Z had at least a value of 1 or higher (after taking the absolute value). This restored some sanity at least. The best news, though, is how well this is supported. From CanIUse:\n\nThat's dang good! About the only issue here is desktop Safari, which we all know is only used to watch Apple keynotes.\nOk, so with that being done, I thought I'd take a stab at building &quot;shake&quot; support. Basically - monitor device motion and detect when it has been 'shaken' - which really comes down to math. Given we know how much they have moved, have they moved &quot;enough&quot; to consider it a shake.\nAbout a year ago, I built a demo of this for Ionic: Working with Ionic Native - Shake, Rattle, and Roll. The logic boils down to tracking the deltas (changes) in motion over the 3 axis and keeping track of when they move a significant amount a few times. Some of the numbers I used came from me simply playing with hardware and seeing what &quot;felt&quot; right, so obviously it could be adjusted. Here's what I came up with:\n\nLooking at the motion event, one of the first things I had to do was account for acceleration being null and switching to accelerationIncludingGravity. If you read the spec, you'll see this little nugget:\n\nImplementations that are unable to provide acceleration data without the effect of gravity (due, for example, to the lack of a gyroscope) may instead supply the acceleration including the effect of gravity. This is less useful in many applications but is provided as a means of providing best-effort support. In this case, the accelerationIncludingGravity attribute must be initialized with the acceleration of the hosting device, plus an acceleration equal and opposite to the acceleration due to gravity.\n\nBasically - it's a fallback. Once I've figured out where to find my values, I begin doing some parsing.\nThe first IF block was used to control logging initially - I was trying to avoid very small movements. In theory I don't need it since I'm more concerned about the deltas, but I kept it there as another way to limit false positives.\nThe rest of the method looks at the deltas, see if they are &quot;enough&quot; (and again, this was arbitrary based on my testing) and when I feel like it's time to consider it a shake, I log it.\nI tested this with my iPhone and it worked really well. I tested this on my desktop Chrome, and it worked in terms of having an event to listen to, but obviously I didn't shake my desktop. I tested this on my Surface Book with Chrome and MS Edge and both worked great.\nHere is a stupid animated GIF where I ran it in MS Edge and actually shook my laptop:\n\nPro Tip: Don't do that.\nSo at this point, I could actually call some other method to actually do something on the shake event, but I'll leave that up to the reader. What's cool is - you could do something like, &quot;Shake to reload&quot;, and as long as you provide some other way to reload, this is totally fine than in cases where it isn't supported.\nYou can find the code for this here: https://github.com/cfjedimaster/webdemos/tree/master/device_motion\nYou can run this as well: https://cfjedimaster.github.io/webdemos/device_motion/\np.s. I probably don't need the DOMContentLoaded event listener at all. When I first started, I thought I'd do a complete demo and maybe show random content on shake, but I figured I'd be lazy and leave it as is. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Updating Your OpenWhisk CLI",
		"date":"Tue Apr 25 2017 18:29:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1493144940,
		"url":"https://www.raymondcamden.com/2017/04/25/updating-your-openwhisk-cli",
		"content":"This is just a quick reminder that the OpenWhisk CLI tool updates often, and unfortunately, doesn't provide a warning when it has become out of date. I've yet to see things break, of course, but obviously as a developer you want to ensure you have the latest and greatest installed.\nFirst - you can get information about your install by running wsk property get:\n\nYou can see the CLI version highlighted above. You can compare this against the binaries hosted here:\nhttps://openwhisk.ng.bluemix.net/cli/go/download/\nJust today I saw a newer version was released. I downloaded the binary, copied it over, and verified the update:\n\nKinda icky for those of us used to doing everything via npm, but hopefully it will get a bit nicer in the future. With OpenWhisk stuff updating rapdily, I've been checking for updates once a week or so.\np.s. And as a total random aside, those screenshots are from the Windows Bash Unix subsystem which is running really freaking good.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Integrating HTML Templating with OpenWhisk Web Actions",
		"date":"Fri Apr 21 2017 17:37:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492796220,
		"url":"https://www.raymondcamden.com/2017/04/21/integrating-html-templating-with-openwhisk-web-actions",
		"content":"As always, when I blog about stuff like this, I want to remind folks I'm both new to serverless and new to OpenWhisk, so while what follows works, I'm not necessarily saying it is the best way to do things, or even a good idea. But let's be honest, that's never stopped me before, right?\nSo the question I had this morning was - given an OpenWhisk action built for Web Action support, how would you make use of a templating engine to help return an HTML-based response?\nMy preference for templating engines is Handlebars (although you can read about more options in my book), so I looked at that first. Here is the sample action I came up with.\n\nSo, the basic idea is this:\n\nSee if we have a template variable setup, and if not, do a file read of our template and create the Handlebars function for it. You may be wondering how that works considering the action is run in an atomic fashion with no real persistence. While that is true, a &quot;warmed up&quot; action will be kept alive. This makes simple caching like this ok. (I talked about this more here: Serverless and Persistence).\nI then get my data, which in this case is hard coded. Obviously I could use arguments passed to the action, make data on the fly, etc, but a simple hard coded object was enough.\nI then generate the HTML using the Handler's compiled function, template. (Not a terribly descriptive name. I apologize.)\nFinally, I return the generated HTML.\n\nThe template is rather simple:\n\nThe final bit is to simply create the action. I'm using a custom npm module so I had to zip it up, but I just used a shell script again:\n\nNote the --web true at the wsk call. This lets OpenWhisk know this is a Web Action. Finally, you can see the result here:\nhttps://openwhisk.ng.bluemix.net/api/v1/web/rcamden@us.ibm.com_My%20Space/templatetest/test.http\nSome thoughts:\n\nIf I was really going to use one template, I'd probably consider just using a template string in the action code. I feel like that would be a bit messy though, and I'd not get my nice HTML syntax coloring/autocomplete in my editor. But I'd consider it for sure.\nAnd of course, don't forget that template strings support variable substitutions, like a &quot;lite&quot; version of a template engine like Handlebars. But you don't get stuff like conditionals and loops though.\nHandlebars supports compiling template strings into functions and saving them as well. So a better approach would be to do that, require them into the action, and deploy that. You could use your shell/bat script to do all of this at once.\n\nAny comments, or concerns, about this approach?\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Artificially Delaying Providers with Observable.Delay",
		"date":"Wed Apr 19 2017 23:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492645440,
		"url":"https://www.raymondcamden.com/2017/04/19/artificially-delaying-providers-with-observabledelay",
		"content":"I'm still - grudgingly - making use of Observables in Ionic 2. As I've said before, I don't see anything wrong with Observables, I just find them overly complex and a pain to use. Half the time I get them working right it's because I've copied and pasted another example. I would say - easily - that out of all the changes with Ionic and Angular 2 (sorry, 4, um, whatever) it's Observables that I've had the hardest time adopting.\nBut while I'm griping about them, I do know they are hella powerful. Here is a great example. I was testing an Ionic demo that showed off the Loading widget. While it worked great, the API I was hitting returned so fast I didn't feel like I could get a good grip on how it would work in the real world.\nChrome has a very cool widget in DevTools that can slow your network requests down:\n\nIt's a cool little addition to the dev tool suite, but there's one problem with this approach. When enabled for an Ionic app running via the serve command, everything will be delayed, even the 'local' files that would load instantly on a device. (Minus the time it takes for the web view to process the code of course.) Because of that, it isn't necessarily a realistic test. Also, it can get quite annoying if you are reloading a lot while working on the app.\nSo - on a whim - I did some searching, and discovered that Observables support a delay operation. It just delays the output from the observable. Yeah, that's it. Certainly not rocket science or anything, but it's also super easy to use. Consider this provider:\n\nI've got a static list of data I'm converting into an Observable and then delaying by two seconds. So now when I run my app in the browser, the main app loads, my loading widget shows up, and then 2ish seconds later my data shows up.\nIn general, I try to avoid testing that involves modifying my code as it is exactly the kind of thing you forget and leave in place, but it worked so well for testing the loading widget I figured I'd make an exception in this case.\nHere is a super-advanced animated GIF showing this in action:\n\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Enabling CORS for an OpenWhisk Web Action",
		"date":"Tue Apr 18 2017 15:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492530300,
		"url":"https://www.raymondcamden.com/2017/04/18/enabling-cors-for-an-openwhisk-web-action",
		"content":"Here's a quick tip for you. If you are building an OpenWhisk action you plan on exposing as a web action, most likely you'll want to look into enabling CORS so you can call your code from JavaScript on the front-end. Since Web Actions can return both a result and headers, this is trivial to do. Here is a simple example.\n\nThere's two things to make note of here. First, obviously, is the Access-Control-Allow-Origin header. I'm using * which means it can be called from anywhere, but I could lock that down if I chose.\nFinally, when you return your data you have to base64 encode it. So the body key handles doing all of that after I've created my result value as I like.\nThe end result then is a simple endpoint I can hit from some client-side code:\n\nI also think it would be valid to build this as a sequence. So you could have webFoo being the action, comprised of a sequence of foo + webify (or some such) where foo represents the real business logic and webify handles the 'complex' output required to return the result.\nThank you to @akrabat on the OpenWhisk Slack for helping me figure this out!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "OpenWhisk, Serverless, and Security - a POC",
		"date":"Mon Apr 17 2017 15:38:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492443480,
		"url":"https://www.raymondcamden.com/2017/04/17/openwhisk-serverless-and-security-a-poc",
		"content":"Before I begin, I want to be clear that what follows is a proof of concept. It should absolutely not be considered a recommendation, but rather a starting point for conversation. I've been thinking a lot lately about how one could use OpenWhisk along with a security model of some sort. Specifically, &quot;Expose action so and so but only for authorized users.&quot; Obviously &quot;security&quot; can imply a lot more, but in this initial post I'm going to keep my requirements a bit simpler.\n\nAuthenticate the user somehow.\nMake OpenWhisk action Foo require a logged in user.\n\nFor authentication, I chewed on a couple of different things. In theory, I could setup a Cloudant database and build actions for the typical user register/login routine, but I really, really didn't want to do that. Instead, I decided to finally take a look at Auth0. I'd heard of this service before but never heard a chance to actually play with it. Turns out, it was pretty easy. (I've got some more comments about Auth0, but I'll save them for the end of the post.) I set up a new Auth0 application using their &quot;Lock&quot; client-side setup to handle authentication. I built an incredibly bad client-side demo with no real UI and - yeah - did I say it was bad? The front end consists of two buttons:\n\nI'm including Auth0 code at the bottom there along with jQuery. My two buttons handle logging in as well as calling the action I'll be demonstrating in a minute. The JavaScript code is mostly from the Auth0 code. Again, I want to be clear this is just some rough code to let me try stuff out.\n\nWhen the login button is clicked, I run the Lock API's show method. This is where Auth0 really kicks butt. It handles the auth process completely and in the end, leaves me with a JWT (JSON Web Token) I can use to authenticate my later calls. The getProfile call is from Auth0's sample code. I don't actually use it for anything practical. Let me show you how this all looks.\nFirst, the page as it loads by default (and again, this isn't meant to be production-ready):\n\nAnd here is how Auth0's auth routine looks like:\n\nBy the way, Facebook and Google were arbitrary choices - you can tweak that (again, I'll talk more about Auth0 at the end).\nAfter logging in, I'm brought back to my web page and have access to my profile, but the important thing is the id_token value. That represents the JWT value. I can use this in my OpenWhisk action to authenticate the request.\nJWT verification is rather easy. Here is the action I built:\n\nThe action needs two values, a key that is specific for my application, and loaded via a JSON file, and the JWT itself which is passed in. I require in the jsonwebtoken package which has a verify method I can run to verify the token is both valid for my application (based on the secret value) and a certain length of time. And that's it. Seriously - nice and simple, Right? I called this action jwtverify.\nSo to bring this together, I need to build an action that will secured. I create the following, super simple action called foo.\n\nI pushed this up to OpenWhisk. I now have a generic &quot;jwt verify&quot; action and a random action I want to secure. How do I do that? With a sequence. I created a new action called secureFoo that was based on the sequence: jwtverify+foo. (Technically I made these all under a package called secblog just to organize the demo a bit.) The end result is an action that acts like an Express app with middleware.\nTo test, I exposed secureFoo via a REST API (not as a web action because CORS isn't a simple addition yet). I then built this function to support that second button in my demo app:\n\nThe URL you see there is the path I exposed for my REST API. The crucial bit is at the end where I pass the token. This gets passed to the first action in the sequence, which verifies it, and then it carries on to the second. The result is what you would expect:\n\nWhew! Ok, done... kinda. Did you notice that my Foo action accepted an argument? I passed name=Ray in the URL, but it wasn't reflected in the result. One important thing to remember about sequences is that the arguments sent as input only go to the first action. If you want any later action to work with input, they have to be passed along as the result of each previous action.\nSince our first action is a generic &quot;secure this process&quot; type thing, one approach we could take is to simply pass along everything that was sent, except the initial token. I modified my verification code like so:\n\nAnd just like that - it works. I won't bother with a new screen shot since it literally just changed from Nameless to Ray. One issue with this particular setup is that 'token' is - essentially - a reserved word now. As this is my app and I'm building the APIs, I can handle that.\nSo... that's that. As I said, I'm definitely not sure how much sense this makes, but I'd love to hear folks opinions. Leave me a comment below! You can find the source code for everything I showed here: http",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "PSA for New Web Developers - Don't Use file://",
		"date":"Fri Apr 14 2017 19:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492199700,
		"url":"https://www.raymondcamden.com/2017/04/14/psa-for-new-web-developers-dont-use-file",
		"content":"If you are new to web development, one of the things you may try is simply opening a local file with your web browser. In other words, you make a file, like cat.html, save it to your desktop, then do a File/Open in your browser to view it:\n\nThis is a common way to learn web development and it's even what Mozilla Developer Network suggests in its Learning Web Development tutorial.\nThis works... but what you may not be aware of is that there are multiple web features that do not work when viewing HTML files loaded via File/Open (when you see file:// in the URL). For example, geolocation is blocked in Chrome when run on a page opened this way.\nI was not able to find a precise list of things that are blocked, but in general, you should probably avoid the issue as a whole and run a local web server. I understand that may be a bit daunting, but here are a few options to consider:\n\nApache is probably the most well known web server out there. It's the easiest to install (and comes pre-installed on Mac), and the quickest to get up and running. To be clear, you do not need to become an expert with Apache! You can literally install it and then drop files in the folder set up to be the web root. Windows has IIS, and I believe it used to come pre-installed, but I'm not seeing it on my Win10 desktop, so maybe it isn't anymore.\nhttpster is my tool of choice, but it requires npm and command-line usage that may be a bit much for someone really new. The benefit of httpster is that I can make a folder, put some random web crap in it, then fire up a web server directly from that folder. It's fast and convenient for testing. I wouldn't recommend this for a new developer on their first day of learning to build web pages, but I'd tell them to bookmark it for later.\nAnother option I see mentioned quite a bit is SimpleHTTPServer for Python. Python is installed by default on Mac, but not Windows. If you have it though, you can open your terminal, change to the right directory, and then just type python -m SimpleHTTPServer. Like the httpster suggestion though, it may be a bit overwhelming for new developers. (Although I hate to break it to you, you will need to get more comfortable with the command line. You won't need to be a wizard, but basic navigation and 'tool running' is something you'll need to become familiar with.)\n\nOk, so I'm going to ramble a bit. As someone who presents to and generally focuses on new developers, I'm always trying to keep things as simple as possible. I think a lot of people in our community greatly underestimate how overwhelming, confusing, and just difficult, many things are to people entering this field. Sure, you can &quot;just Google it&quot;, but if you aren't at a point yet where you even know what to Google, you're stuck.\nI 100% recommend the Mozilla Developer Network tutorial I mentioned earlier, and as far as I know, you can do the entire thing with just File/Open, but at least be aware that you're going to need to switch to using a local web server soon and prepare yourself for that install. (And heck, if you have local friends/coworkers, ask them to install it for you. Watch them, but I think it's totally fair to get some help on this step for now.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Tip for Zipped Actions and Packages in OpenWhisk",
		"date":"Thu Apr 13 2017 19:51:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492113060,
		"url":"https://www.raymondcamden.com/2017/04/13/a-tip-for-zipped-actions-and-packages-in-openwhisk",
		"content":"Just a quick tip to share today. I talked about zipped actions a few months ago. It's how you handle adding non-supported npm modules with OpenWhisk. While OpenWhisk supports a good set of common/popular npm modules out of the box, if you want to use one that isn't on that list, you:\n\nMake a zip of the action code, the package.json file, and the node_modules.\nUpdate your action and point to the zip instead of just the .js file.\nPlus add a bit of metadata (explained in my blog post linked to above)\n\nAll in all this works well, especially once you make a simple shell script to do all of the actions at once.\nToday though I ran into an interesting issue. I've got a simple ElasticSearch package I'm slowly building for OpenWhisk. Right now it has 2 actions: Create, Search. I've got both of these in the same folder, with a folder structure that looks like so:\n\nnode_modules\ncreate.js\npackage.json\nsearch.js\nupd.bat (my shell script)\n\nThe shell script simply handles updating Create and Search:\n\nIf you're curious, 7z is just a Windows-based zip CLI. Ok, so today I did some updates, and while search worked fine, create gave me:\nError: Cannot find module '/nodejsAction/G3hUBA5k`\nI was pretty confused by this - but then I saw the problem. My package.json looked like so:\n\nSee it? The issue is main pointing to search.js. When I ran the create action it couldn't find search. So the solution? I could have used 2 completely separate subdirectories. Instead, I made two new files: package.create.json and package.search.json. The only difference in both is the main value. My shell script then changes to:\n\nNot exactly rocket science, but you get the idea.\np.s. Now that my desktop and laptop both have a decent Bash shell, I'm pretty much done with the Windows shell. It works fine and all, but I'm really digging Bash on Windows, especially after the Creators Update. I really haven't run into anything I can't do yet.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "FusionReactor - Still the Best for ColdFusion",
		"date":"Wed Apr 12 2017 15:15:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1492010100,
		"url":"https://www.raymondcamden.com/2017/04/12/fusionreactor-still-the-best-for-coldfusion",
		"content":"I don't really do a lot of ColdFusion work anymore, mainly just support for clients as my side gig, but when I do think of the platform as a whole, there's two companies that always come to mind - Ortus and Integral. I blogged about Ortus, and specifically CommandBox, a few months ago, but today I want to share my impressions about the latest FusionReactor.\nFusionReactor (who, by the way, help sponsor this blog!) has been around for a long time now. At a high level, their product provides server monitoring and metrics for your ColdFusion server. Adobe released something similar built into the product itself, but let it wither on the vine so to speak. (Last time I checked it was still shipping as a Flex and AIR app which tells you how much attention it's gotten in the past decade.)\nThe last time I played with FR, which was probably a good five years ago, I remember it worked well, but was a bit difficult to setup. Once you got past all that, it worked fine, wonderfully actually, but the setup was definitely work.\nThe current version is light years away from that. Right off the bat, the setup process was much simpler. The web-based administrator includes a scanner that can automatically find and help you setup instances for both Adobe ColdFusion and Lucee.\n\nIn the shot above, you can see my local Adobe CF server as well as my local Lucee install.\nEach instance manager links to a dashboard for the individual server. Inside that you have an incredible amount of detail. The initial dashboard gives you a good high level look at your server. Mine isn't necessarily showing a real-life example as my local Lucee server just has a few files.\n\nI won't go into detail on all the various features, but at a high level, you've got:\n\nA monitor to give you real time reports (see that screen shot above).\nA debugger that actually works in production, letting you set up triggers that can reach out to you via email and let you diagnose a live issue, versus having to wait and try to dig through log files and the such much later. What's cool about this is that you can set it up to pause a live request, but only for a certain amount of time. That means if you can't respond for whatever reason you aren't bringing the live server down for long. Even better, it can be set up to pause just the first instance of an issue. So given that a request for /bad causes a problem, you can tell it to pause just that first request and let other's go through. That way if the issue is intermittent, you aren't blocking other requests from hitting the URL.\nTied to that is the ability to auto kill requests/threads that have been hung for a certain amount of time.\nBuilt in support for &quot;Users&quot;, and by that I mean recognizing sessions, or how folks actually run through your app. I find this aspect incredibly interesting because it's mixing analytics along with basic intelligence of how it relates to actual usage of the server.\nSystem level monitoring to cover things like network IO, disk space, etc. This lets you watch over your box as a whole, not just the application.\nDatabase metrics (more on that in a sec).\nAnd a mobile app too! I didn't get a chance to try it, but it's supported on both Android and iOS.\n\nSo again, I don't really have a lot I can show from my personal server, but I did some quick tests with some intentionally slow code. (Just by adding sleep()!) Here is a dedicated &quot;Slow&quot; report:\n\nThe details you get are so deep it's almost stupid - here is just one tab for that request:\n\nGoing into the profiler reports details about the slow aspect. This is one part where a ColdFusion developer may have some issue as the details refer to the Java code and not the CFML. In this case it's a bit obvious that the culprit is the sleep method as it has the exact same name as the CFML function. I tried a slightly more complex example where my CFM called two functions in a CFC. One of which had the sleep call. Looking at the CFM request, in the profile, I can still see it's a Java sleep call making things bad, but I can't necessarily see that it was the call to the CFC that initiated it.\nSo - speaking of slow bits - the old advice that you're slowest parts are probably the database is still good advice. One of the most cumbersome things I remember about the old FusionReactor was setting up database monitoring. You had to copy over a JAR, modify the datasource, and more if I remember right. Now - it just works. Automatically. And as with the rest of the dashboard, you get a crap ton of data. Again, here is just part of the detail for one database request.\n\nAlong with detailing a request, you also get a dashboard into requests running on the server in real time too. You can quickly see the slowest queries too. Finally, if you use bound parameters in your SQL (which, of course, you are, right?), FusionReactor will automatically show them in the SQL report. So given this:\n\nFusionReactor reports it as:\n\nAll in all, FusionReactor started off pre",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "From Actions to Sequences to Services",
		"date":"Fri Apr 07 2017 19:56:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1491594960,
		"url":"https://www.raymondcamden.com/2017/04/07/from-actions-to-sequences-to-services",
		"content":"I've been thinking a lot this week about OpenWhisk and sequences, and more precisely, how serverless in general can help development by letting you put various actions together to form new ones. Much like how Legos can be broken apart and put together in new configurations, I'm getting really excited the possibilities of serverless actions when they are chained together.\nTo be clear, I know I'm mainly talking about code reuse here and that is absolutely nothing new. One of the reasons I love Node so much is the ease of use of npm and grabbing various disparate packages to include in my app. But it just feels a bit more different with serverless, more powerful and easier.\nFirst off - I can combine actions from completely different platforms. One sequence could be made up of Python, Swift, and Node code, and I wouldn't even have to care.\nSecondly - I feel like serverless code is even more tailored to this 'mashup' type approach. By forcing you to write code that only takes input and returns output, it feels even more suited to be used in combination with each other.\nI'm probably being a bit overly zealous about this, but as I said, I'm excited about it! In that vein, I've built two pretty cool demos of this in action I wanted to share.\nA little over two weeks ago I wrote an OpenWhisk package to work with IBM Watson's Tone Analyzer service. Since then, I've also been working on other various simple actions, including one to work with Twitter and another to work with RSS feeds. Here's the first demo I created.\nRSS to Tone\nFor my first demo, I created a sequence that combines two actions:\n\nRSS to Entries - this action simply takes a RSS url and returns an array of entries.\nTone Analyzer - this takes a string and returns tone analysis on it.\n\nIn order to get these together, I created a third action I called flattenRSSEntries. All this action did was massage the data from one action to make it appropriate for another action. (I've got some thoughts on this at the end of the article.) Here is that action.\n\nEssentially - take the array - reduce it to the description from the RSS entry, and then spit out the output for the tone analyzer.\nAnd yeah - that's it. I literally just had to write 10ish lines of code for the glue. I then made a sequence of the three actions:\nwsk action update rssToTone --sequence utils/rssentries,flattenRSSEntries, mywatson/tone\nAnd then I can run it at the command line like so:\nwsk action invoke rssToTone --param rssurl https://www.raymondcamden.com/index.xml -b -r\nHere is the output:\n\nApparently my strongest emotions are a mix of sadness and joy. Ok, I can live with that. ;)\nTwitter to Tone\nFor my second demo, I created a sequence that combines two actions:\n\nGet Tweets - this action simply lets you search for a Twitter account or keyword. I used it to get tweets for an account.\nTone Analyzer - this takes a string and returns tone analysis on it.\n\nTo combine these two, once again I made a new action called flattenTweets. This was a bit more complex. I decided to remove retweets from the sample output. I also considered removing replies, but was worried I wouldn't have enough data. As it stands, it still feels like the input may not be deep enough for good analysis, but, I figure I can worry about that later. Here is that action:\n\nAs before, I made my sequence, and then I could call it from the CLI like so:\nwsk action invoke twitterToTone --param account raymondcamden -b -r\nIf your curious, this is the result on my Twitter feed.\n\nThe disgust was a bit weird, but I was kind of arguing with someone (nice arguing I'd say!) so perhaps it came from that.\nConclusion\nFirst, let me share the repo where you can find this code: https://github.com/cfjedimaster/Serverless-Examples. Let me know in the comments below if you have any questions about this.\nI just can't get over how... inviting this feels. One of the things I've begun to realize as I grow older as a developer, some platforms are naturally fun to play with. Some naturally invite experimentation and just trying new things. While most platforms allow for the same types of things, I'm just much more eager to use something that encourages my ability to create silly demos (usually involving cats, but today I failed that I suppose). This is why I'm loving serverless and OpenWhisk - I feel like I can do anything.\nOne More Thing\nOk, seriously, you can stop reading now and I won't be offended. So one aspect of this that kinda - I won't say bugged me - but stuck in my craw a bit - was that I had to write my custom 'joiner' code for my sequences to work. For the Twitter one, it kinda made sense as I wasn't just flattening an array into a string, but also applying some conditional logic as a filter. For the RSS one though I was literally just taking one key from an array of objects.\nFor that, it would have been cool if I could have used something like JSONPath to manipulate the results. In cases like that, I'd like to be able to skip having a 'combiner' a",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Extracting One (or more) Pages from a PDF",
		"date":"Thu Apr 06 2017 19:17:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1491506220,
		"url":"https://www.raymondcamden.com/2017/04/06/extracting-one-or-more-pages-from-a-pdf",
		"content":"Ok, this falls squarely into &quot;I Bet Everyone Knows This&quot; category, but have you ever wondered how you could extract one (or more) pages from a PDF? For example, imagine one of the pages is an image and you want just that, how would you do it? Last night I was about to do a screen capture of the page when I tried a simple hack.\nI selected Print, and then &quot;Print to PDF&quot;, and then clicked &quot;Current Page&quot;:\n\nAnd that's it. Pretty obvious, but I never thought of doing &quot;Save as PDF&quot; with an actual PDF. As you can see, you can also select Pages to get a range of pages instead of just one.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "OpenWhisk Sequences and Errors",
		"date":"Tue Apr 04 2017 16:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1491324840,
		"url":"https://www.raymondcamden.com/2017/04/04/openwhisk-sequences-and-errors",
		"content":"As always, try to read the entire post before leaving. I edited the end to add a cool update!\nI first blogged about OpenWhisk sequences a few months ago, but if you didn't read that post, you can think of them as a general way of connecting multiple different actions together to form a new, grouped action. As an example, and this is something I'm actually working on, I may have an action that gets the latest tweets from an account as well as an action that performs tone analysis. I can then combine the two into one sequence that returns the tone for a Twitter account. (Technically I may need a third action in the middle to 'massage' the data between them.) This morning I was a bit curious about how errors are handled in sequences. I had my assumptions, but I wanted to test them out to be sure. Here is what I found.\nI began by creating three simple actions, alpha, beta, and gamma. Here is the source for all three:\n\nI called my sequence, testSequence1, because I'm a very creative individual. I then ran it to be sure I got the right result, 3. Cool.\nAlright, so first, I broke beta:\n\nWhen run, I got an error I pretty much expected:\n\nCool. So next I converted Beta to use promises:\n\nI didn't include an error this time, I just made sure it worked as expected, and it did. Cool. So first I added back in my doBad call:\n\nThe result was a bit less helpful:\n\nWhile not helpful, it is kind of expected. If you work with promises and don't look for an error, they tend to get swollowed up. (I believe Chrome recently adding support for noticing unhandled exceptions, but again, I think that was a recent change.)\nI then switched to this version:\n\nAnd the result was the exact same. I had thought the more formal reject would have - possibly - be handled better, but it was not.\nBut - the good news - is that in every case where I tested my error, the metadata did correctly return a false result for the success key. While the verbosity/reason of the error was lost in the promise, the fact that an error was thrown was still something that could be recorded.\nAll in all - this really speaks to using good testing, and properly handling code that can throw exceptions. This falls into the &quot;obvious&quot; category, but as easy as OpenWhisk and serverless is in general, it is absolutely not a blank check to skip best practices when it comes to handling potential errors.\nEdit: Once again, my good buddy Carlos Santana comes to the rescue with a good fix. If you reject a plain JavaScript object, and not an Error object, you get a better handled result. Here is an updated beta.js:\n\nAnd here is the result:\n\nNice! The use of &quot;error like&quot; keys is arbitrary of course. I modified the reject to include howBlue:&quot;very&quot; and it was available in the result. It probably makes sense to follow a pattern like Carlos used above, just remember you can include additional stuff if you want as well.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Quick Notes on OpenWhisk Packages and Defaults",
		"date":"Mon Apr 03 2017 15:18:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1491232680,
		"url":"https://www.raymondcamden.com/2017/04/03/quick-notes-on-openwhisk-packages-and-defaults",
		"content":"This post is just to clear up some things that confused me. Everything here is covered in the docs (mostly, although I think bits aren't 100% clear) but I wanted to get this down on (virtual) paper to help me remember.\nI am currently working on a set of OpenWhisk actions to work with Elastic Search. I haven't done anything with full text search since I last worked with Lucene and ColdFusion. It's something I always thought was kind of neat, but after moving to Node, I haven't really thought of it. My coworker, Erin, has been singing the praises of Elastic for a while now and this weekend I decided to look into it.\nIt's a bit rough to get started since everything requires a REST API call, but I used Postman to make that a bit easier and once I got started, I was pretty impressed.\nAnyway, as I said, I'm working on some actions to work with an Elastic Search instance. You can see the current work here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/elasticsearch\nSince there is a great npm package for Elastic Search, my actions are pretty trivial. And hey - that's a great thing. :)\nMy plan is to share the package once I get it fleshed out a bit more. Currently it only supports adding/updating content and search. But I was concerned about something. I wanted to set some parameters at the package level (the auth info) but I was concerned about what would happen when I shared the package. The docs aren't terribly clear on the point, but here is how this works.\n\n\nIf you set default parameters for a package and share it, then those defaults are shared too. I think that's fair and expected.\n\n\nIf you bind a package, you can set your own default params. I didn't even know about binding (I swear I read the docs), but you can almost think of binding like creating an alias. That's kinda cool because it lets you convert a long package name to something shorter.\n\n\nAnd yes, you can bind your own packages to yourself. So this lets me test my Elastic Search package and use my authentication information on the bound package versus the 'real' one.\n\n\nAs an aside, when you run wsk package list, it does not flag a bound package versus a non-bound one. Here is an example:\n\nIn the shot above, myelasticsearch is the bound version of elasticsearch. If you get the package summary, you still can't tell it's bound. The only way to know is if you get the entire package, and look at the big JSON package returned. On top, you'll see an annotation about it:\n\nIn the screen shot above you can see it listed twice actually. Also note the default params I was able to set for it.\nThat's it - I hope this makes sense!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Another OpenWhisk Alexa Skill - Death Clock",
		"date":"Fri Mar 31 2017 20:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1490990520,
		"url":"https://www.raymondcamden.com/2017/03/31/another-openwhisk-alexa-skill-death-clock",
		"content":"Earlier this week I had my second Alexa skill released, the Unofficial Death Clock. Like most things, this was a silly demo that became interesting the more I worked on it. I thought I'd share the code and the issues I ran into building it, but as always, I'll warn folks I'm still new to Alexa skills, so I probably (most likely) didn't do this the best way.\nAs a quick aside, I built the original Death Clock many, many, many years ago as just a fun toy. I sold the site probably close to 15 years ago or so and haven't really thought about it much. That being said, I thought it would be a fun skill to build.\nThe basic idea is this:\n\nI ask Alexa when I'm going to die.\nAlexa responds with the question, &quot;What is your birth date?&quot;\nOn answering, I take that date, figure out how much time you have left to live based on an average life span, and then tell your estimated day of death as well as how many seconds you have left to live.\n\nNow - if you're reading this and shaking your head about how inaccurate this is... great. You're absolutely right. I'd get letters about this every week when I ran the site. It's a joke, that's it.\nAlright - with that out of the way - let me demonstrate the first version of the skill. This version does not actually support a conversation (and that turned out to be more difficult than I thought). Instead it simply supported a default greeting, and if you said a date value, it supported returning the 'death' info.\n\nSo the core of the skill is basically a branch based on the intent. As I said, I just supported a 'default' that returned a message asking for your date and then one that actually worked with the date. If a date was passed, and Alexa has really good support for parsing dates, I then run getDeathDay to return your estimated day of the death as well as the number of seconds till that date. I return a -1 value for people who should be dead already. (Sorry, sucks to be you.)\nThis worked, but was not a conversation. In other words, I couldn't say, &quot;Ask Death Clock when will I die&quot; and then immediately respond with my birthday after it asked for it.\nFor some reason, I just could not find out how to do this in the docs. The answer turned out to be a 'reprompt' value. Here is an example of the JSON I returned:\n\nNote that I had to include an outputSpeech and a reprompt in order for it to work. I'm 99% sure I've got that wrong since I'm basically repeating the same text value twice. That being said, when I made this change, I could visibly see the Alexa device wait for my response. (Basically, the light stayed on.) I could answer with just the date and then it would work.\nCool! So I went to verify it and then ran into some interesting issues.\nFirst, Amazon reported that there was a trademark on Death Clock. Fair enough - I sold it. I literally had to rename my skill to the &quot;unofficial Death Clock&quot; and that was enough. Cool.\nThen, and... I'm still shaking my head at this - they told me this:\n\nYour skill's descriptions do not clearly state the skill is a prank skill. We provide our customers with a trusted environment. Please update your skill to edit the description to comply with our content guidelines, and resubmit your skill for reconsideration.\n\nsigh\nOk, Amazon, I get it. We don't want to scare people, but... fine. So I made that change too.\nThe more difficult problems though involved the fact that my app didn't support a &quot;Stop&quot; and &quot;Cancel&quot; event. I guess since my first skill was 'one step' this wasn't necessary.\nI fixed this in two steps. First, I added the intents, and again, Amazon makes this easy:\n\nAnd then I added code to handle these events and return a simple &quot;Bye&quot; message. Here is the complete version of the final skill.\n\nAs a reminder, you can find this and more of my serverless examples up here: https://github.com/cfjedimaster/Serverless-Examples\nAnd that's that. Here it is in action. Forgive the pause on &quot;Death Clock&quot;, I have a bit of a stammer with &quot;D&quot; words so it's a struggle sometimes to say those words.\nDeath Clock Alexa skill in action: pic.twitter.com/wRnTyLUucf&mdash; Raymond Camden (@raymondcamden) March 31, 2017\n",
		"tags":[
	        
            "openwhisk",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Check out PaveAI for Analytics",
		"date":"Thu Mar 23 2017 17:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1490289840,
		"url":"https://www.raymondcamden.com/2017/03/23/check-out-paveai-for-analytics",
		"content":"I'm a bit of a stats junkie, but what I love more than a giant pile of charts and tables are tools that can actually help me understand my stats at a high level. I've reviewed such services in the past and have also blogged about my own experiments building dashboards and other views on top of Google Analytics. Earlier this month I was contacted by the folks at PaveAI.\nThis is a commercial service (with a free trial!) that provides scheduled reports on your analytics. Typically weekly or monthly (or daily for folks with a lot of traffic), the service will spit out a PDF of analytics about your - well - analytics. I was given a complimentary report (that I've attached below), but I thought I'd share some screen shots from it to call out what I thought was really cool.\nI'll begin with the first page, the Overview.\n\nNice, clear, and not more than I can get from Google Analytics, but definitely a bit quicker to read.\nBut then it dives quickly into analyzing my visitors:\n\nThat's kinda cool. But then it showed me this:\n\nSo the red arrows there are - obviously - from me - but note how it didn't just show a bunch of graphs, but actually called out some specific, important, things to note about it. I freaking love that.\nLater on in the report, I saw this:\n\nI really loved that. I kinda knew that my front end interview questions series was getting decent traffic, but this really brought the point home. I'm going to look into possibly adding to this more later this year.\nHere's another cool stat plus insight:\n\nYou get the idea. The service doesn't just present the data in a nicer, easier to read format, it also returns some pretty darn interesting insights. You can find pricing info on the site and it seems pretty reasonable. Note that my report only focused on Google Analytics, they also support integration with AdWords, Facebook Ads, and Twitter Ads as well.\nYou can download my sample report here: https://static.raymondcamden.com/enclosures/paveai.pdf\nNote that I've sent them some feedback about the report you see above. For example, that first page of reports shows deltas but doesn't make it clear it is comparing it to the previous month. I've been told by PaveAI they are already adding that in.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using IBM Watson Tone Analyzer in OpenWhisk",
		"date":"Wed Mar 22 2017 23:34:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1490225640,
		"url":"https://www.raymondcamden.com/2017/03/22/using-ibm-watson-tone-analzyer-in-openwhisk",
		"content":"Earlier today I decided to write up a quick wrapper to the IBM Watson Tone Analyzer using OpenWhisk. It ended up being so incredibly trivial I doubted it made sense to even blog about it, but then I realized - this is part of what makes OpenWhisk, and serverless, so incredible.\nI was able to deploy a function that acts as a proxy to the REST APIs Tone Analyzer uses. All this action does is literally expose the Watson Developer npm package interface to an OpenWhisk user. Here it is in its entirety:\n\nIf you weren't aware, the watson-developer-cloud package provides simple Node interfaces for over twenty different Watson services. And how well does it handle it? I've got one block of code to create an instance of watson.tone_analyzer and one call to tone to perform the analysis.\nSo yes - my code here is stupid simple, but within seconds I was able to deploy this to OpenWhisk in a package, mark the package shared, and now it's available to any OpenWhisk user. I can also use it with other actions as part of a sequence. You do need to provision the service in Bluemix of course, but that's fairly trivial to do as well.\nThis is exactly the kind of thing that is making me love serverless more and more every day. I didn't have to provision a server. I didn't have to setup Express or heck, even a Node.js server in general. I wrote my function and was done. In terms of cost - I just pay for my Tone Analyzer usage and I only pay for my action when it's actually invoked.\nHere's an example of it in action. First, the call:\n\nAnd the result. It's a pretty big set of data, and my input was way too small, but it certainly did pick up on the anger.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Some Thoughts on Static Sites and Security",
		"date":"Tue Mar 21 2017 17:34:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1490117640,
		"url":"https://www.raymondcamden.com/2017/03/21/some-thoughts-on-static-sites-and-security",
		"content":"I've been chewing on this blog post for a little while now and while I'm waiting for a keynote to start I thought I'd spend some time to write it up. Let me preface this blog entry by making it very clear: I am not a security expert. I think I have a good handle on security issues at the level every developer should, but it is not my primary role, so take the following with a grain of salt. And while I always say this, please be sure to comment with suggestions and corrections on this topic.\nLet's begin by defining the &quot;security space&quot; I'm discussing here. I have a site, let's say this site, and I want to ensure that:\n\nno one can modify the content here\nno one can access anything I do not want them too\n\nNever, ever, put security in the corner\nStatic sites sound like they would be much safer, or at least somewhat more safe, than other deployment options. And I think that's right (for the reasons I'll lay out below). But you never, never, simply skip considering security. Every single project should have security review/checking/etc as part of the process from day one. Period. It always needs to be part of the conversation between developers and clients and an integral part of the development process. Period.\nAre static sites safer?\nBy removing the app server from the equation, the potential vulnerabilities are greatly reduced. Every app server has had security issues of some sort over time, requiring the developer to ensure they've properly updated, patched, and locked down the proper settings. Flat files simply don't need this.\nAlso - many app servers rely on some form of persistence, whether that be a NoSQL solution like Cloudant or a traditional database like MySQL. Removing them from the equation means no SQL injection attacks or other database leaks.\nBut...\nYou have to host your site, right? If the client puts those simple, flat files on an Apache server configured to run PHP then you're still running in an environment where an attack can happen.\nFor this site, I used Netlify and Amazon S3. I trust the engineers behind these services much more than I trust myself, but I have to be aware of their services and pay attention to any security issues that may be found on them. The same would apply for GitHub too.\nSpeaking of GitHub, my site's public content is driven by checkins to my GitHub repo. If my credentials were hacked then that would be a vector to change the contents here.\nSo yeah - I've removed ColdFusion, PHP, Mongo, etc from the equation, but I'm certainly not at 0% risk.\nDynamic Aspects\nMany static sites actually contain quite a few dynamic aspects. I've blogged about, and presented on, adding dynamic aspects back to static sites via services like Disqus and Formspee, both options are in use here. If someone were to find a way to attack Disqus than that would impact me as well. Ditto for my form processing service. Every one of these services I add back to a static site is yet another thing I've got to keep tabs on.\nMore reading\nSo those are my thoughts. I think - obviously - you can also do things like adding https, and I found a great article that covers those details here: Which security measures make sense for a static web site?\nI'd love to hear from people working with static sites in terms of what steps they are taking when it comes to security. Do you have a checklist? What do you look for? Let me know in the comments below!\nAs a final note, don't forget that if you want to learn about static sites in general, the book I wrote with Brian Rinaldi was just released. I may be biased, but I think it's pretty great: Working with Static Sites\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "My OpenWhisk Alexa Skill is Live!",
		"date":"Mon Mar 20 2017 23:58:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1490054280,
		"url":"https://www.raymondcamden.com/2017/03/20/my-openwhisk-alexa-skill-is-live",
		"content":"I've blogged a few times now about my attempts to build an Alexa Skill with OpenWhisk (An Introduction to Creating Alexa Skills with OpenWhisk and Creating Alexa Skills with OpenWhisk - Part Two) and I'm happy to say today that my skill finally passed certification!\n\n\nAlexa Skills have their own product pages and you can see mine here: https://www.amazon.com/dp/B06XP7Q3K3/ref=sr_1_1?s=digital-skills&amp;ie=UTF8&amp;qid=1490026649&amp;sr=1-1&amp;keywords=cat+namer\nSo - how was the verification process?\nInitially - I made mistakes with my sample utterances (a configuration you provide for the skill) and the sample tests I sent to the verification team. Stuff like my skill supporting:\nask cat namer to give my cat a name\nand me saying this would work:\nask cat namer to give my cat a new name\nMy brain kinda glossed over the difference there, but it was enough to make it fail verification.\nA second issue I had was supporting the &quot;open&quot; event, or basically, someone using my skill without actually asking anything. That's a requirement and your code has to handle it. Basically, I just sniffed for no intent:\n\nYour skill, of course, may do something different in that case.\nFinally - and this was the tricky one - I had suggested &quot;foo&quot; for a name prefix (my cat namer supports letting you pass in the first part of the name, it then appends a random name after that). Alexa would translate foo to four though. This is what I got from the verification team:\n\nUser: \"Alexa, ask cat namer to give my cat a name that starts with foo\"\nSkill recognizes it as \"Alexa ask cat namer to give my cat a name that starts with four\" and responds with \"Your random cat is 4 King Dumpster Fire\"\n\nNow to me - that smells like an Alexa issue. The user said &quot;foo&quot;, but it heard &quot;four&quot; - I honestly don't know how I'm expected to fix that. But I removed foo from my slot type options, removed it from the suggested type, crossed my fingers and submitted and then it passed. On the Alexa Slack channel, some folks suggested simply resubmitting sometimes worked well.\nAll in all - the process wasn't too painful, and I'm working on a new skill now (but as I've got two big trips back to back, it may be a while). If you want to see the full source for my released skill, I've put it up here: https://github.com/cfjedimaster/Serverless-Examples/tree/master/randomcat3\nIf I can answer any questions about this process, or provide more info on the OpenWhisk side, just ask!\n",
		"tags":[
	        
            "openwhisk",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Creating Alexa Skills with OpenWhisk - Part Two",
		"date":"Fri Mar 17 2017 16:53:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1489769580,
		"url":"https://www.raymondcamden.com/2017/03/17/creating-alexa-skills-with-openwhisk-part-two",
		"content":"This is my followup to last week's post on building Alexa skills with OpenWhisk. What I'm describing today represents some very recent changes and I would warn people that this post may change in the future. The focus of this post involves what you need to do to get your Alexa skill verified. I'm still in the process of doing that myself, but my holdup now isn't related to technical issues so I feel safe in sharing this update. Again though - take with a grain of salt and I'll try to ensure I clearly mark any updates post release.\nWhew. Sorry about all that - I just like to make sure people know this is still a bit of a moving target. ;)\nIn my first post on building an Alexa skill, I talked a bit about the verification process your skill has to adhere to in order to be released publicly by Amazon. Jordan Kaspar did a great job of writing this up in his blog post and since I stole it for mine already, I'll steal it again:\n\nCheck the SignatureCertChainUrl header for validity.\nRetrieve the certificate file from the SignatureCertChainUrl header URL.\nCheck the certificate file for validity (PEM-encoded X.509).\nExtract the public key from certificate file.\nDecode the encrypted Signature header (it's base64 encoded).\nUse the public key to decrypt the signature and retrieve a hash.\nCompare the hash in the signature to a SHA-1 hash of entire raw request body.\nCheck the timestamp of request and reject it if older than 150 seconds.\n\nYes - that is a bit batshit crazy. Luckily though there is a simple library you can use, alexa-verifier. Jordan makes use of this in his post as part of an Express middleware, but obviously our OpenWhisk action is a bit different.\nThe verifier function requires three things:\n\nA header called signaturecertchainurl\nA header called signature\nThe raw body of the post\n\nThe last bit is the crucial bit. When using a web action, a JSON body is automatically parsed and available to your action as arguments. However, Amazon requires the original string as part of the verification process.\nAs of the most recent release (and yes, the OpenWhisk folks need to help broadcast cool changes like I'm about to describe, we're working on it :), you can now ask OpenWhisk to not parse the body and simply make it available to you.\nThis is documented now but it basically comes down to a few steps:\n\nAdd a new annotation to your action: raw-http true\nAccess the body via args.__ow_body. It's base64 so you'll need to convert it.\n\nAnd here comes the super, super, super important thing. When you use this feature, you no longer have access to the arguments from the body as arguments to the function itself. Ok, let me rephrase that with a super small demo:\n\nGiven that action, if I POST a JSON string that included a name, it would be available as used above. But if I switch to raw-http mode (my term, not theirs), then args.name would not exist, unless I have a default parameter for the action with that name.\nYeah, a bit confusing. Personally I don't use default parameters very much, but if you do, and if you use this feature, than you're going to have to be aware of this to ensure you get the right value.\nHere's a new version of the function above that would get name from the raw body:\n\nOk, so hopefully you're still with me. ;) Now let's look at the updated action:\n\nI'll skip over the bits that didn't change from before and focus on the updates. First, I require in the alexa-verifier package. I needed to add a package.json for my action and switch to a zipped file for updates. To make that easier I wrote a simple bat file that zips and updates in one call. (If anyone wants to see that, just ask.)\nGoing down, you can see I fetch the headers as well as the raw body. I convert it to ascii, JSON parse it, and then grab the request value.\nThen, I run my required values through alexaVerifier, and here it just plain works. That's really all there is to it. (As an aside, I had some questions for the maintainer of that project and they were very quick to reply and try to help!)\nThe final changes were to how I worked with the data Alexa sent me. Previously I was used the args object, but as I described above, I needed to change my code to get this from the parsed body.\nAnd that's it! As I said above, my skill isn't yet verified, but I believe I'm close. There is one possible thing that I think I may need to add, and if I'm right, I'll post a follow up. But for now, I hope this is helpful!\n",
		"tags":[
	        
            "openwhisk",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "A Twitter Package for OpenWhisk",
		"date":"Wed Mar 15 2017 21:41:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1489614060,
		"url":"https://www.raymondcamden.com/2017/03/15/a-twitter-package-for-openwhisk",
		"content":"I've been chewing on an idea for something I'd like to build with OpenWhisk and Alexa, and part of it involves Twitter integration. Since working with serverless means working with small, atomic functions, I decided to focus on the Twitter aspect first. I also thought it would be cool to start work on a Twitter package that could be used by other OpenWhisk users. I launched that package today, and while it is pretty small for now, I hope to expand on it over time.\nI blogged about OpenWhisk packages a few weeks ago (&quot;Using Packages in OpenWhisk&quot;), but the basic idea is that they allow you to collect multiple actions into one 'bucket', share default arguments between them, and then share access to them to all users on the platform. (Which is optional by the way - working with packages does not mean you have to share them.)\nMy Twitter package has a grand total of one action currently - getTweets. The name is - possibly - not entirely accurate. It's using the Search API and lets you pass either a &quot;term&quot; parameter or &quot;account&quot; parameter (or both). This lets you search for something, get tweets from an account, or both. Here is the code.\n\nIn order to use this package, you must have your own Twitter app defined in their portal and get access to your consumer key and secret. I use the Twitter package from npm, do some munging on your search input, and then call the API. And that's it.\nInvoking it could look like this:\nwsk action invoke twitter/getTweets --param consumer_key mykey --param consumer_secret mysecret --param account raymondcamden -b -r\nThe result is an array or tweets. I won't show that as it's rather boring list of tweets. The CLI I used above only works for me - you would want to prefix it with my namespace, so in theory, this should work:\nwsk action invoke /rcamden@us.ibm.com_My Space/twitter/getTweets --param consumer_key mykey --param consumer_secret mysecret --param account raymondcamden -b -r\nOf course, you can just use my code as a simple action too.  Finally, if you want to help me improve the action (or add new actions), I've set up a repo here: https://github.com/cfjedimaster/twitter-openwhisk\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Quick console script for O'Reilly Authors",
		"date":"Tue Mar 14 2017 00:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1489449840,
		"url":"https://www.raymondcamden.com/2017/03/13/quick-console-script-for-oreilly-authors",
		"content":"So... let me start off by saying that this blog post will be useful to approximately 0.01% of you. I'm really just blogging this so I can copy and paste the code later\nwhen I want it again. But I thought it also might be a useful reminder for folks that your browser console is useful for many things, including for 'fixing' issues you may have with a particular page.\nI've worked with many publishers, and by a large margin, O'Reilly is my favorite. They aren't too pushy about how you write the book while still working hard to ensure you create something good. In general, every book project I've had with them has been great. There's one aspect though that kinda bugs me. Their portal for authors gives you a nice quick shot of your sales over time:\n\nI've scrambled the text on the left side of the bar (but they are high numbers, honest!) but you can get the basic idea. I can see spikes in my sales and get a general idea of how well the book is doing.\nHowever... notice anything missing from the chart? You can mouse over a data point to see totals, but nowhere is the actual total sales presented. I can get that data if I look at my royalty statements, but what I really want is: &quot;You've sold X units.&quot;\nSo I did what any self-respecting web developer did. First, I emailed O'Reilly. Because, come on, they can't read my mind, right? Then I did view source. I noticed the charts were built from raw data on the page (again, numbers changed):\n\nI noticed there was one variable named &quot;dataXXXX&quot; for each of my products. So based on that, I wrote this script for the console:\n\nIt's not rocket science - it just looks for those global variables and then parses the data. I store the breakdown (video vs print vs digital) and then print it out using console.table. I'm not terribly pleased with the output, but it works:\n\nBoom! And as I said - yes - this will be useful for me and.... um... probably no one else. But hopefully it serves as a good reminder - your browser isn't just a document viewing platform. It is a tool - make use of it!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "An Introduction to Creating Alexa Skills with OpenWhisk",
		"date":"Thu Mar 09 2017 19:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1489086120,
		"url":"https://www.raymondcamden.com/2017/03/09/an-introduction-to-creating-alexa-skills-with-openwhisk",
		"content":"As I mentioned in my post earlier this week (A Tip for Testing Alexa Skills), I'm a huge new fan of the Amazon Echo device and I've begun looking at how to build my own integrations with it. This week I've done some investigations into how to use Alexa with OpenWhisk and I have to say I'm impressed by how easy it is. To be clear, I've only played with the most basic of skills, but it is easy and quite a bit of fun too!\nWhat follows is not meant to be a complete guide to working with Alexa, Amazon has good docs for that and I'll share a bunch of links at the end. Nor am I the first do to OpenWhisk with Alexa, I'll share links of other examples as well. Instead, I'll demonstrate the process I used, talk about the things to look out for, and demonstrate the result. As always, questions and suggestions are appreciated!\nFirst off, make sure you have an Amazon Developer account. You can sign up here: https://developer.amazon.com/. I did this a long time ago when I put some Android apps in the Amazon marketplace. Secondly, you'll want a free Bluemix account as well. You can sign up for that here: https://console.ng.bluemix.net/registration/. As a reminder, using Bluemix is not required for working with OpenWhisk, it just makes it a heck of a lot easier.\nVersion One\nLet's start on the Alexa site. Assuming you've logged into the dev portal, click on the Alexa tab.\n\nYou will want to click on &quot;Alexa Skills Kit.&quot; Remember that the interactions we create with Alexa are called skills. The next page will list your current skills. You will want to click on &quot;Add a New Skill&quot; (unless, obviously, you are editing an existing one).\n\nNext comes up a rather large, complex form that can be a bit scary. (Ok, maybe that's just me.) Just note that you don't have to answer everything right away. One thing you'll see, an Amazon deserves a lot of credit for this, is that they made testing pretty darn easy. Again, the documentation goes into big detail about stuff works and I'll provide those links at the end.\n\nThis first page just needs the name and the invocation name. As the form says, the difference is that invocation name is how you actually address it when speaking to an Echo device. I named my &quot;Cat Namer&quot; for reasons that will become clear later. (Spoiler - it involves cats.)\nNow things get serious...\n\nOn this page, you need to define two things. Intents are how you describe, at a high level, the API to your skill. So imagine your API has one function, return a random number. In that case, you have one intent. If your skills has two features, let's say a random number generator and a random cat namer, you would have two intents.\nThe &quot;Sample Utterances&quot; field is where you describe how people can use your intents. Going back to the example of a random number generator, it may be something like this:\nGive me a random number\nI need a random number\nI need something random\nYou prefix each utterance with the intent name so Alexa knows how to map them. Utterances can also contain variables, and this is where things get really complex. Amazon calls variables &quot;slots&quot;, and it supports a basic system of &quot;slot types&quot;. A good example of this is the date slot type. Amazon lets you define an utterance that includes a date. So example:\nGive me a random number for tomorrow\nGive me a random number for next year\nWhich would be really written as:\nGive me a random number for {date}\nWhere the {date} slot is defined to be a date slot type. Amazon has a bunch of these (Slot Type Reference) covering things like dates, times, numbers, colors, and my favorite - desserts. You can also define your own which is useful for more free form checking.\nAs I said, this part is a bit complex. I began with this intent schema:\n\nIn this case I've defined one intent with no slots. Basically, no real user arguments. (My next version will add that.)\nI then wrote these utterances:\nrandomName give my cat a random name\nrandomName name my cat\nrandomName give my cat a name\nAs I mentioned above, you prefix each utterance with the name of the intent.\nOk, so at this point, let's switch gears a bit. The next step is going to ask for the end point for the skill, so we should probably actually build it. Again, I'm assuming you have OpenWhisk and Bluemix ready to go, if not, you can read my other posts on the topic to get up to speed. I began with an action that actually did nothing but return a static result.\n\nHow did I know how to return this structure? By copying code from my coworker, Lorna. :) (I'll link to her demo at the end.) Obviously the Amazon docs go into detail about what can/should be returned here. Make note of the shouldEndSession. While I'm not using it, you can actually have a conversation with a skill that goes over multiple interactions. That's freaking cool.\nSo I saved this as an action and then exposed it as a REST API:\n\nNow that I had an action available, I went back to my Amazon skill editor:\n\nI selected",
		"tags":[
	        
            "openwhisk",
            
            "alexa"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "New Option for Android Testing - Genymotion on Demand",
		"date":"Thu Mar 09 2017 15:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1489074840,
		"url":"https://www.raymondcamden.com/2017/03/09/new-option-for-android-testing-genymotion-on-demand",
		"content":"I've been talking about Genymotion (technically Genymotion Desktop) for a while now as a speedier alternative to the horribly slow native Android simulator. While Android's default simulator has gotten a lot better lately, I still think Genymotion Desktop is worth your time checking out if you are doing any work at all with Android.\nThe folks behind this cool tool have just recently released another new service, Genymotion on Demand. Basically, this is an EC2-based virtualized Android device you can run from your browser. Here is a screen shot of it in action:\n\nIf you've never seen Genymotion Desktop before, I'll point out that the icons all on the right all help you work with the virtualized Android device and support cool things like specifying GPS and battery values.\nGetting my Android build up there was a simple matter of asking Cordova to generate a build and then dragging and dropping the APK onto the browser.\n\nWhile a developer could use this instead of managing SDKs locally, the fact that it's available in the browser really opens things up, especially for testing. You just pay by the hour for when your running the instance so you can ramp up just as quickly as you need. I think this will be especially useful in larger companies.\nFor some more examples, check out the tutorial videos.\nI only played with it a bit, but it worked rather well, and as a fan of the desktop solution, I feel confident recommending folks at least give it a try. (Or if you think it is too much and you've never tried the desktop version, it's definitely time to try that!)\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "A tip for testing Alexa Skills",
		"date":"Wed Mar 08 2017 16:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1488990900,
		"url":"https://www.raymondcamden.com/2017/03/08/a-tip-for-testing-alexa-skills",
		"content":"A week or so ago I got my first Echo device (a Dot as a speaker gift from Devnexus) and I fell in love with it so much that I've already purchased the larger model. My wife ands kids like it too. So naturally I decided to take a look at building custom skills for it. (For folks who don't know, a &quot;Skill&quot; is basically a program that lets the device respond to voice commands.) While I plan on blogging about the process of getting it to work with OpenWhisk, I wanted to share a really, really important tip.\nAs part of the skill design process, Amazon has a web based interface that, for the most part, works just fine. I had my action up and running on OpenWhisk and I tried my first test, again, via the web interface. When I ran my test, I got an error:\n\nThe error, which you can't read completely on screen there, is:\nThe remote endpoint could not be called, or the\nresponse it returned was invalid.\nAnd... bam. That was it. I was completely stuck. I had some logging on my OpenWhisk side and as far as I could see, nothing was executing at all. I really didn't know what to do.\nThen - on a whim - I looked at my phone's Alexa app. This is the app you install to work with your hardware, configure it and the like, but isn't required for building the skill itself. I just opened it up for the heck of it honestly, and that's when I saw it.\nThe app keeps a record of everything you ask it and presents it in a nice card interface. What I didn't realize was that it also recorded my tests from the web interface and - crucially - returned much more detailed information:\n\nFor the life of me, I can't imagine why this level of information isn't available on the developer web site, but it was exactly the information I needed to fix the problem*. Once I corrected it, I was able to connect to my skill (both in the web app and via voice) just fine.\n\nAs I said, I'll share more details about doing this with OpenWhisk once I get a better demo done.\n* For folks curious about the issue, I'll document this in my larger post later, but the issue was that I needed to select \"My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority\" on the SSL setting. Thanks to Carlos Santana for helping me with that.",
		"tags":[
	        
            "alexa"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with Static Sites - Final Release!",
		"date":"Tue Mar 07 2017 16:40:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1488904800,
		"url":"https://www.raymondcamden.com/2017/03/07/working-with-static-sites-final-release",
		"content":"So I've blogged about this a few times already, but now the final, really final release of my book on Static Sites is available for purchase. I co-authored this with Brian Rinaldi and I think it is fair to say this is a great introduction to the topic with multiple real world (ish ;) examples.\n\nFor folks curious, here is the table of contents:\n\nChapter 1 - Why Static Sites?\nChapter 2 - Building a Basic Static Site\nChapter 3 - Building a Blog\nChapter 4 - Building a Documentation Site\nChapter 5 - Adding Dynamic Elements\nChapter 6 - Adding a CMS\nChapter 7 - Deployment\nChapter 8 - Migrating to a Static Site\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Designing an OpenWhisk Action for Web Action Support",
		"date":"Thu Mar 02 2017 16:37:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1488472620,
		"url":"https://www.raymondcamden.com/2017/03/02/designing-an-openwhisk-action-for-web-action-support",
		"content":"Please note that there is an update to this blog post. Read it here: https://www.raymondcamden.com/2017/07/03/designing-an-openwhisk-action-for-web-action-support-take-two/.\nBefore I begin - a few words of caution. The feature I'm discussing today is - for the most part - bleeding edge for OpenWhisk. It is really new and most likely will change between now and when it is &quot;formally&quot; part of the platform. Secondly, what I built may not actually be the best idea either. Regular readers know that I'll often share code that is fun, but not exactly practical, so this isn't anything new per se, but I want to point out that what I demonstrate here may not be a good idea. I'm still extremely new to Serverless in general, so read with caution!\nAlright, so first off, a quick reminder. &quot;Web Actions&quot; are a new feature of OpenWhisk that allow you to return non-JSON responses from OpenWhisk actions. There are docs you can, and should, read to understand the basics as well as examples (here is my post which links to even more examples) of it in use.\nOne thing kinda bothered me though. It wasn't very clear how I could take a simple action and make it support web action results as well as &quot;normal&quot; serverless requests. Most of the demos assume you are only using it as a web action, but I wanted to see if I could support both. Here is what I came up with.\nI began by writing my action. In this case, the action generates a random cat. It creates random names, gender, ages, and more.\n\nI assume this is all pretty trivial, but as always, let me know in the comments if it doesn't make sense. So I took this, pushed it up to OpenWhisk, and confirmed it work as a 'regular' simple action.\n\nWith that done, I then began thinking about how I'd support web actions. First I enabled it by updating the action: wsk action update caas cat.js --annotation web-export true.\nI thought I'd first start with - how do I tell a &quot;regular&quot; call versus a &quot;Web Action&quot; call. According to the docs, a &quot;Web Action&quot; call will send along additional arguments representing the HTTP request. These exist in args with the prefix __ow. So with that, I wrote this function as a shorthand to detect the request:\n\nThis feels a bit risky, but if a better way comes around, I can fix it up later.\nThe next bit was figuring out exactly how I'd support web actions. I decided on 3 different ways.\n\nIf an HTML request is made, I'd return an HTML string representing the cat.\nIf a JPG request is made, I'd return just the image.\nIf a JSON response is requested, I'd return the JSON string.\n\nYou can determine all of this by looking at the headers sent with the request. Web Actions pass this as __ow_meta_headers and the accept header would tell me what kind of response was desired. I wrote this as a shorthand way of parsing it:\n\nNotice I have no result for an unknown request - I should fix that. So the final part was to just support all this. Here is my modified main function.\n\nSo how do I test? First I figured out my URL (I added a space to make it wrap nicer):\nhttps://openwhisk.ng.bluemix.net/api/v1/experimental/web/ rcamden@us.ibm.com_My%20Space/default/caas\nAnd then I tested the HTML version:\nhttps://openwhisk.ng.bluemix.net/api/v1/experimental/web/ rcamden@us.ibm.com_My%20Space/default/caas.html\nwhich worked fine:\n\nTo test the JSON and JPG versions, I used Postman, which is a damn handy tool for testing various API calls. Yes, I could have done everything in Curl, but honestly, my memory for CLI args is somewhat flakey, and I love the UI and ease of use of Postman in general. To support an image response, I use this URL:\nhttps://openwhisk.ng.bluemix.net/api/v1/experimental/web/ rcamden@us.ibm.com_My%20Space/default/caas.http\nand here it is running in Postman:\n\nAnd the JSON works well too - it uses the same URL, just a different Accept header.\nSo - as I said at the beginning - I'm not entirely sure this is a good idea, but I dig how my one action can have multiple views of the same data. Any thoughts on this approach?\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An Example of a Static Site with a Dynamic Calendar",
		"date":"Sat Feb 25 2017 00:25:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1487982300,
		"url":"https://www.raymondcamden.com/2017/02/24/an-example-of-a-static-site-with-a-dynamic-calendar",
		"content":"Forgive the somewhat clunky title - I wanted to share a little demo I built for my presentation this week at DevNexus. The idea behind the demo was whether or not I could create a simple dynamic calendar system with a static web site. My presentation was all about adding dynamic aspects back into a static site, so this fit right in.\nFor the demo, I made use of FullCalendar. FullCalendar is a simple little jQuery plugin that renders a nice full screen (well, &quot;as big as you want it&quot;) calendar. It can be driven by Google Calendar which is one simple way of adding a dynamic calendar to a static site, but I wanted to try something a bit different.\nI began by creating a new Jekyll site (which is a bit of a pain on Windows). Jekyll has a feature called Collections which allow you to define a basic &quot;content type&quot; for your site. By default, Jekyll has a &quot;Post&quot; collection already built in. Jekyll is primarily used for blogs. But you can create your own collections for whatever makes sense for your site.\nI began by defining a collection called events. This is done in _config.yml\n\nI specified that I wanted my events to be published as pages and that they should use a URL path of /events/X, where X is based on the file name.\nI added a new directory called _events and wrote my first piece of content:\n\nIn a real page I'd have a lot more content about the event (possibly) but pay attention to the front matter. I've got a layout and title which is pretty standard, but event_date is custom. As you can probably guess, this represents the date of the event itself. I made a second page and then verified that they were rendering ok in my site.\n\nI didn't spend a lot of time on the template, but it works, so I'm down with it.\nNow came the interesting part. How do I get these random event pages available in FullCalendar? Here is where some static site generators shine - and some drop the ball. The SSG I used for my blog, Hugo, makes it incredibly difficult (imo) to add ad hoq pages with custom logic. You have to make a page with next to no input, tell it to use a layout that has the code, and then work over there instead of the page itself. I've seen multiple SSGs do this whereas both Jekyll and Harp are much more open in this regard.\nI created my JSON data by adding a new file, calendar.data.html, with this code:\n\nI've specified no layout and title. This keeps the output clean and the &quot;page&quot; invisible from the top header. I then output over my collection data which Jekyll makes available via site.events. The only kind weird part is the {% unless %} block. That's how I handle including a comma between each event item and not after the last one. The end result was a JSON feed available at /calendar-data/ that looked like this:\n\nIt could be formatted a bit nicer, but it works. I then added a new page to render the FullCalendar:\n\nFullCalendar supports a lot of customizations, but all I did was specify the URL where the event data could be found. Here it is in action:\n\nSo yeah, not exactly rocket science, but I've been wanting to build this for a while. This approach would not scale to a huge amount of events, but you could build out events in a &quot;per month&quot; or &quot;per year&quot; file to keep things sensible. FullCalendar can use a function to define how to load data, so you could dynamically create a URL path to the proper data feed based on what month is being requested.\nYou can find the full source for this demo here: https://github.com/cfjedimaster/Static-Site-Examples/tree/master/jekyll_fullcalendar\n",
		"tags":[
	        
            "jekyll"
            
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Another OpenWhisk Cron Example - the Blog Nag",
		"date":"Tue Feb 21 2017 16:31:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1487694660,
		"url":"https://www.raymondcamden.com/2017/02/21/another-openwhisk-cron-example-the-blog-nag",
		"content":"Last week I blogged about my first experience working with OpenWhisk triggers and rules, specifically the Cron trigger which lets you execute actions according to a schedule. Today I'm sharing another example, which, while not as complex as the 911 scraper, I thought was kind of fun.\nAs a blogger, I try to keep to a certain amount of posts per month. While I a absolutely care more about quality than quantity, I still try to maintain a certain amount of content per month. I thought it would be fun to create an OpenWhisk action that would nag me if I hadn't blogged in a few days. This turned out to be rather simple:\n\nFirst, I get the RSS feed.\nThen I parse the XML. There's packages to read RSS, but there's also xml2js which just does a basic conversion.\nI can then check the date of the most recent article and compare it to now.\nIf it's been too long, nag!\n\nLet's start with the action:\n\nStart with the main function which is OpenWhisk's entry point to the function. I use the request library to open up my RSS feed and then parseString from the xml2js library. I can then get the most recent blog entry (which is the first entry in a RSS feed) and make a date object with it.\nOnce I have that - then it's math. I set the constant NAG_DAY to 2, which is a bit too low if you ask me, but I had blogged on Friday so I needed a value that would trigger the alert. (For folks curious, I try to blog once every 3 days.) If we need to nag, we then simply call doNag.\nThe doNag function just writes an email using the Sendgrid API and fires it out. And that's it.\nSo then I had to make this &quot;live&quot; - which beforehand would have meant provisioning a server and all that, but with the wonders of Serverless (yes, I'm half-joking here ;) I just did the following:\n\nSent the action up to OpenWhisk with the CLI (wsk action create --kind nodejs:6 rssnag rssnag.zip)\nMade the trigger (wsk trigger create checkBlog --feed /whisk.system/alarms/alarm --param cron &quot;* * 1 * *&quot;). That Cron value is for once a day, and yes I had to use http://crontab-generator.org again.\nMade the rule (wsk rule create blogNagRule checkBlog rssnag)\n\nAnd that's it. To test I used the OpenWhisk UI on IBM Bluemix and manually triggered it. And the result....\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "VS Code Extensions: mssql and vscode-database",
		"date":"Fri Feb 17 2017 23:18:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1487373480,
		"url":"https://www.raymondcamden.com/2017/02/17/vs-code-extensions-mssql-and-vscode-database",
		"content":"For today's review of Visual Studio Code extensions, I looked at two related extensions that provide basic database support. In the past, when I was a heavy ColdFusion user, I was a huge fan of the database support in ColdFusion Builder. Now - CFBuilder was Eclipse-based so it was quite heavy, but I loved it's database viewer. I don't have it installed currently, but the basic feature set included being able to view all tables and structure as well as write ad hoq queries against it and see results. It supported anything ColdFusion itself supported, which had pretty good support for the RDMS world. I thought it might be useful to take a look at how well this type of feature is supported with Code.\nIn my search of the marketplace, I only ran into two plugs that I thought matched what I was looking for. Surprisingly I couldn't find anything that supported a NoSQL solution. The first one I tried was vscode-database, which supports MySQL and Postgres.\nOne of the things that I noticed immediately, and this is not the fault of the extension, but I find that running everything in the command palette does not always work well. Of course, Eclipse gets around this by having a gazallion panels and dedicated work &quot;areas&quot; for stuff like this. I certainly don't want to see Code turn into that, but my gut feels there needs to be a UI/UX solution that is better suited for more complex tooling like this. That's a bit vague, I know, but I think in the video it will be a bit clearer.\nThe extension provides support for:\n\nConnecting to a server\nSaving that connection (note, the password is stored in plain text)\nSelecting a database\nRunning an ad hoq query\nWriting/Running an &quot;Advancer&quot; query - which I have no idea what that means. It seems to simply make a temp file for you to write a query in, which is helpful, but again shows where a 'panel' ala-Eclipse would come in handy.\n\nI'm going to ding this extension a few points (err, not that I'm assigning ratings or anything) for not having proper documentation on it's marketplace page. It has an animated gif, which is cute, but totally not acceptable by itself.\nI didn't even notice it at first, but when connected, the lower left hand corner will tell you the server and database being used:\n\nUnless you use the &quot;Advancer&quot; features, you'll write your queries in the command pallete - which works well enough but you won't get autocomplete:\n\nThe output is sent to the bottom panel along with the query you selected. As you write more queries, the output is just appended, which I like since it means I can do a few quick queries and check the output later.\n\nAs I said, you do get autocomplete in the &quot;Advancer&quot; query, and it does work well - showing tables and columns. I found it a bit tricky at the column level (ie, the SELECT area), but it worked fine in FROM.\n\nAnd that's pretty much it. As I said, I do think they need to work on the docs, but the extension is usable for sure.\nNow let's look at mssql. The first thing I'll tell you is - it is probably a bad idea to try to use both this extension and the previous one in the same directory at the same time. I did that. Don't do that. The feature set pretty much mimics the previous extension.\n\nYou can define connections to a database.\nYou can connect to one.\nYou can execute queries in a file, but not in the command palette\nYou get autocomplete in SQL\n\nBut where things shine here is the view. Here is an example query. You have to use a .sql file (or a file set to the SQL type), and when you execute the query (via the command palette), you get a much nicer result set:\n\nIt isn't noticeable in a screen shot (the video will make it clear), but you can copy rows, columns, and individual cells. You can also export the result to a .csv or .json file. You also get a basic report on the execution. In the screen shot above it was clipped out, but the total execution time is also returned.\nConnections are stored to a preference which is nice if you don't want to define a configuration in the command palette. (I keep harping on that, but it is a bit of a pain for me.) The vscode-database extension uses a specific file for it's stuff whereas the mssql one integrates into user settings, which I think is a better approach. All in all, this is a much more polished extension, with better docs too, but obviously either will suffice for your needs.\nFor both, I wish I could more easily output table structure. Ie, show me the tables and columns in a dump. I know I can do that via a query itself, but I'd love something built in.\nAnyway, I hope this is helpful, and as before, I'm recording a quick video so you can see this in action. Please let me know what you think!\n",
		"tags":[
	        
            "visual studio code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Building a Form Handler Service in OpenWhisk - Part Two",
		"date":"Wed Feb 15 2017 19:22:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1487186520,
		"url":"https://www.raymondcamden.com/2017/02/15/building-a-form-handler-service-in-openwhisk-part-two",
		"content":"A few weeks ago I blogged about creating a generic &quot;forms handler&quot; with OpenWhisk. The idea was that you could point any form at the action and it would gather up the form data and email the results. One of the issues with my action was that it only worked via a HTTP call to the end point. That made it fine for a simple Ajax call from JavaScript, but it missed one of the cooler features that most form handling services provide - the ability to redirect to another URL when done.\nTurns out that support is now available in OpenWhisk as a new feature, &quot;web actions.&quot; Fellow IBMer Rodric Rabbah wrote up a great post on the feature here: Serverless HTTP handlers with OpenWhisk. Also be sure to see his follow up here: Web Actions: Serverless Web Apps with OpenWhisk\nI won't repeat the instructions, but essentially it comes down to a few changes:\n\nFirst, when creating (or updating) your action, add --annotation web-export true\nSecond, figure out the URL. Your URL will be https://openwhisk.ng.bluemix.net/api/v1/experimental/web/NAMESPACE/PACKAGE/ACTION.TYPE. If you aren't using a package for your action, use default. The TYPE value is one of json, html, text, or http.\n\nBasically, that's it. (There's more, but again, see Rodric's post). Given that my action was already mostly done, the modifications were pretty simple. I'll share the entire action at the end, but here are the mods I added.\nFirst, I simply look for a new parameter, _next. If specified, it means that we want to redirect after processing the form. I made this optional to still support the Ajax version I created initially.\n\nThen I simply look for this when done:\n\nThe final step was updating my action with the new code and that new flag to open it up as a web action. I modified my form to skip using JavaScript for the submission and just post to the new URL:\n\nNote the use of HTTP as the content type. I'm not sending HTML back, but rather HTTP headers. If I wanted to created a dynamic &quot;thank you&quot; page I'd use the HTML extension instead. The _next value I used is just my blog, but in a real world situation it would be a page specifically thanking the user for their feedback.\nAnd that's it. This is a great addition to the platform and I can't wait to kick the tires a bit more with it. Here's the complete source of the action, and as always, if you have any questions, comments, or suggestions, just add a comment below.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Collecting 911 Data with OpenWhisk Cron Triggers",
		"date":"Tue Feb 14 2017 19:56:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1487102160,
		"url":"https://www.raymondcamden.com/2017/02/14/collecting-911-data-openwhisk-cron-triggers",
		"content":"Today I'm sharing probably the most complex thing I've built with OpenWhisk. While I'm proud of it, I will remind people I'm still the newbie to this world, so keep that in mind as I explain what I did.\nMany years ago, like seven (holy crap), I built a ColdFusion demo that parsed local 911 data and persisted it locally to a database: Proof of Concept 911 Viewer.\nI used a Yahoo Pipe to suck in the HTML data provided by a local police web site and convert into something I could store. It wasn't necessarily rocket science, but it was fun to build. It was even more fun when I forgot I had automated it and came back months later to look at all the data I collected: Update to my 911 Viewer\nThat demo was on my mind recently and I thought it would be an excellent thing to try building with OpenWhisk. With that in mind, I built the following:\n\nFirst, an action that parses the data.\nSecond, an action that takes input data, sees if the data exists in a Cloudant data store, and then if not, adds it.\nA sequence to connect the two.\nA Cron-based schedule to periodically check the data.\n\nThat sounds like a lot, and pretty complex, but breaking it down into component parts/features\nmade it simpler to work with and let me try some parts of OpenWhisk that I had not played with yet, specifically the Cron-trigger aspect. Let's take it step by step.\nParsing Raw HTML Data\nThe data I'm parsing lives at http://lafayette911.org. As you can see, it is a table of incident reports:\n\nI began by doing a quick view source to see how the HTML was created. Turned out the table was driven by an iframe pointing to http://apps.lafayettela.gov/L911/default.aspx. Looking at the source code there I saw that the data was driven by an Ajax call to http://apps.lafayettela.gov/L911/Service2.svc/getTrafficIncidents. I got excited because I thought - for a moment - that I wouldn't have to parse anything. Turns out, the JSON was actually formatted HTML (I slimmed it down a bit):\n\nSigh\nOk, so luckily, I knew how to work with this. I did a demo last year involving web scraping with Cheerio (Scraping a web page in Node with Cheerio) and I knew that worked well, so my action focused around working with that. Remember, to include random npm packaged with OpenWhisk, you have to use a zipped action that includes the package.json and node_modules directory. It's a bit more work, but marginally so.\nThe other slightly complex aspect was that I wanted to geocode the addresses. For that I used Google's excellent Geocode API that is part of the Maps SDK. Here is the entire action.\n\nSo from the top - we begin with a generic request for the data. Once we've got that, we can ask Cheerio to turn into a DOM, just like HTML in the browser. I then grab all the table rows, and then fetch the cells inside each row. I do a bit of manipulation of the time to turn it into a JavaScript data and convert the &quot;assisting&quot; cell into an array.\nThe next part is a bit complex. I need to geocode all the addresses and this involves N async processes. So I use an array of promises to get all the results and then update the original data. Unfortunately, it looks like the service has an issue with intersections. So for example, an accident at &quot;Johnston and Camelia&quot; isn't properly geocoded even though the map links from the site seem to work well. This could be my fault. Sometimes it worked, sometimes it didn't.\nIn the end, I get a nice set of data:\n\nNot bad! Ok, on to step two - storing the data.\nPersisting the Data with Cloudant\nTo store the data, I provisioned a new Cloudant service with Bluemix. OpenWhisk can automatically pick up new Cloudant services and add a package to your account with actions/triggers to interact with that service. To work with those actions, I built my own action tasked with handling an input of data, checking to see if it's new, and then adding it. Here is that action.\n\nFrom the top - notice I'm using the OpenWhisk package. It basically lets me use OpenWhisk from my action much like I use it from the CLI. This still feels... wrong to me a bit, but I honestly don't know another way to do it. In theory, I could just make REST calls directly to my Cloudant service, but for now, I'm going to use the package. I definitely think I'll probably be doing things differently here in the future.\nIn the main section, note I've got some hard coded data there commented out. During testing, this is how I handled getting sample data into the action. In the end, it all comes down to the addIfNew block. My Cloudant skills are somewhat weak, but my logic seemed to work well. I query on location, reason, and timestamp, but not the assisting data as I wasn't sure if I could query on array values like that. On the off chance that two accidents happen at the same time at the same location but with different responders I'll just assume the entire multiverse is breaking down and life, as we know it, is pretty much over. (Hey, I won't have to write unit tests!)\nIf no mat",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "More Thoughts on Leveling Up",
		"date":"Fri Feb 10 2017 16:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1486743840,
		"url":"https://www.raymondcamden.com/2017/02/10/more-thoughts-on-leveling-up",
		"content":"Warning - what follows isn't code related - it's a bit mushy. I apologize in advance. Something has\nbeen on my mind this morning and I thought I'd share. A little over a year ago, I shared an article\non how developers can improve their craft, &quot;Leveling Up Your JavaScript&quot;. For some reason, this article has been picked up by a few outlets on Twitter this week and I've seen renewed interest/RTs/etc on the content, so I guess that's a good thing if people are still finding it useful.\nThe central tenant of that article was the idea that it's possible to progress in JavaScript, and really, any skill, by focusing on small steps that improve the way you do things.\nI cannot tell you how often I feel horribly inadequate when it comes to client-side development. I look at folks like Eric Elliott and his incredible &quot;Master the JavaScript Interview&quot; series (note, that's a link to one article in the series, see the rest at the end) and I shudder under the weight of how much I don't know, or to be more fair, how much I don't know completely.\nThe point is - I do not have to be at the top of the JavaScript ladder. It will always be my goal. I will always attempt to move up, but reaching that peak is not necessarily my actual goal. I look at myself on that ladder, maybe, half way up (on a good day), and I remind myself, if I can help someone else move up to where I am, than I'm helping them on the path as well.\nWe can all do that, even those who are barely above that first rung. Every time you share your code, your questions, your suggestions, you can help someone else move up, and as a whole, we all improve.\nKeep that in mind.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Serverless and Persistence",
		"date":"Thu Feb 09 2017 17:20:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1486660800,
		"url":"https://www.raymondcamden.com/2017/02/09/serverless-and-persistence",
		"content":"Today's post isn't going to be anything really deep, more of an &quot;A-ha&quot; moment I had while talking with my coworker Carlos Santana. No, not that a-Ha...\n\nMore a &quot;adjustment of a misunderstanding&quot; of the serverless platform. One of the things I knew right away about serverless is that my code acted as a single, atomic unit, in an ephemeral form. So yes, a server is still involved with serverless, but it's created on the fly, used my code, then disappears when my code is done running. The OpenWhisk docs have this to say:\n\nStatelessness\n\nAction implementations should be stateless, or idempotent. While the system does not enforce this property, there is no guarantee that any state maintained by an action will be available across invocations.\n\n\nMoreover, multiple instantiations of an action might exist, with each instantiation having its own state. An action invocation might be dispatched to any of these instantiations.\n\n\nSo the practical side effect of this is that you can't use non-database persistence. No file system and no memory variables. Obviously you could use a cloud-based file system like S3 and obviously you can persist in a database, but you can't do this within the action itself.\nExcept you can.\nFirst off - when your action is fired up, OpenWhisk does not kill it immediately when done. Rather, it keeps the container used for your code around to see if the action will be fired again soon. You can see this yourself with a simple action:\n\nIf you run this a few times, quickly, the variable will persist and increment:\n\nThe practical use for this would be storing the result of a database call in memory. For example, imagine if your action needs data from Cloudant in order to process. You could store that value in a global variable and check to see if it is undefined before you talk to Cloudant again. You can only do this if the data you need isn't dependent on arguments - unless you want to start caching with keys related to function parameters.\nThe second &quot;a-Ha&quot; moment is that you absolutely do have a file system. That means your code can CRUD locally to work with binary data. You could also simply leave files there and see if they exist on the next invocation. I'd probably treat this carefully, but if you are using one, or a few, particular files, you could consider leaving them there before fetching them.\nSo as a practical example, if you need to work with a file from S3, you could copy it locally, and keep it there. When the action is run next time, see if it exists before copying from S3.\nA real example of this is the incredible demo Daniel Krook made called openchecks. It's an OpenWhisk sample app demonstrating how to build a serverless check scanning/depositing system. As I said, it's really, really cool and you should check it out.\nAnyway, I hope this helps. As I said, this is something that I assume most people working daily with serverless already grasp, but it was eye-opening to me and just adds another interesting level to what you can do with the platform!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Using Social Login with Passport and Node",
		"date":"Wed Feb 08 2017 18:29:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1486578540,
		"url":"https://www.raymondcamden.com/2017/02/08/using-social-login-with-passport-and-node",
		"content":"I've blogged before about Passport (&quot;Some Quick Tips for Passport&quot;) as well\nas an example of social login with Passport (&quot;New POC - Daily Reddit&quot;), but I thought it would be\nnice to share an example focused just on using social login with Passport. As I discovered when\nI first worked with Passport, it makes things really easy, but it can be incredibly difficult sometimes to find the right part\nof the docs to figure out particular usage needs.\nFor me, the biggest thing that helped me was realizing I could not rely on the documentation at the main Passport site. To be clear, it\nisn't bad. It's just incomplete. Going to the repository for the strategies I was using it was helped me get my code working\nand I strongly recommend developers do the same. In case you don't know, when you click on &quot;Strategies&quot; on the Passport site, each\nstrategy listed links to the appropriate repository for more information.\nIn this blog post, I'll try my best to document everything in the hopes that folks reading this later can bypass the issues I ran into. I'm also going\nto share screenshots from the social providers I'm using. Please remember that the UI for these services may change after I've published. I ran into\nthat problem when using social login with Ionic a few months back.\nAlright, so with that out of the way, let me begin by clearly documenting what I'm building here:\n\nI've got a Node app where a person can login via Facebook or Twitter.\nWhen they login, I want to grab their email address.\nI want to store a user record locally, using Mongo, that matches that email address.\n\nPretty simple, right? Let's begin by looking at the basic shell of the app, without any login support. As I said, I'm using Mongo for my persistence, and I've decided to use Mongoose as well.\n\nNothing too interesting here. You can see me loading up my credentials and connecting to Mongo. I'm also using Handlebars for my templating engine. I'm using sessions to recognize when you're logged in. I built two pages - a home page and a login page. The login page simply provides links to begin authentication with Twitter or Facebook. (In a real app, I'd probably just include that in the header or some such.)\nLet's begin by adding support for Facebook.\nFacebook Login\nBefore I do any code, I need to create a Facebook app linked to my project. This will give me the credentials I need and let my application authenticate with them.\nAssuming you have a Facebook account, head over to https://developers.facebook.com/. In the upper right hand corner will be a &quot;My Apps&quot; button - click it to add a new app.\n\nYou'll be prompted for a name and a category. If testing, enter whatever you want. If building something for production, actually enter something that makes sense.\nAfter entering a Captcha, on the next page, click Add Product. Right on top is the product you want - Facebook Login.\n\nFor platform, select WWW:\n\nYou'll then be prompted to enter information about your site. You do not have to have a site in production yet. You can absolutely use localhost for your values. For the first prompt, I used http://localhost:3000 as thats what my Express app used. I skipped the rest of the panels, and when done, I clicked the new &quot;Settings&quot; link under the Facebook Login group in the left hand menu.\n\nOn the settings page, there's one important setting here, &quot;Valid OAuth redirect URIs&quot;. You need to tell Facebook where a user is allowed to be redirected back to after authorization. Again, you can use localhost for this. I used http://localhost:3000/auth/facebook/callback. Why? That's what I saw in the Passport examples. It's arbitrary. Just remember you'll need to add a production URL later.\n\nMake sure you click Save! Then go to the main Settings link in the left hand nav (towards the top) and you'll see an App ID and App Secret field. Copy these locally. For me, I'm using a JSON file. Here is the file (with Twitter stuff already in, just ignore for now ;)\n\nWhew. Ok, so at this point, you've done what's required on the Facebook side. Now let's turn back to the code. You need to install Passport (npm install --save passport) and then the Facebook strategy (npm install --save passport-facebook).\nAlright - now let's walk through the code. As an FYI, I'll share the entire app.js when the blog post is done so don't worry if you get a bit lost. First, require in the packages:\n\nNext, we configure the Facebook strategy. This involves some really important bits, so pay careful attention:\n\nFrom the top:\nFirst, I provide my various credentials.\nNext, I pass an optional (and not well documented) setting called profileFields. By default, you don't get much of the profile back when logging in. I believe id and displayName are default, but I definitely needed\nto add emails. Remember, my plan is to use email as a primary key for my users.\nThe profile object is provided by Passport and attempts to coalesce profiles from various providers ",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "VS Code Extensions: Output Colorizer",
		"date":"Mon Feb 06 2017 21:23:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1486416180,
		"url":"https://www.raymondcamden.com/2017/02/06/vs-code-extensions-output-colorizer",
		"content":"Back when I was a Brackets user and an Adobe employee, I used to do a review of interesting and useful\nextensions. This proved to be a pretty popular series so I thought it might be a good idea to do the same for my current\nfavorite editor, Visual Studio Code. Right now I'm thinking I'll try for two reviews per\nmonth, but we'll see if they prove interesting enough for my readers. (So yes, consider this my plea to let me know in\ncomments if this is useful or not.)\nFor my first review, I'm going to review one built by a good friend of mine, Andy Trice. I\nfeel like I should warn folks before I review things built by my friends, but at the same time, if an extension sucks, I\nprobably won't bother actually reviewing it!\nThe extension I'm covering today is an extremely simple one, but one that adds a really useful feature - Output Colorizer. This extension\ndoes two things: It colorizes output (duh) and .log files.\nLike I said - simple. But when you compare a before and after views you'll see it's definitely worth adding to your VSCode install.\nAs an example, here is Git output without the extension installed:\n\nPretty boring, right? Like drinking a Michelob Ultra. Now here is Git output with the extension running:\n\nMuch nicer, right? That's like going from a Bud Light to a Stone Ruination Double! It's a small thing, but so darn useful. Anything\nthat outputs down there will be updated, and that includes other extensions that send output there as well. As I said, log files\nare also updated:\n\nIn case it isn't obvious, the log on top shows the uncolorized output versus what you get with the extension.\nSo... yeah. Not exactly the most exciting extension, but as I've said a few times now, really darn useful. The extension is currently\nat over eleven thousand downloads which is pretty freaking amazing. If you want to see all of the above in video form, you can watch\nit below.\n",
		"tags":[
	        
            "visual studio code"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with OpenWhisk Triggers",
		"date":"Thu Feb 02 2017 22:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1486072980,
		"url":"https://www.raymondcamden.com/2017/02/02/working-with-openwhisk-triggers",
		"content":"Today I'm covering one of the last major features of OpenWhisk, Triggers. To be clear, there is a lot more\nto OpenWhisk than I've covered on my blog, but in terms of &quot;usage features&quot;, this is one of the last ones\nI've needed to tackle. This was also the feature that was the most difficult to wrap my head around. It isn't\nthat the docs are bad (Creating Triggers and Rules), it's just\nthat I struggled to understand how all the parts come together. I finally got things working today (with a lot of help!) and I thought\nI'd share my thoughts, and a demo, but please remember I'm new at this!\nBefore I begin, I want to thank folks in the OpenWhisk Slack (sign up here: http://slack.openwhisk.org) for helping me out, especially fellow IBMer\nJustin Berstler.\nOk, so what in the heck is a trigger? A trigger is an event of some sort.\nSo far so good.\nIn OpenWhisk, triggers have a name, and that's it - no code or anything else is associated with them. You create\na trigger like so:\n\nOnce you have that, you can then list triggers:\n\nAnd manually fire a trigger:\n\nLike actions, triggers can contain parameters, and you pass them just like you would to an action:\n\nBy themselves, triggers do nothing. To actually have a trigger have some meaning in life, you associate them with an action. You do that by defining a rule. A rule\ncreates a one to one association between a trigger and an action.\n\nYou can list them too of course. This is where things get interesting. While a rule is just a one to one association, you can have as many\nrules as you want, all using one trigger.\nCool, right? So this is where I began to get a bit fuzzy. The OpenWhisk folks created some Triggers you can use with Bluemix and GitHub.\nYou can read more about them in their docs. But I was more interested in\nsomething the trigger docs allude to but don't actually show working - listening for new tweets.\nThe docs use an imagined trigger called newTweet and of course, I actually wanted to see if one could be built. This is how I got it working. (And again, I thank the folks in the\nSlack channel!)\nFirst, I needed a way to know when a new tweet was created. Turns out, IFTTT has a recipe for that. Unfortunately,\nyou can only use it to email when a new tweet is created by a user. Then I was pointed to their new Maker service. This lets you do a variety of things\nbased on an particular recipe, including making a HTTP call.\nThis is when I was told that you can trigger any URL via a REST API. This is documented here and it's\nsomething I had not noticed before. The general URL to run a trigger is to send a POST to:\n\n{BASE_URL} should be openwhisk.ng.bluemix.net and\n{namespace} can be _ to use your default\nspace. Authentication is tricky. You do not use your Bluemix login. Rather you use the authentication credentials\ncreated when you first started using the OpenWhisk CLI.\nIf you go to your user directory and open the .wskprops file, you'll see an AUTH setting\nthat includes your username and password. You can also get it easier by doing wsk property get. Here is an example of that:\n\nOk, so I made a trigger called newTweet, which means I could then execute that trigger by going here with a POST:\nhttps://secret:password@openwhisk.ng.bluemix.net/api/v1/namespaces/_/triggers/newTweet\nHere is how my IFTTT recipe looks - I had to break it up in a couple of screen shots because their UI\nis pretty tall.\n\nThe first portion is simply the text description of the recipe and the user (me) to monitor.\n\nThis is the Maker part - it's basically my URL.\n\nThis part is crucial. First, you need to make a POST to your trigger and you need to use the right content type. The actual\ndata needs to be a JSON packet and you can see there I've defined a key called tweet and included the text. I can put more data\nin there, like the time the tweet was created, and since this recipe runs once an hour or so, that may be crucial to add. For now though I kept it at that.\nLet's pause a bit and define what we've done.\n\nI made a trigger called newTweet. Literally that's all I did, it's just a name.\nBecause triggers have a URL, I know how to activate via the web.\nI used IFTTT to say, &quot;When @raymondcamden tweets, hit this URL.&quot;\nOpenWhisk will take the data and for now, do nothing with it. Remember, a trigger by itself is like a poor little kitty without a home.\n\nAt this point I started testing. You can retrieve activation info via the CLI, but I used the UI (something I'll be\ncovering in more detail next week) to see when/if my trigger fired. To test, I tweeted, and then forced my recipe to\nrun in IFTTT. I then refreshed the OpenWhisk monitor, and BOOM! There it was:\n\nI cannot describe how excited I was by this. Seriously - I was.\nSo to make this actually do something, I needed a rule to connect this to an action. A few days back\nI blogged an example of\nusing SendGrid with OpenWhisk. I made a new action that would simply email me the tweet data:\n\nThat's not too exciting, but you get the idea. I",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "The Apache OpenWhisk Slack",
		"date":"Wed Feb 01 2017 21:13:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485983580,
		"url":"https://www.raymondcamden.com/2017/02/01/the-apache-openwhisk-slack",
		"content":"This is just a quick note (since I know not everyone uses Twitter, and lately, I've been trying my best to avoid it) to let\nfolks know that there is an official Slack org for folks working with Apache OpenWhisk: openwhisk-team.slack.com\nYou can sign up for it here: http://slack.openwhisk.org\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Using Packages in OpenWhisk",
		"date":"Tue Jan 31 2017 18:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485885600,
		"url":"https://www.raymondcamden.com/2017/01/31/using-packages-in-openwhisk",
		"content":"As I continue my exploration of serverless with OpenWhisk, today I'm going to look at\nthe packages feature. While not terribly complex, I thought writing up my take on it and\nsharing some screen shots might help folks better understand the basics.\nAs you play with OpenWhisk, you may be wondering where exactly your actions &quot;live&quot;. Obviously the whole point of serverless is to not\nworry about - you know - the server - but there is a directory of sort for your actions. You can see this yourself\nby simply listing your actions:\n\nWhat you're seeing here is a list of every action I've created. You can see that each one begins with:\n/rcamden@us.ibm.com_My Space/\nThis is documented in Namespaces and packages:\n\nIn Bluemix, an organization+space pair corresponds to a OpenWhisk namespace. For example, \nthe organization BobsOrg and space dev would correspond to the OpenWhisk namespace /BobsOrg_dev\n\nSimple enough - but you can guess that organization is going to become an issue. While you can try to name your actions with good, descriptive names,\nat some point you will have to start giving weird names to actions just to avoid conlicts.\nThis is where packages come in. Essentially, you can think of them as a subdirectory for your actions. (They do more than that, and we'll cover them in a second.)\nTo create a new package, you issue this command:\n\nHere's an example of me creating one called utils:\n\nYou can see your packages with:\n\n\nThat second package you see there was created when I was working with Cloudant back on my first post on OpenWhisk.\nTo see what's in a package, you simply do either:\n\nOr:\n\nThe first command returns a JSON object for your package and the second returns a more readable version. I'll show you both of these later, but first let's add an action to the package so there's\nactually something in it. I've got a simple action I created earlier that just echoes a name:\n\nTo add this to my new package, I simply do:\n\nAgain, pretty simple. Now let's look at what wsk package get returns, both the 'raw' and summary version:\n\nAs you can see, the summary version is what you'll probably always want to use at the CLI. If your curious, the\ngeneric wsk action list returns all your actions, even those within packages:\n\nInvoking the action is the same as any other action, you simply prefix the package:\n\n\nWhew. So I said this was simple, right? It is - but I like to be complete and show these things actually running. So that's the basics, but what else is there?\n\nPackages, like actions, can have default parameters. This allows you to specify a default for every action\nin the package. In case you're curious, an action's default parameter takes precedence over a package's\ndefault parameter. (Thank you to Stephen Fink@IBM for clarifying that for me.)\nThe other big change is that packages can be shared with the wider world. You can specify a &quot;shared&quot;\nsetting (true or false) when creating or updating a package. By making it shared, anyone can use it.\nTo me, this is the biggest use case for packages - providing a way for you to collect related actions and\nthen share them with others.\nAnd then finally, OpenWhisk has a large set of shared packages called whisk.system. They\nprovide various utilities as well as access to common Watson APIs and other useful tools. You can\nbrowse the docs for them or use\nthe CLI, but I'd check the docs as they are much easier to read.\nOk, so really finally - I'll point out you can also put feeds in packages. I haven't yet blogged about triggers, feeds, and rules, because\nit's a bit complex and I'm still wrapping my head around it. They will be the subject of my next OpenWhisk article.\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Some Guidance for Blogging",
		"date":"Tue Jan 31 2017 16:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485879660,
		"url":"https://www.raymondcamden.com/2017/01/31/some-guidance-for-blogging",
		"content":"Earlier this week a reader sent in a great set of questions (I've edited his text a bit for clarity):\n\n\nCould you write a post or point me to a good post if you have ever come across which details the \nfollowing that would be a great help to me. \n\n\nEthics for writing a blog\nHow to credit others or if you link someone else's content on your blog what should be the ideal \nprocess to ask for permission and why is it necessary?\nSome absolutely essential dos and don'ts\nWhat platform to choose - self hosted or service like medium etc...?\n\n\nSo let me address this one by one.\nEthics for Writing a Blog\nThat's a pretty big question. I'll try my best to answer this but certainly follow up in comments if you want any clarification. I created\nmy blog as a way to express myself. While the primary focus was to share technical knowledge, I also wanted a place I could\nshare what I was passionate about.\nI try - always - to be as honest as possible. The only one who really knows how well I do with that is myself of course, but\nit doesn't really gain me anything to be deceitful. Of course, I make mistakes, and may say &quot;so and so will do so and so&quot;,\nbut I try my best to address those mistakes as quickly and completely as possible. For small mistakes, I'm ok with them being\naddressed in the comments. For big mistakes, I'll edit the blog post to point out what was wrong.\nI try to be fair in my posts when doing reviews. This is not Consumer Reports, so when I review some piece of hardware or software\nit's always going to be my personal opinions, but I try to be objective and open to views/experiences that other people may\nhave.\nThe ultimate end goal is for my readers to find something useful here. I don't expect every post to be useful, and sometimes\nI do write just for myself (I find that writing something down really helps cement my own ideas), but I hope that overall the\ncontent here is a net plus for the Internet.\nHow to Credit Others\nIt depends on the context. If I'm quoting someone, then you need to say who are you quoting. If you are using someone's elses code, then\nyou should credit them in the code and in the blog post too if it makes sense. There isn't a hard and fast rule here. If I'm using a library,\nthen I definitely point it out in the blog text, and most likely the entire blog post is about the library. If I'm using a code\nsnippet from Stack Overflow that's just supporting the other, more important, aspect of my code, then I just credit it in\nthe code, not the blog text.\nJust to be clear - you always want to credit someone, the question is just how and where in the blog post you include that credit.\nYou mentioned asking permission to link - I don't believe that is necessary. If I were Slashdot and were concerned about my link\ncausing a server to go down, I'd warn them first, but the web was built on links - just do it.\nEssential Dos and Don'ts\nFor a blog, I think it's essential to have dates on posts. Technology changes, and if I can't tell how old your content is, I immediately distrust it a bit.\nI also am a big believer in comments. I think they help engagement and can provide great feedback. Unfortunately, a large population of people\nlike to be complete shit heads on the Internet since Mommy wasn't nice to them when they were young. If I were a woman in tech, or writing on social issues, I'd\nprobably recommend the exact opposite. And let me get on my soap box a minute here and remind folks that if you know people who are being shitheads on the Internet\nand you aren't calling them out on it - you're part of the problem.\nOh - if you do include comments, you absolutely must provide a 'subscribe' feature so I don't\nhave to keep going back to your blog post to see if you (or anyone else) responded.\nDon't do popup modals begging me to &quot;Like&quot; your content or subscribe to your newsletter. Yes, I know they work. But I feel very strongly\nthat they are incredibly rude and offputting to your readers. I've come to your blog to read your content and you decide to stop me from doing\nthe one thing I came there for to ask me to like your shit? I mean seriously, does that make sense to you?  I know Google is beginning to\npunish sites on mobile that do that and I truly hope they do so for desktop as well. I will flat out refuse to read most sites that do this and\nI never promote, via Twitter or any other form, a URL that has one of these dialogs.\nLastly - provide some way to contact you. That can be a simple email address, a contact form, or a Twitter post. I can't tell\nyou how many times I've seen typos, mistakes, etc on blog posts and tried like heck to let the author know but there was no way\nto do so. Again though, see my warning above about trolls. I can definitely understand why some people would want to protect themselves.\nPlatform\nYeah, that's hard to answer. I started off with my own custom blogware, BlogCFC. I then went to WordPress, self-hosted, and then\nto static hosted by Netlifly. I like building ",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Further Down the Windows Train...",
		"date":"Mon Jan 30 2017 17:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485797520,
		"url":"https://www.raymondcamden.com/2017/01/30/further-down-the-windows-train",
		"content":"About three months ago I wrote up my &quot;finalish&quot;\nthoughts on the Surface Book (which, as a reminder, Microsoft provided) and Windows. As was obvious by the title, I had planned\nto come back later and talk more about how the 'transition' is going, how the SB is holding up, and more. By my calendar\nit is officially &quot;Later&quot; so here's an update.\nSurface Book\nThe Surface Book has 100% replaced my MacBook as my primary laptop. I gave my first presentation on it about two weeks ago,\nbut I have to be honest and say that until I've done a few more, my plan is to travel with the MBP just in case. This is less\na fear of hardware issues and more a general worry that I want nothing, ever, to interfere with me giving a good presentation. I've got\na bunch of presentations coming up so I'll be able to quickly see how well the\nhardware works in front of a crowd. I've got two dongles for the mini display out (one for VGA and one for HDMI) so I should be\ncovered.\nI still have two lingering issues with the Surface Book. The first is the track pad. There's nothing wrong with it, but it's different\nfrom the MBP. For some reason, I adapted rather quickly to the new keyboard layout, but it has taken me longer to get used to the\ntrackpad on the SB. It's hard to describe, but if I had to, I'd say that the issue is that I need to provide about 5% more 'force' when\nI move my fingers on it. My muscle memory for using the trackpad is taking longer to adapt than the my muscle memory for\nthe keyboard. (In fact, now I struggle to use the MBP keyboard.) At home I'm using a keyboard so I find that when I travel and use\nthe SB all day, every day, it becomes a non-issue.\nThe second issue is that - lately - I've seen it fail to come back from sleep and I have to power cycle it. I've disabled sleep\nwhen it is connected to a power supply for now and I'm fine with that. Frankly, &quot;sleep&quot; has been an issue with every single device\nI've ever owned. I figure after the next Windows update I'll try it again and see if it's corrected, but I'm just not concerned about it now.\nAs I've said before - I'm convinced this is my laptop line for the next decade. In a year or two when I'm ready to upgrade, I'll be getting\na SB 2 (or 3). I have no interest in the new Macbook. At all.\nWindows\nI don't have much to report here. I settled on using ConEmu for my console tool with\ncmder as the actual shell. I'm still not using Bash for the reasons I said before\n(having to reinstall Android), and because (afaik), I can't run Bash in ConEmu (no, wait, I can, just found out I can! ;)\nI still find npm tasks to be slower than on OSX. As an example, and this isn't terribly scientific, I ran ionic start jan30 on my Mac and Windows machine. The ionic CLI\nuses Node/npm to execute. It took Windows 30 seconds longer to complete than the Mac.\nOk - so I stopped writing this blog post and did some testing. In Bash, outside of ConEmu, I noticed things ran a bit quicker, cutting the difference\nto about 20 seconds. Still a pretty noticeable change.\nI then tried turning off my AntiVirus real time protection. Back when I first got my SurfaceBook, I seem to remember this being a\nsuggestion (not completely mind you!) and I can verify that the Ionic CLI ran a heck of a lot quicker. In my test (yes, one test), it\nwas 5 seconds slower than the Mac, which is something I probably wouldn't even notice. So now my task is to figure out\nhow to keep real time scanning on but have it not impact npm tasks. (And I just looked - I'm using McAfee LiveSafe and it looks\nlike I can't tell it to ignore certain paths, just specific paths. Not sure what to do now.)\nThe other issue I had was with Skitch, an app no longer produced that I used the heck out of on my Mac. It takes screenshots\nand lets you add annotations. This was a great tool for blog posts. Turns out though that this was wrapped\ninto Evernote, I just didn't know it!\nDell Hell\nOk, not hell, but I'm back on a Dell. I bought my first new desktop in probably near 5 years. I got a Dell XPS with a\nhigh end graphics card as I may try PC gaming again. And because, well I like the shiny. It's a pretty darn fast machine and\nI love the storage (500 gig SSD + 2 TB old style). I'm running Plex on it for my media which is a really cool\nservice for sharing pictures, video, and music in your house.\np.s. I kinda feel like this blog post isn't terribly helpful. To be honest, life has been pretty busy lately and what's been going\non in our world has been crazy. My ability to focus is a bit of whack. Sorry about that. If folks have any questions about\nmy experience moving (back) to Windows that I haven't covered yet, please let me know!\n",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Talking to your Bot on OpenWhisk",
		"date":"Thu Jan 26 2017 16:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485449100,
		"url":"https://www.raymondcamden.com/2017/01/26/talking-to-your-bot-on-openwhisk",
		"content":"As I continue my look into serverless with OpenWhisk, today I thought\nI'd build a quick demo around an incredibly cool bot service I discovered a while ago called\nPandorabots. I first played with their service last summer, and I thought it was\ncool as hell, but I never got around to actually writing up my experience with it. My original exploration of it\nwas via ColdFusion, but I thought this would be a great example of something I could build even easier (and a heck of a lot quicker)\nin OpenWhisk.\nPandorabots provides what it calls &quot;AIaaS&quot;, &quot;AI as a Service&quot;. Basically what this boils\ndown to is the ability to process natural language input and handle responding intelligently based on rules you create. Their actual service\nis pretty simple. You've got APIs to upload scripts for your bots and APIs to 'speak' to your bot, the real complexity comes in at the\nAI level.\nThere is no way I can adequately explain the full power of their bot service, so instead I'll give you some high level points\nto describe what you can do.\n\nSo the simplest, and most direct feature, is to simply say, &quot;If I say hi, respond with Hello!&quot;. That's basic string matching and it's not too exiting. But their\nservice goes beyond this of course.\nYou can define various aliases, so I can map hello, bonjour, etc. to the same response.\nIt has built in spelling corrections and other fixes, so it will recognize &quot;isn't&quot; as &quot;is not&quot; for example.\nIt can automatically handle multiple sentences, parsing each one and creating a response to everything sent to it.\nIt supports both a default (&quot;I have no freaking idea what you said&quot;) response as well as the ability to respond randomly. (So if I said hi, it could respond with hello or some other greeting.)\nIt can do basic pattern matching, so given I said, &quot;I like cookies&quot;, I can train my bot to recognize &quot;I like SOMETHING&quot; and it will correctly pick up &quot;cookies&quot; as the thing I like.\nThe bot can have variables with predefined values. So I can define a variable, botname, that defines the name of the bot, but the bot can also remember your name if you tell it and refer to that variable later.\nYou can define arrays (called sets) and maps to train your bot to associate words or terms together in one unit.\nAnd this truly cool - it supports context. What that means is you can recognize that a response is based on a previous question. The examples the docs give is that if I say the word coffee, the bot could respond with &quot;Do you like coffee?&quot;. If I say just &quot;yes&quot;, my bot can recognize that we're still talking about coffee.\n\nAs I said - the logic of your bot can get really complex. Pandorabots provides a &quot;playground&quot; if you want to try it out.\nI also suggest looking at the Quick Start and then checking out the tutorial. Unfortunately the\ntutorial is a large slide deck, not a proper 'document', but it's pretty verbose and has lots of great examples. If your curious how the logic scripts look, here\nis one sample:\n\nYep, XML-based, but there's nothing wrong with that, right?\nSo as I said, the AI portion is pretty darn complex, but the API side is incredibly trivial. You can manage your bots via a REST\ninterface, but since they have a CLI too, I'd probably just use\nthat during initial testing. The CLI lets you chat with your bot too and is great for configuring the logic. The API I'll use in this demo is the\nTalk endpoint. It lets you have a conversation with a bot you've setup. I've\nused a sample robot they provide on GitHub called Rosie. It's got a deep set of conversation files so I don't have to worry\nabout writing that myself. I named my Allura though. (Bonus points if you get the reference without clicking the link.)\nThe Talk endpoint simply has me hitting: https://aiaas.pandorabots.com/talk/APP_ID/BOTNAME?user_key=USER_KEY&amp;input=INPUT. You can also append\na sessionid value that represents a talk 'session'. When you send data to the bot, it returns a sessionid value automatically, but you need to pick that\nup and send it back.\nLet's start with a simple OpenWhisk action.\n\nI said it was simple, right? All I do is pass my various keys, the input, and a session value if it was passed in. Here's an example of me running\nthe action locally:\n\nThe next step then is to add a REST interface to this action:\n\nAnd I'm good to go! First, let's try talking to it with no input:\n\nWoot! It works. Than let's try something sassy, like, &quot;My milkshake is better than yours&quot;:\n\nWhat the you know what. I wasn't expecting that. Remember, I used a default bot that had logic already. Apparently it recognized the input. So yeah, let's follow up with this:\n\nI think I love this bot. Honestly.\nAnd that's it! Of course I could build the front end, and I will if people ask, but Pandorabots only has a free tier for ten days, so I'm taking down the REST endpoint\nnow and setting a calendar reminder to close down my acco",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Form Handler Service in OpenWhisk",
		"date":"Wed Jan 25 2017 21:42:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485380520,
		"url":"https://www.raymondcamden.com/2017/01/25/building-a-form-handler-service-in-openwhisk",
		"content":"As a fan of static site generators, I've played with, and built, my own form processing services to work with my static sites. My current favorite is\nFormSpree - it's the one I use on this blog. I even built my own version of it in Node (&quot;Building a Simple Form Handler Service in Node&quot;) back\nin October last year. I thought it would be kind of cool to look into building a similar type service with OpenWhisk.\nRight now, OpenWhisk isn't necessarily the best choice for this type of service. The main reason being that you can't return HTTP headers in your response. That\nmeans I can't take a form and simply set the action to an OpenWhisk URL. Hopefully that will come in the future. But I can use Ajax to process\nthe form and that's the route I've taken for this demo.\nThe basic premise is this:\n\nAccept a set of form data\nEmail that form data\nLook for special keys possibly change that email, like _subject, and _from.\n\nFor sending email, I decided to use the sendmail NPM package which is supported out of the box with OpenWhisk. nodemailer is also supported, but I\nwanted to try something new. After creating my API key, I wrote up the following action.\n\nLet's tackle this line by line. I assume you can figure out what SG_KEY is. The variable, RECIPS, is used to create\na whitelist of emails that can be the recipient. This is unlike FormSpree which will email anyone (with confirmation of course!) but I figured\nthis was a more realistic example. I used an array since I thought for a real &quot;dot com&quot; there may be a few different forms in\nuse.\nGoing into main, you can see how I look at my arguments for possible overrides of behavior. You can override the\nfrom address, the to (again though, only within the acceptable list), and subject. I then craft a string of the remaining values and\nsend that to SendGrid. And that's it. I could obviously make this a bit nicer, switch to HTML email too perhaps, but you get the idea.\nI then simply used the OpenWhisk command line to create the action and API end point. (Although before I did that, I tested locally.)\nLet's look at the front end. First, I built a really ugly form.\n\nAnd then wired up the code.\n\nIn the end, my code is simple DOM reading and Ajax-fetching. You do have to be careful about how you POST to OpenWhisk. You cannot send a regular\nform body and must instead send a JSON packet of data. But frankly that wasn't necessarily a big deal. And of course - it works!\n\nYes - the formatting is a bit subpar, but for a quick demo, I'll leave it be for now. So what do you think?\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "New Camera Hotness from Chrome",
		"date":"Tue Jan 24 2017 15:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485272640,
		"url":"https://www.raymondcamden.com/2017/01/24/new-camera-hotness-from-chrome",
		"content":"First off - I apologize for the somewhat lame title. It occurred to me today that it's been a while since I played with new and\nupcoming web standards, and as I recently discovered Chrome was introducing some really cool stuff around camera support, I thought\nit would be fun to explore a bit. Specifically I'm talking about the &quot;Image Capture&quot; API. This API provides access to the camera and\nsupports taking pictures (of course) as well as returning information about the camera hardware itself. Looking into this API led me to\nanother new API, MediaDevices, which replaces the older getUserMedia that you may\nhave seen in the past.\nI've blogged about this topic in the past. Back in 2013, I wrote &quot;Capturing camera/picture data without PhoneGap&quot;. In\nthat post I made use of the capture of the input tag to let users select from their device camera. That post got so much traffic,\nI revisited it last year: &quot;Capturing camera/picture data without PhoneGap - An Update&quot;.\nIn that post, I looked more at the capture argument and the various different ways of using it.\nWith the Image Capture API, we've got something we can use directly in JavaScript without an input tag. In order to use it, you need to either\nenable an &quot;origin trial&quot; in Chrome 56, which will be out very soon, or use\nCanary and enable &quot;Experimental Web Platform&quot; in flag. I tested with Canary - which as a reminder is also available on Android devices. You can't use this\nat all on iOS.\nHere is a simple example, taken from\nthe excellent blog post by Google.\nFirst - request access to the camera. This gives a visible prompt to the user:\n\nFirst and foremost - notice it is promised base. Woot. That argument to getUserMedia is how we tell the browser what we want to access. This\nwill also impact the warning the end user gets. As an aside, you can go batshit crazy in that argument. While I was digging around the MDN docs\nfor getUserMedia, I found out you have options for the resolution you want,\nand by options, I mean min, max, desired and required. That's freaking cool. I can imagine for someone taking pictures for legal, or maybe\nscientific purposes, you may want to demand a certain level of fidelity before working with the camera. This is also where you can ask for the front\nor rear facing camera, and again, what's cool is that your code can say you want the rear camera or you require it. It's a bit off topic for\nthis post, but later on click that link and see how precise you can get.\nAnyway, once you have access to the device camera, taking a picture is trivial:\n\nIn the sample above, $img is an img tag in the DOM. I simply ask for the picture and then display it.\nThe API also supports grabbing a frame (called, funny enough, grabFrame) from a video stream and\nreturns a slightly different, lower resolution, form of the image.\nLastly, and this was the really fascinating part, you can ask for the camera settings (getPhotoCapabilities) and set options (like zoom) as well (setOptions).\nSo with that in mind, I built a simple demo that would demonstrate capturing an image as well as getting the capabilities of the camera. To\nmake it even more interesting, I added in enumerateDevices, part of the MediaDevices API, which does\npretty much what you think it does. Let's look at the code, front end first even though it's not doing much.\n\nNothing much here except for a button and a few empty DOM items I'll fill up later. Now for the code - and to be clear, a lot of code here is taken/modified\nfrom the Google/MDN demos.\n\nFrom the top - I grab some DOM items to reuse later, than try to fire off a request to get a device. On my desktop, where I don't have a webcam yet, this\nthrows an error. (Although I do have a mic, and if I asked for audio support instead, it should work just fine.) Once we get access to\na camera device, I ask for the photo capabilities and print them to the DOM. I do the same for all the media devices, and finally, you can see\na simple click handler to support taking pictures.\nThe level of detail on the photo capablities is pretty interesting. You get results for:\n\nWhite Balance Mode\nColor Temperature\nExposure Mode\nISO\nRed Eye Reduction\nFocus Mode\nBrightness\nContrast\nSaturation\nSharpness\nImage Height and Image width\nZoom\nFill Light Mode\n\nFor most of the above, you get min, max, current, and step values. Unfortunately, desktop Canary (on Mac) reported everything at zero, but Android's Canary reported\ngood values. Here is a screen shot from my Pixel C:\n\nHere is the output from device enumeration on my Surface Book:\n\nOk, let's look at a demo!\nDemo 1\nBack in 2013 when I wrote that first post, my demo code made use of a cool library called Color Thief. Color Thief\nlooks at an image and can return prominent colors. So my first demo simply recreates that. First, the front end:\n\nNot much here - just the UI for taking a picture and the result, empty DOM items to be filled later. Now let's look at the code. (And let me be cl",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "LoopBack now has a CLI!",
		"date":"Mon Jan 23 2017 16:39:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1485189540,
		"url":"https://www.raymondcamden.com/2017/01/23/loopback-now-has-a-cli",
		"content":"The title pretty much says it all. For some time now, you used slc to work with LoopBack apps.\nslc came from StrongLoop and did quite a few other things on top of working with LoopBack apps,\nbut now that we recommend folks make use of LoopBack and API Connect, having\na CLI focused on just LoopBack is a big plus.\nTo install, simply run:\nnpm install -g loopback-cli\nThis will give you the lb command, which is ONE CHARACTER LESS THAN SLC! (Ok, maybe I'm a bit too\nexcited about that. ;)\nYou can than either use lb -l to list all available commands or use lb -h for a more\ntraditional help display. lb by itself will kick off the process to scaffold a new LoopBack application.\n\nFor the most part, everything is just the same in terms of how you work with LoopBack apps, but now you've got\na CLI focused on just LoopBack work, which I think is a great thing. If video's are your thing, here's a video\nof me demonstrating the CLI in action:\n\nWant to know more? You can read the official announcement blog post for more about\nIBM's commitment to LoopBack. You can also follow the GitHub repo for the CLI\nto add suggestions or report bugs. Enjoy!\n",
		"tags":[
	        
            "loopback"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with IonicDB",
		"date":"Thu Jan 19 2017 16:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1484841780,
		"url":"https://www.raymondcamden.com/2017/01/19/working-with-ionicdb",
		"content":"Edit on Match 30, 2017: Unfortunately, the Ionic folks decided to not ship the final version of this service. IonicDB is no more.\nToday marks the launch of a new Ionic service, IonicDB. For those\nwho fondly remember Parse (thanks again, Facebook), this will come as welcome news. IonicDB is a simple data storage\nsystem. It lets you store data in the cloud for your mobile apps and skip building a server just to handle simple data CRUD. It also\nties nicely with the Ionic Auth system and has the ability to listen to changes to your data\nin real time.\nAs always - the docs should be the first place you go, but I've been playing\nwith this service for a little while now and have some demos that may help get you started. Everything I'm going to show below\nmay be found in my GitHub repo of Cordova examples so you'll be able to get the full source.\nA Simple Example\nTo begin, ensure you've followed the directions for working with Ionic Cloud in general. This is the prereq for using any of the fancy\nCloud-based services. This requires a login with Ionic so if it is the first time you've used Ionic Cloud you'll need to set that up one time.\nAfter you've set up Ionic Cloud for your app, the DB instructions tell you this:\n\nOnce you’ve created a database for your app in our dashboard you can start storing and accessing data in your database.\n\nSo to be clear, you need to get out of code for now, and go to the Ionic Apps site (https://apps.ionic.io), find your app, and\nthen enable Ionic DB:\n\nClick this nice obvious button and your database will be prepared for your app. This will take you into the\ndashboard view:\n\nThere's a lot of useful info here as well as a look into your stats (which should be pretty boring in a new app), but let\nme call out a few things in particular.\nNumber one - collections are simply database tables. Ok, not tables, it's all NoSQL and shiny, but basically, database\ntables. For those who didn't grow up writing SQL for back end apps, then just think of these as buckets for your data.\nIf your app works with cats and dogs, then you could have two collections. Really this will be up to you and\nyour app's needs.\nNow look at the toggles. They are all pretty self-explanatory I think. The third one, &quot;Auto Create Collections&quot;, is a setting\nParse had as well. Basically, while you work on your app, you may be defining new collections as you build it out. (&quot;Oh, the client\nwants to support cats, dogs, and mules? Ok - let's add a new collection for mules.&quot;) But most likely, when you've gone\nline, you do not want new collections created. So you'll toggle this setting off. The first two refer to who can\nuse the database and what particular rules apply - and again, this will be based on your app.\nSo what does the code look like? As I said, I'm not going to repeat every part of the docs - the quick start gives you an\nidea of what you need to do. Basically you connect and then go to town. You've got full CRUD for working with data, but as I said\nearlier, you also have the ability to listen for new data live, and I really like how easy the service makes this. While the quick start\nshows you a code snippet, I've built a full (but simple) app that shows everything working together. Let me show you how\nit works, and then I'll share the code.\nThe one page app consists of a list of Notes, which comes from IonicDB, and a simple text field to add new ones:\n\nAs you can imagine, typing a message and hitting the button adds a new one. Where things get interesting is when you\nwork with multiple clients. Here's a video showing this in action:\n\nOk, so let's look at the code. First, the view, even though it's rather simple.\n\nBasically just a form field and button for adding data and a simple ion-list for displaying it. The component code is where the fun is:\n\nThe first important bit is the import up top - that's fairly self-explanatory. I've created an array of chats representing the data\nthat will be displayed in the app. In the constructor, we connect, then open a collection called chats. I order the data\nby the created property, then watch and subscribe to the result. That's it for getting data and knowing when new crap is added.\nAdding data is done via store, and being all fancy and NoSQL, I literally just pass an object representing my data and\nIonicDB stores it. As with most NoSQL-ish solutions, the 'free form' thing is awesome, but you probably want to\nhave a good idea what your data looks like and try to be as consistent as possible.\nAnd that's it! Nice and simple, right? Let's take a look at the collections for this app in the dashboard.\n\nAs you can see, Users is in there by default even if we aren't doing anything with security. Now let's look at chats:\n\nAs you can see, you've got some meta data for the collection as a whole, and the ability to Add/Edit/Delete right from the tool. This is cool\nbecause while most data may be user generated, you may have data that is read only (and yes, the security model su",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "All My Friends Are Superheroes",
		"date":"Wed Jan 18 2017 16:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1484756640,
		"url":"https://www.raymondcamden.com/2017/01/18/all-my-friends-are-superheroes",
		"content":"A few weeks back I created an incredibly practical and not silly at all application\nthat went through your device's contact list and &quot;fixed&quot; those contacts that didn't have a proper picture. The &quot;fix&quot;\nwas to simply give them a random cat picture. That seems totally sensible, right?\n\nI was thinking about this during the weekend and it occured to me that there is an even cooler way we\ncould fix our friends - by turning them all into superheros with the Marvel API. I've built a few apps with this API in the past (I'll link to them at the end) and I knew it\nhad an API for returning characters. I thought - why not simply select a random character from the API and assign it to each\nof my contacts without a picture?\nThe Character endpoint of the Marvel API does not allow for random selections so I hacked up my own solution. First,\nI did a generic GET call on the API to get the first page of results. In that test, I was able to see the total number of characters:\n\nGiven that I assume, but certainly can't verify, that they have IDs from 1 to 1485, I decided to simply select a random number between them. (I ended up going a bit\nbelow 1485 just to feel a bit safer.) I figured this would be an excellent use of OpenWhisk, so I wrote up a quick, and simple, action:\n\nI deployed this to OpenWhisk as a zipped action since\ncrypo wasn't supported out of the box. (As an aside, that's wrong, but it's a long story, so don't worry about it now.) I then used one more\nwsk code to create the GET API, and I was done. And literally, that's it. 55 lines of code or so\nand the only real complex aspect is the hash. I do remove quite a bit of the Character record just because I didn't think\nit was necessary. I'm returning just the name, description, picture, and possibly a URL.\nYou can see this in action here: https://3b1fd5b1-e8cc-4871-a7d8-cc599e3ef852-gws.api-gw.mybluemix.net/api/getRandom\nSo yeah, I'm building something totally stupid and impractical here, but I freaking love how easy it was to deploy the API to Bluemix. As I said, I've got 50ish lines of code\nand I'm done, and as a developer, I think that royally kicks ass.\nOk, so what about the app? I'm not going to go through all the code since I shared it in the earlier post. The basics were - get all\ncontacts, loop over each, and if they don't have a picture, &quot;fix it&quot;, so let's focus on that code block.\n\nPreviously the logic to handle finding a random cat was synchronous, but now we've got an asynch call out to my\nservice so I had to properly handle that in my loop. Everything is still wrapped in a Promise though because I'm still\nconverting the image to base64 for storage on the phone. (And that's probably a violation of the API, but I'm not releasing this to the market, so, yeah.) Outside of that, the code is the\nsame. I call the API in this simple provider:\n\nSo what's with the random code at the end? See this post about a stupid Safari caching bug that impacts it. If you want\nto see the rest of the Ionic code, you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/fixcontacts2a\nAnd the result?\n\nPure awesomeness. (Ok, maybe just to me.) If your curious about my other uses of the Marvel API, here are a few links:\n\nExamples of the Marvel API\nUsing the Marvel API with IBM Watson\nBuilding a Twitter bot to display random comic book covers\n\n",
		"tags":[
	        
            "openwhisk",
            
            "ionic"
            
		],
		"categories":[
            
                "serverless",
            
                "mobile"
            
		]

	},

	{
		"title": "Creating Zipped Actions in OpenWhisk",
		"date":"Tue Jan 10 2017 20:12:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1484079120,
		"url":"https://www.raymondcamden.com/2017/01/10/creating-packaged-actions-in-openwhisk",
		"content":"As I continue my journey of learning serverless and OpenWhisk, today I'm going to talk about\nanother way to deploy your code - zipped actions. So what do we mean by zipped actions?\nPreviously I demonstrated creating actions based on single files. So action Cat was based on the file cat.js. You can also\ncreate actions as a sequence of other actions.\nZipped actions are\nbasically JavaScript files packed up as a npm module. Why would you want to do this?\nFirst - OpenWhisk provides a set of npm modules you can require in your code. And while that last is nice, you may want something not on that list. By\ncreating your own package of code, you can include that in your action. Secondly, you may just want to split your code into multiple files. The procedure\nisn't too terribly difficult.\nLet's assume a simple action:\n\nFirst, I add a package.json to the folder. (It will make things easier if your code for this particular action is in it's own folder.)\n\nThe name doesn't matter (although you'll probably want to pick something that is sensible for your action), but main should point to the JS file of your action,\nthe entry point to this action as a whole.\nNext, modify your action to export the main function:\n\nNow you'll want to zip your files. Be very, very, very careful at this step. Do not zip the folder, but the files in the folder. I used Windows' built in &quot;Send to Compressed folder&quot; feature\non the folder itself, and that created a zip where the first entry was the folder. You want to zip the files, not the folder.\nFinally, you send this to OpenWhisk like so:\nwsk action create packageAction --kind nodejs:6 action.zip\nAfter that, you can run it like any normal action. Note that unzipping your code means it will take a bit longer to run your action if it's cold (a fancy\nway of saying, &quot;hasn't been run lately&quot;), but if your sure your action's going to be pretty active, then you should be fine.\nNow let's consider a real example. In the past, I've used a npm package called FeedParser as a way to parse\nRSS feeds in Node.js. I thought it would be cool to build an action that would let me specify a RSS URL and get a JSON parsing of the data returned. I began with my\npackage.json:\n\nI then ran:\nnpm install\nTo get all those dependencies installed. Next, I wrote my action:\n\nAll I do is take in a URL argument, suck it down via the Request package, and then pipe it over to FeedParser. I noticed that FeedParser would create a 'meta'\nobject for every feed item. This meta object includes the metadata about the feed itself. I thought this was useful, but didn't like it being duplicated\non every feed item. So I take it out of the first item and delete it from every item added to the array of results. I can then return the list of\nRSS entries and the metadata as well.\nAnd that's it! The output is pretty verbose so I won't share a screen shot, but I've included a video about this topic below.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Quick Tip for Testing OpenWhisk Actions Locally",
		"date":"Mon Jan 09 2017 21:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483999140,
		"url":"https://www.raymondcamden.com/2017/01/09/quick-tip-for-testing-openwhisk-actions-locally",
		"content":"January 10: So after posting this yesterday, Carlos and I found some issues with both the 'hack' recommendation you add to your\ncode as well as the test script. I've rewritten the post to reflect those updates. If you read this article already, be sure to read it\nagain for the latest version.\nI'd like to share a quick tip for working with OpenWhisk. Credit for this goes to my coworker\nCarlos Santana. When working with OpenWhisk, you need to deploy your code to the cloud\nin order to test it. This is a very quick operation (and can be even quicker with the Visual Studio Code extension but it\ncan be a bit annoying if you are working on something complex. It would be cool if you could test directly on your machine without the\n'copy to OpenWhisk' command, right?\nYou can do this with two quick changes. First, possibly modify your action to add the following code at the end:\n\nWhy do I say possibly? Because if you are using a 'zip file as an action' feature (which I'm blogging about in a few minutes), you will already have this\nline of code.\nNext, use this script. I have all my OpenWhisk stuff in one folder where I'm testing, so I called this test.js:\n\nThis too is code from Carlos although I modified it a bit to make it more generic. Now I can do this at the CLI to test:\nnode ./test.js rsstest/main param1=paramvalue param2=paramblah\nOf course, on the Mac, I could add the shell thingy on top so I could skip including &quot;node &quot; in front as well. (As far as I know you can't\ndo that on Windows.)\nAnyway, I hope this helps!\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "A Survey for CFML Users (Past and Present)",
		"date":"Sun Jan 08 2017 18:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483900200,
		"url":"https://www.raymondcamden.com/2017/01/08/a-survey-for-cfml-users-past-and-present",
		"content":"A few days ago, myself, Adam Cameron, and others, were chatting\non Twitter about having moved on from ColdFusion, and what's keeping other people from doing the same. I know I've spoken with folks via email who would like to try something new, but aren't sure where to start. One of ColdFusion's strengths is how easy it is to pick up, and certainly it stood out for that quality for many years. However, that ease of use is no longer\nsomething ColdFusion can necessarily be proud of. There's numerous platforms now that are just\nas easy, just as approachable, and far more powerful than ColdFusion. Even more critical, there's far better options in terms of support (both from corporate entites as well as the community) than what you'll see with ColdFusion.\nWith that being said, Adam created a survey, &quot;CFML usage and migration strategies&quot;, that attempts to gather information about just this topic. I'm going to steal another good idea from Adam and share my answers here. You can see his answers on his blog: &quot;Survey: CFML usage and migration strategies&quot;\n1. Provide a brief comment about yourself (don't worry about your CFML usage or dev work just yet: this is just about you). Don't worry if you'd rather not give too much detail, that's cool. Note if you mention your name, I might end up sharing it in the context of what you had to say in your other answers.\nI'm a 43 year old slightly overweight guy living in Louisiana. I've got a great wife and six (yeah, six) kids.\n2. How did you come to be a developer, and are you primarily a developer or is it an adjunct to another role (like a sysop or designer or something like that)?\nI've been using computers since I was 10 or so, and entered college in Comp Sci with the idea of becoming either a hacker for the CIA (yeah, seriously) or a game programmer. I then discovered math was hard. Like real hard. This was about the same time the web was launching and I got into that - building web pages and the such. I quickly found that I enjoyed doing server-side stuff with Perl and mainly focused on the back end for the next 20 years or so. For the past ten or so I've focused more on the front-end and now consider myself more a full stack type of person.\nI'm not primarily a developer. I'm a developer advocate which means my primary job is to help other developers learn, and get excited, about technology. I love sharing things with other people so this job is exactly where I belong.\n3. Summarise your CFML usage timeline (just timeline for this one). Include things like what year you started, when you moved on, if you have. (or what's the time timetable for moving on if it's just planned). Mention versions in this one.\nAs I said above, I started doing back-end work with Perl. I discovered ColdFusion in 95/96 or so. It was version 3.x. I used CF primarily up until the 9 time frame or so.\nI worked on about 6-8 books on ColdFusion, as either a contributing author or main author. I also worked at Allaire for a short time on the team itself.\nI started moving to other technologies about 10 years ago. My &quot;go to&quot; back end technology is Node.\n4. During that time was it your primary or sole dev language, do you think? Or was it always an adjunct to some other language? How did it fit within the mise en scene of your daily usage? For the purposes of this answer, let's consider &quot;a wee bit of client-side JS and a bunch of HTML &amp; CSS&quot; as a given. Only mention those if they represent a significant part of your work.\nI'd say for 90% of my time using CF as my back end tech, it was all I did. I did HTML/JS, but it was a &quot;wee bit&quot;. I'd say towards the end, the JS stuff grew rapidly, and was part of the reason I looked more at Node and less at CF. I also began to realize that the front-end could do a lot more than it used to in the past. That also led me away from CF because I simply didn't need all the stuff CF did.\n5. Did you work on just in-house code bases for your employer, or did you also work on third party code bases - like open source projects - too.\nBoth. I did a large amount of open source in ColdFusion. My blog-ware (BlogCFC) was in use by hundreds of users. I built a forum app, a wiki app, a survey app, and more. I also built RIAForge, a site to help Adobe users adopt open source.\n6. If you're still primarily a CFML developer... why? That's not a loaded question, and I'm not suggesting that you're wrong for being where you are. It's just good framing information. Don't answer this if you've moved on from CFML: the next question is for you.\nI'm supposed to skip this, but I won't. While I don't choose to work with CF anymore when I can, I still do write CF when my clients want me to. So basically, I use it when the client demands it.\n7. If you've moved on from CFML: why? Did you just change jobs? Did other languages you were using just seem more appealing? Over time did you find yourself using CFML less and less? Did you actively change because",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "An Example of an OpenWhisk Sequence",
		"date":"Fri Jan 06 2017 19:50:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483732200,
		"url":"https://www.raymondcamden.com/2017/01/06/an-example-of-an-openwhisk-sequence",
		"content":"This isn't going to be terribly complicated, but as I thought it was kinda cool, I thought I'd share it.\nI'm currently working through the OpenWhisk workshop, a Node School-ish set of\nproblems/tasks to help you learn the basics of OpenWhisk. One of the problems has you build a sequence, and\nwhile I recommend folks try to solve the problem for themselves, I thought I'd share my solution and what I discovered\nwhile working on it.\nSequences are what you would imagine, two\nor more actions that are linked together. You define them by listing the actions you want executed, and when run,\nOpenWhisk will execute each in order and pass the output from one to the input of the other. (And this brings up an important detail I'll cover in a moment.)\nBefore I talk about the problem from the workshop, let's build a simple example. First we'll build an action that takes a string input and returns\nthe size of the string. To be clear, this is trivial to the point of stupidity, but let's keep it nice and simple.\n\nI called this action strlen. Now let's create an action that, given a numeric input, finds the prime numbers. I'm going to use the solution\nfrom this Stack Overflow answer:\n\nIt takes a parameter of number and returns an array in a key called primes.\nAs a quick aside, I used the OpenWhisk extension for Visual Studio Code to create\nand test these actions. While the extension is still in development, it works darn well so far.\nOk, so how do we make a sequence?\nFirst off, you create a sequence very much like you do an action, in fact, the command begins the same way:\nwsk action create name\nBut this is where things differ - instead of passing a filename of a local file for your action, you pass the --sequence argument and a list\nof actions:\nwsk action create name --sequence a,b,c\nOk, so for our code above, I'd do this:\nwsk action create strToPrimes --sequence strlen,primes\nOnce done, you invoke it just like a normal action:\nwsk action invoke strToPrimes -b -r -p text &quot;Raymond&quot;\nNotice I'm passing in my argument text that is expected by my first action. And it works perfectly! (Not!)\n\nWhat went wrong? Did you notice the output of strlen was an object with the key &quot;length&quot;? Did you notice the input for primes was expecting\na param called &quot;number&quot;? When building sequences, you have to ensure that your output/inputs match up. In general, this shouldn't be a big\ndeal. I'm ok with changing strlen to output &quot;number&quot; instead, but I do wish that OpenWhisk would let me define sequences along with a way to 'map' the output/input values from action to action.\nNow it works just fine:\n\nOk, so lets look at the workshop problem:\n\n\nCreate an Action which takes a string parameter (text) containing a sentence. Return an array containing the individual words within the sentence.\n&quot;Hello my name is James&quot; --&gt; [&quot;Hello&quot;, &quot;my&quot;, &quot;name&quot;, &quot;is&quot;, &quot;James&quot;]\n\n\nCreate an Action which takes an array of words and returns an array with those words reversed.\n[&quot;Hello&quot;, &quot;my&quot;, &quot;name&quot;, &quot;is&quot;, &quot;James&quot;] --&gt; [&quot;James&quot;, &quot;is&quot;, &quot;name&quot;, &quot;my&quot;, &quot;Hello&quot;]\n\n\nCreate an Action which takes an array of words and returns the string createdby joining the words together into a sentence.\n[&quot;James&quot;, &quot;is&quot;, &quot;name&quot;, &quot;my&quot;, &quot;Hello&quot;]  --&gt; &quot;James is name my Hello&quot;\n\n\nCreate a new Action (reverse-sentence-words) using a sequence that joins together these three Actions.\n\n\nThis new Action should take a parameter (text) and return a sentence (text) which contains the words in the string reversed.\n\n\nAll in all, fairly trivial. My first action is just this:\n\nMy second one is just this:\n\nAnd my final one is:\n\nHere is my sequence in action. By the way, that -r argument means just return the result - I like that.\n\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "An example of the Ionic 2 Menu Component",
		"date":"Thu Jan 05 2017 17:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483638720,
		"url":"https://www.raymondcamden.com/2017/01/05/an-example-of-the-ionic-2-menu-component",
		"content":"In general, I find components in Ionic 2 to be simpler and easier to understand than their V1 versions, but for some reason, I\nwas incredibly confused by the docs for working with the Menus component. Documentation\nexists of course, but it just didn't make sense to me. (I documented my problems in this issue for\nfolks curious.) What follows is my own understanding of how to work with menus in Ionic 2 and some basic things to keep in mind. As always,\nremember I'm still learning this myself, so keep in mind I may get a detail or two wrong.\nBefore we continue, a few links:\n\nFirst, the main component doc.\nThen the related API doc.\nFinally, the Sidemenu starter app. I did not use this when learning, and it does\na few things differently. I'll talk about that at the end.\n\nAlright, so to begin, I created a new Ionic 2 app with the blank template:\nionic start sidemenudemo blank --v2\nThis gave me a blank slate to begin by demo. In my demo, I want a site with three pages:\n\nA home page\nA cats page\nA dogs page\n\nOut of the box you get a home page, so I used the CLI to generate the two other pages:\nionic g page cats\nionic g page dogs\nIf you weren't aware, the CLI has a generate feature which can write boilerplate\ncode for you. I highly recommend using it.\nAlright, so the first thing you have to do when working with a side menu is create a new page. (See my note at the bottom!) So given my app\nhas three pages, I needed a fourth page to host the menu. I created a new one called main.\nIf you want my demo, you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/sidemenudemo\nionic g page main\nSince my app starts with the side menu, I decided to make main my root page for the app. So my first modification was to app.component.ts:\n\nEssentially I just changed &quot;Home&quot; to &quot;Main&quot; in the import and the line where rootPage is set. So far so good. Now let's set up\nthe main page to have my menu. Here is main.html:\n\nThere are a couple of very important things here to note. First let's start on line one:\n\nThe [content]=&quot;mycontent&quot; aspect confused the heck out of me. All it really means though is this: &quot;When I load crap, I want you to load it in this container.&quot;\nYou'll notice at the bottom I've got an ion-nav component that uses the #mycontent identifier there.\nNow look at the menu code. Most of it is self-explanatory but I want to call out two things.\nFirst, menuClose tells the menu to automatically close when a menu item is clicked. This is not the default,\nso most likely you will always want to add this.\nSecond, what's openPage(x) about? This is shown in the docs but not explained. Basically, you have to write the code\nto load new pages. This is easy of course, but the docs don't spell this out for you. Going from Ionic 1, I didn't have to do this\nbecause I simply used URLs I had already set routes up for.\nHere is my main.ts:\n\nAs you can see, openPage is pretty simple.\nSo that's it - but let me address one thing in particular when you compare my code to the SideMenu\nstarter. For the application I was working on (an existing Ionic 1 app migrating to Ionic 2), the menu loads after an initial\nlogin screen. Therefore, using 4 pages (one for each page plus one for the menu) made sense. If you look at the SideMenu\ntemplate though, they modify the main app.html file that is the root of the Ionic app itself. It is the first page, and the core I guess you could\ncall it, so that made sense. If that's how your app starts up, I'd do that instead of creating another page as I've done here.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Getting Started with OpenWhisk",
		"date":"Tue Jan 03 2017 19:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483473240,
		"url":"https://www.raymondcamden.com/2017/01/03/getting-started-with-openwhisk",
		"content":"For folks who have been reading my OpenWhisk blog entries this past week or so, I've started working on some videos\nthat demonstrate what I've been covering so far. I only have three done currently, and two cover material I've already done, but I thought\nit might be a useful addition to what I've demonstrated on the blog.\nThe first simply demonstrates the CLI and creating/calling actions:\n\nThe second demonstrates how to work with asynchronous actions (and includes the Star Wars API!):\n\nThe third one discusses how to debug your actions when you screw up. And I mean you since I never screw up in JavaScript. Or anything else. Ever. Honest.\n\nYou can also bookmark the playlist. I'm going to record another one\ntoday and hopefully more soon.\n",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Building a Serverless API Proxy with OpenWhisk",
		"date":"Mon Jan 02 2017 15:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483369860,
		"url":"https://www.raymondcamden.com/2017/01/02/building-a-serverless-api-proxy-with-openwhisk",
		"content":"One of the more common tasks we do with a server-side application is to build a simple API proxy. By that I mean we expose an API on our server that simply proxies to another remote server. Why might you do that when you can easily call APIs client-side with JavaScript?\n\nThe remote API may require a key. Including your key in your JavaScript code means it is exposed to the public and can be used by others, potentially locking you out of the API or running up your charges.\nThe remote API may return data in a undeseriable format, like XML.\nThe remote API may return data you don't need, resulting in a slower response that includes data you won't ever use.\nThe remote API may work with data that doesn't need updating often. Your server can then add it's own caching layer.\nThe remote API may go down. By having your own entry point, you could handle this in multiple ways, with either static data or some other result that won't completely break the clients.\nFinally, the remote API may get bought by some large company (perhaps one that rhymes with MaceLook) and be shut down. You could replace the remote API completely and your clients will never know.\n\nFor all these reasons and more, it may make sense to build a simple server for your web apps and point your code to your API proxy instead of directly at the remote server. While this would be rather trivial with Node.js, I thought it might be a great use case for serverless as well, especially with me just discovering how awesome OpenWhisk is. (See my post from last year: &quot;Going Serverless with OpenWhisk&quot;)\nFor my API, I decided to use the Cat API. The Cat API returns random pictures of cats. It lets you filter by categories, vote on cats, and more. While it doesn't require an API key, but if you don't pass one, you are limited to a set of only 1000 cats, which as we all know is far too few. Also, it returns XML. Which is gross, but I'll forgive them anyway since we're still talking about cats here.\nI'm going to assume you read my earlier post, but just in case you didn't, here is a quick overview of how OpenWhisk works. You download a command line program (technically this is optional), write your action (a program that does one thing), and then deploy it to OpenWhisk. The final step is to expose the action via a REST API. Nice and simple, right?\nFor my testing, I decided to build two actions:\n\nThe first action will return a list of 10 random cats. It will return image URLs in a small format approrpiate for a list.\nThe second action will return details for one image and ask for a larger image URL.\n\nLet's start with the first action. I began by simply figuring out the URL:\nhttp://thecatapi.com/api/images/get?format=xml&amp;results_per_page=10&amp;size=small&amp;api_key=SECRET\nNow I began working on my action. One of the first issues I ran into was how I'd handle the XML. While I could try to parse it by hand, I really wanted to use a npm package, but I also knew that OpenWhisk only supports a particular list of npm packages. I checked that list and was happy to see that xml2js was included! Here's the action I wrote:\n\nLet's tackle this bit by bit. I begin by simply requesting the URL that returns my list of cats. Then I pass it to the parseString method of the xml2js package. This will convert the XML into a simple object, but it does so in a pretty verbose manner. To figure out what I needed, I initially just returned the result of the parse itself. I looked at the result in the CLI and slowly trimmed it down to the bare essentials of what I needed. As a reminder, you can invoke actions from the CLI very easily:\nwsk action invoke catlist --blocking\nI also have the massage the results a bit since it was returning key/value pairs as 1-length arrays. Here's an example of the output I now get:\n\nTo be clear, you're seeing the complete result of the action (well, as much as I could fit in the screenshot), which includes metadata about the action call and the result itself. What will be used by our clients though is within the response.result section.\nThe next action handles getting details for a particular ID. Here's that action:\n\nThis one is even simpler as we just need to massage the result of one item. Note how I access an expected parameter for the ID: params.id. And honestly, that's it. One issue you may see is that I've got one API key in two separate files. I could correct this by switching my code into a package (see the docs), but that felt like overkill for this particular demo. It would definitely be the right path to take though if I started added more actions related to this one particular API.\nThe last step was to expose this via a REST API. As a reminder, this is still a work in progress, so you probably don't want to do this yet for real code, but it works great already. The general process with the CLI has you define a root API path, a particular path for this call, the HTTP method, and finally the action to call.\nI've got two actions and therefo",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Advent of Code - Day 15 to 20",
		"date":"Fri Dec 30 2016 15:16:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1483110960,
		"url":"https://www.raymondcamden.com/2016/12/30/advent-of-code-day-15-to-20",
		"content":"So yep - I definitely didn't finish Advent of Code before Christmas, but I'm mostly done (20 out of 25 days) and I plan to keep at it in the next week or so. This post will cover a bunch of days, so forgive the length!\nDay 15\nDay 15 had an interesting concept. Given a set of discs with holes in them that turn every second, at what point would you be able to drop a ball so that as it fell, it would always hit holes. The ball falls through one disc at a time, so you have to simulate the discs turning and the ball trying to fall over time. The solution involves finding out what time to drop the ball so it falls perfectly.\nMy solution was a brute force solution that was very slow:\n\nYou'll notice the last line is a comment with the right answer. How did I get that? I cheated. This Reddit post points out that it can be solved with simple math. Insane.\n\nThe second part of the day simply added another disc - so my solution simply took the above solution and added the additional logic. So yep - I cheated - but it was still cool.\nDay 16\nDay 16 was an easy one. Given an input string, you simply transform it in such a way that it grows to a certain point. When it hits that point, you generater a checksum that shrinks the string again. The second part simply changes the desired length. Here is my solution.\n\nDay 17\nDay 17 involves moving through a room where the possible allowed directions based on a hash of a path you've taken through the room. Yeah, weird. The first part involves just finding the shortest path.\n\nThe second part asks you to reverse this and find the longest path. I couldn't do it so I cheated again using this solution I could paste in my browser console: https://www.reddit.com/r/adventofcode/comments/5isvxv/2016_day_17_solutions/dbatx5a/\nDay 18\nDay 18 was another simple one. The concept is that you are in a room full of tiles and traps and you have to figure out how many safe tiles there are. You generate the room data by simply taking an initial string of room data and generating one line at a time, where each line is based on the last line. Simple! Part 2 just increases the number of lines.\n\nDay 19\nDay 19 involved simulating a game where elves sit in a circle and each elf takes the gifts of the person to their left. When you lose your gifts, you're taken out of the circle. The idea is to figure out which elf will get all the gifts. Part one was easy enough:\n\nPart two added the twist that instead of taking the gift from the elf next to you, you take the one &quot;across&quot; from you. Figuring out which elf to take from was incredibly difficult for me. My logic was:\nthe elf across from me is:\nfloor(len/2)+1 offset\nWhen len is the number of elves. My solution tooked about 40 minutes to run and ended up giving the wrong answer. (I checked it into the repo anyway.) This solution from Reddit ran near instantly:\n\nSome people are scary smart.\nDay 20\nDay 20 seemed simple at first. Imagine you have a set of restricted numbers:\n0-2\n5-9\n4-8\nGiven that set, what is the first allowed number? You can see it is three. Your input is a huge set of ranges like this. My solution was to sort by the lower end of the range and then loop over the ranges creating a smaller set of ranges. For example, imagine:\n0-50\n30-90\nYou can rewrite that as 0-90. Now imagine:\n0-50\n51-60\nThat can be merged into 0-60. While this seems simple, this took me forever to get working right.\n\nPart two had you determining how many 'available' numbers were allowed.\n\nWhew! Only five more days to solve. If I can solve two of them without cheating I'll consider myself lucky.\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My 2016 and my plans for 2017",
		"date":"Tue Dec 27 2016 16:12:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482855120,
		"url":"https://www.raymondcamden.com/2016/12/27/my-2016-and-my-plans-for-2017",
		"content":"Continuing my series of blog posts no one will actually read, I thought I'd spend a few minutes looking at what I accomplished last year and what I plan to do next year. This is not meant to be a &quot;Look at how great I am!&quot; type post, but mainly as a way for me to recognize what I succeeded at and what I failed at. I also assume no one will actually read this, but it helps me gather my own thoughts and helps me prepare to (hopefully!) kick some butt next year!\n2016\nEarlier this year, I announced that I had joined a new team at IBM. My focus this year was on LoopBack, API Connect, and Node.js in general. I've been trying to learn more about Node for a while now and I love APIs, so this was (is!) a perfect fit for me. I've been loving my new team (especially now that we've ramped up a bit) and I've had the pleasure of helping others learn about building APIs with Node.js at multiple venues across the country. I'm really proud of this and hope to continue it in 2017.\nThis year I gave 21 (holy crap, I honestly didn't know it was that much!) presentations. I traveled over 41K miles and had 66 flights. I use (and highly recommend) TripIt for managing travel but it doesn't have very good stats. I just discovered JetItUp which does a really good job of visualizing your data.\n\nI also gave my first keynote this year. This was pretty nerve wracking as it wasn't a traditional code-heavy presentation that I'm used to. While it isn't something I think I'll do often, I think I did ok and I had fun giving it.\n\nI released two books this year (&quot;Client-Side Data Storage&quot; and jQuery Mobile Web Development Essentials) and almost releases a third. The book on static sites (&quot;Working with Static Sites&quot; I'm writing with Brian Rinaldi should be out early next year.\nAs for the blog, this was the year I finally gave up on app servers (ColdFusion or PHP) and went static (Welcome to RaymondCamden.com 2016). It was a lot of work, but now I've got what feels like a perfect system. I commit to GitHub and 2 minutes later my site is live via Netlify. Obviously it can still go down, but it isn't my problem anymore. My writing slowed down quite a bit as my travel went up (147 posts this year versus 252 last year), but my main concern is with the quality of my writing, not the quantity. I also wrote multiple articles for the StrongLoop blog and the Telerik Developer Network.\nTraffic to the blog is up, slightly, with around 1.6M page views in 2016, although my top traffic is still for older articles versus newer ones. None of my top ten URLs are from this year, but I'm ok with that. I'm going to write what I'm interested in and if that doesn't drive the most traffic, then so be it.\nBelieve it or not, my most visited post written in 2016 was the one I wrote for Adobe ColdFusion 2016 being released. Considering my less-than-happy feelings about ACF these days, that's rather surprising. This was the first release of ColdFusion I recommended against on top of my general recommendation for using Node.js anyway. If you must use ColdFusion, then use Lucee and CommandBox.\nI was going to print out my top ten articles, but one of the things Google Analytics sucks at is exporting stuff like this. I mean I can see the URLs in the report, but I can't get a nice export of them with the full domain. I'm going to build that tomorrow I think.\nSo all in all - I think I did ok in 2016. I still have room to improve of course and I still have goals I want to meet, but that's for next year...\n2017\nSo what are my goals in 2017?\n\nContinue to work with Node. I feel like I'm still years away from being a decent Node developer, but that's what I'd expect with any technology. I don't think I became an expert in ColdFusion until my 6-7th year and I was still learning quite a bit until I finally moved on from it.\nContinue to learn/demonstrate API Connect, and think more about API gateways in general. Actually, I want to think more about APIs as a whole, specifically more about testing and best practices.\nContinue to learn Angular 2 and Ionic 2. The more I use both the more I like it. Last night I bought &quot;Angular 2 Development with TypeScript&quot; as a way to help me out on the Angular 2/TypeScript side.\nI've been saying this for a while now, but I really want to get better at NativeScript. I like it - I just haven't dedicated the time I need to really get more familiar with it. This year I spoke at a NativeScript confernece, but it was a general keynote, not a technical session. My goal this year is to present on NativeScript at a conference/meetup.\nLearn and play a lot more with serverless. I just got into this last week and I think it is pretty cool.\nAs a presenter, I want to continue to hone my craft. I think I'm becoming &quot;Good&quot; and I want to continue that progress on to &quot;Great&quot;. I think I need a bit more variety in my topics and a bit less cat pictures. (Ok, maybe not so much the second thing.)\nI love static sites and",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Favorite Media in 2016",
		"date":"Mon Dec 26 2016 15:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482764640,
		"url":"https://www.raymondcamden.com/2016/12/26/favorite-media-in-2016",
		"content":"As the year winds down, it's time for me to switch from mostly technical posts to mostly lame, casual, and fun (for me anyway) posts about the year that is about to end. Most of my traffic\ncomes from folks Googling for particular topics, so the only folks who will end up here (probably) are regular readers. I hope you enjoy this list of all the books, movies, video games and music I really enjoyed this year.\nMusic\nIn no particular order, here are a few of the songs I really dug this year. And by that I mean I played them at least 40-50 times a week probably. First up is &quot;Murmur&quot; by Mint Julep. I highly recommend the entire album. The video for this is incredible.\n\nNext up is another awesome video/song. The artist here is DJ Shadow, but it features Run the Jewels as well who made my list of favorite media last year.\n\nFinally, a mashup of Justin Bieber and Depeche Mode. Yes, seriously. I love this one.\nhttp://bootiemashup.com/wp-content/uploads/2016/04/DJ-Tripp-Enjoy-The-Sorry-Justin-Bieber-vs.-Depeche-Mode.mp3\nVideo Games\nI played some good games this year, but I'm still feeling a bit let down from a few years ago when I played both &quot;The Last Of Us&quot; and &quot;Tomb Raider.&quot; I re-played both of those this year and they still stand up incredibly well. This was the year I finally played an Uncharted game. (And it was pretty cool actually.) Probably the game I was most into was &quot;No Man's Sky&quot;, but as much as I liked it when I was done with it I was really done.\nI picked up an XBox One and so far the biggest feature I like about that are all the old XBox 360 games I got for free with Games with Gold. The Sony version of that (PS Plus) sucks in comparison. &quot;Battlefield 1&quot; was really well done and one of my favorite shooters ever. Speaking of - I didn't bother with the last two COD games. Meh. I'll maybe pick up the latest one once it goes below ten dollars. Maybe. My current game is &quot;Forza Horizon 3&quot; which is fun so far. The graphics are the best I've seen in a racing game.\nAll things considered - I think the PS4 is a better system but XBox has a slightly better library I think.\nMovies/TV\nThere were a lot of great movies/tv shows this year, here is a quick list with some quick comments.\n\nRogue One: I just reviewed this. You can guess what I thought.\nCaptain America 3: I've been completely surprised by how the CA series has evolved. The first movie was pretty much what you expected, but the last two have taken a darker, serious tone that I would not have expected. At times, CA3 was a bit too dark, but about half way through the film it began to have a bit more fun and I enjoyed the hell out of it.\nSuicide Squad: Another film I enjoyed although most people did not.\nBatman vs Superman: An incredible disappointment. Wonder Woman was good and I'm looking forward to her solo film, but this was mostly crap.\nStar Trek - Beyond: I want to like the new Star Trek films and they have cool parts, but I just can't make myself care about them. All three have had some cool parts, and Bones is really well done, but I just don't have any real deep feelings about this series which is truly sad.\nGhostbusters: Fun, great special effects, funny as hell too.\nDoctor Strange: Since the very first Marvel film, I was wondering how they would handle magic. They avoided the question in Thor by making it all high tech. A really good film to see in the theaters.\n10 Cloverfield Lane: Really, really good. Forget the name and any confusion it may cause. Just watch it.\nX-Men: Apoclypse: Probably my second favorite of the new trilogy (&quot;First Class&quot; is just amazing), and easily the movie with the best &quot;superhero scene&quot; ever. Not a spoiler, but if you've never seen it, I'm talking about the Quicksilver rescue scene embedded below:\n\n\n\nIndependence Day: Resurgence: Crap.\nMoana: Really great - I liked it and the kids liked it.\nStranger Things: 80s nostalgia and freaky horror - a great combination. Both my wife and I were really unsure of where this series was going for at least most of the run.\nLuke Cage: Great soundtrack, great characters, great new perspective of the Marvel universe. Unfortunately the story kind of flounders at the end. The entire last episode feels like it should have been cut in half.\nWestworld: Another show where I was truly unsure of how things were going to end up. Great twists, great acting, and another great soundtrack.\nGame of Thrones: One thing to say &quot;Hold the Door.&quot; Ok, more then that I'll say that now that the show has moved beyond the books (mostly), it is really moving along well. The last few episodes were just mind blowing.\nThe Flash: I binged watch this late this year and enjoyed the hell out of it. See my review. I'm now watching the Arrow.\nThe Crown: Another Netflix original and another one I binged watch (along with my wife).\nOh - and this is the year I finally broke my addiction of Young and the Restless. Yes, I was addicted to a so",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "movies",
            
                "video games",
            
                "music"
            
		]

	},

	{
		"title": "Going Serverless with OpenWhisk",
		"date":"Fri Dec 23 2016 19:49:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482522540,
		"url":"https://www.raymondcamden.com/2016/12/23/going-serverless-with-openwhisk",
		"content":"Like I assume most of you have, I've been hearing a lot about &quot;serverless&quot; recently and while I had a passing\nunderstanding of what it was, I had not actually spent any time playing around with it. This week I got a chance\nto play with OpenWhisk, IBM's open source offering in this area and I have\nto say I'm pretty blown away by how cool it is. What follows is a brief explanation of what serverless means to me, why\nI think it is something to check out, and, of course, some sample code as well. Please remember that I am incredibly\nnew to this space. What I write may be completely wrong, so take with a grain of salt. And as always, I highly\nencourage folks to chime in with their own thoughts, corrections, and questions.\nWhat is it?\nFirst off - what the hell is it? I mean, everything runs on a server, so the name doesn't make sense, right? I agree.\nI hate the name. &quot;The Cloud&quot; isn't necessarily accurate either, but &quot;Serverless&quot; feels like the complete opposite of the truth\nas opposed to a fluffy (get it) marketing term like the cloud. That being said, you can think of it like this.\nA typical web site, or app, or API, will run on a server. This is built in some app server tech like PHP, ColdFusion, or Node. In this code,\nyou'll do something to set up how the data is accessed (&quot;A GET request to path so and so will execute this code&quot;) and then\nobviously write the code to actually do what the request is meant to do.\nIn something lightweight like Node, it may look like this:\n\nThat's fairly simple, but of course, there's a lot going on behind the scenes. That very first line which loads\nin Express is doing a lot behind the scenes. I don't necessarily have to worry about it, and this is why Express was\nmy gateway drug to Node, but it's still there of course. Also, the actual setup of running a server and specifying\nthe route isn't much work either - just a few lines. But here's where serverless can make things more interesting. Imagine\nif my code was just this:\n\nThat's hella simple, right? Of course, I need some infrastructure to specify &quot;On /cats, do this&quot;, but that's\nwhere the serverless platform comes into play.\nIt reminds me a lot of switching to static site generators. You may end up with similar complexity, but where that\ncomplexity lies has moved from code to the platform I'm using to host my code. If I can trust IBM, or Amazon, or Microsoft, to\nbe able to handle HTTP routing (and I'm pretty sure I can), then I can focus on just my implementation, which has become\nsmaller and simpler, which is pretty much always a good thing.\nThat's my take, the more formal, intelligent answer can be found on the OpenWhisk site: What is Serverless Computing Here is the part I think makes the most sense:\n\nServerless computing refers to a model where the existence of servers is simply hidden from developers. \nI.e. that even though servers still exist developers are relieved from the need to care about their operation. \nThey are relieved from the need to worry about low-level infrastructural and operational details such as \nscalability, high-availability, infrastructure-security, and so forth. \n\n\nWhat's in it?\nThe OpenWhisk site goes into more detail, but again, here's my take:\n\nAn &quot;action&quot; - this is the actual code of what I'm trying to do.\n&quot;Triggers&quot; that let you specify outside sources as a way to kick off an action.\n&quot;Rules&quot; that simply map triggers to actions.\n&quot;Packages&quot; which - yeah, that's just packages. It's a combination of other actions/triggers/rules that you can link to your own stuff.\n\nAnd most recently - a new feature is an API gateway to all of the above. This is still in beta, but it lets you define\nan API path that points to my action.\nTo start using OpenWhisk, you'll want to download and install the CLI. You can then\nstart playing. Now let's say you want to build an action. I'll use JavaScript because it is perfect and has\nno oddities at all. (OpenWhisk also supports Python, Swift, and even Java.)\n\nYou need to have a main() function and you have to return an object containing a payload key. I can then upload this like so:\nwsk action create cat cat.js\nIn the line above, wsk is the CLI and action create is what you are doing. The next arg, cat, is the name of the\naction and cat.js is the file to use. This will deploy the file. You can then run it like so:\nwsk action invoke --blocking cat\nThe --blocking part simply tells the CLI to wait for a response. You do not need to do that if you don't want to. (And you can still get\nthe response later.)\n\nThe last bit, exposing it via a REST API, is done like so:\nwsk api-experimental create /blogapi /meow get cat\nIn the call above, /blogapi is a root path and /meow is the path for this specific call. get is the HTTP method and cat is the action. The result is a URL:\nhttps://3b1fd5b1-e8cc-4871-a7d8-cc599e3ef852-gws.api-gw.mybluemix.net/blogapi/meow\nGo ahead and click it - awesome, right? A ",
		"tags":[
	        
            "openwhisk"
            
		],
		"categories":[
            
                "serverless"
            
		]

	},

	{
		"title": "Integrating Intl with Ionic",
		"date":"Thu Dec 22 2016 15:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482421500,
		"url":"https://www.raymondcamden.com/2016/12/22/integrating-intl-with-ionic",
		"content":"\nBefore you continue, a quick warning. This article discusses how to localize numbers and formats for\nan Ionic 2 app using the Intl spec. Based on my reading, this should actually be baked into Angular 2\nitself. In my testing, this was not the case. I could be wrong. I could be right and the feature is just bugged. Just know that what follows may not be technically necessary. I'm sharing it anyway as it was fun to write and gave me the opportunity to play with pipes.\n\nOver two years ago I first wrote about the Intl spec: Working with Intl. This is an incredibly cool, and actually fairly simple, way to localize dates and numbers for your web applications. Because it was incredibly useful, of course Apple dragged their heels in supporting it, but as of iOS 10, it is finally baked in:\n\nWith support at nearly 80%, and with it being easy to fall back when not supported, I thought I'd take a look at adding it to a simple Ionic 2 application.\nI started off building a 2-page master detail application. The first page is a list of cats. For each cat, I want to output the time of their last rating.\n\nClicking on a cat loads a detail.\n\nIn both of the above screen shots, you can see dates and numbers printed as is - with no special formatting. While the code isn't that complex, let's take a look at it as a baseline. (Note, in the repo URL I'll share at the end of this post, you can find the original code in the src_v1 folder.)\nFirst, here is the cat data provider. This one isn't going to change as we progress through the various versions:\n\nNow let's look at the view for the home page:\n\nAnd the code behind it:\n\nNext up is the detail view:\n\nAnd its code:\n\nRound One\nThe first change (found in src_v2) I did was to employ Angular's built in pipes for date and number formatting. In the home view, I used this:\n\nAnd in the detail I used this:\n\nNice and simple and we're done, right? Well as I said above, this worked for me, but only in the English locale. Switching to French did nothing to change the output. To be clear, maybe I was doing it wrong. But again, this simply didn't do it for me. On to round two!\nRound Two\nSo I gave up on the pipes (and removed them from the view!) and switched to using Intl (this may be found in src_v3). I began by adding code to modify the result from the provider in home.ts. To be clear, this felt wrong, but was my first draft with adding Intl:\n\nMy function dtFormat sniffs for Intl. If it exists, I format both a date and time string. Notice that in order to do this, I format twice with options. The second call is more complex because I have to pass in a value for locale if I want to pass options (a mistake in the API imo).\nI do something similar for the numbers:\n\nAnd this worked! But I didn't like modifying the data like that. I knew I could write my own pipes, so I did so in the next version.\nRound Three\nI began by adding a pipes folder to my src (this version of the app is in the src and src_v4 folders). Here is my date pipe:\n\nAnd here is my number pipe:\n\nNotice the addition of maximumFractionDigits. This will cut off decimals to 2 places. In theory I could build my pipe so that was an argument passed in by the view, but I kept it simple. I removed my code from the page controllers, and then simply added my new pipes in to the views. (Note - you also have to add in your pipes to app.module.ts. I forget this in nearly every Ionic 2 app I build.)\nFirst the home page:\n\nThen the detail:\n\nHere is the result with French set as my language:\n\n\nFor comparison, here is the home page with English set as my language:\n\nYou can find the full source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionic_intl\n",
		"tags":[
	        
            "ionic",
            
            "cordova"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Having trouble emulating iOS with Cordova/Ionic?",
		"date":"Wed Dec 21 2016 23:27:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482362820,
		"url":"https://www.raymondcamden.com/2016/12/21/having-trouble-emulating-ios-cordova-ionic",
		"content":"Ok, so this one was a doozie. A few days ago, I got a bit ticked off that whenever I emulated iOS I was getting an iPhone 5 device. I know it's just a simulation, but I wanted an iPhone 6 or higher to be the default. It's easy enough to pass a flag to the emulate command to tell it what device/sdk to use (Important note for targeting iOS Emulators in Cordova), but as I have the memory of a kitten, I thought it might be easier to simple delete the older emulators.\nSo I did that. Because why not? If you're curious how that's done - open up XCode, go to Window/Devices.\n\nThen just right click on a device and select delete:\n\nSo yeah, I did that, and then a day or so later began getting errors trying to emulate iOS:\n\nYou'll notice that I'm calling out something specific here - the iPhone5 target. For some reason, Cordova was trying to use the iPhone 5 even though I had deleted it. I confirmed this was the issue by opening up the XCode project Cordova had created and I could emulate just fine from there.\nLong story short - I filed a bug report (CB-12287) and Cordova contributor and all around kick ass dev Kerri Shotts figured out the problem. Turns out one script has a hard coded call to iPhone 5 while another script was updated to use the 'last of a list' of valid emulators. So basically it's a real bug (and not just something else I did stupid).\nKerri provided a workaround:\ncordova emulate ios --buildFlag=&quot;-destination platform=iOS Simulator,name=iPhone 7 Plus&quot;\nOr do what I did - in the project I'm working on I simply modified the script to use the iPhone 7.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "LoopBack 3.0 Released",
		"date":"Wed Dec 21 2016 16:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482339300,
		"url":"https://www.raymondcamden.com/2016/12/21/loopback-30-released",
		"content":"Christmas arrived early for LoopBack developers - today LoopBack 3.0 was released. To update, go into terminal and run the following:\nnpm install -g strongloop\nThen create a new application. Notice that 3.x is now marked as current, but isn't the default. I'm filing a bug report for that now.\n\nSo what changed? You can read a complete list of release notes here: 3.0 Release Notes\nNote that as of right now, the notes say 3.0 is still in pre-release, this should be removed really soon.\nTo me, two changes stand out as particularly important. The first is &quot;Cleanup in conversion and coercion of input arguments&quot; and the second is &quot;CORS is no longer enabled&quot;. Both of these changes help lock down and secure LoopBack APIs a bit more and feel like really smart ideas.\nAnother change I like revolve around strict mode. I just discovered recently that by default, you can pass additional properties to a model and LoopBack will accept them. By using strict:true, you can disable this. The change in 3.0 (I liked to it at the beginning of the paragraph) just simplifies the new behavior.\nAnd finally - a new CLI focused just on LoopBack is in the works. I'll post more when it gets closer to release!\n",
		"tags":[
	        
            "nodejs",
            
            "loopback"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "My Entirely Not-Biased Review of Rogue One",
		"date":"Sun Dec 18 2016 16:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1482078720,
		"url":"https://www.raymondcamden.com/2016/12/18/my-entirely-not-biased-review-of-rogue-one",
		"content":"Unless you've been living under a rock the past few months, you've probably\nheard that a new Star Wars film was released this past week, &quot;Rogue One.&quot; Like\n&quot;The Force Awakens&quot;, I've been looking forward to this for quite some time. Obviously\nwhat follows will be my heavily biased review - I'm a huge Star Wars fan and don't\nhave the most critical eye when it comes to the subject matter, but if you want to know what I thought, read on. I will add a very large and obvious &quot;Spoiler Break&quot; after I share my thoughts, so stop reading at that point if you don't want important details ruined for you. I will also consider the comments to be a spoiler zone as well. Basically - just stop reading at the spoiler break!\nGoing into this movie, I had only one concern. As the first non-trilogy movie (that's not exactly\nright, Clone Wars was the first one), there\nwas a lot riding on it. I really believed that this was a good idea. Like the books, TV shows, etc,\nlet's explore what's happening outside the main big story and flesh out the universe. Right from\nthe beginning though there were concerns. A sizable portion of the people I spoke to about\nthe movie were confused by when it actually took place. Normally that's a pretty bad sign. I\nthink most people eventually got what was going on, but audience confusion is never a good sign.\nAll I wanted for it was to not suck. I thought the idea of &quot;Rogue One&quot; (R1) was great, and I thought\nthe idea of a Han Solo film was also good, but obviously if R1 tanked all of that would go away.\nRight from the beginning, this is a completely unique film. Everything, from the title text, to\nhow things are shot, are completely new to the SW series. Even though &quot;The Force Awakens&quot; (TFA) was\nset past the events of the last trilogy and had a lot of new stuff in it, I really thought R1\nwas more unique. It felt like seeing a completely new side to the classic story.\nWhat comes to mind is &quot;Enterprise&quot;. Yes, I know, talking about &quot;Star Trek&quot; in a &quot;Star Wars&quot; movie review\nis probably breaking some Internet law, and I know most folks didn't like &quot;Enterprise&quot;, but just go with me\nhere a minute. One of the best aspects of &quot;Enterprise&quot; to me were the details it revealed about\nthe early years of Starfleet. Specifically, I thought the Vulcans, and how they treated Earth,\nwas absolutely fascinating. To me, seeing that 'texture' to the history made the show worthwhile\nto me, despite it's other flaws.\nR1 does much of the same. We are presented with a Rebel Alliance that is much more fascinating then\nwe've seen in the past. It isn't uniformly pure and good. There is infighting. It's all a much\nmore realistic and interesting than previously seen.\nThis is a much darker film than we've seen previously. &quot;Empire Strikes Back&quot; (ESB) has a pretty\ndark tone, but I'd say R1 far surpasses it. That could be a bad thing. &quot;Batman vs Superman&quot; was\nincredibly grim, but ended up feeling completely stupid and unnecessary. In R1, it feels entirely\nappropriate for the story. The Empire is at its strongest and there is a desperation to the\nmovie that makes sense for that universe.\nThe main cast was well done, especially Felicity Jones' Jyn Erso. Like most ensemble films though\nyou don't get a deep look at many of the characters. I'm ok with that as - again - it is an ensemble\nfilm, but I really feel like the Imperial pilot defector got shafted. Maybe they were worried about\nspending too much time on a defector when we had one as a primary character in TFA as well, but I truly\nfelt like Bodhi Rook (and yes, I had to look it up) had a much more interesting story to tell.\nKrennic and Galen were well done, but I strongly recommend reading Catalyst\nto get a much deeper look at their relationship. In fact, I'd say that my overall enjoyment of\nthe film was greatly enhanced by reading the book first. It really gives you insight into their\ncharacters.\nThe use of CGI for two characters (I won't name them yet as it would be spoilers) was... an interesting\nchoice. The uncanny valley issue is strong here and I'm not sure I would have made the same choice. When it\nwas done in &quot;Tron Legacy&quot;, it made sense to me. In the real world, Flynn's youthful face was used\nsparingly, and it wasn't too jarring. In the computer world, his CGI face made perfect sense.\nLastly, the music was uninspiring, just like it was in TFA too. That's truly sad. It doesn't ruin\nthe movie for me, not even close, it's just disappointing. I bought the TFA soundtrack and played\nit once or twice, but there's nothing memorable in either's soundtrack in my opinion.\nIt sounds like I'm complaining a lot, but to be clear, I loved it. A coworker asked me how I would\nrank this film, and honestly, I feel like I can't. Every aspect of it seems to different from\nthe 7 main story films it doesn't quite 'sit' right in a simple ranking. I would say it is much better\nth",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Advent of Code - Day 13 and 14",
		"date":"Fri Dec 16 2016 18:33:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481913180,
		"url":"https://www.raymondcamden.com/2016/12/16/advent-of-code-day-13-and-14",
		"content":"Every day I fall farther and farther behind in the Advent of Code, but I figure this\nwill give me something fun to do in the week of Christmas. Days 13 and 14 were a bit rough, but I got through them (with a lot of help).\nDay 13\nDay 13 basically involved generating a maze and then finding the solution. As\nyou can imagine, this is a well known problem and loads of solutions exist. You can also find the general algorithm for\nit as well, but I decided to take the easy way out and simply find a JavaScript module that did it for me. I used\nPathFinding.js from Xueqiao Xu and it worked great. Here's my solution for part 1.\n\nFor me the fun part was actually seeing my maze. I didn't render the solution, but would have been easy.\n\nPart 2 simply has you find all the cells that are within a certain range. This was simple after I discovered that PathFinder modifies the\noriginal data and you need to clone it if you are 'walking' it more than once.\n\nDay 14\nDay 14 seemed easy enough. Loop from 0 and create a hash of some salt plus the number. Look for\n3 matching characters in a row. Remember where you found it. Now keep looping and if you find the same character but with\n5 in a row and if the previous match was within one thousand loops, you add it as a valid key. Once you hit 64 keys, stop.\nI struggled like crazy with this because I missed an important detail. If you have found the 'closing' 5 character match, it can actually\n'close' multiple earlier matches. Credit goes to some smart users on Reddit:  the4ner and\nAustinVeolnaut.\n\nPart 2 simply had you re-hash your hash 2017 times, which slowed things down quite a bit, but still returned an answer in about 5 minutes.\nI saw folks on Reddit who did theirs a heck of a lot faster, but I was ok with five minutes.\n\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code - Day 9 to 12",
		"date":"Tue Dec 13 2016 13:26:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481635560,
		"url":"https://www.raymondcamden.com/2016/12/13/advent-of-code-day-9-to-12",
		"content":"I've got a bunch of code to share so let's get started. As before, be sure to check my repo for the full code of my solutions.\nDay 9\nDay 9 was a doozy. Basically the idea was to work with a compressed string, think a zip file, and decompress it. Part one used this rule: Given the existence of (XxY) in a string, it means that you repeat the next X characters Y times. Your solution needs to decompress the string and output the length.\n\nNothing too crazy here, just a bunch of string manipulation. Part 2 did something crazy. Previously, if you found a marker within text that was being repeated, you ignored it. Now you need to decompress that too. What happens now is that the string gets so big, you run out of memory! So along with continuing to decompress, I had to switch my code to not actually create the result string, but just keep track of the length.\n\nDay 10\nDay 10 was another fun one (two in a row, I should have known what was coming next). Basically your input is a set of instructions to bots that take values from an input bucket and then, eventually, copy them to an output bucket. Bots can hold two values and then have a rule saying, &quot;I give my lower value to bot X and my higher value to bot Y&quot;, or instead they can copy a high/low value to the final output bucket. Your goal is to figure out what bot &quot;held&quot; values 61 and 17.\n\nPart 2 simply asked you to output the product of some of the values in the output array, and since I had output them already, I didn't need to write any code at all.\nDay 11\nWell, it happened. Day 11 was the day that finally brought me to a complete stop. Intellectually I understood the problem, but honestly I couldn't see any way I was ever going to solve it. Last year when this happened I typically found a solution in another language and rewrote it in JavaScript, but I was a bit short on time so I really cheated. I found a Python solution and simply ran it. As I didn't have Python installed yet, I figured I was at least correcting that problem too.\nDay 12\nWoot! A fun one again! And one that's based on a puzzle from last year if I'm not mistaken. Day 12 basically has you implementing a little Assembly-like language. So for examlpe, your input may be:\n\nYour code either moves/sets values in registers or has you change the order of code execution. Outside of a few dumb mistakes I made, this one was simple.\n\nPart two had you simply change an initial value (which is what I ended up committing to GitHub) so I didn't make a new file.\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with Ionic Native - Contact Fixer",
		"date":"Mon Dec 12 2016 17:26:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481563560,
		"url":"https://www.raymondcamden.com/2016/12/12/working-with-ionic-native-contact-fixer",
		"content":"I've blogged a few times now about Ionic Native, but if you're new to it, you can think of &quot;Angular2/Ionic2 Friendly Wrappers&quot; for many different Cordova plugins. Today I'm sharing what may be my coolest demo yet. No, wait, seriously, it is, honest! This demo does something I think every phone should have built in, and if I can get off my lazy butt, I'll be submitting this to the App Store this week. So what did I build?\nI noticed recently that both both iOS and Android will provide a contact picture even when you haven't selected a unique one for them. I believe, in both cases, it won't use the default picture when you get a call from them, but in the contacts app it will display it. So that's cool. I know it isn't new, but I like the fact that I can see someone's face when I get a phone call or text message from them.\nHowever - I don't always have time to snap pictures of people when I'm adding them to my contacts. This made me curious. Given that we have a Contacts plugin and a Contacts Ionic Native wrapper, could I actually set a picture for contacts that didn't have them?\nTurns out that yes, you can! And of course, that means only one thing. I could build an app to &quot;fix&quot; those contacts and give them better pictures. Here's how 3 of the default iOS simulator contacts are displayed:\n\n\n\n\nAnd here are the fixed versions:\n\n\n\n\nIn case it is a bit too small, here is a closeup on the awesomeness:\n\nI'm going to be so rich when I put this in the App Store. Ok, let's take a look at the app itself. First, the UI, which is incredibly simple since the app does one thing and one thing only.\nOn startup, we display some text explaining what we're about to do:\n\nYou then click the button, it presents a 'working' message, and when done, shows you a result:\n\nAnd that's it. I could maybe actually show all the contacts and their new pictures, but honestly, this felt like it was enough. Let's look at the code. First, the view.\n\nAnd now the real meat of the app, the code behind this view.\n\nThings get kicked off when the button is clicked on the view and fixContacts() is fired. I turn on a loading component to present something to the user to let them know the app is doing something.\nNext, I ask the Contacts API to return every contact. I have to pass a 'search field', even though I'm not actually passing a search value. That's a bit wonky, but that's how the plugin works, it isn't a bug in Ionic Native's implementation.\nI then iterate over every contact. The photos property is empty when no pictures exist for the contact. When I find that, I kick off a process to make a new picture using the Placekitten service. I simply generate a random size and that will give me a random cat. I conver that to a base64 string and then store it in the contact.\nBecause this process is asynchronous, I use an array of promises I can then call then() on to know when they are all done.\nFinally, I report what I did to the user using the Alert component. And that's that. Here's output from my real device. First, Max, who already had a picture.\n\nAnd here is Alex, who did not have a picture, but who now does, and is much improved:\n\nYou can find the complete source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/fixcontacts\nSo - who would pay 99 cents for this?\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Advent of Code - Day 8",
		"date":"Sat Dec 10 2016 16:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481385840,
		"url":"https://www.raymondcamden.com/2016/12/10/advent-of-code-day-8",
		"content":"Yes - I'm falling behind, but let's just pretend today is still the 8th. Day 8 was a difficult one (I feel like I'm saying that more and more) as it required working with 2D arrays, something I always have trouble with. Specifically - you needed to represent a 2D array of cells on a screen. You then had to take input in the form of:\n\nrect AxB - which would turn on lights in a rectangle A wide and B high in the upper left corner of the screen.\nrotate row y=A by B - which &quot;rotates&quot; lights in row A B steps lower. You have to handle wrapping too as a light that 'falls off' the bottom then returns to the top.\nrotate column x=A by B - same concept, but in a column.\n\nThe answer to this puzzle would simply be the number of lit lights.\nThe rect part was trivial, but the rotation took me forever. I figured out that given an set of values like so:\nABCDE\nand wanting to rotate by, let's say 2, you could create a new set by starting on the third letter and selecting from the original set until you get to the end, and then wrap around, so:\nCDEAB\nAs I've said many times before, using their sample input/output was a huge help. In this case, it helped so much that when I switched from the sample input to the real input, my code returned the right answer immediately!\n(I have to admit though, this one was so difficult I contemplated cheating. When you enter a numeric answer in Advent of Code, it usually tells if it is too high or too low. So in theory, I could have gotten the max number of lights and then began guessing by supplying a number half-way through the range and just narrowing it down.)\nHere is my solution:\n\nNotice the renderScreen function. I built this to help me debug and it was a fortuitous decision. I noticed when I rendered the 'real' input, the output was a set of letters. Part two to the puzzle was to simply input those letters. Here is what my output looked like:\n\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Updates to Client Storage for the Browser",
		"date":"Thu Dec 08 2016 21:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481232060,
		"url":"https://www.raymondcamden.com/2016/12/08/updates-to-client-storage-for-the-browser",
		"content":"I made some edits to this post after some feedback from Dru Knox, a Chrome PM. I've marked them with &quot;Edit:&quot;, so\nplease be sure to note those changes!\nI've been interested in client-side storage for a few years now. (And in fact, last year I wrote a book on it as well.) When I first began to dig into the topic, my focus was on the various APIs themselves. In other words, what were the mechanics of actually storing\nand retrieving data. What I found is that we had multiple options, some easier than others, but in general, it was pretty cool to have a way\nto store data on the client for both performance and offline support.\n\nHowever... where things began to fall apart, and rather quickly, was on the higher level concept of how this storage was managed on the device itself. Specifically, how\ndo you know your data will actually persist when you store it and what will the browser do when, and if, it determines you've stored too much. I played around with this\na bit last year:\n\nBlowing up LocalStorage (or what happens when you exceed quota)\nIndexedDB and Limits\n\nLuckily, as offline and PWAs have become increasingly more important over the past year or so, browser vendors (some more than others) have\nstepped up to the plate to help remove some of this ambiguity and make the storage system a bit more solid.\nEarlier this week I read a good article on these efforts by Chris Wilson, Persistent Storage. I\nsuggest reading that article before continuing on. The basic premise is that there is a new API for what is called a StorageManager.\nThe StorageManager, at least for now, consists of three basic parts. The first, and this is the main focus of Wilson's\narticle, is the ability\nto specify that data should be really persisted. This is the persists API. Yes, that name is a bit weird.\nBasically the idea is this. I can tell the browser to store the value &quot;Cat&quot; in LocalStorage under the key &quot;BestAnimal.&quot;\nCurrently the browser will store this, but also kick it to the curb when the browser sees fit. Data that\nis persisted though will be permanent.  This is cool, but I really take issue with the naming. I get that there is a difference between\n&quot;lasts a random amount of time&quot; versus &quot;lasts forever&quot;, but even in this it's more &quot;The browser won't nuke it, but the user can&quot;, which isn't truly persistent either. But... yeah, naming\nis hard, and frankly, I can't think of a better way to describe this either.\nAnother aspect to keep in mind - both Wilson's article and the MDN docs are a bit vague about what this applies to. The MDN docs for\nthis API have this to say:\n\nThe persist property of the StorageManager interface returns a Promise that resolves to true if the \nuser agent is able to persist your site's storage.\n\nNice and clear, but &quot;site's storage&quot; could mean:\n\nCookies (yes, cookies are storage)\nLocalStorage\nIndexedDB\nWebSQL (yes, I know it is dead, but it is still very well supported, a mere 2% below IDB)\nOh and that new Cache thing that is for ServiceWorkers that I've not looked at yet.\n\nSo what's covered? Everything but cookies and WebSQL. I think that's fair as you shouldn't be using WebSQL anymore (outside of SQLite in Cordova though) and cookies are - well, cookies.\nOk, so you can ask the browser to really store your stuff. Kinda. See the API lets you ask for this support, but currently, Chrome won't prompt\nthe user. Instead, it uses the following rules to see if you are allowed to use this feature:\n\nThe site is bookmarked (and the user has 5 or less bookmarks)\nThe site has high site engagement\nThe site has been added to home screen\nThe site has push notifications enabled\n\nIn general this makes sense, but I don't understand why a user can't have more bookmarks (I've got a crap ton) and I also don't\nlike the requirement that push notifications are enabled. I get enough notifications from apps. I wish apps would stop thinking they are\nso important that they deserve the right to bother me at any point in time. Ok, a bit of a rant there. I love the idea of notifications, especially\nwith support in the web browser, but I wish it was a feature people would use less of. It's one of those &quot;With great power...&quot; type things that\npeople just seem to want to abuse.\nEdit: To be clear, the list above is a list of options and you only need to satisfy ONE of them!\nThat being said, I did some testing (and I'll be sharing some code below), and it looks like you can't test this feature locally. To be clear,\nit doesn't really break anything per se, even when the API tells you no, you can still store data in your various buckets, but if I want to really\ntest this out I'd have to test on mobile. And I guess delete my bookmarks to if I have too many.\nNotice that the third requirement essentially means this is a non-starter for desktop. Maybe the thinking is that it won't be necessary\nsince desktops typically have lots of free space? But I can tell you right now that my ",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code - Day 6 and 7",
		"date":"Thu Dec 08 2016 17:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481216760,
		"url":"https://www.raymondcamden.com/2016/12/08/advent-of-code-day-6-and-7",
		"content":"And so it begins - the Advent of Code has begun to royally kick my butt. While these two challenges\nweren't necessarily killers, the amount of time I need to solve them is slowly growing. Let's start with day 6.\nDay 6\nThe first challenge asked you to find the most common character in a column of input. So given this:\n\nart\nboo\naot\n\n&quot;a&quot; would be the most common charcter in the first column, &quot;o&quot; in the second, and &quot;t&quot; in the third. My solution creaates an\narray of arrays to store each column of input. I then loop over the columns and create an object containing\neach letter and a count. I can then simply iterate over that and find the letter with the highest count.\n\nNot too difficult. The second part simply reversed the target and asked you to find the least likely letter:\n\nI feel kinda bad that I didn't rename highest, but I got over it.\nDay 7\nThis puzzle was one of those I felt like could be solved with one regex,\nbut I'm just not quite that much of a regex master to figure it out myself. The first part involved looking for\na string of the form ABBA, where you have two matching characters on the 'outside' of a set of 4 and 2 matching\ncharacters on the inside. You needed to see if an ABBA existed in the input string, but also see if an ABBA\nexisting inside any brackets. If it did, that negates it being a good string. So basically, &quot;No ABBA in brackets,\nABBA outside.&quot;\n\nNot elegant at all, but it worked. Part 2 switched to a &quot;ABA&quot;, a pair of characters around another character, outside\nof any bracket, with a corresponding opposite &quot;BAB&quot; inside brackets. Here is my solution.\n\nThis one caused me a lot of trouble just because I kept screwing up my findABA logic. My choice of variable names didn't\nhelp either. But - it worked. Again, roughly.\nAs a warning, my solutions will only get uglier as we go further...\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code - Day 5",
		"date":"Tue Dec 06 2016 15:37:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1481038620,
		"url":"https://www.raymondcamden.com/2016/12/06/advent-of-code-day-5",
		"content":"Another easy day for Advent of Code. The first challenge was to simply iterate over a string (take an input string, add 1 to it, and increase that number), make a hash, and check to see if the hash begins with 5 zeroes. If it does, you take the number after the 5 zeroes as a password character. You keep appending to a password until you have seven characters:\n\nThe only real difficult part was finding the Node Hash function and that was just one Google search. Looking over it now, having a generatePassword function seems a bit silly but I was assuming part would possibly need it. (It didn't.)\nSpeaking of part 2 - all it did was specify that the two characters after the five zeroes now represent a password position and password character. So the solution got a bit more complex.\n\nAs I said - pretty easy. But that's how Advent of Code works. It lulls you into a sense of confidence and then destroys that without mercy.\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code - Day 3 and 4",
		"date":"Mon Dec 05 2016 16:22:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1480954920,
		"url":"https://www.raymondcamden.com/2016/12/05/advent-of-code-day-3-and-4",
		"content":"Only 5 days into Advent of Code and already this thing is kicking my rear. Both challenges over the weekend were (mostly) simply, but I'm already having trouble keeping up. This is a good thing though. I'm still absolutely loving the hell out of these challenges!\nDay 3\nDay 3 was a rather simple problem. Given three numbers, can you determine if they could be a triangle? Turns out there's a simple mathematical formula for that - any two side lengths must add to a number larger than the third side. So given an input of numbers, the task was to just count the total number of valid triangles.\n\nThe second part did something evil. Instead of reading the file line by line, now each column of the file represented input. So you needed to read 'down' each column to generate the list of triangle inputs, and then validate them. I cursed this one, but still had fun.\n\nRandom question - how much of our job is taking crap input and making it useable?\nDay 4\nThis one was a bit fascinating. Our input is a string that looks like so:\naaaaa-bbb-z-y-x-123[abxyz]\nEverything up to the last number represents the name of the room, encrypted. The number represents a 'sector'. Finally, the code inside the brackets is a checksum. You can determine if the string is valid if:\n\nYou count each instance of letters used in the name.\nYou sort out the top 5 used letters, and in case of a tie, sort alphabetically.\nThe checksum then is the top 5 letters as sorted above.\n\nSo I wrote a generic function to validate that input and used it on the samples they provided. I mentioned how they suggest that as a good way to test your code and I cannot recommend that enough. Once done, I modified my simple validRoom function to return the sector if the room was valid. This is because the main goal is to sum all the sectors. I don't like my function returning something non-boolean, but I got over it.\n\nThe second part then had you decrypt the room name using a shift cipher based on the sector. I found a great caesarShift function here and that worked fine. What wasn't fine was that the puzzle said, &quot;What is the sector ID of the room where North Pole objects are stored?&quot;. That made absolutely no sense to me. What they were trying to say is - look at the decrypted room names for something you think makes sense. I saved my output to a file and then CTRL-F for &quot;storage&quot; and found: &quot;northpole object storage&quot;. That felt a bit unnecessarily confusing, but heck, it's just like most client-work, right?\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Advent of Code - Day 2",
		"date":"Fri Dec 02 2016 16:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1480696200,
		"url":"https://www.raymondcamden.com/2016/12/02/advent-of-code-day-2",
		"content":"The second day's challenge for Advent of Code was a bit easier than the first, so it was nice to tackle it a bit quicker. The puzzle involves a set of directions for moving your fingers over a numeric keypad. Imagine a typical security keypad layed out like so:\n1 2 3\n4 5 6\n7 8 9\nThen take input in this form:\nLLRUDD\nUUD\nEach line represents a set of movements to the left, right, up, and down. You navigate those movements, but do NOT leave the keypad even if the instructions tell you to. At the end of each line, you press the button. The solution then is simply the buttons you pressed. Here is my solution:\n\nMy solution simply parses the input, moves the 'finger', and ensures we stay on the keypad which is just a 2d array.\nThe second part mixes this up by changing the keypad:\n\n    1\n  2 3 4\n5 6 7 8 9\n  A B C\n    D\n\nHere's my solution:\n\nThis time, my move logic does two things. It still ensures it is within a main 'bounding' box, but then looks to see if it fell on an 'empty' space, and if so, it ignores the move.\nNote how I copied the array:\nvar orig = Array.from(pos);\nOriginally I simply made a copy, and forgot it was a copy by reference. Oops.\nYou can find my repo of solutions here: https://github.com/cfjedimaster/adventofcode\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Begin the Advent of Code!",
		"date":"Thu Dec 01 2016 17:14:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1480612440,
		"url":"https://www.raymondcamden.com/2016/12/01/begin-the-advent-of-code",
		"content":"Forgive the somewhat dramatic title, but today begins one of the coolest coding challenges I've ever done, the Advent of Code. The Advent of Code presents you with two coding challenges a day. (The second is typically a minor modification of the first one.) You can solve the challenge anyway you want. They start off - mostly - kind of simple and then kind of go off the deep end towards the end.\nLast year I was able to do all but, maybe, 4 of them, without &quot;cheating&quot;, and by &quot;cheating&quot; I just looked at solutions in other languages and rebuilt them in JavaScript. I was still practicing my JavaScript so I still had fun even when I couldn't figure out the solution myself.\nIn order to encourage my readers to participate, I've decided to share, and blog, my solutions, for every day of contest. I'll probably fall a bit behind on the weekends, and I probably won't code on the 25th (last year I just did it the day after), but if folks want to see how I'm solving these challenges, I'm more then willing to share.\nI've set up a repo for my solutions here: https://github.com/cfjedimaster/adventofcode\nTo be clear, my solutions are going to be pure crap. Do not consider these solutions as examples of best practices or anything even close to intelligent code. But they will work, and I'll have fun writing them, so that's good enough for me. ;)\nOne piece of advice I'd like to share, and the site makes this suggestion as well. If your solution isn't working, take the sample, smaller input they provide with answers and check your code again. I had to do that today.\nDay One\nThe first challenge wasn't too difficult, but it took me a few reads to figure out exactly what they were asking. Essentially the challenge is this. You're given input that includes a direction and distance traveled across city blocks. Figure out where you end up, then figure out a minimum distance to that point. This is called Taxiway geometry and it turns out there is a super simple solution once you know points 1 and 2:\nMath.abs(x1-x2) + Math.abs(y1-y2)\nHere is my solution. Again, be gentle.\n\nFor the most part, it should make sense, but the general idea is to read in the input, parse it, and then iterate over every step of the directions. Once I'm done, I simply use the math I described above to get the result.\nThe second part to the challenge was interesting. Instead of getting the distance to the last point of the path, you now need to get the distance to the first point you traveled to twice. My code above made this difficult as I make my moves in whole blocks, ie if I'm going 5 blocks east, I move 5 at once. I needed to change my code to &quot;step&quot; through each part of the movement and keep track of how many times I visited it. Here's that version:\n\nAnd there ya go. Ugly, but workable.\n",
		"tags":[
	        
            "advent of code",
            
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "A Simple Stats Script for Hugo",
		"date":"Thu Dec 01 2016 13:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1480599000,
		"url":"https://www.raymondcamden.com/2016/12/01/a-simple-stats-script-for-hugo",
		"content":"I'm somewhat obsessed with stats, and one of the things I look at is my rate of publishing overtime. I've run this blog since 2003 and have gone from blogging 30+ times a month to - well - somewhat less than that. Quality is - of course - far more important than quantity. But as a general stat, I just like to know how much I'm writing.\nMy static site generator of choice, Hugo, doesn't have anything built in to support getting this. You can get post counts and stuff like, but I wanted something a bit deeper, and something more focused on the amount of content published over time. So with that in mind, I wrote the following script. If your Hugo site follows the same convention as mine (year/month/day folders), then in theory, it should just work for you.\n\nBasically I just iterate over every year, month, and day, and then open up each file. Hugo stores metadata, or &quot;front matter&quot;, on top of each blog post in a JSON string. I can read that, parse it, and then figure out what categories and tags are being used. I can then strip that out and get a basic word count too.\nThe end result is just an object containing the number of posts per year, month, tag, and category. I also store the total word count (why not?) and an average.\nAs a reminder, you should typically avoid using sync functions in Node, but as this was a simple script just for me, I went for simplicity and I'm ok with that.\nThen - for the heck of it - I whipped up a simple stats page to render the data. You can just click that link, but here are the four reports. For the first two, I literally copied and pasted the quick start code from Google's Charting library and modified it slightly.\n\n\nHere's the top portion of my categories and tags stats:\n\n\nAnd finally - a few generic stats:\n\nThe only real interesting part of this page is how I'm handling number formatting - I'm using Intl - a kick ass built in standard for internationalization for the web. Here's how I make it fail gracefully:\n\nI can then just do formatter.format(x) to get my nicely formatted numbers.\nAnyway - is this useful to folks using Hugo? Any suggestions?\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Scraping a web page in Node with Cheerio",
		"date":"Wed Nov 30 2016 14:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1480514580,
		"url":"https://www.raymondcamden.com/2016/11/30/scraping-a-web-page-in-node-with-cheerio",
		"content":"In yet another example of &quot;I will build the most stupid crap ever if bored&quot;, this week I worked on a Node script for the sole purpose of gathering data about SiriusXM. I'm a huge fan of the radio service (mostly because 99% of my local radio stations are absolute garbage, except for KRZS), and I was curious if the service had an API of some sorts. I was not able to find one, but I did find this page:\nhttp://xmfan.com/guide.php\nWhich had a constantly updating list of what's playing. I reached out to the site to ask how they were getting their data, but I never heard back from them. Therefore I figured why not simply scrape the data myself locally?\nIn order to do this I decided to try Cheerio, a jQuery library specifically built for the server. It lets you perform jQuery-like operations against HTML in your Node apps. I first heard about this from one of my new coworkers, Erin McKean, who joined us on the LoopBack team at IBM a few weeks back.\nMy script was rather simple, so here is the entire module I built.\n \nEssentially - I suck down the contents of the HTML and then use a selector to get the left hand column of the tables used to represent the music data. This is - obviously - brittle. But let's carry on. After I have those nodes, I can then iterate over them and find the nodes next to them in the table row. This is all very much like any other jQuery demo, but I'm running this completely server-side. The end result is an array of objects containing a channel, artist, and title.\nTo use this, I set up a simple script to run my module and then insert the data into Mongo. In order to ensure I don't get duplicate data, I store a timestamp with each record, and first see if a matching record within five minutes was stored. Here's my code:\n\nI figure this isn't too interesting, but I will point out one bit I don't like. I'm not running this as a server, just a script, and I needed a way to close down the connection when done. Since everything is async, I could have used Promises, but I decided to go the lame way out and simply keep track of how many results I had processed. This means I've got a bit of duplication in the two blocks that handle closing the connection.\nI'm thinking that the next step will be to add Node Cron, which is fairly easy to install, but always takes me forever to figure out the right syntax. I'll then let it run for a month or so and see if I can get some interesting analytics. For example, how often is the Cure played? This is important stuff, people!\nHere's an example of it working - and you can see where on the second run it ignored a bunch of songs that had been recorded before:\n\nAnd here are a few rows in the database:\n\nYou can take a look at the full code (currently anyway) here: https://github.com/cfjedimaster/NodeDemos/tree/master/siriusxmparser\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Online Pug Testing Tool",
		"date":"Fri Nov 18 2016 16:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1479486900,
		"url":"https://www.raymondcamden.com/2016/11/18/online-pug-testing-tool",
		"content":"I've never been shy about my feelings about the Jade templating language. I absolutely hate it. I think it is weird. I think it leads to moral depravity. I just really, really don't like it. Mainly I think because I find HTML already succinct enough. I mean I get that this:\nh1 Foo\nis less code than\nFoo\nbut my brain just has issues mapping the Jade version to the output. So yeah, I hate Jade. But a while back, due to some legal issues, the Jade folks had to rename to Pug, and who can hate a project called Pug?\n\nTo be honest, even before this rename, I'd been slightly warming up to Jade. I still prefer Handlebars but I've moved past &quot;hate&quot; and am slowly warming up to &quot;Mild Distate.&quot;\nI noticed today that there wasn't a quick testing tool for Pug. The web site says you can open up dev tools and test right there, which is cool and all, but as I had a few hours in the Denver airport, I thought I'd whip up a quick tool. Here's a screen shot of it in action. It lets you input a Pug template and supply JSON data.\n\nYou can test this yourself here: https://cfjedimaster.github.io/webdemos/pugclient/\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A Social Example of Ionic Auth",
		"date":"Thu Nov 17 2016 15:38:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1479397080,
		"url":"https://www.raymondcamden.com/2016/11/17/a-social-example-of-ionic-auth",
		"content":"A few days ago I blogged about using the Ionic Auth service with the latest version of Ionic 2 (&quot;An example of the Ionic Auth service with Ionic 2&quot;. One of my readers asked if I could update the example to make use of social login. I worked on that yesterday and have an updated example to share. Please be sure to read that previous post first so you have a bit of context on what we're building!\n\nAs I said, for this iteration of the demo, I'm going to use social login. While one app could support both social and &quot;regular&quot; login/registration, I thought it would make more sense to only support one or the other. So this version of the app completely removes the registration feature and just supports logging on with Facebook or Twitter. And I chose those two rather arbitrarily. The full list of supported social logins supported by Ionic also include Google, Instagram, LinkedIn, and GitHub.\nFor Facebook, I began by following the directions under &quot;Native Login&quot;. The docs are a bit confusing at times because they no longer match (precisely) the 'flow' you get at Facebook when adding an application. I was able to work around it, but just remember that you're going to have to click around a bit differently than what the Ionic docs suggest. (I filed a bug report for this, but at the end of the day, external sites like this will change from time to time and I'd expect Ionic's docs to sometimes be a bit behind.) There is one major issue with this page though that I'll bring up in a moment.\nOnce you've followed these directions and installed the appropriate plugins, you can then start writing your code. I began by modifying the login page. I got rid of the accordion and just added two buttons:\n\nNote the use of icons in the buttons to make them fancy. Here's the updated UI:\n\nEach button is tied to a method to handle it's particular login. For the most part, this is rather simple. For example, here is the Facebook code:\n\nAt that point the native code takes over. I'm using Genymotion on my laptop to test Android, and I'm not terribly happy with how this looks, but here it is in action.\n\nAs I said, I don't think this is the best screen shot, but it does work well. I was able to login and then I was considered logged in by the Auth system. Here is where I ran into a serious doc issue. Once logged in, the app likes to greet you by name. In my previous version, when you registered I asked you for a name. Facebook certainly knows your name, but how do you get it? Turns out the other doc for Facebook login (http://docs.ionic.io/services/auth/facebook-auth.html), the one covering InAppBrowser, has a section on this, Social Data.\nBasically you will find the data returned from Facebook under the user object in a social.facebook block.\n\nAnd here it is - correctly recognizing my name:\n\nThe last part to this was handling logout. Facebook requires slightly different code, so I've got a bit of logic to handle this. Notice that I could probably handle the &quot;how did you login part&quot; a bit nicer.\n\nSo Twitter wasn't much more work. The login routine is simple:\n\nAnd here is how it rendered the UI:\n\nAnd that's basically it. You saw above how I handled getting the name as well as how I handle logout. Frankly the most difficult part was the stuff outside of my Ionic code. In general, it feels like a simpler, easier solution than requiring a unique registration. You can find the complete source code for this version here:\nhttps://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicAuth2Social\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "November is National Adoption Month",
		"date":"Mon Nov 14 2016 15:48:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1479138480,
		"url":"https://www.raymondcamden.com/2016/11/14/november-is-national-adoption-month",
		"content":"Every year (sometimes sooner than later), I remind folks that November is national Adoption Month. My wife and I have been blessed to have formed our family via international adoption. We have three kids adopted from South Korea and three from China. It is an incredible process. Beautiful, scary, difficult, rewarding, a mix of everything you would expect in bringing a new life into your family. If you've never considered adoption before, then now is a perfect time to start thinking about it.\nYou can begin at Adoption.com. Also check out AdoptTogether - a new site that helps crowdfund adoptions. I was introduced to this site by a friend recently and it looks like a great way to help support the financial burden of adoptions. You can learn more about the idea by watching this video:\n\nI'd be happy to help answer any questions about the adoption process (although my experience is only with international adoption, not domestic). Just leave me a comment below or if you would rather talk privately, hit up my contact form.\n",
		"tags":[
	        
		],
		"categories":[
            
                "adoption"
            
		]

	},

	{
		"title": "Speaking on Static Sites at Ortus Developer Week",
		"date":"Fri Nov 11 2016 19:41:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1478893260,
		"url":"https://www.raymondcamden.com/2016/11/11/speaking-on-static-sites-at-ortus-developer-week",
		"content":"Forgive the somewhat late notice, but I'll be speaking next week at Ortus Developer Week, a 100% free, online conference run by the fine folks at Ortus Solutions.\n \nWhile primarily a ColdFusion-related conference, you'll find some non-CF topics there as well. My topic is &quot;Going Static&quot; and I'll be discussing one of my favorite topics - static web sites. My talk is at 11AM CST, Monday the 14th.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Some Advice for a Web Developer Learning New Skills",
		"date":"Wed Nov 09 2016 15:07:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1478704020,
		"url":"https://www.raymondcamden.com/2016/11/09/some-advice-for-a-web-developer-learning-new-skills",
		"content":"One of the things I'm constantly telling people is that I love it when I get questions. Sometimes they aren't things I can help with and sometimes they are a bit too complex to handle over email, but every now and then I get a great one that leads to a good discussion. Yesterday I got one of those emails and I want to share it with my readers.\n\n\nI'm about to start a season of building some new web sites, web applications and mobile applications, and I want to build with modern practices and not what I learned 10 years ago. \n\n\nWhat do you think I should invest in learning and using?\n\n\nAs I have researched, here are a few things I’ve been seeing and considering...\n\n\nFirebase\nNode.js\nAngular.js\nFoundation CSS Framework\nand/or\nBourbon/Neat\nIonic\n\n\nIt seems like you're a fan of Node and Ionic.\n\n\nThe applications I built with CF all used CFCs for the data interactions and for APIs for our mobile apps. I really liked how clean all that worked. I really liked how easy it was for CFOUTPUT to loop over a query. Most apps I created used CF Mail to send mail and occasionally used cfpop to get mails. \nI also use the Scheduled Tasks and Application.cfc.\n\n\nSo another question is, can Node do all these types of things as well? Or are there technologies you recommend for doing them?\n\n\nI want to split his question into two main discussions. First, let's focus on &quot;What do you think I should invest in learning and using?&quot;\nWhat do you learn and when?\nThe list of technologies he shared was interesting. Heck, it even included a few items I had never heard of (Bourbon and Neat). The number one advice I can give to people hoping to become better web developers is to not get too focused on all the different libraries and utilities out there. Yes - there is a JavaScript library for every single need out there. Cool. But forget about it. Looking at that list above, you need to realize what each of them solves and decide if you need help with that.\nIf you don't have a particular problem, you don't need to worry about it.\nFor me, problems tend to come in two areas. The first is organization. Most of my JavaScript starts off as small enhancements to a simple HTML page. Validate a form. Load some crap via Ajax. Etc. As things grow larger, managing your one large monolithic JavaScript file becomes more and more of a chore. At that point, recognizing the tools that can help you manage that code can be extremely helpful.\nDoes that mean using a framework like Angular? Maybe. It could also be as simple as splitting up that large JavaScript file into smaller bits using something like the module pattern.\nThe second area than is simply handling one particular problem, like CSV parsing, and honestly, all I do at that point is Google for &quot;javascript csv&quot; and pick a solution that looks to be well documented and supported.\nI talk about this a lot more in my article for Telerik, Leveling Up Your JavaScript.\nJust remember - you can keep it simple. You don't have to learn it all today. Baby steps are completely ok and take it one task at a time.\nAbout that Node thing...\nSo now let's address his later point about Node and how it compares to ColdFusion. When I first started using Node, it was definitely a bit harder to get into than ColdFusion. With ColdFusion, if I wanted to try out some random feature, like HTTP calls for example, I'd add a test.cfm file and write up a quick test. For Node, that was a bit harder to do.\nPart of the issue is that I was 'stuck' in ColdFusion mindset where I felt like I needed my test script under a web server. That's because for a vast majority of ColdFusion's lifetime, there was no way to run scripts from the command line. With Node, that isn't an issue. You may forget that when learning. If you want to test HTTP with Node, don't worry about setting up a proper &quot;web app&quot; with Express, just write a simple script and test it from the command line.\nEverything you mentioned (looping over a query, using mail, scheduled tasks), all have multiple different solutions in Node. Yes you will need to look them up. While CF just gives you them, using Node forces you to actually find a solution and add it. But you will quickly learn to appreciate not being locked down to one solution and having the freedom to pick a library that works best for you.\nAt the end of the day, the shear size of the Node community is one of the biggest benefits. I'm not going to say ColdFusion is dying, but the fact is that there is a lot more activity within the Node community than CF.\n\nI'd recommend all ColdFusion developers learn Node. You don't have to abandon ColdFusion, but it is a great skill to add to your toolset and improve your hirability going forward.\nFinal Thoughts\nIt can be scary for someone who has been focused on one small world (like ColdFusion) who is now looking to become a better web developer. I definitely understand that. But it is an absolutely awesome time to be a web developer as well. Our tools and opti",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Quick LoopBack Tip - Using the Client Folder for your Static Directory",
		"date":"Tue Nov 08 2016 16:36:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1478622960,
		"url":"https://www.raymondcamden.com/2016/11/08/quick-loopback-tip-using-the-client-folder-for-your-static-directory",
		"content":"I'm writing this blog post literally because I want a place to store this tip so I can get it easier later on. When you create a new LoopBack application, the command line automatically creates a folder called client for you. That's a folder where you may, if you want to, place a client-side application to use with your APIs. This is totally optional. If you do choose to use it, you may find yourself wanting to run both a web app there as well as the LoopBack server.\nIn the past, I simply went into that folder and used httpster to fire up a quick web server. However, recently it occurred to me that a) LoopBack runs on top of Express and b) Express has a way to point to a static folder and c) LoopBack provides a shortcut for updating that setting. So long as you're OK with your client stuff being on the same port and server as your API stuff, then here is a quick tip.\nOpen up server/middleware.json and find this bit:\n\nAnd just tweak it to:\n\nAnd that's it. Now when you run your LoopBack app, files under the client folder that don't match routes will be loaded as static resources, including your HTML, JavaScript, and CSS.\nOh - one more thing. Most likely you'll add an index.html to your client folder. Don't forget that the default LoopBack app has a route for /. You can remove that in server/boot/root.js:\n",
		"tags":[
	        
            "loopback"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An example of the Ionic Auth service with Ionic 2",
		"date":"Fri Nov 04 2016 19:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1478289540,
		"url":"https://www.raymondcamden.com/2016/11/04/an-example-of-the-ionic-auth-service-with-ionic-2",
		"content":"Note that I am writing this when Ionic 2 was in RC2 status. I expect that things may change a bit between now and the final release, but probably not too much.\nEarlier this year I wrote a blog post demonstrating the newly released authentication system for Ionic applications: &quot;Testing the New Ionic User Service&quot;. I thought it might be fun to work up a new example using Ionic 2. While this example doesn't do anything the docs don't, it does put them all together in one &quot;complete&quot; application. I thought it might be useful to see the various APIs put together into a demo that shows:\n\nChecking if the user is logged in on start up\nMoving the user to a login page if not...\nAnd moving them to a home page if so\nSupport logout and send the user back to the login page\n\nThe full source code for what I'm about to demonstrate may be found here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicauth2\nI began by creating a new Ionic 2 application using the &quot;blank&quot; template. This gave me an app with one page, home. My first task was to add logic to check for existing login and route the user appropriately. I did this within app.component.ts:\n\nI don't know if I need that rootPage declaration there. I kind of think I don't, but I've left it alone for now. The real important bit is this.auth.isAuthenticated(). The Auth system automatically caches your login for you (and that's something you can disable) so you don't have to worry about adding your own storage for logins.\nIn case you're curious about that - the Auth system uses LocalStorage when testing in the browser:\nScreenshot removed\n\nYou'll notice the password is in plain text and they are also storing a third value as well. Outside of the other one, I feel like it's a mistake to use email and password as those are values I could see using myself. (I filed an [issue](https://github.com/driftyco/ionic-cloud-angular/issues/30)). \n\nSorry folks! I was totally wrong about this. I had written my own code to store auth info in LocalStorage but removed that code when I saw that Ionic was handling for me. However, I forgot that fact when I used devtools to look at my localstorage. Ionic does store a JWT token - but definitely not the password!\nFor my login, I decided to use a simple UI that defaults to login:\n\nClicking register simply hides the initial form and shows the (slightly larger) registration form:\n\nI'm not entirely sold on that UX, but it gets the job done. Let's take a look at the HTML behind this:\n\nNothing crazy here except the 2 divs with ngIf controlling their visibility. The real fun is the code behind this:\n\nLet's begin with doRegister. This handles either showing the register form (if hidden) or performing registration. For registration, I do a quick sanity check on the form fields (I know Angular 2 has a fancier way of working with forms, but I don't really know it yet) and if all fields have something in them, I then create a new instance of UserDetails and pass it to the signup method of auth. If it succeeds, awesome. I then log the user in and then send them to the home page.\nIf it fails, I look at the nice errors and create English versions of them to present to the user. Notice too I'm using the Ionic loading and alert controls as well. Also make note of this.navCtrl.setRoot. I use that instead of push because I do not want to provide a link back to the login page. Basically I'm saying, &quot;This is your new home. Love it. Make peace with it. Share a good beer...&quot;\nNow take a look at login. For the most part it follows the same idea - check the form, login, then handle the result. But oddly, errors with login throw exceptions and not &quot;pretty&quot; errors with simple codes like registration. I'm sure there is a good reason for that, but I wish the docs had made it more clear. I literally had copied the error handling code from doRegister with the expectation that only the possible results would change and that wasn't so. For example, a bad email address throws UNPROCESSABLE ENTITY, which, ok, I guess means something, but it isn't terribly obvious. A failed login just returns UNAUTHORIZED, which is a bit more sensible perhaps.\nThe final part of this demo is the home page. All I do is print out the user's name and provide a logout button.\n\nHere's the HTML:\n\nNothing too special there, although I should point out that the name value of a user is optional. And here is the code behind the view:\n\nLiterally the only thing interesting here is logout and frankly, that's not terribly interesting either.\nBut all in all - I now have a complete Auth demo that actually handles the views and such. I built this just so I could write another blog post but that will be for next week. As always, I hope this helps. I've been kind of avoiding blogging much on Ionic 2 with it changing so much over the year, but with today's release of RC2, it's time to get back on the Ionic Love Train again.\n\nWhat I imagine the \"Ionic Love Tra",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Building a Simple Form Handler Service in Node",
		"date":"Mon Oct 31 2016 17:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1477933380,
		"url":"https://www.raymondcamden.com/2016/10/31/building-a-simple-form-handler-service-in-node",
		"content":"From time to time, I'll re-build an app or service I think is cool just to give me a bit more practice in the language I'm learning. Today, I decided to build a simple version of the form handling service from Formspree. Just to be clear, what I'm sharing is not meant to be as good as Formspree. I'm a huge fan of their service (my contact form uses it!) and I strongly recommend it. What follows is just a simple Node app having some of the same features.\nThe app I'm building is a general &quot;forms processor&quot; meant to be used by a static site. The idea is to take a form, point it to the service, and it will handle taking the form input and emailing to to you. That's it. These types of services are great for static sites that don't have a way to process form input.\nI began my little application by installing Express and defining a static directory pointing to my public folder. Here's that snippet of code.\n\nI then added a page in public called index.html. It basicaly just says &quot;hello world&quot;, but in a real world version of this service I'd have text explaining how to use the service. See Formspree for an example.\nSo far so good. Now to begin the real meat - listening for form posts. Formspree uses a system where you simply set the action of your form to //formspree.io/YOUREMAIL. In Express, I know I could use a regex to define a route, so for the heck of it, I tried an email regex I fund from Stackoverflow:\n\nWhile this was a bit insane looking, it did load just fine, but I was never able to get it to match a request. My attempts to hit /foo@foo.com simply never matched the route. I decided to instead ose a simpler version:\n\nWhile certainly not perfect, it got the job done. I extracted the email by looking at the URL:\n\nAlrighty then. So I knew how to get form data (load in body-parser and just look at req.body), but now I needed to handle emailing.\nI decided to use the simple Nodemailer package. I've used it in the past and for the most part, it just plain works. To make things even easier though I decided to not try to setup a real email account for this. Instead I used the simple Maildev program that I've recommended in the past.  It lets you set up a simple SMTP server locally and even runs a nice little web server so you can see the emails being sent.\nWith that out of the way, I got to work. In theory, all I need to do is create a string of the form values and then email it. But I wanted to emulate one of Formspree's features where certain form fields give you a bit more control over the processing. My service would look for these form fields:\n\n_from - Used to set the from part of the email.\n_subject - Used to set a specific subject (defaults to &quot;Form Submission&quot;).\n_next - The URL to load after a form submission (defaults to a &quot;thank you&quot; page on my server).\n\nHere's the entirety of the route.\n\nTo test this, I used the excellent Postman app and just created a few tests.\n\nAnd then I confirmed the emails using the web interface from Maildev:\n\nAll in all, not too terribly complex, but again, I just built a small subset of what a 'real' service like Formspree supports. The biggest thing missing of course is confirmation of email addresses used when sending the contents. Again, if this seems useful to you, check out Formspree. For the full source code of my demo, see the repo: https://github.com/cfjedimaster/NodeDemos/tree/master/formspree_copy\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Final(ish) Thoughts on the Microsoft Surface (as well as Apple)",
		"date":"Fri Oct 28 2016 16:27:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1477672020,
		"url":"https://www.raymondcamden.com/2016/10/28/finalish-thoughts-on-the-microsoft-surface-as-well-as-apple",
		"content":"Before I begin, a quick note. This is not really my &quot;final&quot; post on the Surface Book or Windows, just the last in the series I started when I got the hardware. (Here's part one, part two, and part three.) Also, this post will be, mostly, my personal feelings about the hardware, Microsoft, Apple, and if you'd rather just stick to my technical articles, I more than understand if you bail now. You've been warned.\n\nShould you buy it?\nStill here? Cool. So the big question is - should you buy a Surface Book? I think if you are a Windows user, then hell yes. It is an amazing piece of hardware. I'm kinda iffy on if I'm ok with the 13 inch form factor, especially since my eyes are kinda crap. I do like the smaller size, but if Microsoft were to offer a 15 inch version I'd probably switch to that.\nIf you're a Mac user, or like me, someone who switched to Mac years ago, then I think you need to seriously consider it. I'm not going to say you will be more (or less) productive. The hardware is easily as good as the MacBook Pro, more versatile, and just generally more impressive. Obviously that's a personal opinion. Do like I did though - the next time you see a Microsoft store, or if you're at a conference and they have a booth with the hardware, spend five minutes with it. Windows has it's faults of course. Just like OSX. But if you left the Windows ecosystem in the past I think you really owe it to yourself to check it out and give it a fair shake.\nSo yeah, that's what I think. But let's go a bit deeper, eh?\nWindows and the Evil Empire\nUnlike some folks, I never had a problem with Windows or Microsoft. @DHH wrote a good piece yesterday entitled, &quot;Microsoft, I forgive you!&quot;, and while obviously a bit tongue in cheeck, I never really understood all the hate MS seemed to generate. Yeah they absolutely did things I don't agree with, but so does IBM. So does every company I've worked with. Hell, so do my kids. If I held out working for or consuming products from companies that were perfect then I might as well become a monk and live in a cave. I live in the real world and I'm fine making compromises to get crap done.\nI take the same view on open source. I like open source. I think it is a great thing. I think, all other things being equal, an open source project is probably better than a closed source one. But I've got no objections paying money for a closed source product that lets me do my job better. If you want to get political about it, that's fine, but I'd like to get my job done and maybe have some fun in the meantime.\nI went to the Mac because they had a superior machine. Their OS felt a bit... constrained and dumbed down, but I could get behind a simpler UI. I look at this darn thing probably half the day, so simple can be a benefit, not a problem.\nBut something happened over the past few years. More and more Apple has been put their energy into the mobile platform. They've put more of their energy into regular consumers. All of that makes sense and I certainly understand that they are following the money.\nBut as a developer, I'm feeling more and more like I'm no longer even a tiny part of their attention. We all know Apple doesn't go to conferences or have evangelists that participate in Twitter. That's changed a tiny bit, but it's just not something Apple does. They don't need to, I guess. If you attend a conference, the sheer number of MBPs over Windows machines is overwhelming. They &quot;won&quot;, at least in terms of developer mindshare.\nBut here's the thing - and it's something that really bugs me. Apple acts as if they have the developer mindshare and don't need to do crap to keep it. It's hubris. And yes, I think Microsoft had the same hubris, but look at what Microsoft has done lately:\n\nLaunched their own phone platform. And yes, it failed. But the UI was innovative.\nLaunched Visual Studio Code, which is easily the best editor for coders out there. Open source, and running on every platform.\nAdded Bash to Windows. Yes, it has issues (see my last post), but they are already working to correct that. (One of the issues I raised is already fixed. The 'deal breaker' for me, so I'll be switching to Bash soon.)\nCreated the Surface line. The regular Surface is cool. The Pro is cool. And the Book is awesome. And my god... the Surface Studio is one of the most stunning pieces of hardware I've ever seen. If you missed it, watch the video:\n\n\nDamn. Like, daaaaaaaamn.\n\nAnd speaking of open source, they've also done some incredible contributions to Apache Cordova as well. ALong with probably a crap ton of other things I'm forgetting.\n\nSo yeah, maybe the reason they are trying so hard is that they know they need to catch up a bit, but I'm absolutely loving seeing what they are doing. Some things are going to flop (again, see the phone), but I fully expect a company trying new things to screw up from time to time.\nOn the flip side - we've got a new MacBook Pro that adds a thin strip of touchability y",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Article: Building Node-based APIs with the LoopBack Framework",
		"date":"Wed Oct 26 2016 16:14:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1477498440,
		"url":"https://www.raymondcamden.com/2016/10/26/article-building-node-based-apis-with-the-loopback-framework",
		"content":"Just a quick note to let readers know I've published another article over on the Telerik Developer Network - &quot;Building Node-based APIs with the LoopBack Framework&quot;. As you can tell by the title, this is just an intro to LoopBack, something I've covered multiple times on this blog, but what you may find interesting is that I built a NativeScript front-end to the LoopBack back end. It isn't anything necessarily special, and that's due to LoopBack being so simple to use, but you may find it interesting.\n",
		"tags":[
	        
            "loopback"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Ionic Example: Slides",
		"date":"Mon Oct 24 2016 16:33:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1477326780,
		"url":"https://www.raymondcamden.com/2016/10/24/ionic-example-slides",
		"content":"A little over a year ago I wrote a post (&quot;Ionic Example: ion-slide-box&quot;) that demonstrated how to use the ion-slide-box component with Ionic 1. A few days ago a reader asked if I could update that post to work with Ionic 2. I've done that and am now going to share some of the code from the updated version.\nIn case you don't feel like reading the previous entry, the demo was rather simple. It made use of the Bing Image Search API to fetch pictures based on your input. For context, here's an example of how it looked for Ionic 1 and iOS:\n\nI'm beginning to think it is physically impossible for me to do a demo that doesn't involve cats. Anyway, let's talk about the Ionic 2 version. First, the view:\n\nIn form, it's similar to the V1 version of app, but while V1 still had a mix of HTML and Ionic components, this is nearly 100% Ionic tag-based. The only non-Ionic component there is the button tag and even it uses an argument to flag it as being Ionic-controlled anyway. If you're still new to Angular 2 (like me!), you should pay special attention to the new syntax used for event handling: (click)=&quot;doSearch()&quot; and two way binding: [(ngModel)]=&quot;search&quot;.  Another tweak is to iteration. While V1 had ng-repeat, I'm using *ngFor in V2. All in all, the view here is simpler than my previous version. (But to be clear, the previous version did everything in one HTML file as it was so simple. I could have seperated out the view into its own file.) Now let's take a look at the code. First, the code for the view:\n\nThere isn't anything too scary here. I've got one method, doSearch, that simply fires off a call to my provider for Bing searches. The only real interesting part is mySlideOptions. If you want to tweak how the slide works, you can't just pass arguments via the component tag. That kinda sucks in my opinion, but I'm guessing there is a good reason for that. I had tried adding pager=&quot;true&quot; to my ion-slides tag, but that didn't work. I had to create a variable and then bind to it from the view. Again, that bugs me. I can get over it though.\nThe provider is now all wrapped up in fancy Oberservables and crap which frankly still confuse the hell out of me. But I got it working. The hardest thing was figuring out how to do headers.\n\nAnd yep, that's my Bing key. I'll probably regret sharing it. So how does it look? Here's an iOS example:\n\nRight away you'll notice the pager isn't actually there. I'm not sure why that is failing to show up because it does show up when running ionic serve. I could definitely try to size that image a bit nicer in the view, but for a quick demo, I got over it.\nA few minutes later...\nSo I was wrapping up this blog post when I started chatting about it over on the Ionic Slack channel. Mike, and others, helped me discover a few things.\nFirst, you know how I complained about having to create a variable in my JS code just to handle a simple option? Turns out you can do it all in the view - thanks Mike:\n\nI added that and then removed the code from home.ts, making it even simpler. But I still had the bug with the pager not showing up. It clearly worked in ionic serve, see?\n\nSo wtf, right? Then I remembered - Chrome DevTools has a Responsive Mode. What happens when we turn that on?\n\nBoom. Right away I see the same bug... and I notice the scrollbar. I scroll down and see...\n\nYep, my slides portion is just too big. On my iOS simulator I scrolled down and confirmed. Sigh. So (and again, with help from smart folks on the Slack channel!), I ended up styling the ion-slides components:\n\nI use a set max of 400px, which isn't terribly cross platform compatible, but it helped:\n\nPerfect! Except this is what you see on initial load:\n\nUgh. So I tried going back to having options defined in JavaScript and simply changing it when data was loaded, but that didn't work. I then tried getting a pointer to the slider object and updating it that way. It also didn't work.\nUgh again. So I went back to a simple inline option declaration but also hid the entire slider:\n\nI then modified my code to default haveData:\n\nAnd now it works perfectly! To be fair, this still needs a nice &quot;loading&quot; widget UI when searching, but as I mainly wanted to focus on the slides, I figure I should try to keep this simple. The full source code for this demo may be found here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicslidebox2\nLet me know if you have any questions or comments below.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using HTML Form Validation in Pure JavaScript",
		"date":"Wed Oct 19 2016 15:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1476889860,
		"url":"https://www.raymondcamden.com/2016/10/19/using-html-form-validation-in-pure-javascript",
		"content":"I'm a big fan of HTML form validation because it replaces JavaScript code I've been writing for nearly twenty years. Unfortunately it still isn't supported in Safari (don't get me started on Apple and their priorities when it comes to the web), but I love the idea of being able to do stuff in HTML itself. In fact, a site recently launched that demonstrates many examples of this: You Might Not Need JavaScript. If you haven't checked it out, definitely give it a read. It is a great example of how HTML, and CSS, can replace JavaScript snippets we've used over the years.\n\nSo given all of that, this morning I thought of something that was exactly the opposite of moving from JavaScript to HTML. Can we use HTML-based form validation in a purely JavaScript form? Turns out you can (again, if the browser itself supports it). Given that you can dynamically create DOM items via createElement, could you use that to add easy email validation in JavaScript? Looks like you can:\n\nAll this snippet does is create a virtual input element, set the type to email, and then sets a particular value. It then simply outputs the valid state. You can rewrite this into a simple function:\n\nI don't like the fact that I set a global variable, but I thought it was a nice way to cache the DOM element. If this were wrapped up in a module then I'd store it local to that.\nSo again, it won't work in non-supported browsers, but it sure as heck is pretty simple, right? You could also use this to validate a URL and other values. Any comments on this approach?\nYou can run a JSBin of it here: https://jsbin.com/diqepa/edit?js,console\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Quick DevTools Tip - what is still trying to load?",
		"date":"Mon Oct 17 2016 21:46:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1476740760,
		"url":"https://www.raymondcamden.com/2016/10/17/quick-devtools-tip-what-is-still-trying-to-load",
		"content":"Have you ever loaded a web page, seen that main content render, and then notice that the loading widget seems to rotate (pulsate, etc, every browser is different) for eternity? Obviously there is some network request that is pending, but how do you figure out what that is, especially with a large set of requests to dig through?\n\nIn Chrome, there is a quick way to see this. In your devtools, first ensure the filter field is visible:\n\nThen type is:running in the form field. Here is an example from GMail:\n\nIn case you didn't know it, that filter field supports both name matches as well as property-type searches like the example above. The field will nicely offer suggestions for these properties as you type, but you can find a complete list here:\nFilter requests\nYou should scan that page a bit as I found two interesting tidbits. On Mac/Unix (not sure why not Windows), you can add multiple filters. Also, you can filter by requests larger than a set value. That's a great way to check for fat requests.\nI looked for something similar in Firefox, Edge, and Safari, but didn't see anything that would support the same result.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "The Microsoft Surface Book - Part Three",
		"date":"Sun Oct 16 2016 21:02:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1476651720,
		"url":"https://www.raymondcamden.com/2016/10/16/the-microsoft-surface-book-part-three",
		"content":"Welcome to my third post on my experience with the Microsoft Surface Book and Windows 10. As a reminder, this unit was given to me by Microsoft so I could test and share my experiences. You can (and should) read part one and part two. I began this series talking about the hardware itself. My last post was on Windows. Now I'm going to try to describe how the SB (and Windows of course) performs as a developer machine.\n\nRegular readers of my blog already know where I spend most of my time, but I think it will be helpful to give new readers some context as to what I actually do. (And no, it doesn't involve finding cute pictures of cats. Mostly.) In general, most of my time is spent:\n\nBuilding Node.js apps with a special focus on LoopBack for building APIs. That's my main day to day job at IBM.\nBuilding hybrid mobile applications with Apache Cordova and usually Ionic. I'm also doing stuff with NativeScript.\nBuilding/exploring/testing/etc the web. Yeah, that's kinda vague. Basically, as someone who lives and breathes on the web, I love to test out HTML, JavaScript, and even sometimes CSS stuff. I build a heck of a lot different demos and proof of concept apps. My main purpose for these is to help learn things for myself but I also turn most of those experiments into blog posts.\nWhile I don't do it very often, I'll still do a bit of ColdFusion development. I have refused to install ColdFusion on this Windows machine as I can get by just fine with Lucee and CommandBox. After near 15 years of pushing ColdFusion I've moved on and recommend other folks to do the same. (But that's for another post.)\n\nOk, so that's what I do. And of course, that's just what I do in front of a computer. My real passion is actually talking to developers about all this and that's where I have the most fun!\nThe Command Prompt\nI think it makes sense to start off with the command prompt since that's where so much of my work starts. In general, I'll use the command prompt to prepare my project and then switch over to my editor to get stuff done. I like OSX's Terminal just fine, especially now that it supports tabs. I &quot;prefer&quot; Unix commands to Windows, always have, but to be honest, it's basically a &quot;If I can choose between the two I'll prefer Unix slightly more.&quot;\nAs much time as I spent in the command prompt, the native commands I use come down to - basically, ls and cd. Oh, and mkdir. I can say - easily - that's 95% of my usage. Of course I know there is a heck of a lot more and I use a few more commands, but honestly, I'm mainly going around the file system and that's it. I've got an Evernote entry for a few OSX commands I find useful but can't remember as I don't use them often enough.\nSo landing in Windows I found myself forgetting to use dir a few times and then got over it. In terms of the environment, I'm meh. It's not better or worse, it just plain works and I'm fine with that. I did play a bit with PowerShell where you can run ls just fine. I know PowerShell does a heck of a lot more, but again, I'll never use it.\nThe one thing missing that bugs me is the lack of tabs. I'm trying cmder right now, and I like how it looks (and it supports tabs), but it is a bit annoying. Sometimes the ls command takes 2-3 seconds to run, which isn't terribly long, but you sure as hell notice it. Also, when I open a new tab, it doesn't default to the same directory, which is what I want 95% of the time. You can try to tell it to use the same directory, but it always ignores me. (Turns out this is a known issue.) I'm close to returning back to PowerShell just for that problem alone.\nOf course, you may be wondering - what about that new Bash shell? Yes, it works. See:\n\nIt works. But... I don't know. The first thing you discover is that this isn't just a shell, but a miniature VM. (Virtual Machine isn't the right term actually, it is some kind of magical 'layer' on the Windows OS.) Ok, no big deal, it runs hella fast, but that means it can't (currently) run Windows programs. So not a big deal. You can install Node (this is a great post on that: Installing basic Node.js dev env on Windows 10 bash) and then npm anything you need. But this will be separate from your Windows OS so if you don't dedicate yourself to using Bash you have to install stuff twice. Node stuff is generally smallish, but I was concerned about the Android SDK as I know the VMs can be large. (More on that later.)\nHowever, what really stopped me was when I realized I couldn't run my editors from Bash. As I said above, I use the command line to start up new projects, fire off Node crap, run Ionic, etc, but do most of my work in my editor. I'll use code . or subl . to fire up my editor from my current working directory. I do that a lot. This was enough to stop me from using Bash. It seems petty, but it was enough. The good news is that this is actually one of the most requested features and if I had to bet, I think we'll see it added soon. At that point I may return to Bas",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with SOAP in a Node App",
		"date":"Wed Oct 12 2016 21:56:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1476309360,
		"url":"https://www.raymondcamden.com/2016/10/12/working-with-soap-in-a-node-app",
		"content":"I've been lucky to have been able to avoid SOAP for a few years now, but today there was an API I wanted to work with that had this right on top of their documentation:\n\n\nThe API is implemented as an XML SOAP web service. Please read the the WSDL. There are no plans at present to make it available via JSON.\n\nMy reaction was, of course, quite reasonable.\n\nSo once I got over that initial reaction, I thought, &quot;I wonder how folks in Node-land work with SOAP?&quot; I did a bit of research and found a great solution, node-soap.\nnode-soap lets you easily make SOAP calls to web services as well as setup your own SOAP service if you need to. And let's be honest, that's the only reason you would set one up because, my god... SOAP. (Sorry, I promise not to rag on SOAP and WSDL anymore. Not a lot anyway.)\nThe API in question was one for the site, Brickset. Brickset is a nice little site that contains a huge amount of information on Lego brick sets. It's got a huge product directory as well as an events calendar and forum. I've built a few Lego sets in my life, but I'm thinking about getting a bit more involved next year so this looks to be a great resource.\n\nOn top of that, they have an API! A SOAP API, but no one's perfect, right? I thought it might be fun to build a little app to randomly select a Lego set from their database. I've done &quot;select a random X from the db&quot; thing a few times now (see &quot;Building a Twitter bot to display random comic book covers&quot; as an example) and I don't know but the idea just interests me. I'll sit there and just reload and just... explore.\nSo with that in mind, I began writing. I create a simple web page that would make an Ajax call to my local Node server. I'd have a route that would handle finding the random set and then returning that JSON to the front-end code. I'm assuming most of the front-end code and route stuff isn't interesting, so I'll just share the module I wrote to work Brickset's API. It shows you how nice node-soap was to work with.\n\nTo handle selecting a random set, I used the following logic. First, I searched a bit on Brickset to determine what was the earliest year of data. While I found one year in the 40s with one result, 1950 was the first year to have multiple results. So selecting the year is simply a random selection between 1950 and the current year.\nAs far as I could tell, their API allowed you to select as many items as you want in your call, so I used a pageSize value of 2000 to grab everything. Then it was simply a matter of selecting a random set.\nThe SOAP client is created with soap.createClient, and note that all I have to do is pass in the WSDL. WSDL is the &quot;descriptor&quot; for SOAP services. Imagine asking someone to write up documentation for an API, then throw that away and select 20000 random words from the dictionary. That's WSDL.\nActually calling the API is done via client.X, where X is the specific method. In this case I just called getSets. I noticed that if I didn't pass every argument, the result was null. I don't know if that's a SOAP thing or a particular issue with the API, but that's why that args object has a bunch of empty values.\nThe API also supports returning additional images, so you can see I call that too, but I never ended up using it in the front end. Maybe next time.\nAnd that's it. Here's a random example:\n\nAnd another:\n\nI don't know why, but I just love seeing those old sets. Anyway, you can give it a whirl yourself if you want, although I've been having some issues with Bluemix today so forgive me if it isn't up and running.\nhttp://randombrickset.mybluemix.net/\nAnd as I said, if you want to see the rest of the code, just let me know!\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "I've switched RaymondCamden.com to Netlify",
		"date":"Mon Oct 10 2016 15:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1476111960,
		"url":"https://www.raymondcamden.com/2016/10/10/ive-switched-raymondcamdencom-to-netlify",
		"content":"The title pretty much says it all. Since switching from WordPress early in 2015, I've been using Surge as my web host for raymondcamden.com. Outside of a few hiccups, this has worked well. (And even when their were hiccups, it wasn't on me to fix!) I want to thank the folks at Surge for their consistent support and for providing a cool as hell service.\nThat being said, I've decided to move to Netlify. I was introduced to them a few months ago and decided to include them in the book I'm co-authoring on static site generators. The more I dug into them, the more impressed I was. Here is a short list of some of the features you get with Netlify:\n\nLots of performance features that includes a global CDN, built-in DNS, atomic deployments, and this is slickest of all - they can automatically compress your images, JavaScript, and CSS files. Yes that's stuff you can do yourself with a good Grunt/Gulp script, but I love that Netlify does it built-in.\nThe ability to build your site from git repos and even support different subsites based on a branch. So you could use a &quot;staging&quot; branch to support a stating version of your site.\nFree SSL. Automatic SSL.\nNotifications for builds and other events.\nForm handling. I'm not using that feature because right now it isn't as robust as Formspree, but once they add a few features, I'll switch.\nDNS management on the site itself. So when I moved my site over, I also choose to let them handle my DNS, which means I can also handle stuff like foo.raymondcamden.com at Netlify and skip going to GoDaddy.\n\nThere's more, and I go a bit deeper in the book, but you can read for yourself on their features page. You can find pricing information as well. For folks curious, I'm on the &quot;Pro&quot; level but I've been granted a free open source license.\nSo how am I using Netlify? I've set up my site such that it is connected to my GitHub repo at https://github.com/cfjedimaster/raymondcamdendotcom. You can integrate with a Git repo two ways - either to just use the files as is - or to have Netlify actually build it for you. So basically - I write my post (like this one), update my Git repo, and in two to three minutes, my update is live. It was taking about ten minutes for Surge so that's a great speed improvement.\nI did have to tweak my layout a bit. Previously my layout included a right hand bar of recent posts, categories, and tags. This was dynamic based on my content. I've switched this to an empty div that is loaded in via Ajax. So now when I write a new post, the only files updated are that sidebar file, the home page, and a few pages for the minimal pagination I support.\nIf anyone has any questions about my setup, just leave me a comment below!\nI want to thank Netlify again for helping sponsor this blog with a free license and for all their support the last few days as I migrated.\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "I've fallen in love with The Flash",
		"date":"Sat Oct 08 2016 20:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1475958240,
		"url":"https://www.raymondcamden.com/2016/10/08/ive-fallen-in-love-with-the-flash",
		"content":"I want to talk a bit about &quot;The Flash&quot;, but before I do so, let me talk about something completely different - &quot;Green Lantern&quot;. I'm talking about the 2011 movie with Ryan Renolds. Yes - that one...\n\nPretty much everyone disliked the movie, but I enjoyed it. And the primary reason I enjoyed it is that it felt like the first (in a while anyway) comic book movie that actually felt like a comic book. Now don't get me wrong - Marvel has been kicking ass for a while now and their movies are incredible. But they are also pretty realistic (ignoring the whole giant floating aircraft carrier thing). I can remember sitting in the theater and when GL started, it was all outer space and freaky aliens and it felt like I was watching a comic book. I didn't get that feeling from the Marvel movies. (Since then, of course, we've gotten &quot;Guardians of the Galaxy&quot; which is pretty much in the same vein I think.)\nSo it's in that context that I want to show what is - to me - what of the shining moments of &quot;The Flash&quot;:\n\nYes. That's a giant man shark. Here's another pic to give you scale.\n\nWhen I first saw this on screen, I laughed out loud. It was so over the top. So stupid. So ridiculous. Yet so incredibly fun. I loved it. I mean, I just loved it.\n \nI started watching &quot;The Flash&quot; about a month ago. In some regards, it's typical light-weight TV superhero stuff. You've got a young cast of unusually good looking people. You've got a &quot;monster of the week&quot; type thing much like the old &quot;Smallville&quot;. As I said, it's light.\nBut on the other hand, you've got story arcs that extend over multiple episodes. You've got things brought up from much earlier in the season that rewards dedicated viewers. And like shown above, you've got villains who aren't ashamed to be a bit over the top. A bit... comic-booky... and I can appreciate that.\nMaybe it's a reaction from how much I disliked &quot;Batman vs Superman&quot;. I found myself liking &quot;Supergirl&quot; more after seeing it too. I just found myself having fun watching a show that didn't seem to be afraid to laugh a little and actually have fun. I feel like this could be something DC could run with. Let Marvel be the more 'serious' comic book movie company and instead go for a more silly, over the top vibe. &quot;Suicide Squad&quot; was enjoyable (and again, I'm in the minority of people who liked it), but it had moments of ridiculousness\nthat really lightened things up. I don't really have much faith they can do so - although I will go see &quot;Justice League&quot; and &quot;Wonder Woman&quot; with some hope of getting a decent film.\nTo be clear, &quot;The Flash&quot; can get dark too. But it doesn't feel cheap, or gratuitous, like &quot;Batman vs Superman&quot; did. Maybe it's not fair since &quot;The Flash&quot; had hours to get me to care about it's characters, but in the end, it was DC's choice to rush to pitting two of it's favorite characters against it each other. I think &quot;Batman vs Superman&quot; could have worked, in a few years, much like how Marvel's &quot;Civil War&quot; only worked because we've had enough time to really grow to know Iron Man and Captain America and understand why they could be in conflict.\nAnother aspect that really made me fall for the sure was the use of adoption. The main character (Barry Allen) is taken in by a police officer who raises him as a son. The show kind of steps around the issue of whether or not it was a formal adoption or just foster, but that doesn't matter. Barry considers himself to have two fathers and is proud to say so. (As an aside, it occurs to me there's multiple examples of this in DC. Superman, of course, is another one. And Bruce Wayne was raised by Alfred after his parent's death.)\n&quot;The Flash&quot; just began it's third season and you can watch the first two seasons on Netflix now. In the end, I think the greatest praise I can give it is analogous to how Marvel has succeeded. Before &quot;Iron Man&quot;, I didn't really care about the character. After the movie came out, I was a fan. (And a consumer - I bought multiple TPBs.) I did the same for &quot;Daredevil&quot; as well.\nI can say the same about the Flash. Outside of Batman and Superman, I didn't really think much about the other characters, but now I want to read more about the Flash.  (In fact, I've added a few to my wishlist if anyone is feeling generous. ;) Definitely check it out. And before folks ask, no, I haven't watched &quot;Arrow&quot; yet. I'm going to check it out after I run through a few more Netflix series. (I need to finish &quot;Luke Cage&quot; and the second season of &quot;Narcos&quot;.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "television"
            
		]

	},

	{
		"title": "NativeScript 2.3 Launch Webinar",
		"date":"Thu Oct 06 2016 22:13:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1475791980,
		"url":"https://www.raymondcamden.com/2016/10/06/nativescript-23-launch-webinar",
		"content":"Next week (October 13 at 10AM CST) I'll be on a panel with TJ VanToll and Burke Holland of Progress to talk about the latest release of NativeScript. Mainly this will be them showing stuff off and me commenting and asking questions. As folks know, I'm primarily a Cordova person, but I've been playing with NativeScript and I'm really interested in what it has to offer. This is online, and free, but space is limited to sign up now:\n\nNativeScript 2.3\nLaunch Webinar\n",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "TIL - Form fields and invalid values",
		"date":"Tue Oct 04 2016 17:23:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1475601780,
		"url":"https://www.raymondcamden.com/2016/10/04/til-form-fields-and-invalid-values",
		"content":"For years now I've said the single biggest improvement to the web has been the various updates done to form fields. I'm probably biased, but as I started my web development career building form handlers in Perl, any changes to forms seem like a real, real good use of development time on the part of browser vendors. I wish it would have happened a good ten years ago or so, but beggars can't be choosers, right?\nEarlier this week I ran into something that makes total and complete sense, but still surprised me. Consider the following HTML:\n\nWhat will this render?\nIf you guessed a form field with nothing in it, you would be correct. I mean - think about it - you've said the input field is for numbers and you specified a non-number field, so obviously the field is incorrect. But would you expect it to still show the string since you've said that's the value? Maybe you want an invalid value to force the user to change it. Either way, this still surprised me.\nI ran into this because I'm working on a site using server-side technology with values loaded from a database. Initially the fields were a bit loose. As the project went on, we tightened things up a bit and worked on the front end, and one of the things I did was add better input types where appropriate. What I wasn't considering was that older data wouldn't show up.\nSo given this, I decided to try a few different field types to see how browsers handled the invalid fields. Here's what I used:\n\nAs you can see, I'm testing color, date, email, number, range (twice), tel, and URL. All have invalid values - so what happened? (Try to guess before you read on...)\nI tested with the latest Edge, Firefox, Chrome, and Safari browsers. Here's the results - field by field.\nColor\nFor Edge, Firefox and Chrome, they rendered a color picker with black selected. Safari doesn't support color because Apple hates developers. (Ok, I kid!)\nDate\nFor Edge and Chrome, a date field with no date selected was rendered. Safari and Firefox do not support the date control.\nEmail\nEvery browser rendered the email field as is. Firefox rendered an error state when I clicked into the field and added a space. Every browser but Safari prevented me from submitting with the invalid value.\nNumber\nEvery browser rendered the field blank.\nRange\nEvery browser rendered the range such that when I went too low, it selected the lowest value, and when I went too high, it selected the highest value.\nTelephone\nEvery browser rendered the field as is. No validation was done on the value.\nURL\nEvery browser rendered it as is. As before, only Firefox flagged the field as invalid on modification.\nIf you're curious, here is a screen shot of Edge, Chrome, and Firefox rendering the fields on my Win10 machine. Edge really has a unique look to the range fields. I don't know if I like that or not.\n",
		"tags":[
	        
            "html5"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "The Microsoft Surface Book - Part Two",
		"date":"Mon Oct 03 2016 16:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1475510940,
		"url":"https://www.raymondcamden.com/2016/10/03/the-microsoft-surface-book-part-two",
		"content":"Last week I wrote up my first thoughts on the Surface Book (The Microsoft Surface Book - Part One) and today I'm going to share more of my thoughts - this time more focused on Windows. I suppose I should change the title of the blog post as this is less about the hardware and more about the OS, but as I'm exploring all of this on the SB, I figure I'd keep up the theme at least. Also, I'll be talking a bit about hardware as well (external hardware) so it won't be all software based. Note - I'm going to avoid talking about anything really developer related until my next post.\nAs I said in the first entry, Microsoft was gracious enough to let me borrow this hardware so a big thank you to them!\n\nBefore I start talking about Windows, I'd like to do what I do previously and give a bit of context to my experience with Windows and how I felt about it before I left a few years ago. My first computer was an Apple 2, but my main computer in college and for years after were various different PCs. I was a huge Gateway fan (anyone else remember the cow boxes?) and would generally upgrade my PC every year and a half or so. (I justified the expense by using it as a tax write off.)\nMy last primary Windows OS was Windows 7 and it was fine. Not great, not awe inspiring, not without warts, but it worked and I got crap done and as I spend a heck of a lot of time behind a computer, that's all I really want. I'm hard pressed to really come up with any real issue with Windows that truly bothered me. I know sometimes I had to fight to get the right driver for something installed, but that was maybe once or twice a year. Yes, I had BSODs, but I've had that since day one with every machine I've owned. (On the Mac I'll get gray screens from time to time.)\nFor the most part my move to OSX was just to try something new. In some ways OSX felt like a &quot;dumbed down&quot; OS, but I kinda dug the simplicity of it. Over the past few years it's stayed the same and that's fine too. Things got a bit shinier. But I'd be hard pressed to really tell you how different my current macOS is compared to the OSX I got five of so years ago.\nIn the meantime, apparently a lot was happening with Windows, and a lot of people didn't like it. Apparently the Start Menu morphed into some giant overlay that stood in your way and hid the desktop. I can totally see how that would tick people off. But I guess I missed all of that by going from 7 to 10. I'll talk a bit more about the Start menu in a second, but the point is, going from 7 to 10 doesn't feel like a huge change to me. Everything is where it used to be and the UI isn't radically different, but what has changed seems to be an improvement so far.\nStarting up\nOne of the first things I've fallen in love with is the login UI. Called &quot;Hello&quot;, the new login screen allows for a password, PIN, and the real kicker, facial recognition.\n\nBasically you sit in front of your machine, look at it, and you login. I cannot stress how nice this is. I'm maybe 50/50 now between my MBP and my SB, and my God, every time I type in a password on my MBP I get a little bit annoyed. Of course, the latest version of macOS supports this... if you spend a couple hundred dollars on Apple's watch. Lord forbid they allow me to use the multi-hundred dollar iPhone 7 I have with me. No - that would be crazy. I need not one - but two Apple devices to login quickly. No thank you. I don't even use the facial recognition feature 100% of the time with Windows (due tu my external monitor) but I can't believe Apple didn't reconsider requiring the Watch for login.\nApps\nOne of the first things I did with Windows was to grab all the programs I use on OSX and see how well they worked. Again, I'm holding off on discussing developer stuff (and that new Bash shell) till my next entry, but in general, but here's a quick list of what I installed and noticeable differences/issues:\n\nThe very first thing I went for was Dropbox. I'm a longtime Dropbox user and I pay for the 1TB storage level. It's begun to act up on OSX but I figured that was just temporary. Unfortunately, it's not running well at all Windows either. Maybe I hit some threshold of files, but I can't get it to work properly on OSX or Windows now. Specifically with &quot;Selective Sync&quot;. I use Dropbox's Camera backup feature and I don't need those camera pictures on my laptop. Right now I've had this window for a good 15 minutes:\n\n\nSo I did have a huge amount of crap under Dropbox and I've probably removed 50% of it, but at this point Dropbox is pretty much dead to me. My subscription ends in December and I plan on killing it then.\nOn the flip side, I'm a subscriber to Office 365 and OneDrive gives you a 1TB as well, so my plan is to move my storage there. Of course, OneDrive decided to have service issues the day I tried to move, so it's been a slow process, but that's the general plan.\nIn the end, the biggest issue with both OneDrive and Dropbox is that they don't allow you to ",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Some quick NativeScript tips",
		"date":"Wed Sep 28 2016 20:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1475093400,
		"url":"https://www.raymondcamden.com/2016/09/28/some-quick-nativescript-tips",
		"content":"After attending (and rather enjoying) NativeScript Developer Day last week, I'm trying to get back up to speed with NativeScript development. I've got some demos planned for integration with IBM services that I think will be pretty interesting, but I'm a bit rusty so I'm going through the docs again before I leap into making a new app. I ran into a few things today that are 100% documented, well known, etc, but still tripped me up, so I figured I'd document it purely for my own sake. All of the stuff below I figured out by first screwing it up and then getting help from Brad Martin. Thanks Brad!\n\nDon't work in the platforms folder\nYeah, so this was especially dumb, but while debugging an issue I was having with Android, I tried modifying some code under the platforms folder even though I knew the CLI was going to blow it away. Cordova does the same thing. Stay out of platforms.\nDisabling TypeScript checking (maybe!)\nWhen I first played with NativeScript, I went the NS+JavaScript route. This time I did the TypeScript and Angular 2 route, which I strongly recommend. In general it worked fine, but I ran into some code that had issues with TypeScript, essentially it was using something that wasn't defined or imported. I knew it was going to work in runtime, but VS Code kept complaining and the CLI itself would prevent builds from happening since TypeScript was erroring out.\nWhile this doesn't smell like the best fix, you can tell the CLI to skip over TypeScript errors by editing your tsconfig.json file and seeing noEmitOnError to false. That seems a bit weird. My only guess is that &quot;emit&quot; in this context means to say the error and don't actually throw it. It's true by default, so set it to false. You'll still see the errors in your console, but they won't stop your build.\nAgain, this feels like a bad solution, but it got me around a bug in my project.\nLiveSync does not create new builds\nOk, this one was a bit subtle. The docs tell you that livesync is appropriate for view/JS type changes, but not 'deeper' stuff like plugins and the like. That makes sense. However, my thought was that when you ran livesync, it initially did a new build. What I mean is - imagine I've got livesync running and realize I need a new plugin. I kill livesync, add the plugin, and then run livesync again. In my mind, the first run in that session would do a new build. It does not. So for me, I got used to doing tns build android when I did, well, 'deep' stuff.\nMessing with permissions? Uninstall first\nThis would apply to Cordova as well. If you are doing anything with permissions, you want to ensure you uninstall the app from the device/simulator after you've made that change. That was a big blocker for me until I figured it out.\n",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "The Microsoft Surface Book - Part One",
		"date":"Mon Sep 26 2016 19:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1474919520,
		"url":"https://www.raymondcamden.com/2016/09/26/the-microsoft-surface-book-part-one",
		"content":"A Disclaimer: Microsoft provided me with the hardware I'm reviewing. They asked me to share my feelings about it in exchange for letting me borrow it. Also, I'm not a professional photographer, so the product shots I'll be sharing today will be a bit meh. The lovely picture you see above is courtesy of Microsoft.\nWhile my very first computer was an Apple (the venerable Apple 2e), I spent most of my &quot;professional&quot; developer life using Windows machines. I was fine with that. Windows had warts. My systems would tend to slow down and run out of disk space after a year or so. But honestly, I still see that with my Macs as well. Windows was just fine.\nThat being said, I decided to switch to Apple about five or so years ago. My first Macbook Pro was a revelation. Light weight, fast (to be fair, it was also my first SSD system), and the UI was far simpler than Windows. It was great. Heck, it still is... mostly. I've worked on three different MBPs now and for the most part, they've been good machines, but not necessarily interesting. I've sat through release after release, and while OSX (sorry, macOS, right?) works, I'd be hard pressed to tell you anything actually interesting, or helpful, about the last release. Sure, Siri is built in, and, yeah, shrug, I used it once or twice. I honestly can't tell you off the top of my head of anything else new in Sierra. I know there's a lot more, but nothing that's actually impacting my day to day life. I'd love to be able to instantly sign on with my 500 dollar plus iPhone 6+, but apparently I need to buy the multi-hundred dollar watch first because... reasons.\nSo yeah, I'm rambling a bit but I wanted to give a bit of context as to the mindset I've been in for a while now. A few months back I was attending a conference where Microsoft had a booth setup. They were running a contest where you would do something - sign up for Azure I think - and you would get a pretty nice gift. (I got the Microsoft Arc Touch Mouse - which is freaking cool as hell.) In order to do the sign up, they had a row of Surface Books set up for you to use.\nTo say I was impressed would be an understatement. The screen was incredible. The keyboard was great. Everything worked nice and quick and it just felt like a damn good piece of hardware. Obviously I only had it for 5 minutes or so, but I can never remember being that impressed with a Windows machine before.\nFast forward a few months and I began to think more about the Surface Book and perhaps giving it a try as a new development machine. I had a few friends at Microsoft and when they heard this, they hooked me up with the unit I have now. (Again, see the disclaimer on top!) In this post, I'm going to talk specifically about the hardware aspects of the Surface Book. I'll talk more about Windows, and other stuff, in my next post. (In a few days.)\n\nAgain - I apologize for the quality of these pictures. I tried my best - honest.\nOne of the first things I was worried about was the screen size. I've had 15 inch MBPs since my first MBP, and I like that screen factor. It's big, but I like the real estate. I knew the SB was smaller, but at 13.5, it doesn't really look that much smaller, and it feels like less of a weight on your lap than the MBP. (I just checked Google, and apparently the SB is only about a fifth of a pound lighter.)\nHere's a snazzy side picture:\n\nSo one of the first things I had to contend with was how to plug the darn thing in. The power plug is on the right side (you can see it in the picture above) and the connector itself is a bit freaky looking:\n\nYeah - weird, right? But it works just like my magsafe connector on my MBP and I get the impression that it's actually going to be a bit more stable. I love magsafe in general, but my last generation MBP's magsafe connector falls off if someone even looks at it. I'm still new to this hardware, but the design of this feels like it will stick in a bit nicer. (A quick note - what you are seeing here is the connector for the Surface Dock. I'll talk more about that later in the blog entry. The connector for the basic power cable has the same weird teeth-looking part, but is smaller and more flush to the device.)\nAnd here is a picture with the SB sitting next to the MBP, just to give you an idea of the size comparison. (I think the Mac looks a bit offended to be in the same physical space.)\n\nAs I've said a few times now, and the pictures don't do it justice, the screen is beautiful. Like, damn beautiful. And it's a touch screen. That seems unimportant until you actually start using a device that supports it. I find myself using the touch screen to scroll long articles and I like the tactile feel of it. Heck, it's probably slower than using the trackpad, but in some cases I prefer it.\nAnother surprising aspect of it is the quality of sound it generates. When I'm not at a conference (or on stage), I'd say I have music coming out of my laptop nearly 100% of the time, and the SB creates a really ",
		"tags":[
	        
            "windows"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Recording - What they didn't tell you about Cordova",
		"date":"Mon Sep 26 2016 15:24:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1474903440,
		"url":"https://www.raymondcamden.com/2016/09/26/recording-what-they-didnt-tell-you-about-cordova",
		"content":"For those who couldn't make the NCDevCon (which is a shame, it is a great conference!), here is the recording of my presentation on Apache Cordova and &quot;things they didn't tell you.&quot; As I say right away in the beginning, everything I cover are absolutely things that folks talk about now, but it's sometimes missed in the rush to get things done quickly.\nAs always, I love your feedback and questions, just ask in the comment below.\n&quot;What they didn't tell you about Cordova&quot;\nWarning - it requires Adobe Flash.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Chrome Network Emulation and Change Events",
		"date":"Thu Sep 15 2016 17:58:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473962280,
		"url":"https://www.raymondcamden.com/2016/09/15/chrome-network-emulation-and-change-events",
		"content":"So this is more a FYI then anything else, but a while ago, Chrome released support to emulate different network conditions in dev tools. (Oddly, it doesn't seem to be documented in the Chrome DevTools Network help page.) Basically you can select from different speeds and even quickly toggle offline/online. If you've never seen it - here is what it looks like:\n\n\nClicking the dropdown reveals the following options:\n\nSo this worked well, except in one pretty important aspect. If you had JavaScript code that listened for network changes or simply checked for the connection (see documentation here: &quot;Online and offline events&quot;) it would not pick up on changes done via Dev Tools.\nSo for those of us trying to write code to properly handle a web page going offline, that meant you had to test the old fashion way - turn your wifi off. (Which killed everything, including your super important Twitter feed.)\nI filed a bug report for this nearly two years ago: Issue 422956\nYesterday I was doing some testing and discovered that it appears to be fixed. I tested using the simple Gist in my bug report and navigator.onLine correctly responded. Also, my online and offline event handlers ran fine too.\nNow it worries me that the bug hasn't been updated. It's possible that it hasn't been QAed. But for now, it certainly seems a bit safe to use.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Reminder on using Ionic for the Web",
		"date":"Tue Sep 13 2016 22:16:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473804960,
		"url":"https://www.raymondcamden.com/2016/09/13/reminder-on-using-ionic-for-the-web",
		"content":"Earlier today the Ionic folks published an article about Ionic 2 and PWA (Progressive Web Apps) - &quot;Announcing PWA support in Ionic 2&quot;. The gist is that - you guessed it - Ionic 2 apps will support PWAs out the box. You don't have to actually make use of PWA of course, but if you want to build an Ionic 2 app for the web, and not hybrid mobile, then Ionic has done some of the grunt work to get you started down that path. I thought it might be useful to remind folks how you can build an Ionic web apps instead of hybrid mobile apps.\n\nFirst and foremost, if you've been playing with Ionic V2 apps, most likely you've been using the beta version of the Ionic CLI. A few weeks ago the CLI was updated to 2.0 and you should no longer be using the beta version. To be clear, while the Ionic CLI is at version 2, the Ionic framework itself still defaults to version 1 (and Angular 1). To work with V2 apps, you need to pass the --v2 flag when creating your application.\nTo work with a web app, not Cordova, you should add another flag: -w. This tells the CLI to not add platforms and other Cordova-related stuff.\nSo to summarize - you can create your new app like so:\n\nionic start noHybridNoProblem --v2 -w\n\nOnce done, you've good to go. But then you may wonder - how in the heck do you actually view your app? Normally I use ionic emulate ios to see my builds in the simulator. Since we aren't using Cordova, that's not an option.\nInstead - you'll want to use the Gulp script included in the project. Don't worry if you aren't familiar with Gulp, it's basically a simple automation library for projects. To &quot;build&quot; your app into something you can browse, simply do:\n\ngulp build\n\nYou should see output like so:\n\nAnd you can then see the results in your www folder. When I tested, I used httpster to fire up a little web server. Everything worked as expected, but you want to remove the line including cordova.js in your index.html file since it won't exist.\nFinally, if you want the code to build automatically while you edit, use:\n\ngulp watch\n\nThat's it. Enjoy.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Fixing \"Browser Has Stopped\" Errors in the Android Simulator",
		"date":"Tue Sep 13 2016 00:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473725160,
		"url":"https://www.raymondcamden.com/2016/09/12/fixing-browser-has-stopped-errors-in-the-android-simulator",
		"content":"Before I begin, I want to point out that I encountered this issue both in the &quot;regular&quot; Android simulator (which I don't recommend) and in Genymotion (which I strongly recommend). The fix came courtesy of Julien Bolard, an employee of Genymotion. I'm just blogging it to help spread the word in case others run into the issue as well.\nSo - have you ever tried saving an image from the browser in Genymotion or the Android simulator? First you find your picture:\n\nThen you long tap to bring up the context menu:\n\nYou select the save option, and then boom!\n\nOops. So what's the issue? The browser simply doesn't have permission to use your (simulated) device storage and instead of simply prompting you for permission, it craps the bed.\nSo - go into settings, apps, select Browser, and then permissions. Then simply enable &quot;Storage&quot; and you should be good to go.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Bringing Dynamic Back (Presentation)",
		"date":"Mon Sep 12 2016 14:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473688980,
		"url":"https://www.raymondcamden.com/2016/09/12/bringing-dynamic-back-presentation",
		"content":"This is the video recording of my talk at ForwardJS - Bringing Dynamic Back. It's about adding dynamic aspects (comments, search, etc) to static web sites. I'm covering this much deeper in my next book (&quot;Working with Static Sites&quot;) which you can preorder now. (Not sure what the final release date will be but it's about 75% done currently.)\n\nBringing Dynamic Back\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Looping Audio in a Cordova App",
		"date":"Fri Sep 09 2016 15:20:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473434400,
		"url":"https://www.raymondcamden.com/2016/09/09/looping-audio-in-a-cordova-app",
		"content":"Here's a quickie. How do you play audio in an Apache Cordova app and have the sound automatically loop? For example, maybe you want background music for a simple game. It's pretty simple. Begin by adding in the Media plugin.\n\n\nThen get your music. I chose an MP3 from the cool Arcade Ambiance Project. This is exactly what you think it is - background sounds from what you would hear in an arcade. If you don't know what an arcade is, ask the old coder next to you.\nOnce you have your MP3, then you have an interesting choice. If you are iOS only, then you can actually tell the Media plugin to loop. Technically you can't loop infinitely, but you could use a really large number. Here's an example from the docs:\n\nI'm assuming you could probably use JavaScript's MAXINT here: Number.MAX_VALUE. I haven't tried it, but it should work. If not, then certainly 9999 is pretty reasonable. (Be sure to keep reading. I tested MAX_VALUE. It failed.)\nA cross-platform solution though isn't that much more difficult. The Media object supports a status event. This is fired every time the current media object has a particular change. Values are:\n\nMedia.MEDIA_NONE = 0;\nMedia.MEDIA_STARTING = 1;\nMedia.MEDIA_RUNNING = 2;\nMedia.MEDIA_PAUSED = 3;\nMedia.MEDIA_STOPPED = 4;\n\nSo all we need to do is listen for the stopped event and then use seekTo to return to the beginning of the file. Here is a complete solution. Notice I'm using the Device plugin to handle how Android loads audio files a bit differently. See my older post for an example: Cordova Media API Example\n\nPretty simple, right? Unfortunately there is a slight gap when it resets that may be annoying. I guess it just depends on the track you are using. You could, in theory, use the loop feature in iOS and only use the seekTo option for Android.\nSo I decided to go ahead and try this. The first thing I discovered is that Number.MAX_VALUE really screwed things up. I didn't get an error, but my init function never ran. Weird - but switching to 9999 worked. Unfortunately, you still get a gap, but it is smaller at least. Anyway, here is the version that only does seekTo for Android:\n\nYou can find the full source code for this demo here:\nhttps://github.com/cfjedimaster/Cordova-Examples/tree/master/loopingaudio\nI went ahead and recorded two examples. The sound quality is pretty bad - I was recording from my laptop output. So these samples may be useless. Please note though that the original MP3s sound great, and it sounds great in the Cordova app.\n\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Adding opacity to a background image in reveal.js",
		"date":"Tue Sep 06 2016 21:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473196140,
		"url":"https://www.raymondcamden.com/2016/09/06/adding-opacity-to-a-background-image-in-revealjs",
		"content":"tl;dr - use a slide state of &quot;something&quot; and then html.something .slide-background { opacity: 0.1 !important; }.\nI used to be a big fan of reveal.js, but stopped using it after a while when I ran across too many instances of having to fight it's built in styling to get what I wanted. Don't get me wrong, I loved it, and thought it was a great tool, but somethings seemed unnecessarily difficult. I switched to Powerpoint and Keynote because, frankly, it was just simpler.\n\nFor an upcoming talk (NativeScript Developer Day) I thought I'd give it a try again. Ten minutes in to working on my slide deck I ran into a problem. Reveal makes it easy to add backgrounds to slide - including colors, pictures, and videos. I used the following code to set up a slide with a background image.\n\nAnd it worked, but the result was a bit hard to read:\n\nThe obvious (well, to me and my design-challenged brain) solution was to reduce the opacity on the background image. Reveal doesn't have that baked in, and when I first suggested it be added, I was directed to the &quot;states&quot; feature. Basically, you add data-state=&quot;foo&quot; to a slide and Reveal will apply that as a class to the &quot;document&quot; (more on why that's in quotes in a bit) when it is active.\nOk... so that's easy:\n\nBut then I needed to figure out how in the heck to set the opacity. As far as I know, Reveal doesn't give you directions on what CSS classes are being used to render your presentation. Certainly you can just use Dev Tools, but yeah, even than you have to dig quite a bit to figure it out.\nAfter some Googling, I came across the solution in a StackOverflow post. You can address the current background like so:\n\nSo first off, when the Reveal docs said it applied the custom CSS to the document, I had assumed body. I wish it had made it clear they meant the html tag. (Is that obvious to everyone else?)\nSo given that - I tried setting the opacity:\n\nAnd... Reveal said psyche! Yeah, I dug a bit more into Dev Tools and noticed another class applying opacity. Well, I don't know CSS very well, but I know how to make my crap take precedence:\n\nHere is the result:\n\nMuch better. I am a CSS God! (Ok, not really, but I'm going to pretend for a few minutes until I need to center something and begin wishing for the return of &lt;center&gt;.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Review: No Man's Sky",
		"date":"Sun Sep 04 2016 18:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1473015300,
		"url":"https://www.raymondcamden.com/2016/09/04/review-no-mans-sky",
		"content":"About two weeks ago (right after its release), I picked up a copy of &quot;No Man's Sky.&quot; I knew a little about it and it sounded interesting. I knew it was space simulation focused on exploration with basic economies and the such. But the critical point was - I was only casually familiar with the game. Because of that, I went into with little expectations. And within minutes, I was hooked.\nThe game drops you off on a planet with a busted ship and little to no direction. This would normally frustrate the heck out of me, but as you're busy trying to survive, it's hard to be frustrated. You're on an unknown planet with tools you barely know how to use, hunting for materials to keep you alive, keep you safe, and help repair your ship. It's an incredible way to begin. And even when you take off and leave the planet, an awe-inspiring moment in the game, the feeling of confusion, fear, and wonder continues.\nI don't think I can adequately explain the experience. The first portion of the game, let's say the first ten hours or so, is a complete struggle. Eventually, you begin to get the hang of things. You understand the basics. And you can start focusing on the exploration aspects of the game, and here's where the game's size truly begins to sink in. At over 18 quintillion planets, this is a game you'll never 100%. Heck, I don't think you could 0.001% it. But that's part of it's fascination. Every planet is a new experience. I'm probably a good 40 hours in now and I still see things that freak me out.\nYesterday it was what appeared to be a giant &quot;soda straw&quot; type rock formation. I have absolutely no idea how it could have been created but I just had to stop and look at it. Every hour in the game is like that. Either some random freaky planet atmosphere or freaky plant or really freaky creature.\nAs an example, here is an excellent Flickr collection by Brian Taylor.\n\nOverall, the game is just... enticing. You pick your own path. You decide what to explore. It's really a game of what you bring to it versus the game telling you what to experience. If you want to spend days on one planet - do so. If you want to race along a path to the center of the galaxy, do so.\nOne of the more fascinating aspects of the game is your dealings with the other alien races in the game. Each race has their own language, and as you play, you pick up individual words a few at a time. Slowly, incredibly slowly, you begin to understand the creatures you're dealing with, and it becomes even more important to learn more of the language. So for example, you may know that a particular creature is talking about iron, but you may not know if they want it, or don't want it. This is the only game I can remember where earning money and hardware plays second fiddle to learning a language.\nBut you can't talk about this game without discussing all the controversy as well. I wasn't even aware of the problems until a few days into my addiction when I began perusing the main Reddit forum for the game. Apparently, there were a heck of a lot of features cut from the game before release. It is such an extensive list that when someone wrote it up, it exploded in popularity. The original author deleted both their account and the original post (no one knows why), but you can read the copy here:\nWhere's the No Man's Sky we were sold on?\nI read it, and damn, there was a lot cut. I can understand the hate. And wow, there is a lot of hate out there. The game was also pretty buggy on PC. (On my PS4, I've had 2 crashes, which is a lot for a console game, but over 40+ hours, I'm not too concerned.) The negativity was so strong on the main Reddit forum that another one, NoMansHigh was created for discussion amongst people actually enjoying the game. The negativity was so strong that people were discussing how best to get refunds, even bragging about how to do so after days of gameplay. (Which, frankly, is bullshit if you ask me. If you've put 40+ hours into a game, asking for a refund is unfair.)\nTo me - the strong criticism of the game that rings true is that it is &quot;wide but shallow&quot;. I can see the truth of that. When you look at what was cut from the game, the things you actually do are pretty limited. I suppose I'm just happy with those limitations. One of the reasons I haven't ever finished a Grand Theft Auto is not because the game wasn't fun, but because it simply felt too open ended. I get there's a good thing and I shouldn't be complaining, but sometimes it feels like I'd rather have more constrained games. As I've said, every planet is unique. I may be doing the same thing on every planet, but the visuals, the lifeforms, they never cease to amaze me. I get excited every time I blast down through the atmosphere and finally see my new destination up close.\nSo yeah, I recommend it, but definitely recognize that this is one of those games where there is probably, no, definitely, no middle ground. You're either going to love it or hate it. I k",
		"tags":[
	        
		],
		"categories":[
            
                "video games"
            
		]

	},

	{
		"title": "Pagination and IndexedDB",
		"date":"Fri Sep 02 2016 22:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1472853840,
		"url":"https://www.raymondcamden.com/2016/09/02/pagination-and-indexeddb",
		"content":"For a while now I've been meaning to write up a quick demo of adding pagination to a site using IndexedDB, and today I finally had some time to create an example. I'm not sure this is the best example of course, but hopefully it can help someone, and if folks have a better solution, please let me know in the comments. In order for this to make any sense, you'll need some basic knowledge of how IDB (IndexedDB) works in general. You can pick up my book or video on client-side storage (both of which cost money) or read the extremely well done (and free) documentation at MozDevNet: Using IndexedDB\n\nI began by building an initial demo that would handle creating seed data and simply displaying all the data. I wanted that done first so I could then &quot;upgrade&quot; it with pagination. I began by building a simple front end. I've got a table, pagination buttons, and that's it.\n\nThe only thing really interesting here is that I ensure my pagination buttons are disabled by default. We'll only enable them if necessary, and not till the next version. Now let's look at app.js.\n\nOk, there's quite a bit going on here, but let's take it top to bottom. I begin by creating my IDB database, &quot;page_test&quot;. My object store is called cats and I've made two indexes on it. I didn't end up using those indexes, but they made sense to me so I added them anyway.\ndisplayData handles calling off to getData and then rendering the cats. Again, it should all pretty straightforward, but let me know if not. getData opens up a cursor to iterate over the object store and simply create an array of cats. A &quot;Cat Array&quot; if you will.\nAs an example, here is an array of cats, size three (credit Michelle Gabriel):\n\nseedData is not actually called by any function. I opened up my console and simply ran it there. Yes, it is a lot of code just to generate fake data, but I had this built already for a Node.js app I wrote for work (&quot;Building JavaScript Charts Powered by LoopBack&quot;). Yes, that was real work. Why are you laughing at me?\nSo the end result, after manually running seedData, will be a table of cat data:\n\nSo that worked. This version of the code may be found in the v1 folder in the zip attached to the article. Now let's discuss pagination.\nPagination can be achieved by using two methods. First, we need to determine the size of our data set. Luckily there's a count method you can use on an object store.\n\nThe next thing we need is a way to get one &quot;page&quot; of data. That is possible by using the advance method of the cursor object. This is a bit tricky. The MozDevNet example illustrates going through an entire object store and skipping over two rows on every iteration. What we want is something different - do an initial skip to the beginning of our page and then end where it makes sense. Here is the updated version of the code with this in action. (I left out seedData.)\n\nOk, so again, let's take it top to bottom, and I'll focus on the changes. I've got a few new variables to help me with paging, including position and totalCats. My init method now calls countData first and then runs displayData. This way I know, at the beginning, how big my dataset is.\ndisplayData now checks that count as well as the current position and determines if the navigation buttons should be enabled. These buttons both use a move event that handles changing the value of position and rerunning our display.\ngetData has been updated to handle a start value and a total. In order to handle beginning at the right value, we still open a cursor as before, but on the first call we use advance to set our starting position. The other change is to recognize when we have as many cats as we need (NEVER ENOUGH CATS!) and end iterating early if so. And here is the end result.\n\nIt seems to work well. Have any ideas for improvements?\nDownload Example Code\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "My NodeSummit LoopBack Presentation",
		"date":"Wed Aug 31 2016 16:22:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1472660520,
		"url":"https://www.raymondcamden.com/2016/08/31/my-nodesummit-loopback-presentation",
		"content":"The title pretty much says it all. Here is the recording of my presentation a few weeks ago at Node Summit. Enjoy.\n\nRapidly Building APIs with LoopBack\nBy the way, no, I'm not an &quot;offering manager&quot; at IBM. I can barely manage to dress myself in the morning.\n",
		"tags":[
	        
            "nodejs",
            
            "strongloop"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Using CommandBox for Lucee",
		"date":"Tue Aug 30 2016 16:03:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1472572980,
		"url":"https://www.raymondcamden.com/2016/08/30/using-commandbox-for-lucee",
		"content":"I think I've been very clear about my lack of interest in working with ColdFusion. It served me well for many years, but I've moved on to other technologies (Node!) that are more appropriate for me. When I do use ColdFusion, I've been trying to stick to Lucee. It's open source, free, and incredibly light-weight. About the only &quot;code&quot; problem I've had with it so far is with cfspreadsheet and even that was fixed after a bit of research. I've got other reasons to prefer Lucee over Adobe CF as well, but yeah, I'm still &quot;biting my tongue&quot; on that.\n\nFor the most part, the Luceee Express server worked for me. The biggest issue I had was that my document root was under my installation folder and not a custom directory under Dropbox. Since I was mainly doing small tests, this wasn't a big deal.\nAbout a month or so ago, I began helping a friend out with a &quot;real&quot; site. This grew in size and it bothered me more and more that I was working under Lucee's folder and not my own project root. I decided one day to figure out how to change my document root. I also wanted to enable directory browsing.\nI knew this was all done via Tomcat and I knew it was &quot;possible&quot;, but two hours later I was tearing my hair out. I was able to change the document root and turn on directory browsing, but I broke having index.cfm as a home page. I was - mostly - ok with it, but then my friend started talking about SES URLs and I figured at this point it was time to give up trying to work with Tomcat and simply do what I'd done in the past with Adobe ColdFusion, integrate it with Apache.\nI had just begun my research into doing a &quot;real&quot; Lucee install with Apache when it was recommended (and I forget by whom, sorry!) that I check out CommandBox. I had heard of CommandBox of course. I knew it was a CFML CLI and had a package manager. I also knew it could fire up a server as well.\nWhat I didn't know was how damn useful it was! After downloading the bits, I simply did box server start in the project folder. My first attempt failed:\n\nBut this must be a common issue as Brad Wood in the Lucee Slack channel asked if I had a WEB-INF folder in my project. I did, from my use of Lucee Express, and removing it corrected the issue quickly.\nBut wait... there's more. Not only did it fire up Lucee and use the directory as root, it also had index.cfm working as a home page and directory browsing enabled. And it is 100% a &quot;real&quot; instance of Lucee of course, as you can open up the Lucee admin and work with your regular settings. So for example, I had to setup the DSN I had created in Lucee Express in my new server. This persists as well.\nIt just freaking works!\nNow - that being said - I've run into a few issues with the command line and I can't get it to use Lucee 5 versus Lucee 4, but so far, damn, this is really, really freaking impressive and much more useful than anything I've seen come from Adobe's ColdFusion product in years. I've said it before, but Lucee, Ortus Solutions, and Intergral are single handedly doing more for ColdFusion than the &quot;official&quot; providers have for years.\np.s. So before I could publish this post, the good folks in the Lucee Slack helped me with my issue. As of today, the brew install for CommandBox isn't the latest version. (By the way, I run into this a lot with Brew installs. I am not a fan.) I switched to a direct download and my issue with using Lucee 5 was fixed. Immediately.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Looking for RIAForge?",
		"date":"Fri Aug 26 2016 22:17:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1472249820,
		"url":"https://www.raymondcamden.com/2016/08/26/looking-for-riaforge",
		"content":"So yeah - I've gotten a few emails/IMs/Tweets/passenger pigeons about this today. Apparently RIAForge.org is showing something different:\n\n\nSo just as a reminder, I stepped down from managing RIAForge last year (&quot;Status of RIAForge&quot;). The ColdFusion team was aware that the domain was expiring and was in process of moving the ownership (I didn't own the domain name though) to themselves and I guess it just didn't get done.\nSo... yeah. Not sure what to tell you. Obviously the server is probably still there. I have no idea if they plan on trying to get the domain back or selecting a new one. If you want your files, please reach out to @coldfusion on Twitter. And of course, if you are still doing open source in ColdFusion, you should just use GitHub. RIAForge was built way back when GitHub was still young and not as well known as it is today. I'm real proud of what RIAForge provided, but it's time to move on. (In more ways than one, but I'll bite my tongue.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Working with the Disqus API - Deeper Stats (2)",
		"date":"Thu Aug 25 2016 17:04:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1472144640,
		"url":"https://www.raymondcamden.com/2016/08/25/working-with-the-disqus-api-deeper-stats-2",
		"content":"Welcome to (probably) my final blog post on working with the Disqus API. It's been fun building my own tools for my comment data, but I've scratched this itch enough and will probably not work on it again. My final tool isn't perfect, but it works for me and provides the stats I wanted (that Disqus themselves did not provide) and as the code is up on Github, folks are free to take it and run. (But if you do, let me know!)\n\nThe main focus of this version of the code was to turn what I did in the last post (Working with the Disqus API - Deeper Stats) into a proper application. That version handled getting data and persisting it into IndexedDB. It ran a few analytics on that data and reported it to the console. What I wanted was a &quot;proper&quot; application. By that I mean good UI and some way of working with the user to let them know the current state of their data.\nTurns out - that was a far bit more difficult than working with the API. I don't think I'm necessarily surprised by this, but the concept of &quot;An Application&quot; is much more deeper than &quot;Hit an API and dump some stats.&quot; What I wanted was this:\n\nThe first time you visit the site, prompt for a forum name and fetch the data.\nWhen you return, recognize that last time you used forum X, and you had data up to date so and so.\nAlso, if you use another forum, remember specific details about that one as well. So I can come back and the app will know both the last one I used, as well as a list of all the forums it has data on.\n\nThat was the goal, and you'll see some code related to it all, but I scaled back a bit in terms of the third bullet point. Right now if you go between one forum and another, it won't remember the 'last sync' aspect of the first forum. Again, I leave this to folks who want to file a pull request. I'll be using my tool for my blog comments here so I'm not worried about going back and forth to other forums.\nTo handle this, I used localStorage to remember the name of the forum and the time of the last sync. When the application starts up, it looks for localStorage data and shows or hides a particular div based on that data. So on first hit, you see this:\n\nBut when you return, the app can remind you of the last forum and provide you with a date of the last sync:\n\nNothing too special about that, and as I said, it works well with one forum but not multiple.\nAnother change was to provide feedback during sync. For my blog, the entire process takes about 10 minutes. Unfortunately I can't provide a comment count, but I can show status while it syncs:\n\nTechnically - I could do a comment count by using the mechanism I described in my first blog post. In that example, I get all the threads and then count the comment size from there. Even on my blog that works rather fast as it just takes 57 or so network requests, but adding that to the mix seemed like a bit too much. There's one benefit it would provide that I'll describe in a bit.\nOnce everything is loaded, we can then go to the stats. What's cool is that after the sync, you can reload the app and as long as you're using the same forum, updating is much quicker. First, some general stats.\n\nSo this should all make sense, but obviously you can see one missing. Getting a &quot;per day&quot; average ended up being non trivial. In order for this to properly work, you would need to know the range of your site's existence. My blog launched in 2003, but I switched to Disqus a year or so ago. I imported old comments, but if no one commented on my blog in the first year, I'd need to somehow add that to my stats. Basically the &quot;life&quot; of the site is not something Disqus could know. In theory, I could add a &quot;Settings&quot; panel where I define a forum's site. (I could even go crazy and ask for Google Analytics information and mash that up with Disqus but oh my god no one's got time for that.)\nAnother option would be an &quot;Average Per Thread&quot; figure, but in this version of the code, I'm not fetching threads. I do fetch thread data for comments, but this will not include threads with no comments. So that goes back to what I said earlier about how possibly adding thread data would be a good improvement.\nNow to the first chart:\n\nI'm using Chart.js for my charting in the project. I go back and forth between liking and disliking this solution. Honestly, I haven't found any charting engine for JS that made me completely happy. But for now, this works well enough. One issue though is that for sites with less history, this chart will be near worthless. I thought it might be cool to switch the chart dynamically to a &quot;per month&quot; chart if the 'age' of data was less than some threshold, like 36 months. I also thought that maybe a &quot;per month&quot; version would work fine even for my data. This is something I'd like to come back to later. For now though I like the report. I'm not terribly happy that my engagement is dropping, but it is very useful and clear to under",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Ionic Native - Shake, Rattle, and Roll (Follow Up)",
		"date":"Mon Aug 22 2016 18:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471890720,
		"url":"https://www.raymondcamden.com/2016/08/22/working-with-ionic-native-shake-rattle-and-roll-follow-up",
		"content":"Last month I wrote a tutorial on using Ionic Native and the Device Motion plugin (Working with Ionic Native - Shake, Rattle, and Roll). In that post I detailed how to use the device's accelerometer to recognize a &quot;shake&quot; gesture and then reload data from a service. A reader (on the Ionic blog version of my article) had a great question:\n\n\nThats really useful and it works :-) Can anyone suggest how to implement subscription.unsubscribe(); when the page is navigated away from and then restarted when the user returns to this page?\n\nMy demo was a one page app which isn't very practical, but kept things simple for the demo. However, as soon as you add a new page to the app, you may (or may not!) notice something bad about my code - it continues to listen to the accelerometer after you've left the page. That's going to drain the device battery and make the user angry. You wouldn't like the user when they're angry - trust me.\n\nI began by modifying my previous demo such that the list of cats actually linked to a detail page. In case you don't remember, this is how the list looked:\n\nSo I simply created a new page (don't forget, the Ionic CLI has a cool &quot;generate&quot; feature to make that easy!) and then linked my cats to the detail. So first I added a click event to my list item:\n\nAnd then added a handler for it:\n\nOk, so how do we fix our code so we only listen to the accelerometer when the view is visible? Easy - we use a view event! The Ionic docs do not do a good job of making it easy to find them, but if you look the API docs for NavController, you'll find a list of view-related events you can listen to. For my demo, I just needed ionViewWillEnter and ionViewWillLeave. So I simply moved my &quot;listen for device motion&quot; code out of the constructor and into the enter event. Here's the complete home.ts code:\n\nSo ionViewWillEnter simply has the code I used before. No real difference there - but do note I'm storing subscription globally to the component. That let's me then use it in ionViewWillLeave to handle unsubscribing from the accelerometer.\nI created a new folder for this version in my Cordova Demos repository - you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicnative_shake_2\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with the Disqus API - Deeper Stats",
		"date":"Fri Aug 19 2016 21:49:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471643340,
		"url":"https://www.raymondcamden.com/2016/08/19/working-with-the-disqus-api-deeper-stats",
		"content":"Yesterday I blogged about my first attempts at writing a client-side Disqus API client to provide better stats than the Disqus site itself. While yesterday's demo was more a proof of concept, today I'm attempting something a bit deeper - the beginning of a real power tool.\n\nThis first iteration is somewhat ugly, but will serve as the basis for the pretty, client-friendly version I'll work on next. The idea behind the tool is to create a complete (ish) copy of your Disqus data locally on your client. For that I decided to use IndexedDB. Eventually my code will handle fetching only new comments, but for now it sucks down everything. Even with the 1000 request per hour limit Disqus imposes, I can suck down the entirety of comments for this blog (over 60K). It takes a while, but it's a one time hit, and again, going forward (in the next version at least), it will be a heck of a lot quicker as it only needs to get the latest comments. Let me start by showing the front end and then we'll dive into the code. To be clear, the 'front end' is really just a few buttons and a heck of a lot of console logging.\nOn startup, you need to give it the name of your forum.\n\nThe setup button is responsible for creating the IndexedDB database for your forum. I could have used one db for all my testing, but... I don't know. It just felt right to create one bucket of data per forum. Obviously I may revisit that.\nAfter entering a value and clicking &quot;Setup&quot;, the next two buttons are activated. Get Data begins the data fetching process. I hit Disqus for 100 posts per request and just paginate like crazy. For my blog, it does about one request per second and needs 610 or so requests to finish. That's like super slow, but again, will be a one time import. On the next version I'll provide good feedback. I may even use the techniques in my last post to get a comment count via thread listings first so I can create a status bar. For me that's going to add about 60 seconds to the process though and it may not be worth while. Again, the UX here is squishy - I'll need feedback.\nDisplay Data is where I start actually running reports. Right now all these reports are dumped to the console. My reports currently consist of:\n\nComment Count (to be clear, this is the same stat as yesterday, just fetched a different way)\nThe number of unique commenters\nThe first and last comment\nNumber of comments from 2003 to 2016. Yes, I hard coded it, but obviously this would be based on the previous values.\nThe threads with the most comments. (I actually sort them all, but I just print the top ten.)\nThe top ten authors by the number of comments. I'm thinking of adding a setting that lets you enter your own name so it can be ignored.\n\nAnd here is how this looks. First, the 'by year' stats, comment range, and unique number of commenters.\n\nAnd here are the top commenters:\n\nAnd the threads with the most comments:\n\nOk, I know you're overly impressed by the UI, but let's take a look at the code.\n\nFirst is a utility handler for the setup button. It just does a bit of DOM crap, and then I have the code to setup my database. As I said, I'm using one db per forum and I may revisit that. This is boilerplate IDB crap. The only things of interest are the indexes. I want to be able to sort/filter by date, author, and unique threads, so I have to create an index for each.\nNow let's look at the seeding portion. Again, later this will do things like remembering where it left off and handling hitting the API limits, for now though it just sucks data. First - get the data.\n\nNext, enter it into the db. Note I'm using put which will handle inserting or replacing, but really I'm just inserting once.\n\nThe only thing really interesting there is I create a new data value based on the epoch time. If you don't, the date value of createdAt gets inserted as a string you can't sort on.\nOk, now let's look at the stats. This is all in 3 or so really ugly functions, so I'm going to share a snippet at a time. Keep in mind I wrote this quick, and it's kinda crappy, but it's the first iteration.\nFirst - the number of comments:\n\nYeah, not too complex - just a count call on the objectStore. Lets kick it up a notch!\n\nThis gives me both a count on authors and passes off an array of author objects I can then perform analysis on:\n\nFor dates, it was a bit weird. I knew I could sort by date, so I used a cursor and fetched one object. I then opened a new cursor, reversed, and did the same. This feels wrong.\n\nAnd here is my &quot;per year&quot; code (and again, I hard coded the values):\n\nThis displays well, but I kinda worry that due to the async nature, it's possible one year will come in after another, or vice versa, whatever, you get the idea. I'll probably have to use an object to store results, sort the keys, and then work on the data.\nFinally, here's the top threads report.\n\nHow does this work? I get each unique thread index, get the actual thread object, and then store the thread data. I then ",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with the Disqus API - Comment Count",
		"date":"Thu Aug 18 2016 16:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471539540,
		"url":"https://www.raymondcamden.com/2016/08/18/working-with-the-disqus-api-comment-count",
		"content":"I've been a happy Disqus user for a while now, but I noticed this week that the stats provided by the service are pretty poor. For example, you can't even determine the total number of comments for your web site. That seems... a bit crazy. It isn't necessarily some crazy stat like, &quot;How many Europeans create comments on the weekend.&quot; You can see how many comments you got this week:\n\n\nWhen you go deeper though, you can't get an aggregate anymore of your comments. All you can get is a day by day (or month by month) break down which is limited to one year. Here is the most I can see from my site:\n\nSo obviously I could add those numbers up in my head, but one year of stats isn't nearly complete for my blog. Maybe Disqus offers more stats for paying customers (and to be clear, I'd totally understand that), but I can't find any links/details about that if it exists.\nSo screw it - let's use the API and build our own tools! I've set up a new repo where I'm going to start building my own stats. The API is rather simple and you get a somewhat generous usage allowance (1000 hits per hour) out of the box. Also, you don't have to use OAuth for the types of operations I need, and that makes it even easier to use.\nFor my first demo, I focused on what started this off - just figuring out how many damn comments my site has gotten. Not surprisingly, there is no direct API for this. There is an API to get details for a forum, but that information isn't provided.\nThe best I could come up with was the API to get lists of threads. They do a great job of supporting pagination in all their APIs, so my code simply needs to paginate over all the threads and then do math. Let's take a look.\nFirst, the front end view, which is rather simple.\n\nI assume nothing here is weird, but let me know otherwise. The fun part is the code:\n\nThe bulk of the work is in the doCount and doProcess calls. doCount is responsible for validating your input and firing off the call to doProcess. When done, it simply does some math and reports. (See my notes at the end for how I could go further with this.)\ndoProcess is a recursive function that gathers all the threads. It uses the Disqus pagination support to go over all the threads and create a large array of threads. The thread data contains a post count which doCount uses to create the report.\nHere is the result for my own blog:\n\nYou can find the complete source code for this here - and note - I'm going to be adding more demos soon (again, see notes below): https://github.com/cfjedimaster/disqus-analytics/tree/master/commentcount\nYou can run the demo here - but note that I'm using my public API key. Most likely it will not work for you. If you want to use my tools, download the source, create your own key (at Disqus of course), and go to town.\nhttps://cfjedimaster.github.io/disqus-analytics/commentcount/\nOk, now for some notes!\n\nYou may be curious about 'threads' - threads are simply unique locations for your Disqus embeds. So for a blog, it would be every site visited. That's an important thing to note. If you visit a new bog entry, Disqus will create a 'thread' for it even if no comments exist yet.\nI like that I can see an average number of comments. What I really want to know though is how those comments appear over time. I'm curious both about my traffic per month/year, as well as my traffic in terms of the age of my content. What I mean by that is - let's say my comment traffic is pretty much consistent. What may not be consistent is that people are commenting on older blog posts versus new. Technically Disqus can't help with that. A thread is created when someone visits the blog post. So I may have a very old blog post that no one visited. When someone visits it today, the thread is new, but the content is old. Since my content has date information in the URL, I can use that to perform analytics based on my content. This all comes down to one question - is my content more engaging now than it was ten years ago?\nMaybe I'm wrong about Disqus and I just didn't find the right link for deeper stats, or the upsell to a paid account for more stats. Cool! Tell me where I'm wrong and I'll be fine with that. I had fun writing the code and that's all that matters. :)\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Ionic Native - Using Secure Storage",
		"date":"Tue Aug 16 2016 22:05:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471385100,
		"url":"https://www.raymondcamden.com/2016/08/16/working-with-ionic-native-using-secure-storage",
		"content":"Today I'm reviewing another Ionic Native feature, the Secure Storage wrapper. As the plugin docs explain, this is a plugin that allows for encrypted storage of sensitive data. It follows an API similar to that of WebStorage, with a few differences.\n\nFirst, the plugin lets you define a 'bucket' for your data. So your app could have multiple different sets of data that are separated from each other. (The plugin refers to it as 'namespaced storage', but buckets just made more sense to me.)\nSecond, you can't get all the keys like you can with WebStorage. That's probably related to the whole 'secure' thing, but in general, I can't imagine needing that functionality in a real application. You could also use a key that represents a list of keys.\nSecure Storage is a key/value storage system, and like WebStorage, you can only store strings, but you can use JSON to get around that.\nWith that out of the way - let's build a simple demo. I created a simple two page app to represent a login screen and main page.\nLet's start by looking at the first page, our login screen.\n\nNow we'll look at the code behind this.\n\nAll we've got here is a login handler that calls a provider to verify the credentials. There's one interesting part - the setRoot call you see there is used instead of navCtrl.push as it lets you avoid having a back button on the next view. Finally, let's look at the provider, even though it's just a static system.\n\nBasically - any login with &quot;password&quot; as the password will be a succesful login. That's some high quality security there!\nYou can view this version of the code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/securestorage_ionicnative/app_v1\nOk, so let's kick it up a notch. My plan with Secure Storage is to modify the code as such:\n\nWhen you login, JSON encode the username and password and store it as one value.\nWhen the app launches, first create the 'bucket' for the system, which will only actually create it one time.\nSee if pre-existing data exists, and if so, get it, decode it, put the values in the form, and automatically submit the form.\n\nSince I'm using a plugin, I know now that my app has to wait for Cordova's deviceReady to fire. I've got a login button in my view that I can disable until that happens. So one small change to the view is to show/hide it based on a value I'll use based on the ready status. Here is the new login button:\n\nNow let's look at the updated script. I'll share the entire update and then I'll point out the updates.\n\nSo let's start at the top. Don't forget that your Ionic views can fire before the Cordova deviceReady event has fired. I still wish there was a simple little flag I could give to my Ionic code to say &quot;Don't do anything until then&quot;, but until then, you can use the Platform class and the ready event.\nI create my Secure Storage bucket &quot;demoapp&quot;, and in the success handler, I immediately look for the key loginInfo. Obviously on the first run it won't exist, but the bucket will be created. On the second (and onward) run, the bucket will already exist, and the data may or may not exist.\nIf it does - I decode it, set the values, and login. That last operation was optional of course. Maybe your app will just default the values. There's a few different ways of handling this.\nFinally, in the login handler I both set the value (after encoding it) and clear it based on the result of the login attempt. Notice that both calls are asynchronous, but I really don't need to wait for them, right? Therefore I treat them both as 'fire and forget' calls.\nThey could, of course, error. And there is a very good reason why it could. In the docs, they mention that this plugin works just fine on iOS, but on Android it will only work if the user has a secure pin setup. That's unfortunate, but the plugin actually provides an additional API to bring up that setting for Android users, which is pretty cool I think.\nYou can find the code for this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/securestorage_ionicnative/app\nHow about a few final thoughts?\n\nWhile you can store a username and password, and the docs even say this, I still feel a bit wonky about doing so. I'd maybe consider storing a token instead that could be used to automatically login just that user. And it could have an automatic timeout of some sort.\nIf you read the blog post, Ionic Native: Enabling Login with Touch ID for iOS, then this plugin would be a great addition to that example.\nA bit off topic, but I would normally have added a &quot;loading&quot; indicator on login to let the user know what's going on. And of course, Ionic has one. I was lazy though and since my login provider was instantaneous, I didn't feel like it was crucial.\n\nAs always - let me know what you think in the comments below.\np.s. I'm loving Ionic 2, and Angular 2, and TypeScript, but wow, it is still a struggle. For this demo, I'd say 80% of my time was spent just building t",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Victory!",
		"date":"Mon Aug 15 2016 15:36:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471275360,
		"url":"https://www.raymondcamden.com/2016/08/15/victory",
		"content":"For the past month or so I've been fighting against a jerk auto-copying my content to their Blogger blog (Fighting against a content stealer on Blogger). After constant complaining on Twitter (sorry!), I finally reached a few folks who were able to escalate the issue and take down the blog.\n\nI'm happy it's gone, but it still bothers me that Blogger was a complete wall of silence on the issue. They took down every URL I sent in via DCMA notice, but they couldn't bother to respond to me personally one time. I'm sure they get swamped with these notices, but after I sent in over 400 URLs, I would think I could have gotten something from them.\nAt the end of the day, I think the only reason I got this fixed was because of my large Twitter following and contacts I have at Google. Anyone else probably would have been SOL.\nsigh\nAt the of the day, it's over, and the blog is dead, and I'll chalk that up to a win!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Some Tips for Conferences",
		"date":"Fri Aug 12 2016 20:07:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1471032420,
		"url":"https://www.raymondcamden.com/2016/08/12/some-tips-for-conferences",
		"content":"I've spent the past few months attending way too many conferences, and I've noticed a few things that I'd love to see conferences improve on. Here is my list in no particular order. Enjoy.\nSkip Slack\nI like Slack. A lot. But for the love of God do we need to create a new Slack organization for a conference we'll only be at for a few days? I get that it's an easy way to chat, and it's certainly more user friendly than IRC, but this just seems like a waste of time.\nThat being said - I'll offer a counter proposal. Someone create a &quot;DevConference&quot; Slack group and just use one channel per conference. I'd join that in a heartbeat.\nSkip the Mobile App\nThis has nothing to do with web versus native (although I'm clearly biased towards web), but again, why do I need an app for a few days? Just build a darn web site that's responsive and works offline. Building a mobile-friendly site should not be a problem in 2016. Offline is not much of a problem in 2016. You can skip the sexy service worker stuff and just AppCache if you have to. Or skip offline support completely. While conference wifi can suck, generally the cell networks run just fine.\nThose housekeeping notes\nPretty much every conference I go to starts off the day with housekeeping. Where's the bathrooms? Where's that one room that isn't by the others. What's the wifi password. What time is the social event.\nPretty much no conference ever bothers to put that information on their web site. If you miss those morning notes, you're just out of luck.\nBefore the conference, your web site is a great marketing tool to get people to your event. During the conference it needs to shift focus to being an information source for folks actually attending.\nMisc\nA few more random things:\n\n\nFew conferences make note of the time between sessions. So for example, the one I just left (which was darn good by the way!) had a session at 10:30 and one at 11:30. Speakers were to use 55 minutes. This should have been noted on the schedule too.\n\n\nOh - and it boggles my mind that I have to say this, but have explicit time between sessions. I attend a conference earlier this year that had no time between sessions. I don't know what they were thinking.\n\n\nWhen you provide food, try to be descriptive about what folks are getting. For example, if you've got a hot breakfast, let folks know. That way I can decide if I want to eat elsewhere.\n\n\nProvide water for speakers. This is definitely hit or miss for conferences, and partly it is my responsibility as a speaker to handle this, but I really like it when water is provided in the room.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "How Many Days Since the Last JavaScript Framework?",
		"date":"Thu Aug 11 2016 21:28:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470950880,
		"url":"https://www.raymondcamden.com/2016/08/11/how-many-days-since-the-last-javascript-framework",
		"content":"Earlier today I mentioned discovering a new JavaScript framework that I liked on name alone -\nSo earlier today I had this conversation with an old buddy of mine: catberry. At which point an old friend of mine said:\n\n0 days since new JS framework. https://t.co/YHGMOWJa9q&mdash; Jesse Warden (@jesterxl) August 11, 2016\n\nThis got me thinking - how easy would it be to build a tool that told you the number of days since the last JavaScript framework was released?\nI began by looking at the GitHub API for searching repositories. It seemed perfect. We can:\n\nFilter by repositories that advertise their language as JavaScript\nInclude a text filter for &quot;framework&quot; (obviously not perfect but what evs)\n\nAnd then obviously just sort and find the most recent one. Unfortunately, this is where things break down. You can't sort by a project's created date, just its updated date (and a few other fields).\nI did some more digging, and discovered you could filter by the date a project was created. I then figured out you could use pseudo-code like this:\nStart with today and look for projects with the text &quot;framework&quot; and language &quot;JavaScript&quot; and if any exist, then we have a 0-day situation.\nRepeat 10 times and subtract one from the current date. When you find a match, you know the number of days.\nWhy 10? You can only do 10 anonymous API calls to GitHub per day. Plus, does anyone honestly think it will be more than 10 days since the last JS framework? Technically, we could 'step' back by two day increments letting us check a longer time period. We could also simply tell the user that they have to wait 60 seconds and then do 10 more hits. But for now - I just assumed there would always be a match in a ten day period.\nHere's the code (it integrates with the DOM too but I assume folks don't need to see that):\n\nYou'll notice a bit of caching as well done with LocalStorage. I decided on a five minute cache because the idea of a new JavaScript framework being released more than once per five minutes is pretty freaking frightening.\nWant to see it? Check the demo (and my amazing CSS skills) here:\nhttps://static.raymondcamden.com/demos/2016/08/11/index.html\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Quick tip for installing Bash on Windows 10",
		"date":"Thu Aug 11 2016 15:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470928860,
		"url":"https://www.raymondcamden.com/2016/08/11/quick-tip-for-installing-bash-on-windows-10",
		"content":"To be honest, I can't remember the last time I wrote a blog post about Windows. I've been a Mac person for about five years now, and while I certainly didn't dislike Windows, I've been happy with OSX and haven't really looked back. But lately I've been more and more interested in Windows (and Microsoft as a whole). I could go on and on about all the cool stuff they've been doing lately, but let me get to the point of this post. The most recent update to Windows 10 - &quot;Anniversary Edition&quot; (can we maybe just do Win10AE for short?) includes the ability to run a Bash shell natively within the OS. I've done that in the past with Cygwin, but I was really excited to test it out as an &quot;official&quot; part of Windows.\nI've got a Surface 3, which is a damn nice piece of hardware that has got me seriously considering picking up the Surface Book, and last night I installed the AE update.\nThere's a bunch of blog entries on how to get the new shell, with this one from How-To-Geek being the one I used: How to Install and Use the Linux Bash Shell on Windows 10 (As an aside, I'm breaking my rule of linking to sites that use coverup modals. I still hate them, but the post was helpful.)\nI followed the instructions and bash.exe was installed pretty easily, but I ran into a weird problem. Every time I tried to launch it, it would automatically close. I know I've seen Windows command line programs do that in past, but I wasn't sure what to do.\nOn a whim, I tried running bash.exe from cmd.exe, and from there I got an error about disabling the legacy console. That was the kicker. Go into properties and disable it:\n\nThen I just ran it again and it began another install process:\n\nOnce done - it works as expected:\n\nAs an aside, it looks like cmd.exe was significantly improved as well, which is pretty nice: Console Improvements in the Windows 10 Technical Preview\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Need a Test SMTP Server?",
		"date":"Tue Aug 09 2016 23:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470786240,
		"url":"https://www.raymondcamden.com/2016/08/09/need-a-test-smtp-server",
		"content":"I always feel bad when I blog something I've tweeted about, but then I remind myself that some folks have (wisely) avoided the trap that is Twitter addiction or may simply have missed it. Earlier this week I was doing some work for a client using Lucee (I'm trying to ween myself off ACF completely) and ran into an issue trying to read emails generated by the code. No matter what I tried, I couldn't find the temporary mail files generated by the back end code. I knew multiple different SMTP testing servers existed, but I hadn't installed one in a while. I did a quick Google and came across MailDev.\nMailDev is a quick install via npm but obviously doesn't matter if you use Node, ColdFusion, or PHP for your back end. You simply install it and then type maildev in your Terminal/Command Prompt.\n\nNotice this gives you both a SMTP server and a web interface!\n\nAnd not just a web interface, a pretty snazzy one. As soon as mail shows up, it alerts you and auto updates. The reader is nice and simple.\n\nNote that the email being displayed there is a diagnostic one and kinda ugly. That's not MailDev's fault.\nOn top of being a simple SMTP test server, it can also relay email and be used within a Node.js application as well.\n\nAnyway - I thought this was just darn tootin' nice and thought I'd share.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My Blog Tech Stack",
		"date":"Fri Aug 05 2016 15:34:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470411240,
		"url":"https://www.raymondcamden.com/2016/08/05/my-blog-tech-stack",
		"content":"I've been running my blog as a static site for a little over half a year now and I thought it would be interesting to share what my current setup is. I've already talked about how I'm using Hugo, but I've got a lot of little tweaks/hacks/etc that I think may be of interest to folks considering switching to a static site generator. To be clear, my use case is probably far from the norm, but as a real world example, I think discussing it could be useful. I also want to be clear that all the little hacks and tweaks were built to specifically fit my needs. I don't believe it would make sense for everyone. Ok, ready to go down the rabbit hole?\nBefore we begin - some stats. My blog has over 5,500 entries covering thirteen years of blogging. I've received over 50,000 comments and get about 125,000 to 150,000 page views per month.\nMy Local Environment\nAs I said - step one in my stack is Hugo. Hugo isn't my favorite SSG. Don't get me wrong - I dig it! My favorite is Jekyll, but I wasn't able to get the performance I needed with my content. If I remember right, I saw compilation times of around 6 minutes. Totally unacceptable for my site. For Hugo, it was still a bit slow, around 70 seconds, but much better than Jekyll.\nIn order to improve that, I did a couple of things.\nI began by figuring out that some actions caused Hugo to regenerate every single page. So for example, editing a post impacts the number of posts, categories, and tags, and my default theme included a post count, tag cloud, and tag count. It included a few things like this. I removed them one by one, but didn't really get a good performance boost. (I've slowly added them back - like the post count.)\nI also removed pagination, since having 500+ pages of pagination is a bit silly. I added it back in a few weeks ago and described how I did so: Adding (Limited) Pagination to Hugo\nThen I realized I could simply modify my config to tell Hugo to ignore content. I built a new config called config_dev.toml and added this:\n\nThis tells Hugo to ignore nearly 90% of my content. It speeds things up quite a bit. As far as I know, you can't do that in Jekyll. There's a setting to restrict the maximum number of posts, but I'm not sure what order that uses so it's not something I considered. With this in place, Hugo regenerates my site (well a portion of it) in about one second. The &quot;real&quot; build of all the content only takes about 40 seconds and I only worry about that when I deploy.\nIn order to use this custom configuration, I wrote a script, starthugo, to handle it:\n\nWhy did I write a script just to handle passing an attribute? Because my memory is crap. Utter - complete - crap. I promise you that if I didn't have this script I'd need to check the CLI help for Hugo everytime I started up.\nSpeaking of command lines - there's a bug in Hugo on Mac, or a bug on the Mac, involving having a large number of files open. This is a known issue (and again, I believe it is more a Mac issue than Hugo), and it is fixable via running some obscure OSX commands. I wrote up another script called fixmac.sh for this:\n\nSo whenever I reboot my machine, I open up iTerm and run sudo ./fixmac.sh and then ./starthugo. I reboot maybe once a week so this isn't a big deal. I could probably make the stuff in fixmac.sh permanent, but I've not been motivated enough yet to do so.\nBut wait - we aren't done with the scripts yet! Hugo supports the ability to quickly create new content via the hugo new command. But while this worked ok, I still found myself doing additional work (outside of writing the blog entry of course) to get my file prepared for publication. I decided to write my own tool, a Node.js script, that would make this simpler. I called this genpos.js and at the CLI I can do this:\n./genpos.js &quot;My Blog Tech Stack&quot;\nMy script handles creating a path based on the current date. It also creates a proper URL slug based on the title. Here it is - and get ready - this is some butt ugly Node.js code.\n\nYeah, not pretty, but it works. Pretty much the only thing I tweak (outside of the actual article text) are my categories and tags.\nDeployment - Part 1\nAlright - now for the fun part - deployment. I use Surge to both handle the physical moving of files and hosting. I pay for Surge Plus (13 bucks a month) specifically to get custom SSL and custom redirects. I went into more detail about the https process here: How I added https to my blog. I wrapped up the Hugo compilation and Surge deploy in yet another simpe script, build.sh:\n\nCurrently this is the slowest part of my process. Surge takes about 5-6 minutes to deploy to the server. It was about twice that, and thats when I figured out that I could about 60% of my content (images and attachments) to S3.\nDeployment - Part 2\nSo Surge handles my blog files, but not my media or demos. For that - I decided to use static hosting with Amazon S3 and CloudFront. Initially it was just S3, but you need to use CloudFront for https. The S3 part ",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Excellent overview of Static Sites",
		"date":"Thu Aug 04 2016 18:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470334260,
		"url":"https://www.raymondcamden.com/2016/08/04/excellent-overview-of-static-sites",
		"content":"As folks know, I've been really into the concept of static sites the past few years. I've given numerous presentations on the topic, I turned my blog into a static site, and I'm working on a book on the topic right now. When speaking about static sites, I generally focus on the technology behind them. Today I watched an incredibly good video by Phil Hawksworth:\n \n\nHe does a great job of introducing the concept of static sites and explaining the benefits, as well as some of the negatives. Even if you already have a good idea of what static sites can do, this is a great video you could share with your boss, or client, to help convince them to make the switch.\nOne of the best points he makes about 20 minutes in is that sometimes the complexity isn't necessarily removed, but simply shifted. That's been my experience with this blog. I went from a system that was easy to use, but complex to manage in production, to one that is complex locally, but super easy to handle in production. To me, that's a big improvement, and I was happy to see him bring this up in the video.\nObviously you should leave feedback about the presentation on the Youtube video itself, but I'd be happy to see any comments here about how useful (or not) you thought the video was.\n",
		"tags":[
	        
		],
		"categories":[
            
                "jamstack"
            
		]

	},

	{
		"title": "Working with Ionic Native - Using the Diagnostics Plugin",
		"date":"Tue Aug 02 2016 20:42:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470170520,
		"url":"https://www.raymondcamden.com/2016/08/02/working-with-ionic-native-using-the-diagnostics-plugin",
		"content":"Ionic Native is a set of wrappers for various Cordova plugins that make them easier to work with in Ionic 2 applications. These wrappers cover the core plugins (Camera, File, etc) but also a subset of other popular plugins. One of the cool things I found while going through the docs was that they supported plugins I had never even heard of. (And you should keep that in mind - even if you have no plans on using Ionic 2 or heck, even Ionic, these plugins that Ionic Native wrap are available for any Cordova application.)\nOne of the more interesting ones I discovered recently was the Diagnostic plugin. Initially I assumed this was some weird testing framework for hardware or something that worked with various internal settings. Instead it works with device settings and permissions and can be incredibly useful for providing information about what your app can do on the device. So for example, this plugin can:\n\nTell you if GPS, Wifi, a camera, or bluetooth is available on the device\nSwitch to device settings, so for example, to help a user enable GPS, Wifi, etc\nEnable or disable Wifi and Bluetooth\nCheck to see if the app has permissions for various things, and even ask for permission explicitly. These settings are more than just hardware items like the camera, but also things like contacts and the device calendar\n\nBasically - this plugin can help your app figure out exactly what it is allowed to do and even automate the request for having more access. It is incredibly cool, and frankly, I just wish it had a different name. Maybe something like &quot;AwesomeAppPermissionCheckerUtilityOfAwesomeness.&quot; I think I'll fire a PR right now for that name change!\nFor my demo of this plugin, I decided to build something relatively simple. For a long time now I've been building Camera demos. I've done this for Flex Mobile, PhoneGap, jQuery Mobile, Ionic, and NativeScript. It's simple and fun and demos well.\nOne issue I run into though is that when using the iOS simulator, there's no camera available. (Which is pretty silly if you ask me. Android simply 'fakes' the camera which is what iOS should do as well.) This isn't a big deal as I just use the &quot;select existing photo&quot; option in the Camera plugin, but I've always wished that the Camera plugin itself would have an option to check for the existence of a camera. It's rather trivial Objective-C code (and NativeScript makes it easy to call, see my post on it from a few weeks ago), but we've never had a nice way of doing it in Cordova. Turns out we can do this quickly with the Diagnostics plugin.\nThe plugin supports working with the camera in three different methods:\n\nisCameraEnabled checks if the device has a camera, and on iOS also sees if the application has permission.\nisCameraPresent checks to see if the device has a camera.\nisCameraAuthorized checks if the application has permission to use the camera.\n\nI created a super simple Ionic V2 application to work with the camera and photo gallery. I began with a simple view consisting of two buttons and a space for the image.\n\nOn the back end, I've got a simple method that makes use of Ionic Native wrapper for Camera:\n\nI've said this before but I'll say it again. I love working with Ionic 2 (and Angular 2). I'm still fumbling my way through stuff, but the code just feels better. Ok, back on topic, Ray. So in this version, the user can click either button and either take a new picture or select an existing one. I won't share a screen shot of this as it just plain works as expected. You can find the source code for this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/cameradiagnostics/app_v1\nAlright - so now let's modify it to hide the button that uses the camera when it isn't present. Don't forget to actually add the Diagnostics plugin (ionic plugin add cordova.plugins.diagnostic). Every single time I've used Ionic Native I've forgotten to add the plugin. I'm kinda slow sometimes.\nThe first thing I did was modify the button itself:\n\nAnd here is the updated code:\n\nThe changes include:\n\nImporting Diagnostic from the Ionic Native library. The docs do not tell you to do this (but they do for other plugins) and I've filed a bug report to get this added. It's pretty easy to guess of course, but the docs should be consistent.\nThen I simply use Diagnostic.isCameraPresent() to check the hardware. I can then simply use the result boolean to show/hide the button.\n\nSo here's the code running in the iOS Simulator:\n\nAnd here it is running on the device:\n\nNot terribly exciting in a visual sense, but as a developer, I think this is incredibly cool, and I wish I had known about this plugin earlier. (Note to self - maybe consider a weekly series where I find a random plugin and just build a cool demo of it.)\nThe complete source code (including the release + initial version) may be found here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/cameradiagnostics/app_v1\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Checking (and updating) your Ionic Native install",
		"date":"Mon Aug 01 2016 17:14:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1470071640,
		"url":"https://www.raymondcamden.com/2016/08/01/checking-and-updating-your-ionic-native-install",
		"content":"Ok, so this falls under the &quot;pretty obvious&quot; category, but as I had to figure it out\nthis morning I figured I'd blog it to help myself remember. I've been playing with\nIonic Native as I learn Ionic 2. I wrote up my first\nexperience with it a few weeks ago (&quot;Working with Ionic Native - Shake, Rattle, and Roll&quot;. As I work on my next couple of examples, I ran into a few issues.\nFirst off - I did the right thing and filed a bug report on the Ionic Native GitHub repo. If you run into issues with the code, don't forget to start there.\nSecondly - Ionic Native is installed via npm. That means you can use regular npm commands to both check for and perform updates. So first off - to see what version of Ionic Native you have, simply do npm list ionic-native:\n\nAnd then you can check against the published version by using npm info ionic-native version:\n\nAnd then just npm update ionic-native to get the bits.\n\nSo yeah, this is pretty much the same you would do for any npm package, but I thought it might be helpful for Ionic folks who may not be terribly familiar with npm. Don't forget that you'll want to ionic build to regenerate your code, or just emulate/run.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My next book - Working with Static Sites",
		"date":"Thu Jul 21 2016 16:29:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1469118540,
		"url":"https://www.raymondcamden.com/2016/07/21/my-next-book-working-with-static-sites",
		"content":"I've mentioned this a few times in passing now but never &quot;formally&quot; announced it. My next book, along with my good friend Brian Rinaldi, will be published by O'Reilly later this year. As you know, I've been blogging and presenting quite a bit on static sites lately, and I'm happy to say that I'm part of a team writing a new book on the topic.\n&quot;Working with Static Sites&quot; covers multiple different static site generators as well topics covering publishing your static sites and including dynamic elements in them. What makes this book especially good (in my opinion) is the focus on building different types of sites versus one particular engine. So instead of, &quot;Here's Harp&quot;, &quot;Here's Jekyll&quot;, etc, we instead talk about a particular type of site being built (a coffee shop, a blog, a documentation site) and then use that need to discuss a particular static site generator. We cover over the basics, then actually build the site in question.\nHere's the formal description from ORA and the magnificent cover. You can also preorder the book right now!\n\n\nFor years now, web developers have used powerful application servers like PHP and Node that could create our web apps. But recently there's been a shift between how much we can do on an application server versus how much we can do on the browser itself—as well as what we can do on a mobile app. This practical book shows readers how static site generators provide a powerful middle ground between a full app server deployment and a simple collection of static files.\n\n\nWritten by two leading developers in the JavaScript community, this book teaches front-end designers, front-end developers, web developers, and web designers how static site generators work, the benefits you can expect, and what options are available. You'll learn how to deliver static site files to the end user exactly as they are on the server, without using a server-side language or a database, using HTML, CSS, and JavaScript.\n\n\nStatic websites today are just like vinyl LPs: they’re coming back. With this book, you'll discover just how these websites still fulfill a vital need for bloggers as well as people who just want to disseminate information.\n\n\n\nFinally, if you want to watch a presentation of mine on static sites, here is one I recorded last year for O'Reilly: Static site generators: Why use them and how they work\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "\"Apache Cordova in Action\" 50% off Monday",
		"date":"Sun Jul 17 2016 15:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1468769400,
		"url":"https://www.raymondcamden.com/2016/07/17/apache-cordova-in-action-50-off-monday",
		"content":" \nThe title pretty much says it says it all. My &quot;Apache Cordova in Action&quot; book, along with &quot;NativeScript in Action&quot; and &quot;Intuitive UX&quot; are all 50% off on Monday, July 18. Just follow the link below and use the code: dotd071816au\n\nhttps://www.manning.com/dotd\n\nif(document.location.hostname.indexOf('mr-cordova.blogspot') >= 0) {\n\talert('This site is stealing my content - sending you to the proper blog now...');\n\tdocument.location.href = 'http://www.raymondcamden.com';\n}\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Scraping URLs from a Sitemap File",
		"date":"Sat Jul 16 2016 15:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1468681260,
		"url":"https://www.raymondcamden.com/2016/07/16/scraping-urls-from-a-sitemap-file",
		"content":"Yesterday I wrote about a person who is stealing my content (and others) for their blog. As part of my process to fight against this jerk I had to file a DCMA claim that includes the URLs of the offending content. In order to get all the URLs, I had to work with their site map, copy the content, and use XPath to get the URL values. I decided to whip up a quick tool that would automate the entire process.\nThe app is pretty simple. You enter a URL of a sitemap, hit the button, and stand back while it works:\n\nThe code is pretty simple. I use Yahoo Query Language to run an XPath on the sitemap. I can't just look for URLs though as a sitemap can contain a list of sitemaps instead of URLs. So for example, the asshat stealing my content has a sitemap that looks like this:\n\nSo my code needs to see if this type of data exists in the sitemap first. Here's the entire code for how I parse the sitemap. There's a bit more code (feel free to view source at the demo) for DOM stuff, but this is the important part.\n\nAs you can see, I end up using promises (jQuery-style) to handle the case where multiple sitemaps exist. For each unique sitemap &quot;set&quot;, I run a YQL on it to fetch the URLs. At the end I have an array of URLs you can copy and paste. Yeah, the code is a bit crap, but it works well so far.\nYou can run the demo yourself here: https://static.raymondcamden.com/demos/2016/07/index.html\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Fighting against a content stealer on Blogger",
		"date":"Fri Jul 15 2016 14:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1468594500,
		"url":"https://www.raymondcamden.com/2016/07/15/fighting-against-a-content-stealer-on-blogger",
		"content":"About two weeks ago a reader sent me a question concerning one of my blogs. While not unusual, the URL was. Apparently, a site on Blogger is automatically copying my content from my site (along with content from Andy Trice and Christophe Coenraets). Currently they have 402 copies (I'll explain how I know that in a minute) and of course, they will have a copy of this post too - in about 30 minutes.\nThe site in question is mr-cordova.blogspot.co.za. I'm not using a 'real' link for that of course as I don't want to give them anymore Google power than they already have. (Since at least one person went to his site thinking it was mine.) At the bottom of each post you can see an attribution: &quot;by via Raymond Camden&quot; but no direct link is provided. Even if they did, I certainly don't approve of them copying my content completely on their site.\nWhen I first discovered this, I assumed it would be pretty simple to correct. I've been publishing web sites for over twenty years and have had problems with this since nearly day one. (I used to run a pretty popular site, deathclock.com, that was copied all the time.)\nAt the top of every site running on Blogger is a link that lets you report issues:\n\nThis leads to a &quot;Choose Your Own Adventure&quot; type interface for trying to report a problem. I ended up in an infinite loop at first but finally ended up on their DCMA removal tool. Their form lets you explain what content was stolen and then asks for the offending URLs.\nHere is where the shit hit the fan. (Pardon the language.)\nI explained, very clearly, that the site was stealing content from my blog (and two others). I submitted the request and I assumed it would be fixed rather quickly. Three days later I got a response:\n\nHello,\nThanks for reaching out to us.\nWith regard to the following URLs:\nmr-cordova.blogspot.co.za\nIn order for us to investigate the appropriate content and take further action, please provide us with the specific URLs of the posts where the infringing content is located. You can obtain the post URL by clicking on the title of the post or the timestamp found at the bottom of the allegedly infringing post(s).\nRegards,\nThe Google Team\n\nI responded immediately with an explanation about what the site was doing, and also explaining that even if I got every URL, they would just steal new content.\nI got no response.\nSo I submitted again, one specific URL this time, but with explanatory text about how the site was stealing my content. The good news is that they removed the URL. The one damn URL. And completely ignored everything I said about the rest of the content.\nSo today I decided - what the hell - let me scrape the site. The site has a sitemap.xml which looks like this:\n\nWhich basically leads to 3 &quot;pages&quot; of a site map. Each page is a ginormous XML file of URLs. They look like this:\n\nI knew I could use XPath to parse that data, so I Googled for a random online XPath tool and found this one: Template / XPath 3.0 / XQuery 3.0 / CSS 3 Selector / JSONiq Online Tester\nI used an XPath of //url/loc to parse each page of the sitemap:\n\nI did this three times and ended up with 402 URLs. I then filed a new DCMA request, which again won't be enough to stop this asshat, and ran into a new problem. Blogger only allows me to file 100 URLs per day. So great. I've got 4 days now of filing requests. And I get to repeat this every month or so assuming Blogger never shuts this guy down.\nAnyway - wish me luck. I'll update this post with a comment if I have any luck.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Deciding what client-side storage system to use",
		"date":"Fri Jul 08 2016 15:30:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1467991800,
		"url":"https://www.raymondcamden.com/2016/07/08/deciding-what-client-side-storage-system-to-use",
		"content":"This isn't a new topic for this blog, but as a good buddy of mine asked me about this yesterday, and we ended up having a good conversation about it on IM, I thought it would be nice to write up what we discussed so I could share it with others. The conversation started with a simple question:\n\nAre you a proponent of local and session storage for hybrid apps, since web sql is deprecated?\n\nLet's begin by thinking about the kind of data you need to store in your application. (And I'm going to begin by considering all web apps, not just hybrid mobile apps.) In general, I break down storage into two types:\n\nThe first type of data is what I call \"known data sets\". That is *not* a great term, but it basically means the data is consistent and doesn't expand infinitely. An example would be remembering the user's login name so that you can pre-fill the value the next time they login. Another example would be their preferences, things like what menu items should be shown and how they like to use the site. Another example would be a shopping cart. While this is - technically - dynamic - it is still a known quantity - an array of product items and quantities.\nThe second type is what I call \"unknown data sets\", and again, this is a *really* bad term. This isn't random data you know nothing about of course, but rather data that can grow in a matter you can't really estimate. An example of this would be a To Do list. Another example would be a note system like Evernote. Another aspect of this data is that due to it's unknown size and content, typically search is a required feature to work with the data.\n\nThese are broad, fuzzy categories and obviously you can probably think of examples that would fit in either block, but this is the &quot;mental map&quot; I use to help me decide how I'm going to store data in a web application.\nWithin these two categories, I consider these technologies:\n\n\"Known Data Sets\": Cookies, WebStorage (covers both Local and Session storage)\n\"Unknown Data Sets\": WebSQL, IndexedDB, and for mobile only, the File system\n\nSo for the first group, I almost always prefer WebStorage to cookies. The API is a heck of a lot easier to use. Less people block it. (In fact, you can't block WebStorage. You can - of course - delete or modify the data by hand.) In general I'd only do maintenance work with cookies. I can't imagine every adding cookies to an existing site.\nThe second group is a bit trickier. WebSQL is deprecated. You aren't supposed to use it anymore. IndexedDB (IDB) is &quot;proper&quot; way to store deep, complex data and should always be used. Except... IDB can be difficult to use. I've heard the exact opposite too, but at least for me, coming from a background of building server-side apps with ColdFusion, SQL was pretty familiar to me. IDB is also incredibly bad on iOS. You can read my original article on (IndexedDB on iOS 8 - Broken Bad. Things are definitely improving in the most recent versions, but for a long time, WebSQL worked much better on mobile than IDB did.\nSo what would I choose?\nOn hybrid, I'd probably lean towards using SQLite. This is plugin-based so it is a known quantity and you don't have to worry (too much) about weirdness with the browser on the device and as I said - it may be more familiar and easier for folks than IDB.\nOn the desktop, I'd probably lean more towards IDB. Support is pretty solid and as long as you have good fallback, you're fine with older browsers.\nOf course, an even easier solution would be to consider PouchDB, which abstracts away these details and picks the &quot;best solution&quot; for your current browser. I haven't used it a lot as the biggest feature of it is &quot;sync&quot;, and generally I'm more concerned about just the local storage, but from everything I've seen it is a damn good solution and incredibly well supported.\nWant to learn more? (Warning - advertisment!) You can purchase a 2+ hour video series on the topic I created for O'Reilly or purchase the book I wrote on the topic. (Or both. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Ionic Native - Shake, Rattle, and Roll",
		"date":"Thu Jul 07 2016 17:41:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1467913260,
		"url":"https://www.raymondcamden.com/2016/07/07/working-with-ionic-native-shake-rattle-and-roll",
		"content":"Forgive the slightly dramatic title of the blog post - I just get a bit excited when I test something new and it actually works well! For today's post, I'm looking at something new with Ionic - Ionic Native.\nIonic Native is the spiritual successor to the older ngCordova project. Basically - it provides an Ionic/Angular friendly interface to many common Apache Cordova plugins. To be clear, this isn't something you have to use in your Ionic application, but it can make using plugins a bit simpler. For today's demo, I thought I'd work with the Device Motion plugin. This plugin lets monitor the device accelerometer and do... well whatever based on the motion of the hardware. For my demo (link at the end) I decided on a simple idea - I'd build an app that loads data and then lets you shake the device to update.\nI began by building a new Ionic 2 application based on the blank template. For the initial version, I'd build out all the code to load data, display it in a list, and I'd include a button that could be used to refresh the data. (While &quot;shake to update&quot; is cool, you probably want to provide a simple UI element to click as well.)\nThe first thing I did was create a provider. I made it use hard coded data and set up a simple routine so it could easily add more data to the list. I assume this is self-explanatory, but let me know if you have any questions.\n\nIn my view's logic, I then added in the provider and had it set a local variable, cats, to the result of provider's load method.\n\nAgain - I'm kinda assuming this is all relatively simple, but just let me know if it doesn't make sense. Finally, I built out my view.\n\nHere it is running in the browser:\n\nWoot. Ok - now for the fun part. First, I have to add in the plugin. The Ionic Native docs remind you of this both on the introductory page of the feature and for each individual plugin. For me, this was done via: ionic plugin add cordova-plugin-device-motion\nOk, easy enough. Next, I needed to add support to my logic. First, I imported it:\n\nCool. Then I tried the sample code... and it crapped the bed in the browser. Because - of course - this is a device specific thing. Oops. So the first thing I did was add in support for listening for the platform ready event. Remember - your controller may actually fire before Cordova is ready to let you use hardware features. You can easily listen for this by adding in the Platform object:\n\nAnd then listen for ready:\n\nThat was step one. Step two was to switch to using the incredibly cool Cordova Tools extension for Visual Studio Code. This is an extension created by Microsoft that provides a whole set of awesomeness for folks doing Cordova/Ionic apps with Code. Most recently, they added support for using Ripple within the editor. I haven't talked about Ripple in a long time, but for folks who don't remember, it was a browser based testing system for Cordova apps that included some cool extras - like being able to fake GPS and accelerometer data.\nSo I set up my project for debugging (see the earlier link on Microsoft's blog for more information) and then fired it off. Now - it is a bit difficult to use this on a laptop as it is a bit cramped, but I was able to debug my application and 'shake' it via Code:\n\nNice. So at this point, I needed to do two things - monitor the device motion and then determine when a 'shake' happens. The first one is easy:\n\nThe second one... not so much. Luckily, I've done this before in a demo. Basically - I remember the device's previous values for acceleration, compare it to the current set of values, and if it is &quot;enough&quot;, consider it a &quot;movement&quot;. I can then keep track of movements and when enough has happened, I can consider it a shake. Obviously this can be tweaked. You would need to test on a real device and see what &quot;feels&quot; right. Here is the updated code with that logic in place:\n\nYou can find the complete source code for this up on GitHub: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicnative_shake. Let me know if you have any questions by leaving me a comment below!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "New POC - DailyReddit",
		"date":"Tue Jul 05 2016 18:31:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1467743460,
		"url":"https://www.raymondcamden.com/2016/07/05/new-poc-dailyreddit",
		"content":"For the past few weeks (mainly due to travel) I've been working on a little POC (proof of concept) for an application that most people will probably think doesn't make sense. I'm still somewhat of a new Reddit user. I'm not really an active participant either. I've got some subreddits I check almost daily, some a bit less, and I'll maybe do 3-4 posts per month. Maybe. I found myself doing the same thing whenever I wanted to check the site.\nI'll load a subreddit. Click on new. And then scan for interesting titles. Then I go to the next subreddit I care about. Of the 20 or so I've subscribed too, I'll probably 3-4 every day. I found this process to be... annoying.\nFirst - I don't like the fact that I can't get Reddit to default to &quot;new&quot; versus &quot;hot&quot;. Frankly, I don't necessarily care what's hot - I care about what's new.\nSecond - I know that the home page would let me view new items all at once, but it also mixes up all my subreddits in one big mess, and I don't like that either.\nAnd yes - I know I'm being a bit picky here, but it occurred to me - hey - if Reddit has an API, couldn't I build something that does what I want it to?\n\nSo for my POC, I decided on the following features:\n\n\nSocial login via Passport. I blogged about my first experiments with Passport a few days ago and it was directly related to this app.\n\n\nOnce logged in, allow the user to search for subreddits via the Reddit API. Note - I could let the user authenticate with Reddit directly and then get their subscribed subreddits, but as I said above, even though I'm subscribed to a set of subreddits, I only really care about a subset of them.\n\n\nAllow the user to remove a subreddit from their subscription.\n\n\nAnd then every day, we get all the users, get their subscriptions, get the latest content, and then email them a nice report.\n\n\nSo with that out of the way, let me talk about how I did it. All of this code is in my GitHub repo: https://github.com/cfjedimaster/dailyreddit\nPersistence\nSince it's the simplest aspect of the app, I'll cover it first. I decided to go with MongoDB for persistence and Mongoose to wrap the calls. I built a User model that consists of an ID, email, and array of subscriptions.\n\nAnd that's pretty much. In a bit you'll see where I store the user and how I fetch it, but in general, I'm not doing anything at all fancy with my persistence.\nLogin and Authentication\nI've already mentioned that login was done via Passport and in general, it was pretty easy to do. I decided on supporting Facebook and Twitter so I had to create applications for both and then configure my Node code to make use of them. Facebook was slightly more difficult in that I had to specifically ask to get the email field, but it took about sixty more seconds to figure that part out. Here's the code block related to Passport.\n\nI don't know if it &quot;just happened&quot; or if Passport goes out of it's way to make it easier, but the profile returned by both Twitter and Facebook followed the same form allowing me to easily handle the login part. Notice that I create a unique ID based on both the ID from the remote social network and the name of the social network as well. Because - who knows - maybe a person could have the same Twitter primary key as your Facebook ID.\nReddit API\nTo work with the Reddit API, I used the snoowrap library. It was a bit awkward to use at times - just because I had a bit of trouble understanding the docs at times, but when I reached out to the author they responded really darn quickly which was great support. I'm only doing two things with the Reddit API (search subreddits and get the latest posts for a subreddit) so my helper module is pretty small.\n\nThat's pretty much it for the Reddit aspect.\nSending Email\nFor email, I decided on MailGun as it had a free tier that was incredibly generous. I had a lot of trouble actually trying to use it via Node though. Nodemailer seemed really nice, but I couldn't get it to authenticate with MailGun. I ended up using a package called mailgun-js and it &quot;just worked&quot;. Here is an example of it in action.\n\nMailGun supports both HTML and plain text emails, but I decided on just using HTML email for now. Speaking of...\nThe Email\nFor the email itself, I had some basic requirements:\n\nFor each post, I wanted both the &quot;external&quot; URL and the reddit URL. Not every post has both, but I wanted a clear distinction between them both. This lets me decide if I want to go to the main link or go look at the comments.\nAnd for each post I wanted to know how many comments there were.\nWhere it made sense, I wanted to provide a thumbnail preview. This is great for posts that link to images.\nFor posts with text, I wanted to include the text as well.\n\nSo with those basic rules in place, I began building a template to handle my report. Since it's HTML email and HTML email is pretty much the HTML you had in 1992, I used a bunch of inline styles and super simple layout:\n\nAnd here is i",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "An Ionic 1 and 2 app side by side",
		"date":"Thu Jun 30 2016 12:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1467291540,
		"url":"https://www.raymondcamden.com/2016/06/30/an-ionic-1-and-2-app-side-by-side",
		"content":"Yesterday I gave a presentation to the Ionic NYC Meetup (a damn nice group of people!) and needed to quickly build a pretty simple Ionic app to speak to a LoopBack server. Because I wanted something quick and dirty, I just whipped up an Ionic 1 application. I wrapped it earlier than expected, so decided to see if I could switch it to Ionic 2 before the presentation. I was able to finish it, and I thought it might be kind of cool to compare both versions. To be clear, I'm not offering up either app as a &quot;Model&quot; Ionic application, but since they do the exact same thing, I thought it would be cool to share as a comparison. Both code bases are up on GitHub (I'll share the link at the end) so you can download and run for yourself if you want.\nSo before we begin - let me describe the app. It has a grand total of two screens. The first screen is a list of cats fetched by the LoopBack-powered API. The second screen is a detail page for the cat. And yeah, that's it. As I said, this isn't a terribly complex app.\nLet's begin by comparing the initial page for version 1 and version 2. In version 1, this is index.html:\n\nLet me focus on what I modified. First, I've got three different JavaScript files. One covers the main application logic (app.js), one the controllers for my views (controllers.js), and one for my services (services.js). Also note I've a nav-bar and nav-view defined here. Ionic is going to replace these items on the fly with each particular view.\nOk, on the Ionic V2 side, technically we have an index.html, but generally you don't (I believe) modify it. Rather I think the closest analog is app.ts:\n\nPretty much the only modification I need to ever worry about here is telling it what my first page is and then setting it as the rootPage. Ok, I dig this so far. I don't know if it is necessarily fair to see v1's index.html is equivalent to v2's app.ts, but I'm going with it for now.\nNow let's compare the first view. In V1, this is driven by a few different files. I've got a view (partials/home.html) and the controller (js/controllers.js). My view is singularly focused on the home page, but my controllers file actually has code for all the controllers in the app. Ok, first the view:\n\nAnd the controller:\n\nWe're going to skip the service until later in the post. Now let's compare this to V2. Right away, one thing cooler is that my view (pages/home/home.html) and JS code (pages/home/home.ts) are contained within one folder. I could have built my V1 code like that, but I love that V2 kinda forces the organization. Here's the view:\n\nAnd the code:\n\nOne thing I want to point out - notice how in V2 I handle my navigation. This was handled in V1 in app.js via the routing system. V2's simpler navigation API is a heck of a lot easier (imo, obviously) than the URL-based routing in Angular 1.\nOk, let's move on the detail view. Here is the view in V1:\n\nAll fairly simple. And here is the corresponding code from controllers.js:\n\nAlright, now let's look at V2. First the view:\n\nAnd the corresponding JavaScript:\n\nAlright - so the last bits to compare are the service. While they do virtually the same thing, they're written pretty darn differently. Here is the service in V1:\n\nAnd this is version 2:\n\nAlright so that's it. As I said - not the most complex apps, but I like having two such similar apps in both v1 and v2. Makes the differences really stand out.\nYou can see the full code yourself here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/v1andv2\nNow that I've done a few v2 apps, and just had a chance to compare them, here are some random thoughts, in no particular order.\n\nI love the organization of Ionic V2 apps. Currently my V1 apps typically have one big controller and one big service file. All my templates are in a views folder. In V2, everything is contained within it's own individual folder. To be clear, I absolutely could build my Angular 1 apps this way. But I like that Ionic forces (strongly leads) me into a cleaner (imo) organization.\nIonic's generate CLI commands are a huge time saver. Be sure you make use of it when playing around - it saves a lot of boilerplate work.\nI'm still not digging the big new \"Observables\" feature, and yes yes, I know, they are the new hotness, rah rah rah. I get it. I just don't like em yet. They feel really weird and awkward. To be fair, I felt the *exact* same way about Promises, and shoot, I feel like I just got comfortable using them and now we have this new thing. I'm whining - I admit it - but so far this is the only thing about V2 (specifically, Angular 2) that I'm not terribly happy about. Yet.\nOn the other hand, even though I'm finding a lot of Angular 2 a bit hard to get used to, like (click) and *ngFor, they are slowly making more and more sense and I'm digging those changes more and more as well. As I told someone at the Meetup last night - it's frustrating - but enjoyable at the same time!\nI'm also finding TypeScript a bit difficult at times, but I'm lov",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "No back button in your Ionic header?",
		"date":"Wed Jun 29 2016 16:51:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1467219060,
		"url":"https://www.raymondcamden.com/2016/06/29/no-back-button-in-your-ionic-header",
		"content":"A few months ago I wrote up a quick article about titles not correctly updating in Ionic V1 apps (Is your Ionic View title not updating?). Today I've run into another little issue with the header. I was working on a very quick demo for a presentation tonight and had an app with a grand total of two views - a master list and detail.\n\nEverything was working fine, but then I noticed I didn't have a back button when looking at the detail view. As far as I could tell, my code was fine. Here's what I had in the index.html file:\n\nAnd each view was pretty simple as well. You can see the problem in action at this CodePen: http://codepen.io/cfjedimaster/pen/WxpPap. (I apologize for the formatting in the code - I was cutting and pasting rather quickly.)\nI brought it up in the Slack chat and Mike Hartington came to the rescue rather quickly. Turns out the fix was... applying a class. Seriously. Even though the header has a class by default, if you don't explicitly specify one, then the back button won't show up. Literally - the fix is just this:\n\nYou can see this working in a CodePen Mike made for me: http://codepen.io/mhartington/pen/YWZBdK.\nObviously I think this is - well - bunk (grin) - so I'll filed a bug report for it here: Back button will not show up if you do not specify a class for the nav bar.\nAnd in case you're curious - here is the &quot;Before&quot; picture of this mission-critical Enterprise demo:\n\nAnd here is the &quot;After&quot; picture:\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Linking to PDFs in Cordova apps",
		"date":"Sun Jun 26 2016 21:08:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1466975280,
		"url":"https://www.raymondcamden.com/2016/06/26/linking-to-pdfs-in-cordova-apps",
		"content":"In today's &quot;I wonder what happens when...&quot; post, I decided to take a look at what happens when you try to use a PDF with a hybrid mobile application. I know that PDFs, in general, &quot;just work&quot; on both Android and iOS, but I was specifically curious about how you would use PDFs within a Cordova app. As usual, what I thought would be rather simple turned into anything but that.\n\nFirst, I whipped up a super quick, super minimal Cordova application. Even though I'm &quot;All Ionic, All the Time&quot;, I specifically avoided it in this case to keep my code as simple as possible. I decided on three separate tests:\n\nA simple link to a PDF.\nUsing JavaScript to load the PDF via document.location.href\nUsing the InAppBrowser Cordova plugin\n\nTo be clear, I expected both 1 and 2 to act the same, but I figured I might as well be complete and check it out. Here's the HTML:\n\nAnd here's the JavaScript behind it:\n\nI assume this is simple enough that it doesn't require explanation, but let me know in the comments if anything seems off. Ok, let's test iOS!\niOS 9.3\nThe first two links &quot;just worked&quot;, but not as I expected. Here's the initial page:\n\nAnd here is what happened when I clicked:\n\nYes - the PDF is rendering beautifully. (And it's easy to read, but to be fair, I use a Keynote slidedeck as my source.) However... notice something missing?\nYep - there's no way to get back to the app. Technically you're still in the app, but the entire webview is the PDF, and since iOS doesn't support a Back button, you're screwed. I had to kill the application to get it back to normal.\nOf course, the InAppBrowser makes this easy enough to handle:\n\nIn case you can't see it, there is a bar at the bottom with a &quot;Close&quot; link that will bring you back to the app.\nOk, so that's easy - Android will probably respond the same. Let's take a look!\nAndroid 6.0.0\nAlright - so going into my test, I expected the exact same results - except that Android would let me go back from the PDF using the first two tests. I loaded it up in my emulator, and...\nNothing.\nZip. No responses. At first I thought maybe it was a CSP issue, but when I opened up the console I saw this:\nResource interpreted as Document but transferred with MIME type application/pdf: &quot;file:///android_asset/www/assets/foo.pdf&quot;.\nWeird. I've seen issues with dynamic apps (ColdFusion) outputting binary date without the right content type, but if my memory serves me right, normally the browser just tries to handle it as best it can. In the past (far past) I can remember browsers trying to render the binary data as text, but it seems like I've not seen that in quite some time.\nCorrection - I decided to actually test that hypothesis and desktop Chrome barfed on ColdFusion outputting a PDF without the right header. Chrome, Safari, and Firefox all crapped the bed trying to load it.\nAnyway - that's with the first two links. The third link, the InAppBrowser one? Returns nothing. I kid you not. The new window opens, and nothing loads. I get zip in the console as well. Or so I thought. Returning back to Chrome's device window shows that it did load as a new web view:\n\nBut that console has nothing in it. I can't even execute JavaScript in the console. It's like the Phantom Zone of debugging.\nOk - so after a bit of searching, I found someone recommending using the download attribute. This is a newish HTML5 feature that tells the browser that it should download the asset instead of trying to render it. This also did nothing. No error, zip. (In case you're curious, iOS ignored the download attribute and just responded like it did with the first link.)\nIt turns out that the Android web view simply doesn't support PDFs. That seems... crazy - especially considering how many PDFs are out there. One could argue that they probably don't fit the mobile form factor very well, but I'd have assumed that showing something would be better than nothing. And heck - I'm sure Google could license a PDF viewer from Adobe. They probably have enough money for that.\nNow what?\nSo - I did some Googling around, and asking on Slack, and Simon Prickett shared some things that worked for him. One of them in particular looked interesting, cordova-plugin-file-opener2. This plugin tries to open a file in a local viewer. It seemed easy enough so I decided to try iOS again. I added a new button and used this code (after adding the plugin and the File plugin):\n\nAnd it seemed to work fine. I had the Adobe PDF viewer installed on my iPhone and it was suggested:\n\nand it viewed just fine - and I absolutely love the new iOS feature that provides links back to previous apps:\n\nOk - let's try Android. It's going to work. I bet.\nExcept no, of course it doesn't. Android reports this (via the plugin's error handler):\n Error status: 9 - Error message: File not found\nSigh. I did some digging on the plugin's GitHub issues though and ran across this report: Opening local file (pdf) : &quot;not found&quot;.\nIf",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile",
            
                "javascript"
            
		]

	},

	{
		"title": "Some quick tips for Passport",
		"date":"Thu Jun 23 2016 15:18:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1466695080,
		"url":"https://www.raymondcamden.com/2016/06/23/some-quick-tips-for-passport",
		"content":"Yesterday I decided to take a look at Passport, an open source library for Node.js focused on authentication. LoopBack supports Passport too, but when I first looked at it, I realized that I knew nothing about Passport itself and it would make sense to try it by itself before I try wrapping it with LoopBack.\n\nAt a high level, Passport lets you abstract away some of the details of authentication in your application. It has plugins which allow you to easily add in support for Twitter, Facebook, or Google authentication. (Passport calls these &quot;Strategies&quot;, and there are over three hundred of them!)\nWhen I began trying to test Passport, my first question was whether they had some form of simple authentication not tied to an external provider. They did - which is cool - but I really struggled with trying to figure out how to use it. To be fair, everything that confused me was documented. I just couldn't figure it out as is. So what follows are simply a few notes on things that didn't make sense to me.\nLocal Authentication\nAs I said - I assumed (hoped!) that Passport would have a 'simple' authentication that I could use while prototyping my app. That way I could build stuff and then drop in Twitter/Facebook/etc later. Turns out I was right - they did support this, and they call it the LocalStrategy. However, the code example was confusing to me. The docs show it as such:\n\nLooking at this, I thought, &quot;Hmm, that looks like Mongo a bit, but they never mentioned using Mongo, so does Passport support a User object?&quot; And I kid you not - I was stuck here for a good hour or so. Turns out, it is Mongo code, and maybe they assume all Node users know Mongo and are familiar with the API, but I wish they had actually said that in the example. Even the validPassword code is weird to me. All in all, it feels like a &quot;real&quot; code sample, and I appreciate that, but without more context it also feels unnecessarily complex. As a blogger, I definitely understand the problem you face when writing docs. You want something useful, something real world, but you also want something the reader can properly grok.\nAnother thing not spelled out well (or not to me anyway) was the fact that the object you return in the callback can be, as far as I know, anything that represents the user. Basically, you decide what represents the &quot;User object&quot;.\nHere is how I got my 'fake' login working:\n\nI'm not necessarily saying my code is any better, I just wanted to get it working, but hopefully you get the idea. I can no login with any username and a password of admin. Note the &quot;object&quot; I return is completely arbitrary.\nSession storage\nPassport also supports storing the user information in the session if your app supports it. This is done by a custom serializer/deserializer. Here is what I used:\n \nIn this case, the Mongo code (which again, isn't called out as Mongo) is a bit more clearer:\n\nIn this case, they store just the ID and then load it from the database when deserializing. As far as I can tell, this will run on every request when you've logged in.\nAm I Logged In?\nThis too is documented but wasn't clear to me at first. Given a route with a req/res pair of arguments, req.user will exists if the current user is logged in. So here is a simple example where I wrote some middleware to see if logged in and then push to the login page otherwise.\n\nFlash messages\nOk, I know most people know that &quot;Flash messages&quot; are simply temporary messages stored in a session, but I always think of Adobe Flash first. Anyway, Passport supports using Flash messages as a way of passing along a message to the user that their authentication passed or failed. This is documented, but I ran into an issue. Flash messages are stored via a key, but the docs don't tell you the name of the key to use to fetch the message. All it says is:\n&quot;Setting the failureFlash option to true instructs Passport to flash an error message using the message option set by the verify callback above.&quot;\nNotice how error is in a different font? That's the clue - you fetch the message by using req.flash('error'). Again - I guess it may be obvious, but this took me a while to get right as well.\nMore to come\nI hope this helps folks. Again, check the docs for more info, and as I play with this more I'll share anything else that trips me up!\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionic 2 Weather Application",
		"date":"Fri Jun 17 2016 17:54:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1466186040,
		"url":"https://www.raymondcamden.com/2016/06/17/ionic-2-weather-application",
		"content":" Edited on January 16, 2017: I received reports that the app was not working with the\nmost recent versions of Ionic 2. I've built an updated version that seems to work well, but\nI did not test it heavily. You can find that version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicweatherv3 \nBefore I begin, a quick reminder that I am still way new to Angular 2 and Ionic 2. I'm trying to learn, but you should treat what follows as the ramblings of a semi-intelligent monkey doing his best to learn something new. So with that disclaimer out of the way, I'd love to share an application I built this week - Ionic Weather.\n\nIonic Weather is heavily influenced by the excellent Yahoo Weather mobile app (available on both iOS and Android, the previous link is just for the iOS version). I was motivated to build this app by the NativeScript Weather App challenge, and while obviously this isn't NativeScript, I figured I'd try it first in Ionic 2 and then see if I could build something similar in NativeScript.\nIonic Weather has a simple interface. When you start up, you are prompted to select your first city.\n\nClicking Add Location (or the + in the UI) opens up a prompt:\n\nEnter a location and then it will be added to your list of cities. I then render a picture based on the weather and report on it in text.\n\nThe text here was arbitrary. I initially went with a tabular interface, but I thought a descriptive text version would be kind of cool. The storm thing is kind of personal. I love storms, but I've got an incredible phobia of them as well. Not sure if that makes sense, but when I found that the API I was using supported the feature, I had to include it.\nWhen more cities are added, you use a swipe interface to switch between them. I don't yet support a way of deleting cities, but I'll add that sometime in the future. (Maybe. I tend to say stuff like that and never actually get around to doing it.)\nNow let's take a look at the code.\nFirst, the main (and only) view, home.html:\n\nThe top portion of the template is the simple header. You can see the button used to add locations as well.\nNext, I've got two &lt;ion-content&gt; blocks. This is how I handle that initial view when you have no locations. When there are locations, I loop over them and display weather data for each. You'll notice I'm using a second array, weatherData, for probably what will end up being a dumb reason, but let's worry about that later.\nThat's the view, now let's discuss the services I use. When you enter an address, I use Google's Geocoding API to reverse geocode the address to a longitude and latitude value. Here's that code.\n\nAll I do is call their API with the address and massage the result a bit to make it simpler for the caller.\nThe Weather service is provided by Forecast.io. They have a great API, and I thought the storm info was neat. My only real concern is their support. I'm not a paying customer, but I reached out to them two days ago with a question and never heard back from them. (For folks curious about my question, the storm info they provide includes a distance and a bearing, but not a location. So you know how far away a storm is and the direction it is heading, but not the exact location of the storm. So basically you can't tell if the storm is approaching you.) Here is the code for that service.\n\nIn this service, I don't change the result at all so it is much simpler.\nSo - all that leaves then is the logic for the home page itself. It is... a bit messy. I want to talk about what I did before I just dump a bit page of code down so that it will - hopefully - make a bit more sense.\nIn general, my logic begins with a check to see if you have locations. I use LocalStorage to store an array of location objects. I kept this code within the home logic and didn't abstract it out to a service itself, but obviously that's something I could do too. When I store a location, it is only after the Google Geocode call, so I have a place name and longitude/latitude pair.\nHere comes the fun part. When I render out the location, I do an async call to fetch the weather for the location. I ran into issues when I added new locations and they didn't have their weather yet. This led me to working with 2 different arrays, locations and weatherData. I keep them in sync so that index N of locations matches index N of weatherData.\nI want to stress that I don't necessarily think this makes sense... but it worked. The last bit I added was the background image. That ended up being a royal pain in the rear because... well... CSS. The Yahoo Weather app does some cool Flickr integration where they'll show a picture relevant to your location. I decided to go much simpler and pick hard coded images based on the weather type. Here is what my CSS is now:\n\nSo my code checks the weather and then applies the appropriate class. I've only done two so far (and Forecast.io doesn't actually give you all the possible condition values) but obviously it wouldn",
		"tags":[
	        
            "ionic",
            
            "cordova"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Assets and slides from my JS Templating Presentation",
		"date":"Thu Jun 16 2016 17:35:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1466098500,
		"url":"https://www.raymondcamden.com/2016/06/16/assets-and-slides-from-my-js-templating-presentation",
		"content":"The title pretty much says it all. If you attended my session at devObjective today, I've attached the\nslides and demos for my talk on JavaScript templating below:\n\nhttps://static.raymondcamden.com/enclosures/devobjstemplating.zip\nAs a reminder, if you want to learn more about this topic, you can pick up my O'Reilly video\nseries on the topic here: JavaScript Templating. I cover four different products in depth as well as ES6 Template Strings. The video runs for a bit over 2 hours and all source code is included.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Very cool browser extension - Wappalyzer",
		"date":"Mon Jun 13 2016 15:08:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1465830480,
		"url":"https://www.raymondcamden.com/2016/06/13/very-cool-browser-extension-wappalyzer",
		"content":"This is normally the kind of thing I'd just Tweet about, but the more I thought about it the more I wanted to ensure I reached out to people who don't use Twitter. A few days ago I discovered an incredibly cool browser extension called Wappalyzer. The extension (natively supporting Firefox and Chrome but available as a bookmarklet too) serves a simple purpose: Given the current site you're on - what technologies are being used?\n\nSo that may seem a bit weird, so let me share a few examples. First, here's what you see on raymondcamden.com:\n\nAs you can see, it's reporting on the various JavaScript libraries I use - which is easy enough to figure out - but it also determined that I'm using Hugo for my static site generator, which frankly blogs my mind. Here is another example - IBM.com:\n\nAnd here is Adobe.com:\n\nOk, so what's the big deal? As a web developer, I'm fascinated by how people build sites. As we know, there's an incredible amount of options there for the modern web developer and sometimes it's useful to see where these things are actually used in the wild. This is one of the reasons I built my own Chrome extension, LocalStorage Monitor, that lets me know when a site is making use of LocalStorage. It wasn't that I couldn't find this on my own (DevTools makes that trivial), but I wanted my browser to alert me to when a site was using the feature so I could see how they did it. (As an aside, I've already filed a request with Wappalyzer to add a similar feature.)\nIn the same way, you could just view source. While &quot;view source&quot; is probably one of the best things you can do as a new web developer, I love that the extension makes it a bit simpler to see the details while browsing.\nAnyway - I think this is pretty darn cool and I definitely recommend adding it to your browser!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An example of Nexmo's Communications API",
		"date":"Thu Jun 09 2016 20:16:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1465503360,
		"url":"https://www.raymondcamden.com/2016/06/09/an-example-of-nexmos-communications-api",
		"content":"Earlier this week I got a pretty interesting request from a client: He wanted to use a service that would make a voice call to a number, ask them to record a message, and then store the result. He had an API already in mind, Nexmo.\n\nNexmo has a suite of APIs related to communication, of which voice processing is just one of them. Their Voice API covers a variety of different aspects including the ability to design a complete &quot;phone tree&quot; - you know - the &quot;Dial 1 if you want X, Dial 2 if you want Y&quot; thing we all love. Their docs are good too, but I had some difficulty wrapping my head around the process first.\nSo given our need (given a phone number, call me and get a recording), the process works like so:\n\n\nPrompt the user for the phone number.\n\n\nYour server code makes a HTTP request to Nexo saying, &quot;Hey, I want you to call this number, and I want you to execute the script at URL so and so.&quot;\n\n\nThis is the part that confused me. Instead of just telling the API want to do, you have to set up a script on your server that includes the directions for the call. This is written in &quot;VoiceXML&quot;, which I had never heard of before but anyone can write XML.\n\nOk, so you have to build that XML, and the XML is essentially the script that Nexmo will use when calling you. This is basic TTS (Text to Speech) stuff so you want to be a bit careful what you type. My client had a web 2.0 name, and you know what those look like, so I spelled it phonetically instead. You also include instructions telling Nexmo where to send the recording. You can also include other information, like a session ID, etc, to help associate the recording with the file.\n\nAll in all not too terribly complex. So how about a demo? First off - before you use this code you will need to sign up for an account. This will give you your account keys as well as setup a number that is usable for receiving calls. You get 10 English pounds of credit which I think is like 5 million American dollars or so. No idea. In all my testing though I've only used about 25% of my credit so it has been enough so far. I'll skip over the code that is boilerplate Node/Express, but if anyone wants a complete zip, just ask.\nSo my first page is just a regular HTML page:\n\nNothing too scary here. Note that I used both a placeholder and a value attribute for the code, which doesn't make sense, but whenever I'm working on code that includes a form, I almost always hard code crap into my forms so I don't have to type. The idea being that I'd remove those hard coded values before going into production. The JavaScript is literally nothing but &quot;listen for the form submit, get the phone number, and do an Ajax post to /ring. Again, I'll happily share it if folks want to see it. Ok, so let's take a look at the Node side. Here is a snippet of app.js:\n\nSo a few things here. First, I'm getting a copy of my little Nexmo &quot;library&quot;, which right now is all of one method that I'll show in a second. I initialize it with my account keys and a &quot;from&quot; number that will be used when dialing. My /ring handler looks for a form POST with the number to dial and then runs a method on my library called call. I pass in the phone number to call as well as the URL Nexmo should use to get the VoiceXML stuff I mentioned before.\nThis is where we do a quick digression. For Nexmo to work, it needs to hit your server, which in development can be a problem. But then I remembered the awesome ngrok. This is a free service that creates a tunnel on the Internet to your local machine. I even wrote about the service way back in 2014 (Expose Yourself with ngrok).\nAt the command line, I simply typed ngrok http 3000 and I had my tunnel set up:\n\nAlong with a nice 'report' in the terminal, you also get a cool web based admin which lets you inspect the calls to and from and your server. For something like the Nexmo API, this was crucial and incredibly helpful. While I don't have it in my demo here, the original demo I did for my client had dynamic aspects to the VoiceXML and ngrok was able to show me the XML I was sending back to Nexmo. In case you're curious, here is how it looked for my testing.\n\nOk, so once ngrok gave me an 'external' domain, I was able to use it in my code. You see it in use in the code above, now let's look at my nexmo.call method.\n\nAs I said, I've only built one method, and to be clear, the APIs at Nexmo are quite a bit more involved, but this was sufficient to do what my client needed. As you can see, all of the arguments passed to call basically just get appended to a URL. That includes the responseUrl thats a route on my local app that ngrok will proxy access for me. Whew. Got all that? And yeah - I'm not doing any error handling in this demo. Ok, so let's look at /response:\n\nThe comments in the handler mostly explain what I'm doing. I wrote my VoiceXML response as a file but I needed one small part of it to be dynamic. I could have used a templatin",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Introduction to API Connect Webinar",
		"date":"Wed Jun 08 2016 14:31:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1465396260,
		"url":"https://www.raymondcamden.com/2016/06/08/introduction-to-api-connect-webinar",
		"content":"Yesterday I gave a presentation on IBM API Connect. If you want to learn more about it, you can watch the recorded presentation via the link below. Unfortunately, you need to register, but that should be a pretty painless process. Check it out and if you have any questions, let me know in a comment below.\n\nIntroduction to API Connect\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Capturing camera/picture data without PhoneGap - An Update",
		"date":"Fri Jun 03 2016 21:41:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1464990060,
		"url":"https://www.raymondcamden.com/2016/06/03/capturing-camerapicture-data-without-phonegap-an-update",
		"content":"Over two years ago I wrote an article about using the device camera on the web without using PhoneGap (Capturing camera/picture data without PhoneGap). While an old blog entry, it is easily one of the most popular entries on this blog in terms of how much traffic it gets. It is also a great reminder that most of us web developers probably forget just how powerful the platform is. The original article discusses the use of the capture attribute in input fields to allow for camera access. The idea was simple - take a regular input/type=file and add the capture attribute:\n\n\nWhen used, the browser would then prompt you to select a picture or take a new one with your camera. You can see the original demo below, and my god, iOS has really come a long way since then.\n\nI thought it would be interesting to take a look at this feature and see if it has changed any in the past two years.\nI began by checking into the official spec. This proved a bit difficult. While there is a spec for Media Capture and Streams, I was more interested in the particulars of the syntax in regards to the input tag.\nI went to MozDevNet's input page and it had this to say about the capture attribute:\n\nWhen the value of the type attribute is file, the presence of this Boolean attribute indicates that capture of media directly from the device's environment using a media capture mechanism is preferred.\n\nNote - &quot;this Boolean&quot;. When I last worked with this code it wasn't a boolean but rather a value specifying camera.\nTo make things even more interesting, this older article from 2013, Capturing Audio &amp; Video in HTML5 used the accept attribute instead:\n\nBack on the MDN page, if you look at the accept attribute, nothing about this is mentioned. However, their docs on capture link to a spec that mentions:\n\nIf the accept attribute's value is set to a MIME type that has no *associated capture control type*, the user agent must act as if there was no capture attribute.\n\nNotice the &quot;associated capture control type&quot;? So that seems to imply that - officially - you should use capture=&quot;true&quot; along with the accept attribute which may or may not specify a particular capture type or may stay nice and vague, i.e. accept=&quot;video/*&quot;.\nClear as mud, right? Ok, so let's play with this a bit. I built a demo that tried to hit a few variations of this:\n\nYou can see four different input fields. The first uses the capture attribute, while the second uses it as part of the accept attribute and the final two use them both, as I believe the spec dictates. Also note the last is asking for video. So let's see what happens.\nMobile Safari\nThe first and second buttons behave the same way. Consider the screen shot below.\n\nNotice the &quot;Take Photo&quot; and &quot;Photo Library&quot; options allowing for both a new and old picture. Notice Dropbox. I only see that because I clicked More:\n\nI was a bit surprised that Google Photos didn't show up here, but maybe they need to do something to let iOS know they are a &quot;provider&quot; for images.\nWhile iOS prompted for a picture, I was able to go into the photo library and select a video. I was also able to go into Dropbox and select a file that wasn't media at all. This is not a bug and I would hope that if you are building an app that accepts any type of upload that you always verify on the server that the right type was sent!\nHere's where things get interesting. My third test, which asks for an image but doesn't specify camera anywhere, had this result:\n\nAs you can see, now it's asking for either a picture or video. Finally, the fourth option - and it's what you expect - prompting for a video:\n\nMobile Chrome\nMobile Chrome does things a bit differently. The first input opens up the device camera. The second prompts you for an 'action':\n\nCamera and Camcorder act as you expect. Documents let me browse my tablet, and notice Dropbox shows up here too.\n\nAnd just like iOS, I can go into Dropbox and select something that isn't an image or video.\nInput #3 acts just like Input #1 - it opens the camera. I honestly can't tell you why.\nFinally, input #4 also opens up the device camera, but sets it in video mode automatically.\nDesktop Chrome\nSo for the heck of it, I decided to try a few desktop browers as well. Here is what I saw.\nLike Mobile Chrome (and it shouldn't be surprising), input #1 and #3 act the same. The open a file selection prompt that defaults to Images:\n\nAnd notice how you can simply change Format and select non-image files. Input #2 acted as if I didn't want an image:\n\nFinally, option 4 prompted for video files:\n\nDesktop Firefox\nFirefox acted the exact same way as Chrome. Inputs #1 and #3 selected a format of Image Files, #2 wasn't filtered at all, and #4 set a format of &quot;Video Files&quot;.\nSo at this point - I tried a fifth test. You won't see it in the earlier screen shots, but here it is:\n\nThis &quot;felt&quot; like it should match the spec best, but both Firefox and Chrome tre",
		"tags":[
	        
            "javascript"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adding (Limited) Pagination to Hugo",
		"date":"Tue May 31 2016 22:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1464732060,
		"url":"https://www.raymondcamden.com/2016/05/31/adding-limited-pagination-to-hugo",
		"content":"This is probably only going to be useful to a limit audience but I thought I'd share. When converting my site from WordPress to Hugo I discovered that the built-in pagination support was a bit problematic for my site. Why? I have a large amount of content and at the default page size, I ended up with over five hundred pages of - well - pages.\n\nTo me - the solution was simple. Just get rid of the pagination. Frankly, I rarely &quot;page back&quot; to see older content. Heck, I rarely visit a blog's home page anyway. I'm typically on a blog because of a Google search result. Recently though I found myself trying to get links for my own content from a week or so ago and wanting to do a bit of paging. I did some basic research into how I could add pagination, but only for a limited subset of my content, say five pages or so. This is how I did it.\nFirst, I edited the main pagination partial. My copy was blank as I didn't want pagination at all, so I copied the version from my theme and added a bit of logic.\n \nEven if you've never seen Go templates and Hugo before, you can probably figure out what's going on here. My change was literally just the IF condition wrapping the logic. I chose 5 as the total number of pages but you could use any number here.\nThe next change was in the article_list partial. My code was initially this:\n\nAgain - I assume if you've never seen Go/Hugo before you can guess as to the logic here. The default code uses pagination and had looked like this before I removed it:\n\nSo my goal was to bring pagination back, but to limit it to the subset of posts that would be covered by the 5 pages of content I wanted to support. Here is what I came up with:\n\nObviously that's a bit of a hack. In theory I could use a site-wide variable and handle both parts dynamically, but for now, this is what worked for me.\nActually, it didn't quite work. It seemed fine when I tested, but when I generated my static output, I was getting a huge amount of additional pages due to pagination. I posted on the forums and actually got support from the developer who implemented pagination for Hugo. That's what I call good support!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working on my NativeScript RSS Application",
		"date":"Fri May 27 2016 15:32:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1464363120,
		"url":"https://www.raymondcamden.com/2016/05/27/working-on-my-nativescript-rss-application",
		"content":"A few days ago I blogged about my experiences building a simple RSS reader with NativeScript. Today I'm coming back to the application to take care of two problems I mentioned in the previous post.\n\nThe first problem I wanted to handle was checking for network connectivity before trying to load the RSS feed. In Apache Cordova, this is done via the Network Information plugin. I began searching the NativeScript docs for something similar and was surprised to discover that nothing like that existed.\nExcept I was wrong.\nI kinda touched on the docs in my first post on NativeScript. I mentioned how I thought the tutorial was well done, but I found the rest of the docs a bit awkward at times. I'm upgrading that initial impression from &quot;awkward&quot; to &quot;frustrating.&quot; There are multiple issues with the docs that makes finding particular APIs kind of difficult. As an example, I needed to figure out how to navigate to a page but specify no back button. I knew that existed but it took maybe 15 minutes to find the darn docs, and I only found it this time because I remembered the particular name of a class. I couldn't get there via straight clicking.\nTo make matters worse, the code that handles what I needed to do is found under the &quot;Cookbook&quot; section of the docs. Now I don't know about you - but to me - &quot;Cookbook&quot; implies a set of examples in the form of, &quot;Do X with NativeScript&quot;. It absolutely does not imply &quot;API Reference&quot;, but that's what it is. Kinda. I honestly don't know. I did find the network connectivity stuff there, but then other things like UI components are not there. The docs really need a clearer TOC or organization. All the stuff is there, but now that I'm beyond &quot;Hello World&quot; in NativeScript, I'm struggling to actually use it.\nEnough complaining - and as an FYI - I did the right thing and tried to summarize this in reported issues on their GitHub repo.\nTo detect a network connection, you simply need to import the connectivity module. I added this to my initial JavaScript file for my view. Maybe I should do this in my app.js instead. I then simply ran connectivity.getConnectionType(); to get the type. This returns a value that represents either none, wifi, or mobile. You can also use it to monitor changes as well. To keep things simple, I just check it once. Here's my new main-page.js.\n\nAs you can see, I simply navigate to a new view if the user is offline. Note the use of navigationEnty as a way to pass configuration information about the transition itself. For my new view, I simply added a message and a button the user could click to see if they were online. Again - the connectivity module supports events so I could have automated this. First - the view.\n\nAnd here is how it looks.\n\nNot the prettiest UI, but that's my fault, not NativeScript. For now, it gets the job done. Here's how the button works.\n\nThis is pretty much what you would expect - check connectivity and if still bad, let the user know, otherwise send them to the home page.\nWoot! Ok, now for the next change. Some RSS feeds only provide a snippet of text. I wanted to do something similar to Cordova's InAppBrowser, a &quot;mini&quot; web browser that can be closed leaving the user back in the app. I was not able to find that for NativeScript. I did, however, find a way to at least open the system browser. I began by modifying the view to include a bottom at the end of the page. (Thanks to the helpful folks in the NativeScript Slack channel!) Here's the new code.\n\nAnd here's the result:\n\nThe code for this is pretty simple:\n\nAnd it works as expected:\n\nThe big issue though is that the user has now left the app and may not know how to return. Maybe someone in the NativeScript community would have a suggestion to help out here.\nAnyway - as before - I've included the full source code of this in my NativeScript GitHub repo. You can find it here: https://github.com/cfjedimaster/NativeScriptDemos/tree/master/rssTest2\n",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Building an AJAX-based form for Formspree",
		"date":"Wed May 25 2016 01:50:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1464141000,
		"url":"https://www.raymondcamden.com/2016/05/24/building-an-ajax-based-form-for-formspree",
		"content":"Back when I converted my site from WordPress to Hugo, one of the issues I had to take care of was setting up a processor for my contact form. I decided to go with Formspree as their free tier easily handled the amount of form submissions I got per month. While most folks will use Formspree with a &quot;regular&quot; old form post, you can also use a fancy-pants Ajax submission as well. The Formspree folks document this, but their example is rather short, and when I was asked by someone on the Surge Slack about a full example, I decided to whip something up.\n\nTo be clear, and more on this at the end, this was a quick bit of code just to give that user a &quot;real&quot; example they could take and modify. There's many different ways of doing this and what I've built here was done in about five minutes.\nMy example consists of two files - an HTML file and a JavaScript file. There's no styling involved but I assume folks can handle that on their own. First, the HTML.\n\nThe only thing particularly interesting here is a hidden div used for the &quot;thank you&quot; message. Formspree supports sending you to a &quot;thank you&quot; page when you send a regular form POST to it, but when working with the Ajax version we need to handle this ourselves. I could have also just changed the HTML on the fly and injected a message, but this seemed simplest. My form has 3 fields arbitrarily chosen for the demo.\nNow for the JavaScript:\n\nSo I assume this is pretty vanilla jQuery, and of course, you don't have to use jQuery. I get my data, validate it (well, I wrote a comment saying I would), and then simply POST to Formspree. I do manipulate the data a bit. First, I pass the email value twice. Why? By passing it as _replyto, I can actually reply to the email Formspree sends me with the form contents. (As an FYI, I completely missed the fact that Formspree will treat a field named &quot;email&quot; as the replyto as well. So my code there was unnecessary. This is definitely documented, but I missed it.) I still want to see the address so I include it again. _subject doesn't come from the form at all, but is used by Formspree to set the subject line of the email sent.\nWhile that's it - let's quickly look at what happens when you use this code. First off, don't forget the Formspree requires you to validate a form before it will email you. If you actually look at the result of the POST in your form tools, you'll see this the first time you run it.\n\nThe good news is that Formspree still sends the email, but it only does so once and you must confirm it. When you deploy this code to your production site, be sure to quickly confirm it. Their email does a good job of making it real clear you darn well better do so:\n\nWhen you do confirm, this is the response JSON you get:\n\nThe Formspree docs don't really describe in what situations you would get an error. You could, I suppose, notice when success isn't there and handle it by telling the user that your forms are currently broken and they should simply email you instead. And of course, it just plain works:\n\nSo since this discussion came up on the Surge Slack, I went ahead and Surged it. Because why not? You can run this demo here: http://hospitable-cushion.surge.sh/test.html. I can't promise it will be up forever, but I'll run it for now.\nI hope this helps, and definitely check out Formspree - it is a great service. If you want, you can stop reading now, in fact, I encourage it. What follows is 100% off topic for the rest of the post.  Seriously - I don't mind if you stop.\n\nIn a recent blog post, I got called out about the quality of code I used in an example. Many of the points made were absolutely true, but frankly, it was incredibly insulting and literally had me close to simply not blogging again. (To be clear, I'd still write, just not on my personal blog.) Because I feel it needs to be said, here are some things to keep in mind when reading this blog.\n\nThe code I write here is part of how I learn. That means, many times, you're seeing code from the beginning of my process of learning a particular language or technique. I think that has merit. I wish more beginners would share this part of the process. It helps flesh out issues with documentation and process that you don't get when only the experts are speaking.\nWhen I'm explaining something, I absolutely do not go for the most tight, or short, code and will often break things that could be written on one line into many. I want my code to be readable, and approachable, and that is not always the same as production code.\nDo I worry about misleading people? No. Frankly, there is a group of developers who cut and paste. You can call them StackOverflow developers if you want. You know them because as soon as they need to do something beyond the code they copied, they have no idea how to do it. I'm not going to belittle them because I've done it myself. Now - I think a good developer needs to recognize when they're doing that and take resp",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "A simple RSS reader in NativeScript",
		"date":"Mon May 23 2016 14:17:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1464013020,
		"url":"https://www.raymondcamden.com/2016/05/23/a-simple-rss-reader-in-nativescript",
		"content":"Last week I wrote up my initial\nthoughts on working with NativeScript. After working through the getting started guide, I thought I'd take a stab at building a simple app, a RSS reader. Before going further, note that you should assume my code is crap. It works - but I'm sure I've done things like - well - a noob. Because I am. So I'd think twice before using my code. (Although you are welcome to it - I'll have a link to the code at the end.)\n\nMy RSS reader consists of two screens - an initial list based on the entries from an RSS feed and a detail page for the actual blog entry. Here's the initial screen.\n\nTo be clear, that lovely red header there was me using my design chops. Don't blame NativeScript for that. Anyway, here's the detail view:\n\nThat's a lot of text (partially because that blog entry really does have a lot of text at first) and not terribly nice looking, but it works. Now let's take a look at the code.\nFirst off, the home page view, which is really just a list.\n\nNothing too special here. You've got a list with a tap handler. On top you can see a call to loaded() for when the page loads. Now let's look at the code behind this.\n\nSo for the most part, this too is rather simple. Most of the logic is in a view module. This file basically handles asking the view model to do it's thing and return a list of RSS entries.\nI do want to point out one thing. Notice in loadItem() I call a set operation. This is how I handle &quot;I'm leaving this view but want to remember what I clicked.&quot; This one thing took me roughly 70% of the development time for this project. Why? At first, I was creating an instance of my view model, not just requiring it. I did this on my detail page too. That meant when I set a value on it on the list page, I lost that when the object was recreated on the detail page. That seems trivial, but it took me forever to get around that.\nI also discovered later that you can pass random data to another view via navigate. You can see that described here in the docs. I didn't see that at first because when I went to the API reference in the docs, I was initially on the &quot;Module&quot; for Frame and not the &quot;Class&quot; for it. I honestly don't know the difference (I just asked on Slack though so hopefully I'll get a clue ;).\nNow let's look at the detail page.\n\nAgain - fairly simple. I first used a TextView and of course that doesn't render HTML. I did find odd performance issues with this control. The first few views worked perfect. Then I saw a noticeable lag in rendering the view. I'd say maybe 2-3 seconds. I'm fairly certain it is probably my code, but I've let me friends on the NativeScript code know about what I encountered. Ok, so now the code behind the view.\n\nBasically I ask for the data I saved in the previous view and update a local observable. I had tried to bind directly to my instance of RssListViewModel, but noticed that content only updated one time. Again - that's possibly my fault.\nFinally, let's look at the view model. I used one of the methods I described in my blog post on the topic - Parsing RSS Feeds in JavaScript - Options. Just in case it isn't obvious - yes - I used something in NativeScript that worked perfectly fine for Cordova too. While I may struggle a bit with the UI of NativeScript and other aspects, being able to re-use stuff I've already learned in the hybrid space is a big win. Anyway, the code:\n\nThis is - I assume - mostly self-explanatory, but let me know in the comments below if anything isn't clear. There's one more file I didn't show yet - a simple config object I can use to quickly change the title of the app and the RSS feed:\n\nThere's two things missing from this app that I'd like to correct. First, a good mobile application should recognize when it is offline. I need to update the app to notice that, let the user know, and possibly start working again once network connectivity is restored. Secondly, many RSS feeds only contain a small portion of the entry text. I'd like to add a button that would open the entry in the native browser for proper reading.\nWant the complete code? (And again, remember that it is code being written by a noob. I'd hate to be accused of leading people to bad code.) You can find the complete source here: https://github.com/cfjedimaster/NativeScriptDemos/tree/master/rssTest1.\n",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Thoughts on NativeScript 2.0",
		"date":"Mon May 16 2016 16:55:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1463417700,
		"url":"https://www.raymondcamden.com/2016/05/16/thoughts-on-nativescript-20",
		"content":"When I began working with mobile development, it was with Adobe Flex Mobile. It was pretty interesting, but we all know where that ended up. Fairly shortly after joining Adobe's developer evangelist program (ditto), Adobe acquired PhoneGap and I began to really dig into the hybrid mobile development platform. I think I've been pretty obvious about my love for it (and Apache Cordova and Ionic) but certainly these are not the only way to build mobile apps.\n\nI did a bit of research into pure native development with iOS, and I thought it was interesting, but it just didn't quite appeal to me the way hybrid did. I won't deny that native gives you the top level of performance and customization, but being able to use my web skills to create mobile apps was just too damn fun for me to ignore.\nI've been aware of NativeScript since it's release but I just never had the chance to actually sit down and play with it. With the release of 2.0, and it's Angular2 connection, I figured it was probably time to make the time to at least kick the tires a bit and see what it was like. Obviously what follows are my initial impressions and not based on any real work. But hopefully this will get people interested enough in the product to check it out themselves. As always, I encourage folks to leave some comments below with their own thoughts (especially if you've actually gone to production with it).\nFirst and foremost, how does NativeScript compare to hybrid solutions like Cordova? Cordova works by creating a mobile app with a web view. That means all of your UI and UX are driven 100% by what is supported by the web browser on the device. Technically that isn't entirely accurate as you can use native APIs via plugins (see the Dialogs plugin) but for the most part, you're going to be using HTML, CSS, and JavaScript for everything. Native functionality is done via a plugin API that lets you use JavaScript to speak to the device using custom native code.\nNativeScript bypasses this and goes straight from code to native UI and UX. It does this via JavaScript, CSS, and XML. Yes, XML, and I know some folks are kinda throwing up at that, but for me, it reminded me a lot of Adobe Flex, and for layout, it works just fine. It does mean that you have something new to learn, but layouts pretty much follow a standard pattern (lay crap out horizontally, lay crap out vertically, etc).\nInstallation is a quick npm setup as you would expect, but note that you'll want to have your Android and iOS SDKs downloaded and prepped as well. One thing I ran into right away was that even though I had those SDKs working well with Cordova, something in NativeScript wasn't necessarily happy with how I had them set up. I ran into two issues that the CLI complained about. One was easily solvable (the error itself told me what to do), the other wasn't as easy to get around but I was lucky enough to get some help from the NativeScript team. My engineer contact helped me out and logged a bug about the not-so-helpful error I ran into so it's something that will go away in the future. (And if folks are curious about what I hit I can share in the comments.)\nOnce you get it installed, you have the option of selecting two different tutorials: Get Started with JavaScript or Get Started with TypeScript &amp; Angular. Now - as much as I'm really interested in both TypeScript and Angular, I just felt like it would make more sense for me to go with the simpler route.\n\nThe tutorial took me about 2 hours - although I broke that up over a week so I could take a chunk in at a time. It does a great job of slowly introducing you to the various components and demonstrating multiple different aspects of NativeScript development.\nSo what's the code like? Let's start with one of the XML files for the view. I'm presenting this as is and I know it won't make much sense to you, but read it. I'd be willing to be you can grok what's going on. This one represents the view for a list of items you have saved to a back-end system.\n\nEach view can have customized CSS. Here's the CSS for that view:\n\nAnd then finally the logic behind the view, written in trusty-old JavaScript:\n\nSo this one probably looks a bit confusing but not scary. I know I'm presenting this all with little to no context, but if these code samples look like something you can work with, then you can definitely give NativeScript a shot.\nFrom a developer perspective, it also feels a bit like Cordova. You use the CLI to send it to the emulator or device and can even do a live-reload which is handy during development. (This live-reload feature doesn't cover all cases, specifically changing JavaScript, but there's ways to enable that too.) You can also get an extension for Visual Studio Code to add some hooks to the command line from within the editor.\nOne of the more fascinating aspects of NativeScript is the ability to talk right to the native layer via JavaScript. Both Cordova and NativeScript support plugins for native access, but ",
		"tags":[
	        
            "nativescript"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Is it ever appropriate to hide an async process behind a sync one?",
		"date":"Fri May 13 2016 12:46:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1463143560,
		"url":"https://www.raymondcamden.com/2016/05/13/is-it-ever-appropriate-to-hide-an-async-process-behind-a-sync-one",
		"content":"So this is something I'd consider posting to StackOverflow, but it feels like it may not be 100% appropriate for SO and as we know, if you ask something not appropriate, you get knocked down pretty quickly. While this is a technical question, it is also an opinion one as well, so I'm not sure. I figure if I'm not sure, I might as well just ask here and not worry about the SO Overlords approving of my question. I approve, and that's all that matters. ;)\n\nOk, so let me start with some background. Almost two years ago I created a simple Cordova app that demonstrated writing to a file: Cordova Example: Writing to a file. I won't repeat all of that code here, but this is the simple utility function I wrote to support a basic logging system.\n\nlogOb is a File object made on startup. This method simply takes input, like &quot;Button clicked&quot;, prefixes it with a timestamp and then writes it to the end of a file. Notice, and this is crucial, it has no result. It just does its work and ends. To me, that's appropriate for a logging system, you don't really care when it finished.\nUntil you do...\nBack on that earlier post, someone noticed an issue when they did:\n\nBasically only one or two items were being appended. Looking at the code now, it is pretty obvious why. Since the logic is async, the multiple calls to it firing at the same time mean the file object is being manipulated by multiple calls at the same time, leading to random, unreliable results.\nSo the fix is easy, right? Add a callback to writeLog or perhaps a Promise (shiny). Then it is the user's responsibility to ensure they never fire call #2 till call #1 is done. Maybe something like this:\n\nThat's not too bad, but here's where my question comes in. What if we made writeLog (and pretend for a moment that I have it in a module) more intelligent so it simple queued up writes? So in other words, if I did N calls, it would simply handle storing the 2-N calls in an array and processing them one by until done. I'd essentially hide the async nature from the caller.\nNow everything about me says that smells wrong, but on the other hand, I'm curious. Is there ever a time where you would think this is appropriate? Certainly for a logging system it seems ok. At worse I send 100K calls to it and the app dies before it can finish, but in theory, we could live with that.\nThoughts?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Uploading multiple files at once - with Fetch",
		"date":"Tue May 10 2016 20:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1462911660,
		"url":"https://www.raymondcamden.com/2016/05/10/uploading-multiple-files-at-once-with-fetch",
		"content":"So, my last couple of posts (Uploading multiple files at once with Ajax and XHR2 and Uploading multiple files at once - for Cordova) have both discussed a similar idea - using JavaScript and XHR2 to create POST operations with multiple files. As I mentioned in the first post, XHR2 (really just XHR) represented an updated version of the original XHR spec. What you may not be aware of is that there is another (because, well, JavaScript) API that aims to both improve basic HTTP operations as well as working with other new tech like Service Workers.\n\nWhile working with XHR isn't necessarily difficult, Fetch can be quite a bit simple, let's even say jQuery-simple. Here's an example:\n\nFetch does a lot more, and as always, I'm going to point out the super excellent resources at Mozilla Developer Network:\n\nUsing Fetch\nFetch API\n\nFor a new API, support is actually pretty decent. The CanIUse table for Fetch shows pretty good support in every browser except Safari and iPhone:\n\nAlthough apparently Fetch will be in the next update to Safari. (Right after they get IndexedDB working right.)\nSo all in all, it is an interesting API, and I was curious to see what it would be like if I updated my first demo to make use of it. Here is the original code fired by the upload event.\n\nAnd here is the Fetching version.\n\nAll in all, I think this version is one line longer, but I actually included error handling so that's not a fair comparison. That being said, I actually prefer the Fetch API, so to me, this is a net win. And of course, there is a heck of a lot more to the API than what I've shown here. The Google Developer's site has an excellent article on the topic (Introduction to fetch()) that includes a great example of some of the power you get:\n\nYeah - I dig that. Anyone using this in production yet?\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "development"
            
		]

	},

	{
		"title": "Uploading multiple files at once - for Cordova",
		"date":"Fri May 06 2016 16:45:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1462553100,
		"url":"https://www.raymondcamden.com/2016/05/06/uploading-multiple-files-at-once-for-cordova",
		"content":"Yesterday I blogged about how you could use XHR2 to upload multiple files in one POST operation. This was a followup to an earlier post (Processing multiple simultaneous uploads with Cordova) discussing how to send multiple files with the Cordova FileTransfer plugin.\nUnfortunately, while my post yesterday will work fine on the desktop, it won't work in Cordova apps that disable the use of file inputs. Another issue is that it doesn't demonstrate how you would integrate it with the Camera. I've figured out how to get this working so let's take a look.\n\nFirst off - be sure to look at the older older demo so you have a basic idea of the functionality. Basically we've got an app with 2 buttons - select a picture and upload.\n\nWhen you select a picture, it is 'drawn' onto the app so you can see what you will be uploading.\n\nAll of this code can be found on the earlier blog entry so I won't go over it again. Instead, let's focus on the upload part.\nWe've got an array of image URIs that represent paths on the device file system. My initial plan was to loop over the array and create File objects from each URI. My first draft was something like this:\n\nBasically we turn the URL into a FileEntry object and then turn that into a File object because the FileSystem has to be complex so we can all keep our jobs. The crucial part is this: fd.append('file',file). This should have worked exactly like the code from yesterday's post. However, something weird happened. When the post was sent, my Node server never got any file data. When I used dev tools to inspect the network data, I saw this:\n\nFor some reason toString was being called on the File object when passed to Form data. I then tried to pass a blob:\n\nBut that failed too. I did some searching and came across the solution (I'll credit them at the end!) - using a blob was right, but I really needed to use a FileReader to get the data. Now - I thought my file.slice code was doing the same, but... I don't know. It just didn't work. Adding in a FileReader finally did it. Here is the final part of the code (with another modification I'll explain in a bit as well):\n\nOk, so you can see the FileReader in play so I've got one more level of callback hell, but this isn't too bad (more like Callback Heck).\nEdit on May 9th: Please see Akash's comment where he noted the third argument to FormData.append allows you to specify a file name. I missed that! It should be used instead of my workaround here.\nOne thing I discovered though was that my Node server wasn't getting a good file name - it showed up as 'blob'. I added a new form field called fileNameX (X being an index) that included the file name. So on the server I have fileX which is the actual file and fileNameX which is the file name. Unfortunately, Android passes 'content' as the name. I decided to stop messing with the code at that point. You could add a bit of logic in there to rename it client-side or even do it server-side (perhaps a simple UUID). But I can confirm it works in both Android and iOS.\nYou can find the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiupload2\nCredit for this workaround goes to two people with similar solutions:\n\nThis gist by Fesor.\nThis StackOverflow solution by Joe Komputer.\n\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile",
            
                "javascript"
            
		]

	},

	{
		"title": "Uploading multiple files at once with Ajax and XHR2",
		"date":"Thu May 05 2016 20:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1462479660,
		"url":"https://www.raymondcamden.com/2016/05/05/uploading-multiple-files-at-once-with-ajax-and-xhr2",
		"content":"Almost a year ago I wrote a blog post discussing how to use the Cordova FileTransfer plugin to upload multiple files (Processing multiple simultaneous uploads with Cordova). In that post I demonstrated how you could wrap the calls to each upload in a Promise and then wait for them all to complete. A reader pointed out the obvious issue with that solution - for N files you're creating N HTTP requests. While probably not a big deal (and as the developer, you could put a limit on how many files were allowed to be sent), I thought it would be interesting to demonstrate how can upload multiple files with one POST request using XHR2.\n\nFirst off - a quick refresher. What is XHR2? Easy - the second version of XHR. Ok, sorry, that was a bit sarcastic. XHR is the abbreviation of XMLHttpRequest and is how JavaScript performs Ajax requests. Basically, XHR is what you think of as Ajax. If you've only ever done Ajax via jQuery, then just know that $.get is wrapping calls to an XHR object behind the scenes. Working with XHR without jQuery isn't terribly difficult - you can start with this great guide at MDN for a refresher.\nXHR2 represents the more recent version of the overall spec (details here) and includes the ability to send file uploads via Ajax. Previously this was done with products like Adobe Flash. Support for XHR2 is rather nice:\n\nAnd of course, if you want to use this in Cordova, you are definitely safe to do so, especially if you use Crosswalk on the Android side.\nSo, how do you send a file using XHR2? For that I'd suggest reading the excellent MDN article, Using FormData Objects, which walks you through the process of creating a FormData object in JavaScript and assigning a file to it. I won't repreat the documentation there as it is short and sweet (and once again I will remind folks that the Mozilla Developer Network is one of the best damn resources on the Internet for web developers) but rather focus on how we could use it to send multiple files at once.\nLet's begin with an incredibly simple handler for our uploads. The following Node application defines a route, /upload, that uses the Formidable package to process file uploads. It literally just dumps them to console so it really isn't &quot;handling&quot; it, but it gave me an easy way to see my form POSTs come in and ensure they were being sent the right way.\n\nOk, now let's look at the front end. For my first demo, I created the following HTML.\n\nAs you can see - I've got 3 form fields - all of which are file types. Now let's look at the code.\n\nSo first off - yes I'm using jQuery and naked XHR calls. That's ok. You don't have to use jQuery and that's fine too. Don't stress over it. I begin by adding a submit handler and a quick reference to my three fields. When you submit the form, I create a new FormData object. The MDN article I linked to earlier talks about it more, but you can think of it like a virtual form. I can add simple name/value pairs (like name = 'Raymond') as well as files. I check the value of each field to determine if something was selected. Here's an important point though. The value will be a string. In Chrome it looks something like C:\\fakepath\\aliens.jpg. Yes, I'm on a Mac and yes, that's a fake Windows path. The idea here is to obscure the real path so that nasty code can't be used as a vector to attack your system.\nThe FormData object though wants a real file. We use $f1.get(0) to connect to the real DOM item (not the jQuery-wrapped object) and then use the files property to gain read access to the selected file.\nThat's repeated for each of the three fields and then I simply post to my server.\nAnd yeah - that's it. When I test, I can see my files come in as expected.\n\nSo far so good. But don't forget that some HTML form tags support a multiple attribute. In v2 of my sample, I changed the three form fields to this:\n\nI can now select 0-N files when submitting my form. Here is the modified version of the code to handle that.\n\nI grab that files property and treat it like a simple array. It isn't quite an array though (MDN docs for FileList) as I have to use a item method to fetch the particular value. Then it's simply a matter of appending to formData with a unique name for each file. This is treated the exact same by the back-end so it just works.\nAll in all - pretty simple I think and could be used easily in a Cordova application. You can also attach a progress event handler and monitor the process of sending up the binary data. Enjoy and let me know if you have any questions by leaving a comment below.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Disabling Forms Disabling Autocomplete",
		"date":"Wed May 04 2016 21:21:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1462396860,
		"url":"https://www.raymondcamden.com/2016/05/04/disabling-forms-disabling-autocomplete",
		"content":"This isn't going to be the most important tip I've ever shared here, but as it is annoys the you know what out of me, I thought I'd share. I fill out a lot of forms, and one of the first things I do on a form is double click in the first field to give autocomplete a chance to fill in as many fields as possible. Unfortunately, sometimes this isn't possible because the site creator, for whatever reason (maybe even a good one) decided to disable this.\n\nAutocomplete can be disabled at both the form level and the field level. To disable it for the entire form, you would do:\n\nTo disable it on a field by field basis, you would simply use the same attribute in the field itself:\n\nAs I said, maybe you have a good reason for this. Wufoo seems to do this by default for their forms. However, this bothers me. I'm seeing it being used in forms that have nothing necessarily private/secure in them and it just annoys me. So here is a 30-second video showing how to quickly get around it in Chrome's Dev Tools. You can do the exact same thing in your browser of choice.\n\nNow if I could only apply autocomplete to all the various medical forms that require me to enter the same information multiple times my life would be complete.\np.s. As another tip - please don't ask for someone's birthday and their age. You don't need both.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "LoopBack, StrongLoop, and API Connect - how in the heck do they relate?",
		"date":"Wed Apr 27 2016 20:43:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1461789780,
		"url":"https://www.raymondcamden.com/2016/04/27/loopback-strongloop-and-api-connect-how-in-the-heck-do-they-relate",
		"content":"So first and foremost - let me start off by being explicitly clear that what follows is 100% not IBM-approved material at all. Yeah, I work for IBM, but I think folks know that when I blog, I have my own voice and my own way of saying things. I'll include links to all the boringofficial resources for folks who want that side of the story, but what follows is 100% my own words only.\n\nSo earlier today I was sent the following tweet:\n@raymondcamden what is diff between loopback &amp; API connect? SL docs say use apiconnect but loopback.io says use Strongloop&mdash; Justin James (@digitaldrummerj) April 27, 2016\n\nJustin is referring to a product, API Connect, that I haven't yet discussed on the blog. It is a big deal, and I've been trying to get up to speed on it (along with LoopBack and StrongLoop), which is why I haven't blogged on it yet. If you haven't heard about it, well, that's part of my job, and you will be seeing me present on it and blog about it more (both here and on the StrongBlog. But for now, let me give you some context to how these three products relate.\n\nI've blogged (and presented) on LoopBack a bunch of times now. LoopBack is an open source Node.js/Express-based framework for rapidly building APIs. What that means is I can quickly create a REST-based API based on a model, where a model represents the content I'm working with. If I'm a cat web site, I can quickly create a full API around getting cat data. We're talking five minutes from concept to an API. That's super powerful.\nOf course, in the real world, you need custom business logic, security, etc. LoopBack supports all of that.\nIn a nutshell though - LoopBack is what lets you build the API. It's open source and 100% free.\nMore details at http://loopback.io.\n\n&quot;StrongLoop&quot; by itself means the company that IBM consumed a few months ago. Many people, including myself, use StrongLoop as a way to referring to the 'product' but technically the product name is &quot;StrongLoop Node.js API Platform&quot;. That's a mouthful, hence people simply saying &quot;StrongLoop&quot; generally.\nStrongLoop is a commercial product that uses LoopBack. It has:\n\nA visual composer for working with LoopBack models and datasources. LoopBack's CLI can do the same, and you can just edit JSON files, but the web-based version may be simpler for folks.\nA process manager and deployment system that lets you push out your application to a production server as well as handling clusters.\nMetrics, tracing, and profiling services for debugging and performance tuning your application.\n\nSo basically - you built your cool LoopBack application, but now you want support in terms of ensuring it can handle load and for deploying to production. This is where you pay money for the added benefit. You can use StrongLoop for free, but only the visual composer and process manager features.\nNot to be confusing, but to work with LoopBack, you actually npm install strongloop. This gives you the slc command line that you use when working with LoopBack apps. If you watch my videos, you'll see me use it. To be clear, you do not need to use this CLI. You can add the right crap to package.json yourself, but the CLI is what you want to use, and it is free too.\nOk, so technically you can find out more at https://strongloop.com/, but here's where things get a bit weird. You can't buy StrongLoop licenses anymore because the future is API Connect. On to the future!\n\nAPI Connect, sorry, IBM API Connect, is a new offering, but the evolution of an older one called IBM API Management. So API Connect (APIC) is version 5, even though it is new (well mostly new).\nThis will be a gross oversimplification, but APIC can serve as the way you bring a LoopBack-created API from your local machine into the real world. What do I mean?\n\nWith APIC, I can take an API I made with LoopBack and require you to get a key before using it - like most APIs out there today. With APIC, the entire &quot;register and here is your damn key&quot; aspect is baked in. Done.\nWith that same key, I can also say that you get X hits per day. Or maybe Y. Whatever.\nHow do I know I should give you X? With APIC, I can look at stats and see which APIs are being used over others.\nUpdating your APIs? You can group together your current APIs as v1 and create a new grouping for v2.\nLoopBack has a nice 'explorer' which provides documentation and testing. APIC comes with the ability to create an entire &quot;Developer Portal&quot; out of the box.\nOh yeah, it can do Java and SOAP too, but, ugh, gross.\n\nSo as I said - I see LoopBack as being awesome for rapid prototyping. It does more, but, let's just go with that. I look at APIC as the &quot;lets release this to the world&quot; part. And yes, this part costs money (although you can test it and run it locally, forever, for free), but if you want to build all that yourself and just use LoopBack, be my guest.\nYou can find out more at https://developer.ibm.com/apiconnect/.\nSo... here's where thin",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Speaking at Gluecon 2016 - Want a discount code?",
		"date":"Wed Apr 27 2016 17:46:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1461779160,
		"url":"https://www.raymondcamden.com/2016/04/27/speaking-at-gluecon-2016-want-a-discount-code",
		"content":"I'll be speaking at this year's Gluecon on LoopBack. If you want to attend and need a bit of help to make it more affordable, use my super secret code here when you register:\n\n\n",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An example of virtualScroll and Infinite Scroll in Ionic 2",
		"date":"Mon Apr 25 2016 15:50:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1461599400,
		"url":"https://www.raymondcamden.com/2016/04/25/an-example-of-virtualscroll-and-infinite-scroll-in-ionic-2",
		"content":"Before I begin, a warning. At the time I wrote this blog post, Ionic 2 was still in beta. Also, I've barely begun to learn Ionic 2 myself. You should consider this code beta-level quality written by an inexperienced dev. On the other hand, if it still works perfectly, I'm going to pretend I was brilliant all along.\n\nA few days ago, the Ionic blog released a great entry on Ionic 2 and APIs: 10 Minutes with Ionic 2: Calling an API. In this post, Andrew describes how you can use the Ionic CLI to generate both an application and a boilerplate HTTP service, or more accurately, a &quot;Provider&quot;.\nIf you walk through this his tutorial you'll end up with a simple application that drives a list of people via the Random User Generator. I was thinking about how you would take this application and convert it to use an infinite (well, near infinite) list of people instead.\nTo begin, I used the Random User Generator to output a huge list of users. I didn't want to abuse their API so I did one big call, saved the JSON, and then imported that data into my LoopBack application running locally. (If anyone wants to see how that's done, just ask. I basically reused some of the logic from my blog post: Seeding data for a StrongLoop app). The net result was that I had a lot of &quot;People&quot; data that I could use via a REST API - a bit over two thousand.\nMy first change was to people-service.ts. The original code would cache the result. My modified code removes this cache and supports a parameter telling it what index to begin fetching results. This is all part of the LoopBack API and I'll be talking about that in a blog post over on the StrongLoop blog later this week.\n\nNote that I'm hard coding a perpage value. In theory the service could let me change that, but I wanted to keep things somewhat simple.\nNow let's look at the Home page. First, let's consider the view.\n\nThe changes here are two-fold. First, I changed the list to support VirtualScroll. This is the Ionic V2 version of collectionRepeat, basically a list optimized to handle a butt load of data.\nSecondly - I added the InfiniteScroll directive. This is pretty simple to use (you'll see the code in a moment), but don't forget this little gem in the docs: &quot;When this expression has finished its tasks, it should call the complete() method on the infinite scroll instance.&quot; Yeah, that's pretty important. But I'll pretend I didn't miss that. Ok, so let's look at the code behind the view.\n\nOk, so there's a few important changes here. First, I made loadPeople return a promise. I needed this so I could listen for it to complete when running my &quot;get new stuff&quot; code. I'm keeping a variable, start, to know where I am in the list of data and you can see doInfinite as the handler for fetching more data. Pay special attention to the infiniteScroll.complete() call. As the docs say, you need to do this to let the InfiniteScroll control know stuff is done. Also note that this.start+=50 is problematic since 50 has to match the perpage value in the service. I could make it detect how many items were added in the last call, but again, I wanted to keep it simple.\nHere is a snazzy animated gif of it in action:\n\nUnfortunately, the code does not appear to work in iOS. I have no idea why (no error is thrown), but it works fine in Android and via ionic serve.\nIf you want the complete source code for this, I'm more than happy to share it, but bear in mind this is part of a larger LoopBack app. I'll share a link to the entire application, but the Ionic specific stuff may be found in the app1 folder.\nhttps://github.com/cfjedimaster/StrongLoopDemos/tree/master/superlongscroll\n",
		"tags":[
	        
            "ionic",
            
            "strongloop"
            
		],
		"categories":[
            
                "mobile",
            
                "javascript"
            
		]

	},

	{
		"title": "GET vs POST for Ajax Requests",
		"date":"Thu Apr 21 2016 15:36:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1461252960,
		"url":"https://www.raymondcamden.com/2016/04/21/get-vs-post-for-ajax-requests",
		"content":"I was preparing to work on another blog post when it occurred to me that I'd share a tip that new JavaScript developers may appreciate. For people new to JavaScript and Ajax, one of the things you may not necessarily think about much is what method to use when creating a network request to your server.\n\nImagine for a moment that you've got a form that you want to send to your server via Ajax instead of a regular form submission. You include jQuery, because why not, it makes working with Ajax incredibly easy, and write up a simple handler to do the request. Here's some pseudo-code for how you may handle it.\n\nGiven that we have an incredibly simple form, our code isn't too complex here. Let's look at how this looks in Chrome's Network panel:\n\nWhen making a GET request, your data is passed over the URL. You can see it in devtools. I entered &quot;Raymond&quot; as a value and it was appended to the URL. Both the value and the key for the variable.\nSo far so good. But let's modify the form a bit. We'll add a comment field (with a textarea) and modify the JavaScript code to grab the value and include it as well. Here's the updated code.\n\nAnd here is the result in dev tools:\n\nNotice that the text from the comment field was appended to the end of the URL. Again, this is expected, but this is where you can get bit in the rear. What happens when you release this code to the wild and a person enters a lot of text? Bad things. Well, possibly bad things.\nIn the past, browsers had limits on how long of a URL they would support. So if you tried to send an XHR to a URL that was beyond this limit, the browser would simply trim it. It wouldn't throw an error, and the server would have no idea what happened, but the result you got on the server would not match what the user actually entered.\nI ran into this myself when working on the front end to a CMS. I used a simple GET request to send form data to the server and part of that was a big block of text and HTML. In my testing, where I entered just a few quick characters, it always worked, but in 'real' testing, it failed pretty quickly.\nModern browsers, however, seem to have removed this limit. You can read more about it here on Stack Overflow. However, you can then run into web server issues. Here is how my Apache server responded when I had too much text in the query string:\n\nThat's a 414 error - Request-URI too long.\nThe fix, of course, is to switch to a POST operation where there is no (practical) limits on the size of the values you send. For jQuery it would be all of two seconds to switch from $.get or $.post, or if you are using $.ajax to set the type to &quot;post&quot;. Your server side code, if it cares about the method, will need to change too. (Some languages will let you easily collapse querystring/form variables into one scope. That's convenient for sure.)\nOk, so the summary for all this is rather simple. When building an Ajax application, actually take a few minutes to think about the method you'll use to send data to your server. You can stop reading now if you want.\nSo... at this point, I know there are some REST purists who want to start the discussion of how a proper API will enforce GET for retrieving data, POST for creating data, and so forth. There is merit to that. (Although what you would consider &quot;sending a contact form&quot; as is beyond me.) I also know that a lot of us aren't necessarily building (or working with) fully REST compliant apps. Heck, your entire server may have a grand total of one &quot;API&quot;, that contact form, and it is in that spirit that I hope people find this useful.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "How I added https to my blog",
		"date":"Fri Apr 15 2016 15:15:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1460733300,
		"url":"https://www.raymondcamden.com/2016/04/15/how-i-added-https-to-my-blog",
		"content":"Hopefully you noticed the nice little green icon in the URL bar above:\n\n\nOn a whim I decided yesterday afternoon that it was time to switch to https for my blog. It turned out to be a multi-hour task, but all in all, not too terribly difficult. Here is how I did it. To be clear, I may not have done it the best way, but this is what worked for me and it seems to be rather stable, so I'm satisfied.\nGetting the Certs\nFirst I needed to get my certificate. I started with Let's Encrypt, a free command-line driven tool to generate https certificates. I was a bit worried about this solution for two reasons. First, this warning made me a bit nervous:\n\n\nLet’s Encrypt will issue a limited number of certificates each week.\n\n\nI wasn't necessarily worried about getting the initial certificate, but I was worried about renewing that certificate. What if I couldn't get a new certificate in time? Maybe that was a silly thing for me to be worried about, but I just couldn't get over it.\nThat tied nicely into my +second+ concern which was the fact that their certs expired every 90 days. Not only would I have to ensure I scheduled time to update the certs, I'd have to worry about actually being able to get the cert when it was time.\nI did take a stab at using Let's Encrypt, and when I ran into trouble making it work with Surge, I decided to just move on. Let me be absolutely clear - I greatly appreciate what Let's Encrypt is doing here. They should be applauded. I'm sure if I worked on it longer I would have gotten things working, and as I said, my concern over renewing was probably overblown, but ultimately I found SSLMate much easier to use.\nSSLMate is also command-line driven, but not free. Each cert costs 16 dollars per year which seemed pretty cheap, especially since my host charges in general are pretty low now.\nUpdating the Site\nOnce I had my cert, I simply followed the Surge docs on SSL and it worked so quickly I thought it was broken. I had https running fine on my blog (www.raymondcamden.com), but that left one big issue - all of my images are on an Amazon S3 server, static.raymondcamden.com. If you mix https and http assets on the same page, you'll get a warning in your console and you won't get the friendly seal of approval with the pretty little green lock.\nWhy do I use both S3 and Surge? Well, that's a bit of a long story. Basically Surge has to push my entire site whenever I add new content. I've got 5.5K blog entries and a crap ton of media associated with those posts. While Surge is working on a rsync-based approach, right now it is has to push everything. For my blog, that meant an approximately 15 minute deployment when I blogged. Not the end of the world, but a big long. I decided a few weeks back to move my images to S3. This meant another certificate purchase of course.\nWhile S3 supports https natively, it does not support it for a custom domain. In order for that to work, you have to use Amazon CloudFront to proxy your bucket and handle the certificate from there.\nIt is... complex. Surprisingly so. I used this blog as a guide: Implementing SSL on Amazon S3 Static Websites. My biggest struggle was the damn command line to upload the certificate. I suppose a simple web-based solution would be crazy (and maybe one exists), but I spent a majority of my time just trying to get one particular command line working.\nOnce it did, I then found out how slow CloudFront is. It took about 30 minutes for it to properly set up and work correctly, and then I discovered how aggressively it caches content. To be clear, this is expected, and isn't a bug, but it was a surprise. To replace existing content and have it show up immediately, I need to go to my CloudFront console and create an invalidation request. In my test, it took about 15 minutes to replace cached content. Amazon actually recommends just adding a new asset with -1, -2, etc in the name, which feels lame, but has the benefit of working immediately.\nAgain - this is all how it is supposed to work, but was part of my learning process in getting things set up.\nAs a final nit, Surge asks you to do one small mod to your deployment in order to force all http requests to https. Oddly, I didn't seem to need this. I would request the http version and Chrome, Firefox, and Safari all loaded the https version. After speaking with Brock Whitten on the Surge slack, he mentioned this may just be the default behavior of newer browsers. He checked with curl and http was still served that way. So the final, final step was one small change to my build script to tell Surge to push only to https.\nConclusion\nAnd that's that. In my informal testing on Twitter, everyone said it was working correctly except for one user who said they saw an issue on an Android device. If anyone else can confirm that, I'd appreciate it. Otherwise, let me know if I can answer anything else about my process by leaving a comment below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Rogue One Teaser Released",
		"date":"Thu Apr 07 2016 15:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1460041740,
		"url":"https://www.raymondcamden.com/2016/04/07/rogue-one-teaser-released",
		"content":"As an early birthday present, Disney decided to release the Rogue One teaser. It is certainly possible that it\nwas just a coincident, but I don't believe that.\n\n\nSo far so good. Every single shot in this looks freaking awesome. Let the slow wait for December begin... again!\n",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Articles on TDN and StrongLoop.com",
		"date":"Wed Apr 06 2016 15:19:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459955940,
		"url":"https://www.raymondcamden.com/2016/04/06/articles-on-tdn-and-strongloopcom",
		"content":"Just a quick note to share that I've been published again on the Telerik Developer Network. My latest article discusses learning JavaScript by &quot;playing&quot;, or writing short bits of code, versus traditional book or classroom learning.\n\n&quot;Tools to Learn JavaScript by Doing&quot; talks about various sites, tutorials, etc. that have you learn, or at least practice, JavaScript by either trying to solve coding challenges or get various tests to pass. I find this type of learning to be fascinating as it is much more active than just reading a book or attending a conference session. So I wrote up a few suggestions for things to try in this space and I hope folks find it useful.\nI'm also now writing for the StrongLoop blog. I won't update my blog here every time I post there, but I strongly urge my readers who want to learn more about StrongLoop, LoopBack, and Node.js in general to sbuscribe there as well. My latest article there, &quot;Examples of Validations for LoopBack Models&quot;, discusses some specific examples of validation within LoopBack. I'm currently working on an article now that deals with the ORM API and relationships.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Basic Node performance monitoring with Node Application Metrics",
		"date":"Mon Apr 04 2016 20:13:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459800780,
		"url":"https://www.raymondcamden.com/2016/04/04/basic-node-performance-monitoring-with-node-application-metrics",
		"content":"While I still have a long way to go before I consider myself a &quot;Node.js Expert&quot;, one of the areas I've been most interested in recently is performance monitoring. There are tools out there for this purpose, of course, and I work for a group that has some. StrongLoop has performance tools as part of the commercial offering for Node.js applications, and while obviously I hope to get up to speed on them soon, I ran across an open-source project at IBM that I thought was rather neat - Node Application Metrics.\n\nNode Application Metrics, or appmetrics, is a utility that adds interesting metric data to your Node.js application. Out of the box, you get support for:\n\nCPU metrics\nMemory metrics\nGarbage collection\nEvent Loop data\nMySQL, LevelDB, MySQL, MongoDB, PostgreSQL, OracleDB, and Redis database metrics\nWebSocket monitoring\nAnd more of course.\n\nappmetrics supports reporting two ways. You can either hook it up to Health Center, a free Eclipse plugin, or you can write code to handle metric events yourself. As I don't really use Eclipse, I played around with the server-side implementation.\nRight away - I have to warn you. appmetrics is not currently supposed under Node v5. I got it running (in fact, the error you get when you try to npm install it will tell you what to do), but obviously your mileage will vary unless you're using v4. Apparently v5 support is coming soon so there's that. I took an existing application that uses Mongo and simply dropped this into the top of my main application file.\n\nThe fist two lines simply turn on appmetrics. The first event is fired when metrics are ready. The http event is fired when an http request is made to your application. It is not fired when your application itself makes a HTTP request. I think that woud be a really useful metric and I've filed an ER for it. The mongo event is - obviously - fired on Mongo operations. One thing missing from the event that I think would be super useful is a metric on how many objects are returned. You may ask - why would you need that? As a developer, don't you know what your queries are doing? And the answer is - of course not. We've all dealt with code that we didn't write and unoptimized queries. I can't tell you how many times I saw code that returned thousands of results but only worked with the first one. Having a metric report on the result count would be super useful. (And yes, I filed an ER for that as well.)\nIn testing, it worked as expected, but the http event recorded every http request, even static resources. Luckily you can easily tweak that. Here is how that could be done:\n\nIn this case, I'm simply ignoring calls to URLs that match that regex. I could also conditionally ignore stuff in my event handler too.\nHere is an example of it running.\n\nCurrently the Mongo data is a bit hard to grok. You can see a query of {} above, which represents me asking for all objects, but it should (imo) also return the fact that I was asking for all objects of type X.\nAll in all - this project feels like exactly the kind of metric tool I was looking for - something incredibly simple and easy to use - but with some rough edges that need to be tuned up a bit before I'd recommend it wholesale. The biggest issue right now is that I'd like to see the data that feeds the Eclipse plugin available over regular HTTP. That would allow me to build my own front end to it and skip using the console or Eclipse. (Mainly I want to skip Eclipse because, well, it's Eclipse. ;)\nBut I have to be honest - most performance tools create output that is incredibly hard for me to grasp. Seeing simple, &quot;You requested this and I took N milliseconds to process&quot; is night and day easier for me to process. Don't get me wrong - I know the more complex tools are there for a reason, but stuff like this feels like a good half way point between no metrics and complex CPU memory dumps.\nAre any of my readers making use of it?\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Introduction to LoopBack Presentation",
		"date":"Thu Mar 31 2016 20:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459454400,
		"url":"https://www.raymondcamden.com/2016/03/31/introduction-to-loopback-presentation",
		"content":"Earlier today I gave a presentation on LoopBack and StrongLoop. I've embedded the recording below:\n\n\nI'm not including a link to the slides/demos because they were extremely simple, and you can see the slides in the presentation itself, but obviously if you want it, just ask.  During the presentation, there were a few questions that came up that I wanted to address in the blog post.\n\nWhat are the minimum requirements to install LoopBack/StrongLoop?\n\nI'd check the Installing StrongLoop documentation for that. Also the specific doc for Windows and OSX.\nIn terms of the supported version of Node, both docs say this: &quot;For best results, use the latest LTS (long-term support) release of Node.js.&quot;\n\nHow do you upgrade from earlier versions of LoopBack?\n\nCheck the docs: Updating to the latest version\n\nDebugging - basically - how?\n\nSee my blog entry here: A quick look at debugging Node.js with StrongLoop and Visual Studio Code\n\nRelationships - basically - how?\n\nStart with the docs here: Creating model relations. As I mentioned in the presentation, I'm working on a blog post about the topic, but you can find some sample code for it here: https://github.com/cfjedimaster/StrongLoopDemos/tree/master/ormdemo\n\nUpdates for Angular 2\n\nI was asked if the AngularJS SDK would be updated for Angular 2. Right now - there are no official plans for an update. As I said multiple times in the presentation, you can absolutely use Angular 2, Ionic, jQuery Mobile, Vanilla JS, etc, with LoopBack with no problem at all. This SDK is just an optional utility for working with LoopBack and Angular.\nThat's it - I hope you enjoy the presentation and let me know what you think in the comments below.\n",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Webinar Tips for Presenters",
		"date":"Thu Mar 31 2016 15:05:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459436700,
		"url":"https://www.raymondcamden.com/2016/03/31/webinar-tips-for-presenters",
		"content":"My buddy Dan Vega recently hit me up Twitter about how to give online presentations. Not that I think I'm an expert in the field, I do have some experience with this type of presentation and I thought I'd share some general tips. Here they are - in no particular order. I definitely hope some of my readers chime in here with some comments on their suggestions.\n\nIn General\nFirst and foremost - an online presentation (or &quot;webinar&quot;) can be an extremely convenient way to reach a lot of people at once. As a speaker, you don't have to travel. As an attendee, you don't have to travel. Because of this, I can say that online presentations are the way that I've reached the most people in my career. I've given webinars to nearly 500 people - and heck - I didn't even know it till later when the organizer shared the stats with me. All in all, the ease of attendance is a great benefit, but there are some negatives as well.\nAs a speaker, I rely strongly on how my audience is reacting. If I see people fidgeting, checking social networks, etc, then I know I'm boring them. If I see a lot of confusion, then I know I'm not explaining things well. You get none of that when presenting online and it can be very offputting for the speaker. Because of this, I typically scale back on the jokes and funny pictures. I don't cut them all out, but if I know a particular bit of humor relies on me saying something fast, slow, sarcastically, or whatever, or with a crazed look on my face, then I'll cut it out. In general, I give very casual (heck, bordering on unprofessional maybe) presentations, but for webinars I'm just a bit more serious. Again - without the audience being able to see my face, I feel like I need to tone down my personality a bit.\nOn the flip side, you may not always have the most attentive audience either. There isn't much you can do about that, outside of trying really hard to give a great presentation, but I can say that I've attended webinars in the past where five minutes in, it was in the background and I was doing something else.\nAnother issue for the audience, and the speaker, is that Q&amp;A will not be as easy as in person presentation. Different webinar platforms have different ways of handling this. If you're lucky, you'll have a system that separates questions from general chatter. (Oh, huge tip - ignore the chat. Trust me - ignore it.) If you're even more lucky, you'll have someone who can help filter questions and remove things don't belong. In every webinar I've given with a dedicated question system, someone has put comments there and &quot;+1&quot;s.\nIn general, if you are used to stopping for questions at various points within the presentation, you should instead plan on addressing questions at the end. I will typically warn people about this. I also try to ensure folks see my contact information in case they have to leave early, or if the webinar system doesn't have a nice Q&amp;A system.\nThe Software\nMost times, I do not have a choice in what software I use to give the presentation. You want to make sure you test it out beforehand. Be aware of how you switch to screen sharing and how to turn it off. Many webinar tools have the ability to show slides and do screen share, but for me, since most of my presentations are &quot;slides + demos&quot;, I will not use this feature. Instead I screen share and do my slides there instead.\nIf the system supports broadcasting your audio over the Internet, use that. It seems like organizers always prefer you to dial in. Frankly I think that is silly. Even in the best of situations, a call can drop. Or heck, you get call waiting coming in which can be distracting. If a system doesn't support VOIP, then I'm almost consider that a red flag for the overall quality of the system as whole.\nAlso watch out for Flash and Java-based systems. I promise you that as soon as you begin presenting, you'll get some bullcrap warning about either of them being out of date. Be prepared for it and don't let it throw you. You definitely don't want to hit the wrong button and fire off an update right as you start your presentation.\nI used to love Adobe Connect, but the last few times I've used it, it stopped broadcasting audio about twenty minutes in. I avoid it like the plague now. I think it is run by the same group doing ColdFusion, so there ya go. (Sorry, that was bitter and I should delete it, but I won't.)\nWhen I get to pick, I use Google Hangouts on Air. It has all the features you expect (video, audio, screen sharing, even various other plugins), and has the extra benefit of saving your presentation to YouTube when done. You can see an example of this in my last webinar.\nAnother tip - don't forget to lower your resolution. Normally when you present in person, the hardware itself will drop you down quite a bit. This is actually a good thing as it will make your slides and code a bit more readable. Many webinar tools will not drop your resolution. Do it beforehand so you're r",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "jQuery Mobile Web Development Essentials - Third Edition",
		"date":"Tue Mar 29 2016 14:46:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459262760,
		"url":"https://www.raymondcamden.com/2016/03/29/jquery-mobile-web-development-essentials-third-edition",
		"content":"Yesterday the third edition of my jQuery Mobile book, jQuery Mobile Web Development Essentials, was released by Packt. I don't talk a lot about jQuery Mobile anymore now that I'm an Ionic fanboy, but I've got a lot of respect for the framework and I find it one of the simplest mobile frameworks around. In fact, I used it in my Apache Cordova book because I felt it was the simplest way to introduce a mobile-friendly UI/UX for folks learning Cordova.\n\nThis update covers jQuery Mobile 1.4.5. 1.5 is &quot;on the way&quot; and that may mean another update to the book in the future, but for now, the book covers the most recent version of jQuery Mobile. And just look at that cover. It's beautiful - you just have to buy it, right?\n",
		"tags":[
	        
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Testing the New Ionic User Service",
		"date":"Mon Mar 28 2016 17:26:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1459185960,
		"url":"https://www.raymondcamden.com/2016/03/28/tesing-the-new-ionic-user-service",
		"content":"Please note the date of this article! At the time I wrote this, Ionic Services had recently been given a major update, but they were not fully released yet. What you see in the future may be different than what I demonstrate here!\nA few weeks back Ionic released a brand new version of their services platform. (I discussed it here: Ionic Services enter Beta). At that time, I didn't really have the bandwidth to play around with the updates. This morning I spent some time focusing on the User system and I thought I'd share my findings.\n\nAs I mentioned in my earlier post, the User system went from &quot;kinda nice but not terribly useful&quot; to &quot;really freaking good&quot;. You can now handle social network logins, custom logins, password resets and custom data. You can also get access to your users via a REST system for complete back-end integration. I built a simple demo to test various aspects of the User API. You can find the complete code for my examples here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicUser2. Please keep in mind though that this is not a proper 'app', but mainly a test bed, so the code is not optimized and not appropriate for a real application.\nTesting Social Login\nI started off testing the social login feature with Facebook and Twitter. I created two simple buttons, one for each platform. I also did the required set up (see previous link) for each. In both cases, it took less than five minutes to set up and was painless. Here are the buttons:\n\nNote that I've got a bit of logic to hide them when a user is already authenticated. The user system lets you cache logins so your application needs to prepare for that. Now here is the JavaScript code.\n\nAt this point, what you see is based on what button you click. Here is what Facebook login looks like. Notice that I've previously logged in. It is just asking me for my password at this point. Oddly - I don't see a way to change that, and in fact, I could click on the top bar there to browse my Facebook account. That could be a problem. It may be outside of Ionic's control, but I'm going to file a bug report for it right now.\n\nOn the other hand, Twitter did not seem to cache anything, which is good.\n\nNotice that it is telling you that the app has write access to the account. If you are only using Twitter for login, you will want to go into your app settings and change it to read only. I just did that, and it was reflected right away.\n\nYou may be wondering, what happens if you just click &quot;Done&quot; in the bottom of the UI? Nothing. Neither the error nor success handler is run. It basically acts as a 'cancel' for login. Here's where things get interesting. What if you actually hit Cancel in Twitter's UI? First you get this:\n\nIf you then do, &quot;Return to ionicUser&quot; (that's the name of my app, so I could have picked something nicer), you get this:\n\nThat's not very nice, but again, I'm not sure what you can do about it. (But to be safe, I logged a bug for that as well.)\nOk, so assuming you login correctly, at the point everything is kosher. Unfortunately, you don't get any information about the user. I expected basic profile data, like their name, or email address, but all you get is a user id. Here is an example after I've logged into Facebook:\n\nI apologize for the slightly ugly display there. Notice that my Facebook ID was returned correctly, but nothing else. (I haven't filed a bug report for this yet as I'm waiting on confirmation that we should get something.) In theory, I could then make use of the Facebook Graph API to get information, but I don't have access to the access token that Ionic used to authenticate. That means - afaik - I'd have to prompt again for the user authentication which wouldn't be nice. The same issue applies to Twitter and I assume the rest of the social login providers.\nUsing Custom Data\nCustom data is easy to use. I used it above: user.set('lastLogin',new Date());. It just plain works. Don't forget though that when you want to read it, you need to use get. I forgot this and was curious why it didn't show up in user.details. I build a function to dump user details just to test this:\n\nThe object, user.details, is just the metadata that Ionic sets. Anything you do custom must be fetched using get. Here is an example of it running:\n\nYou can store simple values and arrays, and they even support custom types if you want to get fancy.\nAs an FYI, user data is stored in LocalStorage on the device. This enables you to know about the user even when you are logged out.\n\nLogout\nAs an FYI, this information is not removed from LocalStorage when you run Ionic.Auth.Logout(). I think that is a mistake and I've logged a bug for it. When I mentioned it on Slack, another user mentioned it may be useful to keep it around, but at minimum, this should be documented.\nI also logged a bug asking the Ionic folks to clearly state where keys they use in LocalStorage. The docs tell you they use LocalStorage an",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Getting Images from a Twitter Account",
		"date":"Fri Mar 25 2016 16:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1458921960,
		"url":"https://www.raymondcamden.com/2016/03/25/getting-images-from-a-twitter-account",
		"content":"I've mentioned before that I follow a few Twitter accounts that are primarily picture driven. For example, @classicairline posts historical pictures of commercial aircraft. I even created my own account, @randomcomicbook, which posts pictures of Marvel comics. (You can read how I created that here: Building a Twitter bot to display random comic book covers)\n\nTwitter provides a way to look at the media for a given user, you just click the &quot;Photos &amp; videos&quot; link on their profile, but I thought it would be neat to build an application that shows just the images by themself. Let me show you what I built, then I'll talk about how I built it. You can find the demo here:\nhttp://twittersuckimage.mybluemix.net/\nOn first hitting the site, you'll be prompted to authenticate with Twitter:\n\nAfter you've done that, you can then enter a username:\n\nAfter entering a name, it will then use the Twitter API to find tweets from that account that include pictures. It then renders them in a grid:\n\nClicking on one result opens up the image full size:\n\nOk, so how did I do it? Let's focus on the back end first.  Here's the first portion of my application:\n\nIn order to use Twitter authentication, I used the oauth package from NPM. It works well, but I find it a bit awkward at times. I created a simple route, loginstatus, that is used to see if I've already authenticated. If you aren't authenticated, here is the route that will start that process.\n\nUpon returning from Twitter, you then end up here:\n\nI'm storing tokens for the auth in session - and to be honest - I may not be using the oauth code entirely correct here, but it seems to work. So how does search work? If you check the docs for the Search API, you'll see you the two parameters we need: from will let you filter to one account and filter:media will let you get Tweets with media attachments. Here is the route that handles the search.\n\nThe end result of this is a simple array of URLs. What's cool is that one media URL can represent both a thumbnail and the original image. You'll see how when we get to the front end.\nSo let's talk about the front end. I tried to use a few different &quot;image layout&quot; plugins, but every one I tried didn't work well for me. I ended up just laying them out side by side and using a bit of CSS to add some padding. To create the detail view, I used Colorbox, a jQuery plugin.\nHere's the entire front-end JavaScript code.\n\nThe first function simply handles seeing if we have an active login on the server side. Searching is just a quick call to the back-end and processing the array result.\nYou can find the complete code for this application up on GitHub: https://github.com/cfjedimaster/TwitterSuckImage. Let me know what you think by leaving me a comment below!\n",
		"tags":[
	        
            "nodejs",
            
            "bluemix"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "The Cordova Browser Platform",
		"date":"Tue Mar 22 2016 22:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1458684540,
		"url":"https://www.raymondcamden.com/2016/03/22/the-cordova-browser-platform",
		"content":"Nearly two years ago I first wrote about the Cordova Browser platform (Browser as a platform for your PhoneGap/Cordova apps). After seeing an update on the Apache Cordova blog (Cordova Browser 4.1.0), I thought it might be nice to take another look at it and see how far along it has come.\n\nFirst and foremost, what is the point of the browser platform? Speaking for myself, and I'll clarify what that means in a second, I think the browser platform is a way to test your Cordova applications in the browser. You have other ways of doing this. For example, cordova serve and ionic serve. However, the &quot;Browser platform&quot; acts a bit differently. It attempts to actually make your application work on the browser. What do I mean by that? If you simply serve up your www assets, then you won't have a deviceready event (technically, Ionic will fire one for you) and you won't have support for things like the camera or other &quot;device only&quot; features.\nOn the flip side, the Browser platform actually supports the Camera. I'll be demonstrating how it does so a bit later, but you can use the same APIs you use on your device within the browser and actually get things done.\nNow - here is where my opinion and those of the Cordova group (which technically I'm part of too ;) differ a bit. I think the intention of the browser platform is to actually support going to production with your app. To me, that just seems a bit too much. The camera does work, but it is more of a workaround then what I think a typical end user may expect in a production app. Again, this is just my opinion, but to me, the Browser platform is great for testing, but not necessarily something I'd go live with.\nWorking with the Browser Platform\nBefore you begin working with the Browser platform, there are a few things you should know. First, you have to actually add the platform. That means running cordova platform add browser. And if you want the latest, then you need to specify cordova platform add browser@4.1.0. Soon that version will be the default, and if you're reading this in the future you should probably just take the default, it could 6.0.0 by now.\nAnd obviously, if you are only using the Browser platform to test and you find it isn't working out for you, then be sure to go ahead and remove it: cordova platform rm browser.\n\nTo start testing the platform, simply cordova run browser, or cordova run if you only have one platform. When you do, a new instance of Chrome will pop up. I use Chrome as my default browser, but a new instance was launched as opposed to a new tab opening in my current browser. That's good imo but just keep that in mind. You'll also get a running log of requests in Terminal. What you see will be based on what plugins and other assets you use. Here is an example:\n\nThis is a live listing and will update as your app requests new resources. Can you configure how the browser is loaded? Yes. If you look in platforms/browser/cordova/, you'll see a run file whichi is used by the Cordova CLI. It's also a quick way to see the help. The run script supports two arguments: browser and port. browser defaults to Chrome. port defaults to 8000. Here is how I'd switch the port and browser:\ncordova run browser -- --port=8001 --target=Firefox\nAs you edit files in www, remember that Cordova runs your code out of the platforms folder. For my testing, I opened a new tab and ran cordova prepare browser whenever I edited. That got old pretty quickly. I knew I could set upa Grunt task to automate that, but it seemed over the top. Then I found a cool utility called filewatcher. This is a command line tool that lets you watch files and run an arbitrary command when something changes. This is what I used:\nfilewatcher &quot;www/**/*.*&quot; &quot;cordova prepare browser&quot;\nWorking with Plugins\nOk, so how do you use plugins? Exactly the same way you do for any other platform. Find the plugin you want, install it, and then follow their API. At the Plugin Repository you'll notice a button to filter by browser support:\n\nNotice that there is a bug with the registry in that you don't get a pretty little icon indicating that the Browser platform is supported. This is a known bug. (As in a known bug as of a few hours ago.)\nOf course, the devil is in the details. For each supported plugin, you need to carefully read to see exactly how the platform will be supported. The barcode plugin, for example, doesn't actually let you scan a barcode. Instead, it simply prompts you to manually type in a value for the 'scanned' barcode. I used this recently in a production app though and it was great... for testing.\nI decided to take a quick &quot;tour&quot; through the official plugins for Cordova and see which supported the Browser platform and how well they did. Below you'll see a report detailing what I found. All of the code used in this test is up on my GitHub repo here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/browser1\nBattery\nThe first plug",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "DS9 Rewatch Complete",
		"date":"Sun Mar 20 2016 19:52:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1458503520,
		"url":"https://www.raymondcamden.com/2016/03/20/ds9-rewatch-complete",
		"content":"Almost two years ago, I finished my rewatch of ST:TNG. This was a series I had seen most of as a kid, so it wasn't terribly new to me, but it was great to experience the series again. I left it with a greater appreciation of Star Trek and TNG in general. Sometime after that, I began rewatching Deep Space 9. This series came out while I was in college, and to say that I was a bit too busy for TV would be something of an understatement. I did watch some episodes, but probably not more than ten or so. While I knew the setting and a bit about the characters, I didn't really have any feel for the overall tone or quality of the show. Now that I'm done, I want to kick myself. DS9 is - easily - the best Star Trek I've ever seen.\n\nI really enjoyed TNG, and at times, it was incredible. But DS9 was consistently one of the best television shows I've seen. During my rewatch of TNG, I tended to focus (and get more excited about) the story arcs, the &quot;big events&quot;, etc. One thing I noticed about DS9 is that while they had great multi-story arcs, even the simple &quot;one offs&quot; seemed to be given the same amount of attention and care in terms of writing and acting as the big events. I can remember an episode starting off and thinking, &quot;Ok, so this is one of the funny ones, it won't be terribly deep&quot;, and then something would happen that give me an incredible new insight into a character, and I would absolutely see these details brought up again in later episodes. It's like every episode was treated like the premier/finale. No one in this show ever slacked off. They gave it all of their passion for every single episode over seven years.\nSo what did I like...\nThe Ferengi, which were barely used in TNG, really shined here. Most of the time there were played for comedic effect, and that was fine, but I loved how much attention they got as a race. Quark, especially, was a great character. The interaction between him and Odo was great to see over the years. But my favorite Ferengi was Nog.\n\nDuring my rewatch of TNG, I paid special attention to Crusher's character. Mainly because everyone seemed to hate him and I wanted to know why. While he was a bit annoying at times, I honestly felt like I would have been the exact same in his position. Given a chance to join Star Fleet, I would have been just as eager as Crusher.\nNog reminded me a lot of Crusher, but while Crusher was eager, Nog was completely passionate about his dream to enter Star Fleet. This may sound ridiculous, but I swear I felt proud when he was able to get in. I had a huge grin across my face and I was really happy about it. His development through the series was very well done and one of my favorite parts of the show.\nOn the flip side, the Cardassians were great too. They were evil, but... stylish. Nothing like the Klingons or Romulans, they had a way about them that made them a joy to watch. I actually felt a bit sorry for them when they joined the Dominion. You just knew they were going to be crapped upon and would end up regretting it, and of course that's exactly how things ended up.\n\nSpeaking of the Dominion, I thought the Vorta were really interesting as well. There was one episode where a Vorta and Cardassian were confronting Sisko I believe. The Cardassian was being very aggressive, and Sisko was having none of it. If you watch closely, the Vorta is actually watching the Cardassian instead of Sisko. He's watching him with a look in his eyes like he wants the Cardassian to lose the argument. He is actually enjoying seeing his ally lose the argument. Now it becomes obvious later that this is probably how the Dominion operate, pitting one of their races against an another, but it is a subtle effect and so well done in the show.\nOh, and I was happy as hell that someone recognized that Worf was one of the best parts of TNG and found a way to get him on the show. As before, he had some of the funniest lines, but even more interesting was to see him in comparison to the other Klingons.\n\nWhen rumors of a new television series were being thrown around, at least one of them involved the idea of a show with Worf as the captain, and frankly, I think that would be a damn good idea.\nSo was there anything I didn't like? Not really. I seem to remember one episode in season six that I thought was a bit crap, but it mainly stood out as the rare exception. I can say that I thought it was odd that the main computer system of the station devoted something like 50% of the visible screen space to some weird circle UI. Here's an example:\n\nI mean look at that - at least 50% of the available space is wasted. Who in the heck designed a UI like that?\nI enjoyed the last two Star Trek movies. They are certainly better than the last bunch before them. They are cool. They are fun. But in the end, they don't have nearly the same passion that I feel DS9 has. DS9 was an act of love and respect for Trek whereas the last two movies were mostly about doing something &quot;",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Recording for Browser Tools Presentation",
		"date":"Fri Mar 18 2016 14:51:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1458312660,
		"url":"https://www.raymondcamden.com/2016/03/18/recording-for-browser-tools-presentation",
		"content":"Last night I gave an online presentation to the Northern Virginia ColdFusion User Group on working with browser tools. The main focus was on finding and trying to correct problems with your code. The video is available below. There is a bit of quiet spell towards the beginning as I wait for the user group to confirm they can see/hear me, but after that you should be good to go.\n\n\nHere are some links related to the presentation:\n\nThe Chrome extension I demoed for flagging JavaScript errors: JavaScript Errors Notifier\nA Review of JavaScript Error Monitoring Services\nA better link for the Console API: https://developer.mozilla.org/en-US/docs/Web/API/Console\nChrome Dev Tools\nProfiling JavaScript Performance - Chrome\nFirefox Developer Tools\n\nAs always, please leave me a comment if you have questions!\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "JSONPath for parsing JSON",
		"date":"Tue Mar 15 2016 17:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1458061740,
		"url":"https://www.raymondcamden.com/2016/03/15/jsonpath-for-parsing-json",
		"content":"This is just a FYI that there is a cool little project that provides XPath like support for JSON values: JSONPath Plus. I think this is rather cool as XPath is probably the only nice thing about XML over JSON. If you've never heard of XPath, you can think of it as a query language for filtering/parsing XML data. Everything from getting a subset of an XML document to even doing things like addition. If you'd like to learn more, check out the MDN documentation on XPath. JSON doesn't provide any similar functionality built-in, but JSONPath provides an alternative.\n\nHere is an example of it in action. Let's take the result of calling the Ships API from the Star Wars API. The URL for this JSON is http://swapi.co/api/starships/?format=json. Here is the result - with some of the results taken out just to make the snippet a bit smaller.\n\nJust by eyeballing that* you can see the object contains a count key, a next and previous set of keys for pagination, and a results array. Using JSONPath, I can request just the result by using this path: $.results. Or I could ask for the total sum of the cost values like so: $.results..cost_in_credits.\nPretty cool, right? The readme contains many examples. You can use this both in 'regular' client-side JavaScript as well as Node. I wanted to do some testing with the library so I built a quick online demo. Here's a screen shot in action. I used all my design skills on this one:\n\nWhile JSONPath itself has a few configuration options, my tool simply takes the defaults. You can paste in a JSON string and then play around with the path options to see the results.\n\nMy code isn't anything special here - basically take in the inputs and render them out. I'll remind folks that JSON.stringify lets you pass in basic formatting instructions. That's how I got the nicely layout dump of the data. You can try out the demo here: https://static.raymondcamden.com/demos/2016/03/15/.\n* As a quick aside, I've been using this online JSON viewer for a while now to render complex JSON objects. I need to build an extension for Visual Studio Code so I can use this in my editor.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Webinar next week on Browser Dev Tools",
		"date":"Wed Mar 09 2016 14:23:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1457533380,
		"url":"https://www.raymondcamden.com/2016/03/09/webinar-next-week-on-browser-dev-tools",
		"content":"Just a quick note to let folks know I'll be giving a webinar next week on browser dev tools. This will be for the Northern Virginia ColdFusion Users Group but will be open to the public. If you can't make it, it will be up on YouTube afterwards and I'll post the link back to this blog post.\nThis session will introduce the basics of using the browser's developer tools to help you debug/test your web applications. Most likely this will only be appropriate for beginning developers but all are welcome.\nHere are the details:\nURL: https://plus.google.com/events/cp7o1hqo4afsq334ps60lhvhdt0\nDate: Thursday, March 17\nTime: 6PM CST\nHope to see you there!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "How I added search to my static blog",
		"date":"Mon Mar 07 2016 21:01:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1457384460,
		"url":"https://www.raymondcamden.com/2016/03/07/how-i-added-search-to-my-static-blog",
		"content":"I was talking with fellow coder Nic Raboy about static sites, and specifically Hugo, and he asked me how I built the search form here. This is something I've written about before (see my article over on Modern Web from a few years back: Moving to Static and Keeping Your Toys), but I thought it would be worthwhile to discuss my specific implementation on raymondcamden.com\n\nAt a high level, I'm using a Google Custom Search Engine. This is a free tool by Google that lets you create a client-side widget to add a search engine for a particular domain, or set of domains. You have control over a number of features, like the CSS and specific paths under a domain you want to include, and for the most part it just simply works as you expect. This is what the administrator looks like. Notice the preview on the right hand side. It is &quot;live&quot; and you can see how I've entered a text string for testing.\n\nI didn't customize much. Under look and feel, I switched to &quot;Full Width&quot; so it would look good under my blog theme.\n\nFor some reason, they don't make it easy to get the code you need to put on your site. You won't find it under Setup, the default page you land on when working on your CSE. But you will find it under Look &amp; Feel. I cropped it out of the screen shot above but there is a Save &amp; Get Code button.\nThat would be it, but I needed to make two more modifications. While you can &quot;land&quot; on my search page as is, most folks\nwill probably enter a term in the box on top and submit. In order for the Google CSE to recognize that you're loading the page\nwith a term already, you need to tell it what to look for - in this case a query string variable named q. Secondly, the CSE\nopens up search results in a new tab. I don't like that. So I had to tell it to use _parent for the target in results. Both\nof these can be done by modifying the HTML code Google gives you.\nThis is how your code will look when you grab it from the admin:\n\nNotice the gcse tag at the bottom? You can customize the heck out of it as documented here. For me, my modifications were trivial:\n\nAnd that's it. Remember, if you want more tips on &quot;bringing dynamic back&quot; to your static site, see my article and feel free to drop me a comment here with any questions.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Ionic Services enter Beta",
		"date":"Fri Mar 04 2016 13:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1457099040,
		"url":"https://www.raymondcamden.com/2016/03/04/ionic-services-enter-beta",
		"content":"I've done multiple blog posts and presentations on Ionic services, and I'm happy to see that they are (albeit a bit slowly) moving closer to 1.0. While I do not see an &quot;official&quot; launch yet, this week the suite of Ionic services (Push, Analytic, Deploy, Package, and User) all entered Beta. So what's changed?\n\nUsers\nIn the past when I've spoken about Ionic services, I've always described the User system as &quot;Users Light.&quot; It was a pretty - well - 'casual' user system. It didn't do authentication. It basically acted as an association system. You say you're user X? Fine - then when I do a push I'll let you target X. When you fire off an analytic event, I'll associate it with X. I would specifically compare it to Parse's excellent User system and say that this was not like it at all.\nIn the Beta, this has changed completely. While not 100% up to par with what Parse had, it now has proper authentication. If you try to add a new user that already exists, it will recognize that. If you properly handle authentication of a username and password. Not only that, you've got built in support for social network via Facebook, GitHub, Google, Instagram, LinkedIn, and Twitter. You can even set up your own authentication provider on your own server.\nThe more I look at it, actually, the more I think it is pretty darn close to what Parse provided. I'm a bit rusty (and frankly, why bother checking since it is on the way out anyway), but I think it may be pretty darn close to feature parity with what I remember. Right now I can't even say what else you would need.\nThe important thing to keep in mind though is that it's gone from pretty much an &quot;accessory&quot; service to the 'real' ones to something that feels a heck of a lot more solid and complete. That's awesome!\nPush\nPush has been updated as well, including code changes and updates to the UI you can use on the Ionic site itself for testing. Especially nice are testing options for the iOS and Android specific push items, like badge numbers and sounds. This was useable before of course, but having it in the UI to test easier makes things - well - easier.\nAs for the service itself, you can now send a message to everyone or specific users. Error messages and message status reporting has also been improved.\nSecurity Profiles\nThis isn't new, but had a limited launch recently. Security profiles simply handles certificate information for various services in one convenient location. Currently this integrates with the Package and Push service. Once you've got a security profile setup for your platforms, each of the supported services can make use of your certificates without you having to constantly re-apply them.\nAPI\nFinally! I've been bugging the Ionic folks about this since the initial release of their services. While they have always had an API behind them, of course, they weren't documented. Now there's documentation for the User, Push, and Deploy APIs. Analytics will come in the future. This lets you create your own dashboards/admins to integrate with your Ionic mobile app. Note that for the supported services, the docs aren't complete yet so more will be coming there as well.\nAnd more!\nThis is just a quick overview of the updates, and as I said, you can expect an official announcement from the Ionic folks in a few days. Check out the docs for more information. I'm planning on creating quite a bit of media for their services, but I'll probably wait until the RC or final version is launched. In the meantime though if you have any questions, let me know and I'll try to help. Even better,\nsign up for the Ionic Slack channel to speak to the Ionic folks directly!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Starting a new role at IBM - StrongLoop Evangelist",
		"date":"Thu Mar 03 2016 16:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1457021340,
		"url":"https://www.raymondcamden.com/2016/03/03/starting-a-new-role-at-ibm-strongloop-evangelist",
		"content":"A little over a year ago I joined IBM as a developer evangelism for the MobileFirst server. I was incredibly excited to be back as an evangelist after my small taste of it at Adobe. Over the past year I've travelled to multiple countries, spoken in front of hundreds of developers, and have had the great pleasure of introducing people to IBM's mobile offerings.\n\nA few months ago, I began spending time looking at LoopBack and StrongLoop. I was incredibly impressed with the tools. I was reminded about the\nfirst time I saw Express. Before Express, I thought Node was &quot;cool&quot;, but I wasn't really interested\nin ditching ColdFusion and rewriting everything in JavaScript. Express changed all that. With Express handling so much boilerplate code I began to get really excited about building things in Node.\nFor me, LoopBack and StrongLoop represent the same type of &quot;jump&quot; that I saw with Express. As more and more development moves to the client and our servers turn into - essentially - just API providers, I really see these frameworks as being essential tools for Node developers.\nSo much so that I've changed roles at IBM as of this Monday. I am now on the StrongLoop team as a developer evangelist. Of course, you probably guessed that by the title. I'm already speaking on StrongLoop next week at Fluent, and at a few other conferences coming up soon. I'll still be blogging about my other passions of course, and the Ionic folks would have to adopt Jar Jar as a mascot to get me to stop blogging about them.\nFinally - thank you again to everyone who reads this blog, posts comments, and keeps me on my toes. I love being able to share my knowledge with you and I hope to do so until I'm old and gray. (Ok, I'm already a bit old and gray, but let's just ignore that fact for now. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding an API to a static site",
		"date":"Tue Mar 01 2016 21:59:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1456869540,
		"url":"https://www.raymondcamden.com/2016/03/01/adding-an-api-to-a-static-site",
		"content":"Ok, so I'm starting off this tutorial with something of a lie. By every practical definition of an API, we really can't create one on a static site. An API typically involves a complete CRUD cycle (reading data, creating and updating data, and deleting) process for content and since a static site doesn't have an app server, than that's just not going to happen. However, if we can stretch our point of a view a bit, there's room for a bit of interpretation here.\n\n\nIf you consider just the read aspect of an API, it is possible to create such a thing for a static site, but only if you ignore things like searching, filtering, or otherwise manipulating the data.\nThat certainly sounds like a heck of a lot to give up, but it doesn't mean your site is completely devoid of any way of sharing data. Consider for a moment that static site generators (SSGs) all have some way of outputting RSS. Folks don't tend to think of it because it &quot;just works&quot;, but RSS is a data-friendly format for your blog and is - barely - an API for your site.\nLet's consider a real example. You've got a site for your e-commerce shop and you've set up various products. We'll use Jekyll for the demo but any SSG would be fine. I created a new Jekyll site and then added a _data folder. Within that folder I created a file called products.json. It was a simple array of products.\n\nThis is the standard way by which you can provide generic data for a Jekyll site. See the docs on this feature for more examples. At this point I can add product information to my site in HTML. I edited the main home page and just added a simple list. I decided to list the name and price - this was completely arbitrary.\n\nAnd here it is on the site:\n\nYeah, not terribly pretty, but for a demo it will suffice. I could also create HTML pages for my products so you can click to view more information. (For Jekyll, that could be done by hand, or by using a generator to read the JSON file and automatically create the various detail pages.)\nSo let's create a JSON version of our products. Technically, we already have the file, but it isn't accessible. In order to make this public, we need to create a file outside of _data. I chose to make a folder called api and a file named products.json. Here is how I made that file dynamically output the products in JSON format.\n\nYeah, that's it. So a few things. In order to have anything dynamic in a random page in Jekyll, you must use front matter. For me that was just fine as I wanted to ensure no layout was used for the file anyway. Jekyll also supports a jsonify filter that turns data into JSON. So basically I went from JSON to real data to JSON again, and it outputs just fine in my browser:\n\nOf course, this assumes that my core data file matches, 100%, to what I want to expose to my &quot;API&quot;. That may not work for every case. I could manually output the JSON by looping over my site data and picking and choosing what properties to output. Heck, I could even make new properties on the fly. For an example of this, see this Jekyll snippet: JSONify your Jekyll Site.\nCool! But what about sorting, filtering, etc? Well, we could do it manually. For example, I made a new file, products.qty.json, that supports a sorted by qty list, with highest first:\n\nThis resulted in this JSON:\n\nI could do similar sorts for price or name. How about filtering? I built a new file, products.instock.json, to represent products that have a qty value over zero. I had hoped to do this in one line like in the example above, and Liquid (the template language behind Jekyll) does support a where filter, but from what I could see, it did not support a where filter based on a &quot;greater than&quot; or &quot;not equal&quot; status. I could be wrong. I just used the tip from the Jekyll snippet above.\n\nAnd the result. Note the white space is a bit fatter. I could fix that by manipulating my source code a bit.\n\nSo I think you get the idea. If I wanted to, I could add any number of possible combinations (in stock, sorted by name, but with a price less than 100). It is definitely a manual process, and I'm not supporting dynamic sorting and filtering, but it is certainly something, and it may be useful to your site users.\nI'd love to know what you think of this technique, and if you are making use of a SSG and have done something like this, please share an example in the comments below!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using the Onymos Media Component",
		"date":"Tue Mar 01 2016 15:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1456844760,
		"url":"https://www.raymondcamden.com/2016/03/01/using-the-onymos-media-component",
		"content":"One of the more interesting aspects of the rise of hybrid mobile development is the rich ecosystem\nof plugins available to developers to make use of within their applications. When I started with PhoneGap nearly five years ago, there were a core set of plugins covering basic device functionality and that was it. Since that time, the available plugins have exploded. Today I'm going to review a commercial plugin offering from Onymos. The Onymos Media plugin is like the default Camera plugin on steroids.\n\nFirst and foremost - let me state up front that this is a commercial plugin. It isn't open source and it costs money. Frankly I'm ok with paying for functionality so this isn't a deal breaker for me. I know it is for some folks so I want to get that out of the way first. The plugin (and related services) cost $99 a month which isn't cheap, but I can honestly say that this seems like a reasonable price for everything that is offered.\nSo what does it do? As I said, the plugin acts like an enhanced version of the Camera plugin so the features - for the most part - revolve around capturing pictures and working with videos.\n\nIt helps correct orientation differences.\nIt expands the locations that a user can select media.\nIt has client-side compression technology for both images and video. What that means is a large video on the device can be compressed before uploading. This makes it much easier to share videos via your application.\nIt lets you convert media into Base64, including video.\nIt can generate thumbnails from video (pictures too of course).\nAnd here is the coolest part. It provides a storage mechanism for your media. This feature supports both upload and download, as well as the ability to search against stored media. You can either use storage directly from Onymos or point to your Amazon S3 bucket.\n\nSetup of the plugin is a bit more complex than typical. The plugin itself is installed just like any other: cordova plugin add onymos_components/onymos-plugin-media. But then to use it, you will need to initialize it with your credentials (for the purchased product) and then wait for a callback to know when it is completely ready. Obviously how and when you do this is application dependant. For my test, I simply put it in my main controller and inside an ionicPlatform.ready call.\n \nIn the code block above, you can see I'm setting up my configuration values and then passing it to a onymosInitialize function. I then handle the success or failure. In my case, I set some flags in the scope to let me know things were ready.\nFor my test, I simply wanted to see a thumbnail for video. Currently you can only select an existing video, you can't take a new video, but you could do that with another call and then ask the Onynmos plugin to work with it.\n\nSo I won't go into terribly deep detail here, you can read the docs yourself, but one of the core concepts of the plugin that was a bit confusing at first was the idea of a callId. This is basically a unique identifier for a particular media usage. For most people, they will have one media &quot;use&quot; on a view, ie a button to click to work with media. In some apps you may have two. The callId basically creates a handle to one set of data at a time. The rest of the code should make sense. I'm selecting from the photo library and working with all media. I then ask for a thumbnail and set it to a scope variable. Back in my view, I have a simple img tag with a ng-src pointing to this.\nHere is a screen shot of the video selection in action. You can see it compressing the video automatically.\n\nOnce compressed, the thumbnail generation works as you would expect. I could have made this quite a bit smaller if I wanted to.\n\nThe compression technology is pretty impressive. I didn't play with it much in my demo, but the Onymos folks have an online demo that demonstrates this rather well. The video demo portion is especially cool - they got the size down to 16% of the original and the quality looks darn good.\n\nThe other big benefit of the Onymos Media plugin is the upload/search/download aspect. As I said above, you can upload media and store it either on their server or your own S3 bucket. The Upload API is pretty powerful. You can:\n\nResize\nOptimize\nTarget a device type (mobile, tablet, and desktop)\nDesignate a thumbnail size\nSpecify an upper limit for size\n\nAnd on top of that you can then set an array of tags to associate with the media. This then ties into the Search API. It lets you specify tags and then provides a nice list of results that includes thumbnails. You can also include the media itself as well by pointing to the URI. So for example, a video tag could simply change it's src attribute to load in the result.\nAll in all, this seems like a very powerful plugin, and while it isn't free, you get support as well and in my own work with the company, they were very quick to respond to my questions as well as my suggestions. In a few places I found the docs to be a bit awkwa",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Performing sentiment analysis of Twitter data",
		"date":"Thu Feb 25 2016 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1456358400,
		"url":"https://www.raymondcamden.com/2016/02/25/performing-sentiment-analysis-of-twitter-data",
		"content":"One of the more interesting services available on IBM Bluemix is Insights for Twitter. This service\nprovides a deep look at real-time Twitter data. The API provides a basic &quot;search&quot; feature but can also include some incredibly detailed filters. So for example, you can look at data from users who are (possibly) married and have kids. As part of the analysis, you can also get a sentiment value: positive, negative, neutral, and ambivalent. I thought it would be interesting to build a tool that let me compare the &quot;general&quot; sentiment for a search term compared to that of more focused segments of the audience. Obviously this isn't 100% accurate, but it provides an interesting look at how different types of people view/discuss the same topic.\n\nThe API supports multiple filters:\n\nBy language\nBy location\nBy country\nBy the number of followers\nBy the number of people the person follows\nBy people with children\nBy people who are married\nBy verified users\nBy people in a range of lists\nBy people within a circular region around a long/lat.\nIn a certain sentiment\nIn a certain date range\nAnd more\n\nFor my demo, I decided to focus on a certain set of filters that would be most applicable to more terms. (I'm toying with the idea of building an &quot;advanced form&quot; later.) Given a term, I will return the sentiment for:\n\nThe general dataset\nPeople with 5k+ followers\nMarried people\nPeople with children\nResults over the last year (Jan1-Dec31 of 2015 as of the time I'm writing this blog post)\n\nThe docs explain how the API works including the query language. In general it is pretty simple, but the best news is that you can skip getting results and just use the count method instead. That means I can easily get sentiment analysis with 4 API calls (one for each sentiment). As an aside, I ended up not displaying all four sentiments in my charts, but the back end code still fetches them all.\nI'll include a link to the code base below, but here is a snippet showing how I fetch the general sentiment for a term.\n\nHere is the block that handles married (again, possibly married) people:\n\nAs you can see, I'm using promises to handle the fact that I'm firing a bunch of async processes. As I've got 5 charts and 4 sentiments, each query performs 20 different HTTP calls. Handling that isn't too hard:\n\nThe code in the forEach there is handling applying each HTTP result to a proper location in my main result object. Generally your array of results will match how they were applied to the array, but as I'm using 5 forEach statements for my reports, I can't know for sure the order they will be added to the array. Kind of complex, but that's all nicely hidden away in my library. Back in the main Node.js route that handles all of this, it is rather simple.\n\nSo how about the front end? I decided to use Bootstrap to create a simple UI for the term searching. For my charts, I decided on Highcharts. It is free for commercial use and I liked the animation of the charts. Here's a sample report.\n\nYou can see all the code for this project on the GitHub repo I just set up: https://github.com/cfjedimaster/twitterinsights.\nAnd you can run the demo yourself here: http://twitterwall.mybluemix.net/. I had some good folks test this and they managed to crash it a few times, but hopefully I've covered up most of the issues. Let me know what you think. And remember, the sentiment analysis is not going to be perfect. I'm sure you can find examples that don't make sense. Be nice. :)\n",
		"tags":[
	        
            "nodejs"
            
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Building a Twitter bot to display random comic book covers",
		"date":"Mon Feb 22 2016 17:42:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1456162920,
		"url":"https://www.raymondcamden.com/2016/02/22/building-a-twitter-bot-to-display-random-comic-book-covers",
		"content":"A bit over two years ago I played around with the (then) recently released Marvel API to build some cool demos. The end result of that experiment was a simple web app that randomly displayed a Marvel comic book cover every minute:\nhttp://marvel.raymondcamden.com/.\nThis weekend I was thinking about a few Twitter accounts I follow that just post random pictures. (I'll share a list of them at the end.) I like these accounts because they're easy to ignore, provide something simple and cool to my feed, and are just a random piece of coolness during the day. I thought it might be kind of fun to build a similar mechanism for comic books (well, Marvel comics, I need to see if DC has an API).\nIn theory - all I needed to do was:\n\nCreate a way to select a random cover (which was already done - by me)\nCreate a way to Tweet (there's probably a npm library for that - yep - there is)\nCreate a schedule (there's probably a npm library for that too - yep - there is)\n\nIt ended up being very quick to develop - maybe two hours total. Here is the complete source of the main script file. (Note, the entire thing is up on GitHub - the link will be at the bottom.\n\nLet's break it down bit by bit, focusing on the important parts. To handle the Twitter API, I used the twitter Node library. As you will see a bit later in the code, it is incredibly trivial to use, even when creating Tweets with media attached.\nThe Marvel API is just a copy of the code I used before, although I've modified it a bit so I can pass in my credentials.\nThe real meat of the code is in tweetRandomCover. We begin by asking the Marvel API for a random cover. If you read my post from two years ago you'll note that I have to fake that a bit. I essentially select a random month+year and grab everything I can from there - then select an item.\nOnce I have the random issue, I use the request library to suck down the binary of the image into a variable. I've heard of this library quite a bit, but I've never actually used it. Big mistake on my part.\nFinally - I have to create the tweet. Twitter requires you to upload the media first so it is a two step process. First the image is posted and then the actual Tweet is created. I've got a bit of &quot;Callback Hell&quot; going on here and if this app did anything else I'd abstract this logic out of the main script, but since this isn't a web app people will hit, I'm not going to worry about it.\nThe final aspect is scheduling - which you can see is done via node-cron. Easy to use - it took me longer to figure out the right cron syntax than it did to implement the code. As you can see, I've selected a schedule that should post tweets three times a day which &quot;feels&quot; right for this kind of account. I may tweak that later.\nYou can find the complete code (although there's not much else) up on GitHub: https://github.com/cfjedimaster/randomcomicbook. I'm hosting the app up on IBM Bluemix.\nAnd of course, you can (and should!) follow the Twitter acount: https://twitter.com/randomcomicbook\nIncredible Hulk (1962) #146 published December 1971https://t.co/3VyfTGup9r pic.twitter.com/5jJFJHj58h&mdash; Random Comic Book (@randomcomicbook) February 22, 2016\n\nPS...\nSo yeah - about those random Twitter accounts I follow for pictures? Here they are:\n\nhttps://twitter.com/EmrgencyKittens\nhttps://twitter.com/iLove_Aviation\nhttps://twitter.com/Aviation4_Life\nhttps://twitter.com/ClassicStarWars\n\nI used to follow some related to historical pictures, but they either turned to spam or shared pictures unrelated to history, which to me is a cardinal sin of these types of accounts. (Another example - news organizations that will RT their sports or entertainment accounts. I freaking hate that.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Creating an unread count for a static site",
		"date":"Thu Feb 18 2016 21:23:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455830580,
		"url":"https://www.raymondcamden.com/2016/02/18/creating-an-unread-count-for-a-static-site",
		"content":"Lately I've noticed some sites will include a little &quot;unread&quot; icon for their blog, showing you how many new articles they have since your last visit. As an example, here is how the Apache Cordova displays it in their header:\n\n\nI've seen this on a few other sites (like Ionic) and I assume it is a simple plugin, but I thought it would be kind of fun to build a prototype of this myself. What follows is a simple example of the feature that can be used for static sites. There's probably many different ways to add this feature (and I'll discuss some options at the end) and I'd love to hear from my readers how they've accomplished this if they have it on their site. As a reminder, everything I share here is free for you to make use of on your own site. All I ask is that you show your appreciation with a quick visit to my Amazon Wishlist if you can.\nOk - so before we get into the code, let's discuss how we can handle this. When comes to the site, we can see if they've been to the site before. There are a variety of ways to persist data on the client, but the simplest is localStorage.\nGiven that a user first hit the site on February 1, 2016, we can then ask the blog for a list of articles. Luckily there's a simple way to do that - RSS. By parsing the blog's RSS feed, we can iterate over every entry and see when it was published. If it was published after February 1, we can increment a counter of unread articles.\nSo far so good, but then we have a few questions. What if the user has never been to the site before? Should we show an unread count of 10? (10 being the &quot;typical&quot; number of items in an RSS feed.) In my opinion, no. It feels a bit pushy.\nHow and when do we update the value? In theory you could update the date value immediately. I may choose to go the blog or not, but since you've already told me that the site has X unread articles, there isn't necessarily a need to keep telling me that. Or - I could only update the date when you visit the blog (either the home page or any particular entry).\nTo keep things a bit simpler, we'll say that when you visit the blog home page, we'll automatically update the &quot;last visited&quot; value so you no longer see an unread count. Alright, let's write some code!\n\nFor our first iteration, we've got a bit of simple code that says - well - you can probably read that out loud and figure out exactly what we're doing. If we aren't on the blog, get the unread count. Now let's flesh out those methods.\nFirst - onBlog:\n\nA bit lame, but all we do here is see if 'blog' exists in the current path. Obviously your site could use 'news' for the path so you may need to modify that logic to match your particular site.\nNow let's look at getUnReadCount:\n\nSo notice that we're now expecting an argument to the function that will be a callback to fire when we're done with our work. If the user has never been to the site, we shortcircuit out by returning 0.\nWe then hit the RSS feed for our site. I talked about parsing RSS on my blog a few months ago and at the time I mentioned YQL as an excellent replacement for the Google Feed API. In our case, we get the items from the RSS feed in a nice array we can loop over.\nNote the array iterator. For each article get the time in milliseconds and compare it to our lastvisit value. We increment every time the article is newer. Now is probably a good time to go back and show the complete code.\n\nYou can see now that we're handling the display update as well as storing your last visit when you are on the blog. The DOM selector is just running against a super simple Bootstrap template I whipped up just for this example.\nHere is a screen shot of in action:\n\nI went ahead and put up the demo here: https://static.raymondcamden.com/demos/2016/02/18/test.html But obviously it will be a bit weird since the RSS is on my site, not the demo and, but, you can get the complete HTML templates from there if you want.\nAlternatives\nRight off the bat, I can think of one quick way to simplify this a bit. Don't forget that most static\nsite generators let you have dynamic files of all sorts - not just HTML. Imagine if my JavaScript code was dynamic as well. I could dynamically generate an array of the last ten date values. Then my code could skip going to YQL. Heck, it wouldn't even need to be async anymore. It would add a tiny bit of weight to the download of the file, but the network speed optimization should make up for that I think.\nYou could replace the use of localStorage with a cookie and - in general - get slightly more support - but that seems like overkill to me. (However, I would consider adding a simple localStorage check in my code before doing any of the checks.)\nWhat do you think?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My PhoneGap Day 2016 Presentation",
		"date":"Wed Feb 17 2016 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455667200,
		"url":"https://www.raymondcamden.com/2016/02/17/my-phonegap-day-2016-presentation",
		"content":"I was honored to be chosen to speak at this year's PhoneGap Day, but honestly, even if I wasn't chosen to speak, I would have tried like heck to get there. I missed last year, but every PhoneGap Day I've been too has been incredible. If you missed it, all of the presentations will be available online via the PhoneGap blog. You can also read their\nexcellent recap of the event too.\nMy session is available now, and you can watch it below.\n\nIn my session, I talked a bit about one of the things PhoneGap/Cordova developers need to keep in mind - internationalization. My focus was specifically on number and date formatting (hey, I only had twenty minutes!) and I demonstrated how you can use the Globalization plugin to achieve this.\nIf you have any questions about what I demonstrated, just leave me a comment below!\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Adobe ColdFusion 2016 Released",
		"date":"Tue Feb 16 2016 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455580800,
		"url":"https://www.raymondcamden.com/2016/02/16/adobe-coldfusion-2016-released",
		"content":"\nThis morning Adobe released the latest version of ColdFusion, Adobe ColdFusion 2016. Yep, they switched from a simple version number to a year, which frankly I prefer so I think this is a good change. This also applies to ColdFusion Builder as well. So what's new and should you upgrade?\n\nThe Good(ish)\nYou can start off by looking at a broad overview of what's new in ColdFusion 2016. I won't repeat what's there as you can read it yourself just fine, but will point out that the specific section\non language enhancements may be of interest to developers. The &quot;Safe Navigation&quot; feature can let you simplify your code a bit, but note that the docs for this in the previous link are poorly done. You can now do both ordered and sorted structs, which I know some folks really want. I always thought of structs as inherently unordered, but I understand that adding order/sorts to them are useful in some situations. Unfortunately, the docs don't demonstrate how to use these features in short notation. I haven't typed structNew since {} was added. I'm also happy to see that you can now disable scope search by adding searchImplicitScopes=false to your Application.cfc file. Unfortunately this feature isn't mentioned in the docs and as we can't edit/comment on the docs anymore, hopefully it gets corrected soon.\nIn fact, from what I can tell, none of the docs are updated yet. A new function, valueArray, is documented but not discoverable in the search. I'm assuming this is just a temporary issue. You can't even find the PDFs for the CF2016 docs. Update - I see the new docs now - somewhat. The missing item in App.cfc is still missing. I also heard from an Adobian that this should be cleared up by the end of the day. Let's be blunt though. It is 2016. If you can't schedule a product release with documentation then you are failing at doing your job. This may be totally out of the ColdFusion's team hands - but customers don't care whose fault it is. It is ridiculous, unprofessional, and something that should have been addressed years ago.\nFor a better list of language enhancements, check out the New and Changed Functions/Tags page. querySort and queryEach are especially nice additions.\nThe other big update is the addition of a CLI, which is nice too. I can't say how it compares to CommandBox though which has been available for a while now. At minimum, CommandBox is free and available to people using ColdFusion 11 so there's that.\nAnother big update is the API manager. This is something I didn't get a chance to test (more on that later), but it is a pretty impressive feature, much more than CFCLIENT was in my opinion. It provides a large set of features around documenting, handling, analyzing, your APIs. As this is something I'm digging deep into on the Node side, I'm impressed to see ColdFusion support it. There's some good videos on this feature (and others) available here. Just try to ignore the robo-voice and you'll see what I mean.\nFinally (and to be clear, I'm just calling out some items I think are interesting), ColdFusion Builder 2016 includes a cool little feature called the Security Analyzer. Right from your editor you can get a scan of your code for common security issues. To be clear, this does not replace a real, very deep, security analysis of your code. However, I think it could be really useful for getting some of the simpler stuff out of the way before the real/deeper scan is done later.\n\nUnfortunately, this feature is tied to your ColdFusion server and only works with ColdFusion Enterprise. I cannot stress enough how much of a mistake I think this is. Yes, you do get 3 copies of CFB 2016 with your purchase, but I can't imagine a ColdFusion team using Enterprise with only three people. I think CFB is worth the price, don't get me wrong, and I say that even though I pretty much despise Eclipse, but I think this upgrade is not worth charging for. It should be a free upgrade to CFB. To be clear, nothing else changed outside of the syntax library.\nAnd by the way, if you are not planning on upgrading to CFB2016, you can download the trial and then copy the library definitions to your CFB3 install following the instructions here. I did a quick test and can confirm it worked for my copy of CFB3.\nBut it just seems insane to me to make a security feature, one that can help ColdFusion as a product in general, is Enterprise only. It was just two months ago that ColdFusion turned up on a list of insecure languages. This is not how you fight that perception.\nThe Bad\nI was a member of the Pre Release for ColdFusion 2016. Obviously the details of the PR are under NDA. I do not believe what I'm about to say will break the rules of that NDA.\nI love the ColdFusion community. I even did a sappy video about my love for this community back in 2014.\nIn regards to the Adobe ColdFusion team, I know almost everyone on that team and have met them multiple times. I like them. But I cannot describe how deeply disappointed I am in how",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Missing DevNexus",
		"date":"Mon Feb 15 2016 21:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455572640,
		"url":"https://www.raymondcamden.com/2016/02/15/missing-devnexus",
		"content":"So yeah, the title says it all. Due to a family issue, I had to cancel my speaking engagement at\nDevNexus this year. The good news is that my session on Ionic Services\nis being covered by a member of the Ionic team, Mike Hartington. That's about the best kind of replacement\nyou can get.\nFor folks who want to learn more about Ionic services, I'd be happy to give a Google Hangout presentation online sometime next week. If I can get at least two people to comment and say they would attend, that's enough for me to take an hour out of my day and stream a quick presentation.\nAs an aside, the 'family issue' is a good one and isn't anything to be concerned about. (And no, it doesn't involve me flying to China again.)\nMy next presentation will be at FluentConf where I'll be speaking on StrongLoop.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Displaying Google Street View images",
		"date":"Sat Feb 13 2016 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455321600,
		"url":"https://www.raymondcamden.com/2016/02/13/displaying-google-street-view-images",
		"content":"A few days ago a buddy of mine brought me an interesting problem. He was using JavaScript to dynamically\ndisplay a Google Street View image of a property. Instead of making use of the Google Maps APIs, he simply\ncrafted the URL dynamically based on an address. Here is a snippet of the code that did this.\n\n\nAs you can see, the image tag passes parameters for height, width, and address, with the address portion being dynamic. This worked well, except that in some cases, a Street View image wasn't available:\n\nHe wanted to know - was it possible to handle that and do something special when an image wasn't available? I did some quick research and ran across a Stack Overflow answer by @user1852570 (probably not a real name - just assuming):\nHow to detect if Google StreetView is available in an address/coordinates\nHis answer describes using StreetViewService of the Google Maps API. If you follow the link to the Google API example you'll see that you can request a Street View result and check a result value to see if data was available. I took that initial example and integrated it into a dynamic example like he was using originally. Here is the updated code. As a note - this script expects URL parameters for a location that get translated into longitude and latitude by a back end service.\n\nThings kick off with the Google Maps script tag at the very bottom of the page. It is set to run initMap when done. If you go up to that, we begin by firing off the call to the service to figure out the longitude and latitude of the particular thing we're looking for. That service isn't important - just note that it returns a longitude and latitude. As an FYI, the &quot;image url&quot; shortcut allows for generic addresses, like &quot;So and So Elm Street&quot;, but the API we're using requires a longitude and latitude instead.\nNext we create a Street View Panaroma object to display the image. After setting the address we can use the callback and the google.maps.StreetViewStatus result to see if we had good data. For my friend, he simply wanted to hide the image if nothing was available. Obviously you could do something else instead.\nAnd that's it. Now to be fair - the end result is a bit different. Instead of just an image we actually have an &quot;embedded&quot; Street View that can be rotated and changed.\n\nI believe you could use the API to prevent that if you really wanted to, but I doubt that really matters. You could also skip setting the result at all and just use the initial image url version on a good result.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Time for Ionic 2",
		"date":"Thu Feb 11 2016 22:53:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455231180,
		"url":"https://www.raymondcamden.com/2016/02/11/time-for-ionic-2",
		"content":"A few weeks ago I wrote about my experience looking into Angular 2 (Time for Angular 2?). I haven't had a chance to dig deeper into Angular 2 since then but it is still high on my list to get more familiar with over the year. About a week and a half ago I had pleasure of meeting Mike Hartington from the Ionic team at PhoneGap Day. I sat in his Ionic 2 workshop and came out convinced it was time to give it a try as well. And of course, just a few days later the Ionic folks released the beta version of 2.0. It is definitely beta, but I was able to build an incredibly simple app with it (after some help from Mike and others) and I thought I'd share my experience.\n\nOne of the most important things to note is that you can install Ionic V2 without screwing up your ability to do Ionic V1 apps or work with existing projects. It may sound silly, but as easy as npm can be, I didn't want to have to worry about switching back and forth to work with existing projects. Heck, the CLI even defaults to V1 anyway so in case you forget, are in a rush, or whatever, it will continue to work the way you expect it to.\nYou'll want to spend some time in the docs of course, and specifically the Getting Started guide. Note that there are mistakes in the guide (hey, it is a beta) that may trip you up. Specifically it seems like some of the code samples are a bit out of date. For the most part I think you can figure it out, but these issues have been reported and may be corrected by the time you get to it.\n\nWorking through the Getting Started guide gets you through a simple application and will give you a super\nbasic idea of what's going on, but I'd strongly urge you to go through the Angular 2 quickstart first just so things aren't completely alien to you.\nYou'll also want to peruse the components docs. From my quick look through it, things look quite a bit simpler to use, but again, this is still in development. A notable missing component currently is the loading widget. Be sure to notice that the component examples\ngive you a quick way to toggle between Android and iOS views, and specifically that Android now has a Material look that is really freaking sweet. As an example, here are tabs in iOS:\n\nNow compare it to the Android version:\n\nThat's damn spiffy.\nSo what's the code like? As I said before, Angular 2 is different. Not bad different, just... different.\n\nWhile working on my first demo, I ran into things I immediately liked. For example, I love that my &quot;page&quot; template and controller are in a folder nicely packaged together. I could have easily done that in Ionic/Angular1 too I guess, but I like the organization.\nI'm also really liking working with ES6. I'm barely scratching the surface of course, and probably doing it wrong, but even stuff I thought was overblown, like fat arrows, are really growing on me.\nOn the other hand, some things I ran into felt... wrong. I got over it. I also know there were things like that in Angular1 as well. But sometimes I just bit my tongue and went with it. So without further ado, here is an Ionic 2 version of my RSS Reader app. I did not build it out completely like the version I have on the Ionic market (RSS Reader), but I built in basic list/detail support. I'll share the code, but I want to give big thanks to Mike Hartington (again), @adbella on the Ionic slack, and others.\nPlease do not treat this as good code! Treat this code like a slightly drunk cat walked over your keyboard and then had a little &quot;accident&quot;. It works - and the part that @abdella helped me with is elegant, but I'm sure I could have done things better. With that out of the way, let's first look at the home page. It simply lists items from the RSS feed. First I'll show the view:\n\nNothing really weird here except the new way Angular 2 does attributes/code/etc. But I'm assuming you can figure out what is happening here. Now let's look at the code for this page.\n\nSo yeah - this is where things got a bit weird. One of the new things in the Angular 2 world is the idea of Observables. These replace (ok, not replace, but my understanding is that they are preferred) Promises and are supposed to be quite a bit more powerful. You can't really see the Observable, but see the subscribe() call? That's using it. Part of what makes them powerful is that they would support live updates. That doesn't really come into play with RSS parsing, but if it ever were added to my project, in theory it would just plain work. (Note to self - I'm going to try to make a demo of a live updating Ionic 2 app soon!)\nAlso note the navigation in openPage. I don't have to build an app router now. I just load the page and pass data. I really, really appreciate that.\nLet's look at the service now.\n\nI apologize for the large blocks of commented out code, but I wanted to keep a record of my previous versions for reference later. The slick http.get().map().map() syntax is an example of Oberservables in action and is thanks to @abdella. The basic ",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "HTML Escaper Visual Studio Code Extension",
		"date":"Wed Feb 10 2016 00:00:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1455062400,
		"url":"https://www.raymondcamden.com/2016/02/10/html-escaper-visual-studio-code-extension",
		"content":"Just a quick note to say I've released a new extension for Visual Studio Code. This extension simply takes a document and outputs an escaped version of it appropriate for blog posts\nor other online forums. This is basically the same thing I built for Brackets, but it wasn't possible for\nVisual Studio Code until the release this month. Here's an example of how it looks.\n\nThe code I used was based on a simple example made by Erich Gamma of Microsoft. Shoot, 90% of the code is his and I just modified the bare minimum to create my extension. You can find my code here (https://github.com/cfjedimaster/htmlescape-vscode and you can install it directly within Visual Studio Code itself. You can also download the packaged version\nfrom the gallery as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Speaking at DevNexus 2016",
		"date":"Fri Feb 05 2016 21:06:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1454706360,
		"url":"https://www.raymondcamden.com/2016/02/05/speaking-at-devnexus-2016",
		"content":"Just a quick note to let folks know I'll be speaking at DevNexus in ten days. I'm giving a presentation on Ionic services and will also be covering my coworker Andy Trice's presentation on cognitive computing and mobile applications.\nUnfortunately it looks like the conference is already sold out, but if you're attending and read this blog, please come by and say hello. I'll be bringing a copy of my last two books with me and will pretty much give them out to the first person who asks for them. Hope to see you there!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Playing with StrongLoop - Building a Blog - Part Three",
		"date":"Wed Feb 03 2016 20:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1454530260,
		"url":"https://www.raymondcamden.com/2016/02/03/playing-with-strongloop-building-a-blog-part-three",
		"content":"Welcome to the third in my series of building a (somewhat) real-world application using StrongLoop. In the first entry I built the beginnings of a simple blog engine. I defined two models (entry and category) and whipped up a quick front end for the blog. In the last entry I locked down the APIs so that unauthenticated visitors couldn't create content. Today I'm going to demonstrate an administrator for my blog. My administrator will be a desktop tool built with Electron and Ionic. I first blogged about mixing Ionic and Electron about six months ago. It is still rather easy and you can check out the results on the GitHub repo for this project when your done reading. (I'll include the link at the end.)\n\nLet's take a quick tour through the app and then I'll demonstrate it in action with a quick video. On startup, the application prompts you to login. Please do not blame Ionic for my poor color choices.\n\nAfter login, you're presented with a list of existing blog entries as well as a button to add a new one. For this quick demonstration, I did not add editing or deleting capabilities, but it wouldn't be too difficult.\n\nClicking Add Entry brings you to a simple form:\n\nAnd that's it. As I said, proper edit/delete isn't built in yet, but that's all it would take to turn this into a real CRUD desktop app for the server.\nTo be clear, I'm really barely scratching the surface of what Electron can do. I've basically used it as a simple wrapper for a web view and nothing more. Off the top of my head - here are some more interesting features I could add to it:\n\n\nDrag and drop images. I could capture the drop event - upload the file to the server, and automatically inject the HTML for the image into the source. This is how WordPress does it and it would certainly be possible with Electron.\n\n\nOf course, I could customize the icon like a &quot;proper&quot; desktop application.\n\n\nAnd probably more that I'm not thinking about.\n\n\nSo how about the code? First and foremost I want to point out that StrongLoop has an AngularJS library and I that I should have made use of it. I did not. I want to - eventually - but I thought it might be a good opportunity to work more with AngularJS's $resource feature. To be honest, I had a few problems with it and I should have taken that as a clue to just switch to StrongLoop's stuff, but I was stubborn.\nHere's how I designed my Services:\n\nAs you can see - I simply $resource-wrapped my two main APIs - one for users and one for entries. (I'm still not really supporting categories yet.) For users I had to add the custom login method that ships out of the box. On the calling side, here is the controller code for doing login.\n \nFirst off - I'm hard coding the username and password in there just to save me on typing. That's a pro-tip there. The login call is pretty simple, but I need to remember the auth token returned by the Loopback API. I both store it in rootScope (that's bad, right? I'm ok with bad) and add it to my $http headers. I did that because I had trouble getting custom headers to work with $resource. That's most likely my fault, but this worked for now. You can read more about authenticating requests and StrongLoop at the docs: Making authenticated requests.\nListing entries is simple - I had to include the ordering argument in the controller code which also feels like a mistake (it should be in the service I think), but it worked well enough:\n\nFinally - here's how I save a new entry. Note I automate the slug and published values.\n\nYou can find the source code for my app here: https://github.com/cfjedimaster/StrongLoopDemos/tree/master/blog2/client/electron-quick-start. I also built a quick video showing the app in action. Enjoy!\n",
		"tags":[
	        
            "strongloop",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Article on Improving Your JavaScript Skills",
		"date":"Mon Feb 01 2016 19:12:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1454353944,
		"url":"https://www.raymondcamden.com/2016/02/01/article-on-improving-your-javascript-skills",
		"content":"Nothing much to say here really except that I've released another article for the\nTelerik Developer Network - &quot;Leveling Up Your JavaScript&quot;.\nAs you can probably guess, the article discusses how to improve your JavaScript skills. This is not meant to replace any of the good books out there. Rather, this is a high level view at what helped me improve my own skills. It is focused on a few specific strategies that I found useful for becoming a better developer.\nAnd to be clear - I'm talking about a &quot;better developer&quot;, not a &quot;great&quot; developer or heck, even &quot;good&quot; - just better. Becoming better at JavaScript (and web development in general) has been a fun, and sometimes painful, process. So I've tried to provide some guidance as to what worked well for me. Obviously these tips may not work for you, but I hope they provide some useful suggestions for you.\nPlease let me know what you think in the comments on the article!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "development"
            
		]

	},

	{
		"title": "Visual Studio Code extension for Cordova",
		"date":"Fri Jan 29 2016 00:27:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1454027244,
		"url":"https://www.raymondcamden.com/2016/01/28/visual-studio-code-extension-for-cordova",
		"content":"Earlier today Microsoft released a set of updates for TACO. TACO stands\nfor &quot;Tools for Apache Cordova&quot; and has been around for a little while yet, but I've not had a chance to give it proper review on the blog. I still plan on doing so, but I wanted to specifically call out part of what was released today - tooling support for Visual Studio Code.\n\nObviously this only helps you if you are a Visual Studio Code user, and if you are not, I highly suggest you take a look at the rest of the TACO site to look at the other parts of the suite. As I said - there is some impressive stuff here. If you do use Visual Studio Code though you'll want to grab this extension right away.\nThe extension provides three main features:\n\nFirst, you get a debugger for iOS and Android. You can debug applications running on both the simulator and a real device.\nSecond, you get Cordova commands in the command palette. Right now this is just Build and Run, but it's something.\nThird, and my favorite, you get intellisense for the plugin APIs. This provides code completion for Cordova plugins. Even cooler - the extension is smart enough to know what plugins you have installed in the current project. So if you don't have the Camera plugin installed, you won't get code completion. How does it look? Here is a quick example:\n\n\nTo give you an idea of how the debugger looks, I did a quick video. I'm coming down with a bit of a cold, so forgive the somewhat scratchy voice.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile",
            
                "development"
            
		]

	},

	{
		"title": "Survey Results for Rebecca Murphey's Learning JavaScript Survey",
		"date":"Tue Jan 26 2016 21:40:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453844446,
		"url":"https://www.raymondcamden.com/2016/01/25/survey-results-learning-javascript",
		"content":"A few weeks back, Rebecca Murphey tweeted out a link to a survey she was running involving how people learn JavaScript and how it has impacted their careers.\nHave you worked on getting better at JS in the last year? I’d love your input https://t.co/qEGaIWW6ha I’ll share what I’ve learned soon!&mdash; Rebecca Murphey (@rmurphey) January 12, 2016\n\nMore recently she shared the raw results as a CSV dump and I thought it would be fun to parse the results. I was genuinely interested in the data and I thought it would give me a chance to play with a JavaScript charting engine. I've taken a stab at rendering the data and I thought I'd share the results. I'll link to the demo at the end, and please note that any mistakes are entirely on my end, and not Rebecca's fault.\nI began by looking into CSV parsing with JavaScript. A quick search turned up PapaParse which has an incredibly simple API. Here's what I had to do to parse the data.\n\nThat's pretty darn simple. PapaParse has quite a few options so I definitely recommend checking it out.\nI then looked into charting the results. I began with Chart.js which was pretty and easy to use, but I couldn't figure out how to make the pie chart labels show up all the time and not just on mouse over. I found a workaround, but honestly, it just kind of bugged me that I couldn't do it easier so I punted and went over to Chartist.js. Personally it was the mascot that sold me.\n\nThe API was a bit weird in places, but I was able to get some basic charts written out. I thought the engine made some odd choices for colors. For example, a two-slice pie chart would use a red and then a near-red color. You could distinguish the slices, but they were pretty close. You can tweak the colors of course, but the defaults for the pie chart seemed odd in my opinion.\nAnyway, here are the questions and results, taken out of my ugly demo app and positioned a bit nicer.\nHave you focused specifically on improving your JS skills at any time in the last year?\n\nAbsolutely no surprise here.\nWhich resources did you use to help you learn?\n\nKinda surprised how high blogs rank here, especially since blogs seem to be better for one offs, like, &quot;How do I do cowbell in X&quot;, versus more broad training. Then again, maybe people need more concrete examples versus learning JavaScript at a broad level.\nDuring your learning, which areas have been particularly challenging?\nJust an FYI, I skipped a few question. Anyway, the chart. And I apologize, this is a big one.\n\nThe labels are pretty much unreadable there. You can find a larger version here. Again, just click for the &quot;full&quot; view. Top three issues were:\n\nBuild tools\nUnderstanding how to apply things to the real world\nLack of mentoring/guidance\n\nNumber two in that list is a particular pet peeve of mine. I've seen far too many examples that are so far removed from reality that they are near useless. (And to be fair, I've done it myself.)\nWhich technologies do you use in your current role?\n\nAgain, no real surprises here, although I would have thought Node would have been higher. I apologize for the clunkiness of some of the labels.\nHow long have you been working professionally in web development?\n\nWoot - I'm not the only old person in tech! I'm squarely in the 10+ years bar there. I began working with the web around 1994 or so.\nHow would you rate the improvement of your JS skills in the last year?\n\nThis is a good chart. Well, not my design - I don't like the lack of space. What I mean is - it looks like most people are improving, and improving at least a little bit. Hey, you don't have to go from noob to demigod in one year. Baby steps is just fine.\nHave you gotten a new job, a promotion, or new responsibilities since improving your JS skills?\n\nI'd say that's a damn good chart too.\nThe full report\nOk, I should clarify - this isn't a full report - I didn't chart the questions focused on the one resource that was best/worst. But you can view everything here: https://static.raymondcamden.com/rmurphey/\nPlease - for the love of God - do not view source. Seriously. I'm not being humble. This is absolute crap code. Thanks again to Rebecca Murphey for creating this survey. I'd strongly recommend checking out her js-assessment project as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "development"
            
		]

	},

	{
		"title": "Apache Cordova SplashScreen Change",
		"date":"Mon Jan 25 2016 22:16:10 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453760170,
		"url":"https://www.raymondcamden.com/2016/01/25/cordova-splash-screen-change",
		"content":"A few days ago I began to notice something odd with my Apache Cordova tests. When the application would launch, the splash screen would fade away as opposed to just disappearing. I thought this seemed familiar so I double checked the Apache Cordova blog and re-read the last plugins release post. I had read it when it was published, but not terribly closely. In it, it mentions that the SplashScreen plugin was updated to 3.1.0. In the notes, you can see &quot;Implementing FadeSplashScreen feature for Android&quot; and something similar for iOS.\nIf you head over to the SplashScreen plugin doc though, this update isn't mentioned in the main Preferences section. Rather, you have to scroll down to &quot;Android and iOS Quirks&quot; to see that both a FadeSplashScreen and FadeSplashScreenDuration preference were added. (I've logged a bug about documenting this up in the top preferences section.)\nAs the docs say - the default is true, so if for some reason you don't like this new behavior, you'll need to add this to your config.xml:\n\nWant to see what this new feature looks like in action? Check out the video I created.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile",
            
                "development"
            
		]

	},

	{
		"title": "Dynamic Subjects with Formspree",
		"date":"Fri Jan 22 2016 14:29:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453472986,
		"url":"https://www.raymondcamden.com/2016/01/22/dynamic-subjects-with-formspree",
		"content":"Whenever I present on static site generators, I spend some time discussing how to get &quot;dynamic&quot; features back into the site. One of the most important things people lose when switching to flat files is the ability to process forms. Luckily, there are a variety of different ways to get that feature back (I'll share some alternatives at the end). For my blog, I've gone with Formspree.\n\nFormspree is a simple to use service where you simply point your form at their servers and they handle the rest. They have a free tier that supports up to 1000 emails a month which is more than enough for me. Here is a simple example of how you can make use of the service.\n\nTo have Formspree send you an email when a form is filled out, you simply set the action of the form to include your email address. The first time someone uses the form, Formspree will ask you to confirm it, but once you do, the emails will be sent to you automatically.\nFormspree also supports a few special field names that change how the form behaves. Notice the _subject field. This will set the subject of the email you get. Notice _reply to on the email field. This will let you hit reply in your email program to respond to the person who filled out the form. You can find out more if you read the docs on their site, but in general, it is an incredibly simple service to use and you can have it up and running in minutes.\nOne issue bugged me though. Notice how my subject is, &quot;Blog Contact Form.&quot; When I would get multiple emails from my blog, GMail would thread them all into one thread. This is expected I suppose, but it made it a bit more difficult for me to respond to form submissions. It occurred to me that I could easily use JavaScript to modify the form while it was being filled out. I decided to include the email address in the subject itself. Here is a sample of how you could do that.\n\nPretty vanilla jQuery code here and it could probably be done nicer, but it works just fine:\n\nAs an FYI, I emailed Formspree asking for a feature like this before I figured it out it would be easy in JavaScript. The folks at Formspree replied really quick, and while I didn't end up needing their help, it was great to see how quickly they responded to a support request. (Also note that they said they kinda liked the idea of a dynamic subject like I described and it may end up becoming a feature in the future!)\nAlternatives\nSo while I'm perfectly happy with Formspree, here are a few other alternatives you may consider:\n\nWufoo\nFormKeep\nGoogle Docs (you can embed a form)\nAnd hell, a mailto: link works too!\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Speaking at PhoneGap Day",
		"date":"Thu Jan 21 2016 14:02:27 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453384947,
		"url":"https://www.raymondcamden.com/2016/01/21/speaking-at-phonegap-day",
		"content":"I'm a bit late on announcing this, but next week I'll be speaking at the 2016 PhoneGap Day in Lehi, Utah. Unfortunately tickets are sold out, but if get a chance to attend next year, I highly recommend it. PhoneGap Day is an\nincredibly casual, fun, and just plain exciting way to learn more about Apache Cordova and hybrid mobile development. There's also a full day of deep workshops before the event where you can learn both basics and advanced topics. This year I'm giving a presentation\nconcerning internationalization of data and I hope folks find it useful.\nWhile the US PhoneGap Day is sold out, the European one in Amsterdam is not sold out.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Welcome to RaymondCamden.com 2016",
		"date":"Wed Jan 20 2016 20:46:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453322814,
		"url":"https://www.raymondcamden.com/2016/01/20/welcome-to-raymondcamden-2016",
		"content":"Well, I've been alluding to this now for a few months, but I've finally pulled the plug on the old site and migrated everything over to a set of flat files with Hugo powering the generation. I'm using Surge for hosting and will be upgrading to the paid plan (all of 10 bucks) as soon as possible.\n\nI already blogged about the process a few days ago, so I don't have anything to add to it, but I can say that - as always - my concern is to ensure that folks can come here to learn new things, talk about technology (and kittens!), and find value in this site as a whole.\nAs far as I know, everything should have migrated over except for my old blog attachments. I had about 800 megs, yes, 800 megs, of attachments associated with blog posts, and frankly, most of those were a minimum of two years old or older. So for now, I'm not shipping any of them, but on a case by case basis will restore them - most likely to my Amazon S3 bucket.\nI want to be clear that I think WordPress is a damn fine platform, but I don't want to worry about my server going down due to some misconfiguration or XML-RPC hack. I'm also extremely impressed with Google's Cloud Platform technology. I found it easier than Amazon's and in general, it just plain worked well. My costs were never too high (around 20-25 bucks per month or so) and everything pretty much worked as it should.\nIf you do run into anything weird, just drop me a comment below.\nAnd in case you're curious, my first site, before even the blog, looked like this:\n\nDon't even ask me what I was thinking about when I chose those colors.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "What happens when you screw up an Ionic Deployment?",
		"date":"Wed Jan 20 2016 03:48:27 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453261707,
		"url":"https://www.raymondcamden.com/2016/01/20/what-happens-when-you-screw-up-an-ionic-deployment",
		"content":"Last week I had the honor of presenting at the Ionic NYC meetup. While talking about the Deploy service, someone asked what would happen if you sent bad code to the deployment. I thought that was a great question and I did exactly that in front of the audience so we could all see.\n\nFor folks not familiar with what Ionic's Deploy service does, it basically lets you push updates to your application without doing a formal app store approval. There are restrictions of course. You can't add (or remove) plugins. But basically anything under the www is fair game. Things like typo fixing, new images, audio assets will work fine. You can even add completely new features by updating your app's JavaScript and templates.\nAnd yes - this is allowed by both Apple and Google. You don't want to abuse this though. If your app is a &quot;Kitten Viewer&quot; and you push an update that changes it to a post-apocalyptic shooter (still involving kittens) then you will most likely get a slap down. (If someone has created a post-apocalyptic kitten game, please let me know.)\nThere is a bit of a setup to enable this feature (fully documented via the link above) that takes roughly five minutes, and the code is really simple considering how complex the actions are. Here is an example taken from the docs that demonstrates how to check for and actually do an update.\n\nIn general, it just plain works, and works really well! Using the code above, if you run doUpdate(), then the app will grab the assets and automatically reload the app. (And if you are curious, you can also get an update and not automatically reload. You've got a lot of options actually - check the docs!)\nSo what happens when you screw up? Like - skip testing? No one ever skips testing, right? I built an incredible simple app with the grand total of two buttons:\n\nThe red button handles both checking, and installing, an update. The green button calls a service.\n\nBy the way - I used an alert in the code because it was quick and dirty. In a real app, avoid alert and use the Dialog plugin. The service is incredibly simple:\n\nAlright - so as a first test, let's break the service.\n\nIn the code above, I've added a runtime error to the service that will only be a problem when the user clicks the button. I deployed via the CLI: ionic upload --note=&quot;screw up&quot; --deploy=&quot;production&quot;. I then clicked the red button, noted the update in my console, and tried the green button. As expected, clicking the green button will no longer work, and in the console, the error is clearly visible.\n\nOk, so in theory, not the end of the world. You can deploy a fix, or roll back, and life goes on. But what if you really screw up? In my main JavaScript file, I added a syntax error on top. I then deployed that - ran the update - and...\n\nSo yeah, at this point, you're screwed. You can't do an update anymore as the core functionality of the entire app is broken. You would need to do a &quot;real&quot; app store update to correct it.\nTo be absolutely clear - this is not any kind of bug on Ionic's side. This is completely my fault. That being said, I can say that the Ionic folks are looking into ways to help prevent stuff like this from happening. Or you could actually just test your code. Just an idea - not trying to be pushy.\nAnother option to consider is making use of deploy channels. As you can guess, these are 'groups' that let you specify who should get an update. You could make a channel just for yourself and your own device and send it just there.\nAnyway, I hope this was interesting, and between you and me, I like breaking stuff. Here is a video where I go through the same process and you can see everything in action.\n\np.s. Thanks again to @ericbobbitt for help with this post and my understanding of Ionic services in general!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "DefinitelyTyped project for IBM MobileFirst and Hybrid Mobile Apps",
		"date":"Tue Jan 19 2016 04:48:55 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453178935,
		"url":"https://www.raymondcamden.com/2016/01/19/definitelytyped-project-for-ibm-mobilefirst-and-hybrid-mobile-apps",
		"content":"I've blogged before about the client-side API for hybrid mobile apps built on <a href=\"\nhttps://ibm.biz/IBM-MobileFirst\">IBM MobileFirst. One of the things I've discovered recently is the library of DefinitlyTyped definition files for TypeScript developers. These files provide intellisense for a huge set of various frameworks and client-side code written in TypeScript. Turns out though that you can also use them in regular old JavaScript files too. My editor of choice (Visual Studio Code) has great support for this. You can simply get the file, drop it into your project, and go to town.\n\nSo with that in mind - I started working on a DefinitelyTyped file for MobileFirst. I had to guess a bit at exactly how to do it, and I probably did a few things wrong, but you can get the work in progress here: https://github.com/cfjedimaster/MobileFirst-Typings. As you will see in the ReadMe, I've covered a few of the main classes in the WL namespace (this is the core namespace for the API). I'm looking for feedback on how I built it as well as volunteers to help complete the library with a pull request.\nIn case your curious as to how well this works, check out the video below:\n\nAs a side note - you can get definition files for Apache Cordova and Ionic as well!\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Time for Angular 2?",
		"date":"Mon Jan 18 2016 06:32:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1453098732,
		"url":"https://www.raymondcamden.com/2016/01/18/time-for-angular-2",
		"content":" I've been avoiding even thinking about the latest rev of Angular as every time I did look at it, I came away with a headache. It was weird and the docs were even weirder. I saw plenty of blog posts on the topic, but in general they dealt with one small slice of Angular 2 and were too confusing for me to grok.\nOver the holidays, I checked out the web site again (Angular 2 can be found at https://angular.io/ whereas Angular 1 is still at the old site: https://angularjs.org/). I tried the five minute quick start and while it took me a bit more than five minutes, it made a bit of sense. It certainly wasn't crystal clear to me, but it wasn't crazy either.\n\nI then went through the tutorials and things began to make even more sense. I'm far from being even close to being able to build a demo with it, but the basics are beginning to click for me.\nThere were three things in particular I ran into that caused me grief.\n\n\nWorking with Angular 2 means working with TypeScript. I like TypeScript. But I'm kinda disappointed that working with Angular now means working with a build system to get it into the browser. To be absolutely clear, I'm not saying this is bad. I'm just saying I feel a bit disappointed that this is required now. I'll get over it.\n\n\nThe @Component stuff was terribly confusing to me until it finally sank in that those blocks are providing metadata to the classes. It seems so obvious now, but I just couldn't understand what in the heck stuff like this was doing:\n\n\n\n\nOne thing I really didn't like in Angular 2 was all the different types of &quot;syntax sugar&quot; being used in templates. Here are just a few examples: [(ngModel)]=&quot;foo.name&quot; and *ngFor and (click)=&quot;something&quot;. I figured there was no way in heck I'd be able to get that right. Luckily - there's a great cheat sheet that nicely documents all this and is easy to use.\n\nSo what's next? I plan on making my way - slowly - through the rest of the docs. I'm also going to reread the tutorials a few times. I then need to make the time to look at Ionic 2.\nFor a while now I've been telling people that - at least in my opinion - it was too early to start playing with Angular 2. Now I definitely feel like it would be a great time to start playing with it and - possibly - even building real apps with it. I'd love to know what my readers think so leave a comment below. Are you using Angular 2 yet or have you been holding off?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Hugo on RaymondCamden.com",
		"date":"Sat Jan 16 2016 05:02:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1452920551,
		"url":"https://www.raymondcamden.com/2016/01/16/working-with-hugo-on-raymondcamden-com",
		"content":"I've mentioned before that I'm considering moving my blog from a Wordpress install to purely static. I've made some progress on this effort and while I'm not 100% sure I'm going to pull the plug, I'm real close to it.\nI thought I'd talk a bit about how this is going and share some of the things I've learned about Hugo\n\nI had looked at Hugo a while ago and I really didn't like it. It's difficult to explain why, but it was just plain weird to me. A few weeks ago I decided to give it another look, and while I still found it weird, I kept at it and I'm kinda digging it now. In no particular order, here are some things I discovered/struggled with/built/etc:\nInstallation\nSo... this is the biggest issue. To install Hugo, you use brew. I only use brew when some random app asks me too. Most apps use npm. Ok, no big deal, they tell you exactly what command to do so you can cut and paste.\nHowever...\nWhen I installed, it gave me Hugo 0.13, an older version. This caused a number of problems while I was going through the docs.\nTurns out - Brew has some kind of internal registry and you have to update brew itself before you get an app.\nNow - I'm sure there is a good reason for that. But on first blush, it seems really stupid. If you can never install an app (safely!) without updating brew first, then why isn't that baked in? Or at least have a warning of some sort? This one issue took me a good day to get past. I filed a bug report for it and now the Hugo docs include updating brew as part of the install process.\nContent versus Themes\nThe biggest issue I had was understanding the relationship between the files of your project versus a theme. So for example, it took me a while to figure out where my home page was. Why? I had thought my &quot;theme&quot; was just layout junk and nothing more. But that's not the case. The theme actually includes your home page and other assets.\nOk, so a theme then is kind of like a full application. That took me some time to wrap my head around, but I get it now. If you want to modify how your site behaves, like for example, how many entries to show on a blog's home page, then you either edit the theme or you can copy the file. If Hugo sees the same file name in your app directory versus the theme, it will run your file instead. I figured that early on and assumed it was best practice.\nGo Templates\nSo Hugo uses Go Templates for its dynamic templates. Go is weird. I like it. But it is just plain weird. Here is an example:\n\nIt is readable - just... odd. Odd or not, I liked working with it although I had a hard time with the docs sometimes. For example, just figuring out how to limit a loop to N results took me a while.\nContent pages versus Layouts\nThis was the last issue I hit, and is probably the only thing that really bugs me. Your layout files can include Go in them to make them dynamic. Your content pages cannot. So I built a contact page and I couldn't include a simple Go variable for my site's URL. However - while you can't use logic/variables, you can use something called shortcodes instead. For my contact form, I had to build a shortcode that literally just output the site's URL. That seems stupid too, but I'm assuming there is a good reason for this. (And as it took me 2 minutes fix I got over it. ;)\nPopulating Hugo\nTo populate my Hugo site, I used WordPress's XML export feature. I then wrote a Node.js script that read it, parsed it, and output all 5000+ blog entries in the proper format. I can share this code if anyone wants to see it, but it isn't very pretty.\nHugo Performance\nHugo is really fast. But even with it being fast, at 5K+ blog entries, every modification to source files took about 30 seconds for Hugo to handle. That was too long. So while I was working, I cut out about 4k of my blog entries. At that point, Hugo was taking about a second or so to update which was fine. Hugo will also auto reload in server mode which was pretty cool. I just had to open my browser and wait for it to reload when I saved.\nTheming\nHugo has a bunch of themes you can browse at http://themes.gohugo.io/. I went with Icarus. Here is a screen shot of blog so far.\n\nAnd here is a random blog entry.\n\nI dig it. I'm not 100% sold on the look, but I like it.\nForms and Search\nFor my contact form, I use Formspree. They let you POST to them and send an email for free. It is super easy to use.\nFor search, I'm using a Google Custom Search instance. I've used this in the past (see the search at CFLib) and it works fine. I kinda think Google knows a thing or two about search.\nHosting\nHosting will be via Surge. With me no longer using Google Cloud Compute Engine (which, btw, works darn well and is recommended), I'll put my money into Surge instead for their professional plan. Deploying the full site of 5000+ files takes about a minute.\nWriting\nAnd this is the sore point for me. I really, really like the WordPress creation process. I can easily drag and drop images. I can easily assign categories and tags. I can sa",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Going from static to dynamic with Ionic Creator",
		"date":"Mon Jan 11 2016 08:28:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1452500912,
		"url":"https://www.raymondcamden.com/2016/01/11/going-from-static-to-dynamic-with-ionic-creator",
		"content":"As I've mentioned more than once now, I'm really happy with how much Ionic Creator has improved recently. For this blog post, I thought it might be useful to demonstrate how you could go from a &quot;static&quot; Ionic Creator proof of concept to a dynamic one that made use of a real API. For hard core developers, this is probably not going to be very helpful. But I imagine Creator will attract folks who may not have a lot of experience working with JavaScript and APIs so I thought a concrete example would be helpful. As always, if anything doesn't make sense, leave me a comment and I'll try my best to help out.\n\nLet's begin by discussing the type of application we're going to build. It will be a simple &quot;Master/Detail&quot; example where the initial page is a list of items and the detail provides - well - detail. As a completely random &quot;not related to anything recent&quot; idea, let's use Star Wars films for our data.\nIt just so happens that an API exists, SWAPI, that provides information about Star Wars films. In fact, I've already released a helper library for this API: SWAPI-Wrapper. We won't be using that helper in this blog post, but just remember it if you decide to actually use this data in a real application.\nCreating the Static Proof of Concept\nWe'll start off by creating a new application in Ionic Creator. Remember that this is 100% free to try. You only need to pay if you want additional projects. (You can find more detail on their Pricing Page).\nBegin by creating a new project, the name doesn't matter, and use the Blank template:\n\nThis will drop you into the editing interface with a blank page. On this page we'll do two things. First, we'll edit the title to give it something that makes sense for the app.\n\nThen we'll drag a List component onto the page:\n\nNotice how it adds 3 list items automatically. If you want, you can remove some, or add some, but for now, three is just fine. If you click each one, you can give them a unique text value. While not necessary, I'd go ahead and do that just so you mentally keep in mind what we're actually building.\n\nNotice that the list items have a &quot;Link&quot; attribute. We can use that to add basic interaction to our demo, but for now, we don't have a page to actually target for that link. Let's fix that by adding a new page. Be sure to use the Blank template again. I gave it a simple title too:\n\nThis page represents the detail view of the film. Right now we don't necessarily know what we're going to show, so let's keep it simple and imagine we'll just show the opening crawl. On the off chance that my readers have never seen a Star Wars film (for shame), this is an example of what I mean:\n\nFor now, let's just use a bit of static text. Drag the Paragraph component onto the page and then edit the content to be something that describes the purpose of the text block.\n\nNow let's hook up the list from the first page to the detail. Now, in the real application, each list item would link to a page showing different text based on the selection. However, the dynamic aspect will be handled by code we add later on. If you were to demonstrate this dummy app to a client, you may need to make 3 distinct pages so they don't get confused. If you do, don't forget that you can select the page in the left hand nav and click the &quot;Duplicate&quot; icon.\nFor now, click back to the first page, and select the first list item. Note that you can now select a link that points to the new page.\n\nGo ahead and do that for all three list items (and again, you don't really need to) and then click the Preview icon on top to test out your beautiful, if fake, application.\n\nWoot! We're done with the prototype!\nCreating the Application - Part 1\nOk, so at this point, we've got a working prototype. The first thing we need to do is get a copy of the code. You can use the Export menu option to open a window showing you four different ways of working with the code. I recommend using the Zip File. While we can create a new application from the code of the prototype directly with the CLI, I think it would be nice to have a copy of the prototype locally to compare and contrast while working on the new version.\n\nI recommend creating a new folder for this project, and then extracting the zip into a folder. (All of my code for this blog entry is in GitHub, and that's the way I laid out stuff there as well.) Assuming you've done this in a folder called creator_version, we can use the Ionic CLI to create a new application based on the contents. That command is:\nionic start v1 ./creator_version\nThe &quot;v1&quot; part there is the name of the subdirectory. As you can guess, we're going to iterate a bit from the original Creator version to our final version. Why?\nWe currently have a static version of the application. It doesn't use any &quot;real&quot; data. Our first iteration is going to make the application dynamic, but it is going to use fake, static data.\nOk, that probably sounds confusing.",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Playing with StrongLoop - Building a Blog - Part Two",
		"date":"Thu Jan 07 2016 09:03:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1452157428,
		"url":"https://www.raymondcamden.com/2016/01/07/playing-with-strongloop-building-a-blog-part-two",
		"content":"Welcome to the next blog entry in my series describing building a real (kinda) application with StrongLoop. In the last entry, I talked about the application I was creating (a simple blog), built the model, and demonstrated how to work with the model on the server-side. I completely skipped over using the REST APIs to focus on a simple content-driven dynamic site.\n\nIn this post, I'm going to cover how I can lock down those APIs. This is in preparation for working on an admin for the blog in a later post. This post will be a bit short as this a topic I covered in depth a few months ago (Working with StrongLoop (Part Four) – Locking down the API), but I wanted to discuss the topic again to call out a few things I missed last time. I suggest reading that earlier post to give some additional context to what I'm describing below.\nSo first off - out of the box your models are completely CRUDable (create/read/update/delete) via the REST API. This is handy, but of course you want to lock down these APIs so folks don't pepper your site with SEO spam.\nAs I discussed in my earlier post, locking down your API is as simple as using slc loopback:acl. Here is an example:\n\nIn the end, the CLI simply updates the model's JSON definition file. In general, the lock down process is:\n\nBlock everything!\nAllow anon folks to read\nAllow auth users to write\n\nHere is how this looks in JSON:\n\nOf course, this assumes a security model where every logged in user is an admin. More complex apps will probably have different roles associated with users. So in a blog, you may have users who can write content, but only some who can publish content so that the entry is publicly readable. For now I'm sticking with the simple system of allowing logged in users full power.\nI did this for both entry and category. Remember, these are the primary model types for my blog.\nI then created a new model called appuser. Again, I discussed this in the previous entry, but while Loopback has a core User object, it is strongly suggested you extend this type into your own for your application. One thing I didn't demonstrate in the previous post was how this was done in the Composer app. It is a simple matter of making a new type and changing Base model to User.\n\nNext I needed to test this. Again, I mentioned in the other post about how you can use the web-based Explorer to login, but it occurs to me that I didn't actually show how that is done. Login is just another REST method! If you select your user type, you can scroll down to a login method. You then need to enter a JSON object containing your credentials.\n\nAfter you login, make note of the response. The ID contains a token:\n\n(Image taken from StrongLoop docs: Introduction to User model authentication)\nYou take that and then paste it into the token field on top of the explorer.\n\nThis ensures your later calls via the explorer are authenticated. To be sure, I tested posting a new entry before being logged in:\n\nThen I confirmed I could create after logging in:\n\nI bet your curious about the server-side API. Is it impacted by ACLs? Nope. I guess that's expected, but I was curious. Basically your Node.js code executes like a root user.\nThe updated version of the code can be found here: https://github.com/cfjedimaster/StrongLoopDemos/tree/master/blog2.\n",
		"tags":[
	        
            "nodejs",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Seeding data for a StrongLoop app",
		"date":"Wed Jan 06 2016 09:21:03 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1452072063,
		"url":"https://www.raymondcamden.com/2016/01/06/seeding-data-for-a-strongloop-app",
		"content":"Please Read! A few hours after posting this, a member of the StrongLoop team pointed out an alternative that did exactly what I wanted to accomplish in about one second of typing. I still think the core of this blog entry makes sense as is so I'm not editing it, but see the note at the bottom!\nThis is just a quick post as a followup to something I mentioned in my post yesterday on building a blog with Strongloop. I mentioned that while working on my application, I kept losing my temporary data as I was using the &quot;In Memory&quot; datasource that is the default persistence method for data. That's not a bug - in memory means exactly that - in memory - and as I restarted the app (using nodemon), I had to re-enter fake data to test.\nWhile it takes all of three minutes to connect your app to Mongo, if you don't have Mongo (or MySQL, or a db in general), it would be nice to be able to stick with the simple RAM based system while prototyping.\n\nOne of the things I realized is that Strongloop will run a set of scripts inside the boot directory on startup. In theory, that could be used to set some seed data. Jordan Kasper (evangelist for StrongLoop, which sounds like a fun job, ahem) shared this script with me as an example:\nhttps://github.com/strongloop-training/coffee-time/blob/master/server/boot/create-sample-model-data.js\n\nI'm still new to Strongloop and Loopback in general, but this makes sense. My needs were far simpler, so here is a script I came up with (and again, Jordan helped me make it better) that just writes to a model in the in-memory datasource.\n\nPretty simple, and it works nicely.\n\nBut Wait - There's More!\nSo as I said up on top, a few hours after posting this, Rand Mckinney from StrongLoop shared this link with me: Data persistence. In  this doc they mention that you can simply specify a JSON file for the datasource and the in memory data will persist to it. Like, seriously, exactly what I had wanted. Here is an example:\n\nStill - probably - a bad idea in production - but as I said - this would be incredibly useful when prototyping!\n",
		"tags":[
	        
            "nodejs",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "My first O'Reilly Book: Client-Side Data Storage",
		"date":"Wed Jan 06 2016 02:04:37 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1452045877,
		"url":"https://www.raymondcamden.com/2016/01/06/my-first-oreilly-book-client-side-data-storage",
		"content":"While I've released a few videos for O'Reilly already (you can find all my ORA material on my author page), today I'm happy to announce the release of my first book. This is - to me - the coolest day ever. Since I began working in this industry (a long, long time ago), ORA books have always been the best of the best. While I've worked for multiple publishers over the past twenty years, this was my first opportunity to write a book for ORA. It isn't terribly long - but frankly - shorter technical books can be more impactful I think.\nThe book, Client-Side Data Storage: Keeping it Local, focuses on a topic that I've been interested in for a few years now - client-side storage for web apps. While this is still somewhat of a chaotic space, platform support for data storage is constantly improving.\nIn my book, I go over all the major types of storage (even cookies, because yes, they still work) and provide easy to understand explanations and plenty of demos.\nI was honored to have Nolan Lawson review the book, and this is what he had to say:\n&quot;Provides a great overview of the (oft-underexplored) browser storage landscape. A must-read for anyone looking to get started with offline storage and rich webapps.&quot;\nI consider myself an expert in this space, and Nolan is the person I go to so I think this is pretty incredible praise.\nAnyway, as always, I'd love to know what you think. If you pick up the book, let me know in the comments below, and enjoy!\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Playing with StrongLoop - Building a Blog - Part One",
		"date":"Tue Jan 05 2016 10:16:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1451989010,
		"url":"https://www.raymondcamden.com/2016/01/05/playing-with-strongloop-building-a-blog-part-one",
		"content":"This year I've decided I'm going to write the hell out of some Node code. That's both good and bad. It means I'll be learning more and more about Node as a side effect but on the flip side, I'll probably be producing a bunch of silly, not-terribly-practical examples as I go along. As always, I encourage people to remember that I am not an expert at this. I'm learning. So please feel free to comment about what you would do differently. On the flip side - if I actually make something kinda cool, then let's just pretend I'm brilliant, ok?\nDuring the holiday break, I decided to work on a new demo application using StrongLoop. I've blogged about StrongLoop before, specifically about using it to help build APIs powered by Node. There's a lot more to StrongLoop and I hope to share that with you as the year goes on. But for now, I want to focus on the Loopback-powered aspects of StrongLoop, specifically using the ORM-like APIs to work with data.\n\nWith that in mind, I decided I'd begin by building a blog. To be absolutely clear, I'm not advocating that you go out and build a blog with StrongLoop. Just use WordPress. (Yes, even with my complaints about it, I'd just use it.) Or a static site generator. But when I'm practicing a new language, I like to build things where I can focus on the language and architecture instead of figuring out features. We all know what a blog is. That makes it easier to get started.\nFor the first version, I figured I'd support a home page that lists blog entries and a detail view of the blog post. That's it. I'm going to save both administration, and security, for the next update.\nI created a new StrongLoop app (as simple as slc loopback) and then fired up StrongLoop Arc to work with the composer. I decided on two different models: entry and category. Entry, obviously, represents a blog entry. Here is how I designed it in the web app:\n\nI assume most of this makes sense as is, but you may be confused by the slug property. The slug is what comes at the end of the URL and is typically the title minus any special characters. In a real world app the editor would default this for you and you would only modify it on rare occasions. We could also set it automatically via Loopback too. (And we're going to do something kinda like that in a few minutes.)\nI then defined a category type:\n\nI then went back to Entry to set up the relationship. This is where I hit my first issue. While you can define a property of another type, it is a singular property. So I could add a category field to Entry but I'd only be able to assign one category to an entry. Of course, Loopback supports all kinds of &quot;multi&quot; relations, but unfortunately, the web based admin doesn't support setting it. Nor will it report it either. In the first screen shot, I've already got things working fine, but there's no indication of it.\nLuckily, it takes about 5 seconds to define the relationship via the CLI. You simply type slc loopback:relation and you are prompted for the model to modify:\n\nThen the type of relation:\n\nThen what to connect to:\n\nAnd then finally - what to call the relationship:\n\nI have no idea how it figured out that the plural should be categories. You can define a plural name for your models but I never did for category. If it figured it out automagically - then cool.\nThere's a few more prompts you can just accept, and at the end, your modal JSON is modified:\n\nFrankly, looking at that JSON, it is just as easy to type it as it is to use the CLI, so I'm not too bothered that I can't do it in the web app. (Although I still wish it was at least recognized.)\nOnce I had that - I went ahead and opened up the StrongLoop API explorer and made a few blog entries:\n\nI don't have a proper &quot;admin&quot; yet, but it takes mere seconds to use the explorer. That's damn convenient.\nOk, so just to recap - at this point I've used Loopback/StrongLoop to define my content models. I even made a bit of content. I then turned my attention to actually building the application.\nA Loopback application is a Node.js app using Express. That's it. However, there's a default structure to the app that you should familiarize yourself with. This structure is nicely documented (Standard project structure). A particular note is the default routes.js file. You'll find this in the boot directory which is automatically loaded by your application on - you guessed it - boot. I began by adding a route for my home page:\n\nFor the most part this is boilerplate Express, but note how I can use Loopback's APIs via the models object. The find method is a powerful query tool and in this case, we're simply asking for items released and doing a sort. The result will be an array of objects that I can use as - err well - simple objects. For example, this is my view:\n\nNothing special about that, right? Do note though that I'm using a URL property. That didn't exist in the model. How did I do that? I built an observer in my entry.js file to recognize load events:\n\nI",
		"tags":[
	        
            "nodejs",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Speaking on Ionic in NYC Next Week",
		"date":"Mon Jan 04 2016 02:13:47 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1451873627,
		"url":"https://www.raymondcamden.com/2016/01/04/speaking-on-ionic-in-nyc-next-week",
		"content":"Live in NYC? Want to hear me speak about Ionic services? Want to just heckle me instead? Come check out my presentation to the Ionic NYC meetup group next Wednesday:\nIonic Services - Raymond Camden\nThis is my first time presenting to this group and I plan on bringing some schwag as well as a copy of my Apache Cordova book to give away. See you there!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Another year, another Wordpress issue",
		"date":"Sat Jan 02 2016 10:28:28 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1451730508,
		"url":"https://www.raymondcamden.com/2016/01/02/another-year-another-wordpress-issue",
		"content":"Thanks to a reader (thanks Theo!) I've discovered that there is a problem with the latest Wordpress install and my blog. If you try to access a URL and get an infinite redirection loop, please let me know. At the end - this is my fault. When I switched from my custom blog to Wordpress, the &quot;pretty urls&quot; were in a slightly different format than my old site. To fix this, I hacked up Wordpress itself, which I know was dumb, but I somehow managed to survive for a year and multiple WP updates without it ever coming to bite me in the ass - until 4.4 was released a few weeks ago. I didn't even know, but some URLs began infinitely redirecting on themselves. As far as I can tell, the issue is related to the hack I did, but oddly I can't find a consistent way to recreate it. I can fix it for one URL at a time by editing the post, which makes no sense to me.\nFor now - my plan is to check my access log for URLs returning 301 and then fix them one by one - while also looking real hard at moving to a completely static solution so I can finally remove PHP/MySQL/NGINX from the equation and go to simple flat files.\nSigh.\nProgramming would be a lot easier without all the darn programs.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Doing some testing...",
		"date":"Sat Jan 02 2016 05:09:14 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1451711354,
		"url":"https://www.raymondcamden.com/2016/01/02/doing-some-testing",
		"content":"Sorry for the noise folks, but I'm doing a bit of testing.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Favorite Media in 2015",
		"date":"Mon Dec 28 2015 04:25:47 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1451276747,
		"url":"https://www.raymondcamden.com/2015/12/28/favorite-media-in-2015",
		"content":"Following up on my previous non-tech/just for fun posts (Books I Read in 2015 and My Year), here is my favorite media of 2015. This was originally going to be my favorite music of the year, but I figured I'd expand it a bit to cover movies and video games too. This is obviously a very personal list, but hopefully you'll get some ideas about things to watch/play!\n\nFavorite Music\nThis is in no particular order, but here are the tracks I listened to a lot this year. The first track, &quot;Close Your Eyes&quot;, is incredible. I first heard it on Sirius XMU and was just blown away. It sounds like a pop/rap/NIN mashup and is a great song to turn up real, real loud - just be sure the kids aren't around.\n\nSpeaking of mashups, my favorite this year was a mix of NIN and Taylor Swift. The video is really well done too. Even better, my 5 year old just loves the hell out of it.\n\nAnother surprise was the new Prodigy album - the first single was freaking awesome.\n\nMore recently, this Grimes track has been on repeat for me as well:\n\nAnd while not new, my favorite song from the past few years has been pretty consistent - &quot;Wings&quot; by HAERTS.\n\nMovies\nSo yeah, the big movie for me was - obviously - the Force Awakens. I reviewed it already. But what else impressed me this year? I thought both &quot;Avengers 2&quot; and &quot;Ant-Man&quot; were great Marvel films. I liked how different they were. One was a huge scale/popcorn chomping film while the other was much smaller, personable, still with great action. And while I'm not specifically writing up a TV section, &quot;Jessica Jones&quot; was damn good too. As an aside, Netflix is the best thing to happen to TV in a long time. From their own content to their documentary selection to even some of their &quot;B&quot; flicks, I feel like I could easily pay more for the service. Speaking of Marvel - don't bother seeing the Fantastic Four film. It isn't watchable even if you can see it for free.\n&quot;Inside Out&quot; was easily one of the best children's movies I've seen since &quot;Up&quot;. I loved &quot;The Martian&quot; and will be reading the book next year. &quot;The Visit&quot; proved to me that M.Night may have a tiny bit of talent left (stress - &quot;may&quot;).\nI was initially a bit disappointed by &quot;Mad Max&quot;, but after watching it a second time I appreciate it a bit more. I still feel like it was more a &quot;Film set in the Mad Max universe&quot; then a proper &quot;Mad Max&quot; film, but maybe I'm just being too picky.\nIf there is one film I'd like folks to consider, it is &quot;Maggie&quot;. This flew way under the radar - I don't think I saw it advertised once - but it is an incredibly well done zombie film. Outside of &quot;The Walking Dead&quot;, I'm really tired of zombies, but this is the best zombie film I've seen since &quot;28 Days Later.&quot; I highly recommend seeing &quot;Maggie&quot; as soon as you can. I never thought I'd see a zombie film that was slow and thoughtful - but this movie really pulls if off.\n\nOh, and as for stuff I'm embarrassed to admit - my guilty pleasure recently is &quot;The Royals.&quot; It is pure and utter trash, but fun to watch.\n\nFor next year, keep an eye out for &quot;Colony&quot;. I'm a fan of alien invasion stories, especially ones that are far removed from &quot;Independence Day&quot;, and this one looks fascinating so far. You can watch the first episode online now if you don't want to wait. Of course, like most sci-fi shows with a &quot;mystery&quot;, you have to assume it will probably get cancelled before they resolve anything.\n\nVideo Games\nSo I just checked, and apparently, I only reviewed 2 video games this year - Battlefront and Destiny. I liked them both, but I'm not sure I'd consider them great. I'm spending a lot of time during this Christmas break playing Fallout 4, and I like it a lot too, but again, I'm not sure I'll be thinking about it in a year's time. As an example of what I mean - my eldest son recently borrowed my PS3 (the PS4 now rules my TV) so he could play &quot;The Last of Us&quot;, and I still tend to think of that game when I think about what makes a truly &quot;great&quot; video game. (And if folks are curious, my all time favorite game was &quot;A Mind Forever Voyaging&quot;.) I guess the best I can say is that I'm enjoying the three games I mentioned and I recommend them, but nothing has blown me away this year.\nI will add that both Sony and Microsoft have deals where if you pay for a yearly multiplayer subscription, you get free games every month. While the quality goes up and down, there have been some really great freebies. So even if you don't play online a lot, you should definitely consider subscribing.\n",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "movies",
            
                "video games"
            
		]

	},

	{
		"title": "Books I Read in 2015",
		"date":"Wed Dec 23 2015 09:16:33 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450862193,
		"url":"https://www.raymondcamden.com/2015/12/23/books-i-read-in-2015",
		"content":"Yep, another lame, non-tech post. Sorry (not sorry). Today's post is my list of books read in 2015. I record my books via Goodreads which has a set of widgets that - in theory - I could use - but every single one of them is broken. Oddly - dev tools didn't reveal why they were so broken. So I decided to do something lame. I took their cool &quot;year in review&quot; blog post, snapped a picture of it with Firefox's screenshot command, and pasted it here. You won't be able to click on any book and order them via Amazon, but I figure we're all capable of searching, right?\n\nI'm rather proud of the number of books I read - and even happier that there were so few stinkers.\n",
		"tags":[
	        
		],
		"categories":[
            
                "books"
            
		]

	},

	{
		"title": "My Year",
		"date":"Tue Dec 22 2015 05:51:01 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450763461,
		"url":"https://www.raymondcamden.com/2015/12/22/my-year",
		"content":"Ok, so technically the year isn't done yet, but I was inspired by Nic Raboy's post (My Activity Report for 2015) to go ahead and post a summary. I figure posts like these really aren't that popular, but it gives me a chance to look at and think about what I did and try to benchmark where I want to be next year. I'm also planning a 'favorite music of 2015' and 'books read in 2015' post as well, so if it isn't terribly obvious, I'm enjoying the end of the year by slowing down quite a bit. I actually did write a good 2000 words yesterday on a technical topic, but that post is going to be delayed till next year (which is like 2 weeks away). Ok, so with all of that out of the way, here's a rough summary of what I got this past year and what I hope to improve on. As always, I appreciate comments (both positive and critical). My goal is to make raymondcamden.com useful to my readers while still maintaining my own personal brand. I assume people come here not just to learn X but also to hear how I specifically would teach something. I'm not the smartest tool in the shed, but I think I've got a knack for figuring stuff out and breaking down complex processes into something even an idiot (specifically me) can handle.\n\nThe Blog\nThe year started off a bit rough. Late last year I made the crazy decision to completely move away from ColdFusion for my blog and switch to WordPress. I wanted to focus on blogging and not maintaining the code base for my blog. I was incredibly impressed by WordPress and was happy to change, however the server itself suffered quite a few issues. In the end, I migrated from Apache to NGINX and that seemed to help. I also had to cache quite a bit. Frankly, that was a bit disappointing. My blog gets &quot;ok&quot; traffic, but not anything that I think would require a lot of horsepower. I truly expected WordPress to work fine &quot;out of the box&quot; for a site of my nature, but I guess that's just not the case. Once I got caching setup right things seemed ok for the most part. Most recently I got hit by a xmlrpc DOS style attack and it took me a while to figure out how to correctly block it with NGINX. As soon as I did I had smooth sailing. I'm still not convinced things are perfect and I'm still considering a completely static solution, but the process of writing in WordPress is so darn nice. I don't want to lose that. I'm toying with the idea of a desktop based app built in Electron that would give me a nice editing experience while supporting a static solution. We'll see.\nLet's talk stats.\nThis year I wrote 252 entries which is a pretty good pace I think. Obviously quality trumps quantity but I want the blog to be active. My best month was March with 30 entries (how in the hell...) and my worst was June with 13. (I think that's when I took a 10 day vacation.)\nI'm currently sitting at 5,619 blog entries total of which probably 50% have silly cat pictures of Star Wars references. Let's be honest - that's why you come, right?\nMy page views for the year are 1,523,380. For the same time period last year the count was 1,682,475. That's a drop, but it seems pretty small and hopefully not a trend. Ok, so I was curious - in 2013 it was 1,713,716 but in 2012 it was 1,116,842. So I probably need to just not worry about it.\nIn terms of engagement, I'm right over 2000 comments for the year. I'd like that number to be a lot higher, but frankly I've yet to find the &quot;magical formula&quot; for getting people to comment. Typically a post I think is important will get fewer comments than a post I think is frivolous, so, yeah, I'll just keep going. (I do try to specifically call out people to comment to help encourage them. Not sure if that helps though.)\nMy most popular posts written this year were:\n\nStrategies for dealing with multiple Ajax calls\nSelecting multiple images in a PhoneGap/Cordova app\nBlowing up LocalStorage (or what happens when you exceed quota)\nHaving trouble with splash screens, Cordova, and Android?\nSome initial thoughts on building desktop apps with Ionic and Electron\nAn early look at Ionic Push\nTracking and notifying geolocation status with Ionic\nImportant information about Cordova 5\nWorking with the new PhoneGap/Cordova ContentSync Plugin\nIonic Example: ion-slide-box\n\nEvery single article here is web related, with 70% being mobile specific. Four of the top ten are Ionic related which should come as a surprise to absolutely no one.\nUnfortunately, not one of the 'real' top viewed posts for 2015 were actually written in 2015. My number one post for the year\nwas my 2011 post on logging into a server with PhoneGap. I may need to write an updated version of that uses Ionic instead of jQuery Mobile.\nWriting\nThis was a really good year for my writing. I published 6 articles for the Telerik Developer Network. They have been a great outlet for me to reach other developers and I want to give them a big thank you.  I keep a list of all my articles on my About Me page if you want to peruse that ",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Article: A Review of JavaScript Error Monitoring Services",
		"date":"Mon Dec 21 2015 09:06:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450688772,
		"url":"https://www.raymondcamden.com/2015/12/21/article-a-review-of-javascript-error-monitoring-services",
		"content":"Just a quick note to say that the Telerik Developer Network has published me again: A Review of JavaScript Error Monitoring Services. As usual, please post any comments there, not here.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Is your Ionic View title not updating?",
		"date":"Fri Dec 18 2015 09:21:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450430471,
		"url":"https://www.raymondcamden.com/2015/12/18/is-your-ionic-view-title-not-updating",
		"content":"Ok, so I run into this once a month or so. I'm writing this just so I can - hopefully - remember it this time. This isn't a bug or anything in Ionic - but I'm wondering if it should be documented a bit more clearly for people like me. (AKA old dense people.)\nAlright - so given an Ionic app where templates are a dynamic title, this is not going to work:\n\nOddly - it will work sometimes - like if you happen to reload on that page itself - but not consistently. I'm sure there are Good(tm) reasons for this that make perfect sense, and I bet it revolves around Scope. I love Angular. Scope makes me want to push needles into my eyes though.\nSo how do you fix it? Switch to using &lt;ion-nav-title&gt;.\n\nAs I said - this is documented. Kinda. The docs for ion-view say:\n&quot;A text-only title to display on the parent ionNavBar. For an HTML title, such as an image, see ionNavTitle instead.&quot;\nBut in my mind, {{film.title}} resolves to &quot;Foo&quot; which is text only, so it should work. I looked at the docs for ionNavTitle too and nothing there really seems to make it obvious. Maybe the ionView docs should have a callout/note/etc about this situation? Like I said - I swear I hit this once a month - but admittedly my memory is crap and I tend to repeat mistakes all the time.\nThoughts?\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Review: Star Wars - The Force Awakens",
		"date":"Fri Dec 18 2015 07:47:25 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450424845,
		"url":"https://www.raymondcamden.com/2015/12/18/review-star-wars-the-force-awakens",
		"content":" Normally I wait till Sunday to do non-tech posts, but with the memory of the film still burning bright in my mind from last night, I thought I'd write it up today. The first of this review will be spoiler-free and I'll clearly mark the beginnings of spoilers. Also, the comments are fair game for spoilers too, so stop reading where it makes sense for you. This will be long winded and emotional, so if you want the tl;dr - it rocked. See it now.\n\nMy memories of the first Star Wars film are a bit hazy. I was four years so I'm not even terribly sure I'm remembering correctly. All I know is that I saw it with my father and the only scene I remember is R2 being lowered into X-Wing.\nFor Empire Strikes Back, all I remember is the line. It stretched around the movie theater.\nFor Return of the Jedi, I saw it in a little town in Louisiana. There were - maybe - five people in the theater and I sat in the very first row. (Don't ask me why - I was a dumb kid.)\nI've been a Star Wars fan for as long as I can remember. I've certainly seen better sci fi in my life, but nothing has left the impression on me as much as these movies. I love them - warts and all. When I ride my bike, I'm flying through the woods of Endor. As I write this - I'm in an office completely covered in Star Wars toys. (And some Star Trek too - along with Robotech and a few other geeky properties.) For the past few years I've walked my kids to school in a full on Sith robe with a fancy (i.e. not cheap) light saber. I'm actually considering picking up some boots/pants to make it a bit more realistic.\n\nSo yeah - I love Star Wars. Really love it. So when I heard about the new trilogy, I was pretty damn excited. Unlike a lot of Star Wars fans, I did not hate the prequels. Did they come close to what I, and many other fans, had hoped for? Not at all. To be fair, there was no chance in hell it could. And in the end, Vader's portrayal was a let down. That being said, the prequels had some great aspects to it. Without a doubt, Ewan McGregor absolutely, 100%, nailed Obi Wan. Ian McDiarmid's Palaptine was wonderfully evil. His scene with Anakin at the Space Opera was chilling. The Jedi being portrayed as all powerful, yet seemingly blind, has been called one of the flaws of the prequels, but to me, this is one of the strengths. They had grown complacent and I think that made for a great part of the story.\nI also didn't have the Special Editions. To me - they let me see the movies again on the big screen - and actually remember them this time. Did I like seeing Greedo shoot first? Nope. Did I loose any sleep over it? Nope.\nMy biggest concern with the new movie was it turning into something cool, but lacking in the original spirit of the movies. I enjoyed JJ Abrams updated Star Trek film, but the more time passes, and the more I watch DS9, the more I feel like he really missed out on the &quot;feel&quot; of Trek. (And yes, I'm a huge Trekkie too.) The trailers that were released really felt like they got the emotional aspect right, but I wouldn't be sure until I actually saw it.\nAnd as the lights dimmed last night and I saw those famous words on screen - my fears melted away.\n\nFrom the first sentence of the crawl to the last shot - this was everything I had hoped the prequels would be and every bit a &quot;proper&quot; Star Wars movie. You've got great action, incredibly funny scenes, awesome bad guys, and a great story as well. The cast, especially the new heroes, were wonderful. Kylo was incredible. So many movies have trouble getting the bad guys well and I thought he was awesome. I really liked Hux too, even when he was (and maybe this is a spoiler) bat-shit fanatical. Rey and Finn had great chemistry, and while at times they were a bit annoying in their youthful excitement, it was a good annoying if that makes sense. As I told a friend, sometimes the jokes were bad - but even when they were bad, I still grinned like a fool.\n&quot;The Force Awakens&quot; met every expectation I had. I won't call it perfect (and few films are) - but I'm so happy now I feel like I can bust. If I had to call out one thing I felt let down by it would have to be the soundtrack. While it was good to hear those old melodies again, not one of the new songs really feels like it made an impression on me. There was no &quot;Imperial March&quot; or &quot;Duel of Fates&quot;. I'm going to give it another chance though - I'm buying the soundtrack today. But at least while I watched, I can't say that any of the new music was memorable. It certainly wasn't bad, but John Williams has really set the bar high. (The music in the cantina though was pretty good. I'm not really counting that as it feels like it is set apart from the soundtrack itself.)\nSeriously - don't wait - go to the movies today. Now. See it with your kids. Enjoy the hell out of it and lets hope the next movies are just as good!\n\nHere are some random, SPOILER-ish, thoughts in no particular order.\n\nThe shot of the Star Destroyer co",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "A quick example of the Ionic Loading Widget",
		"date":"Thu Dec 17 2015 03:36:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450323406,
		"url":"https://www.raymondcamden.com/2015/12/17/a-quick-example-of-the-ionic-loading-widget",
		"content":"One of the things I love most about Ionic is how rapidly you can build applications. Many of the cooler features are simple things that can be quickly implemented for an easy win. I like easy wins. Here is a great example of that - the Ionic Loading widget.\n\nImagine you've got a simple service method runs over HTTP. This process can be fast or slow based on network conditions, size of the data, and other factors. (Like the Force. Hey, it can happen.) Your code probably looks like this:\n\nWe're not concerned about the service itself. It returns a promise and will take &quot;some time&quot; to return. So if that service happens to be slow today, it could look like this:\n\nNotice on click there is no visual feedback to the user that anything is happening. If they are impatient (and what user isn't), they could click multiple times and fire off numerous Ajax requests. Let's fix that:\n\nThere are precisely three changes here. I added $ionicLoading to the controller - I ran the show() method on it before I began the async process - and finally I hid it using hide(). That's it. I could customize the widget with a message if I was feeling fancy, but today isn't a fancy day. Here is the change:\n\nOk, so this isn't exactly rocket science, but for about 30 seconds of coding I got a much improved experience.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "My first extension for Visual Studio Code - CSSLint",
		"date":"Wed Dec 16 2015 08:49:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450255798,
		"url":"https://www.raymondcamden.com/2015/12/16/my-first-extension-for-visual-studio-code-csslint",
		"content":"Well, it took a while, but I finally got around to writing my first extension for Visual Studio Code. As I've said a few times recently, VS Code is my new favorite editor now that Brackets seems to be on the back burner at Adobe. (And ok, maybe it isn't. But VSC is faster, updated more often, and seems to have a lot more energy behind it.) I will say that the process for writing extensions for VS Code (documentation) is somewhat... complicated. To be fair, extensions for Brackets at first were a bit difficult at first too. But right now I'd say I understood about 1/2 of what I was doing when building this. It is fragile as heck so use with extreme caution. So how does it look?\n\n\nIn the screen shot above, you can see the green squiggles from the linting reporting on the issues. If you click the little warning icon at the bottom, you get a formal list.\n\nAs just an FYI, VS Code has internal CSS linting and they do not document how to turn this off. You can disable their linting by adding this to your preferences:\n\nSo - the code itself is a bit... intense. For linting specifically, VS Code requires you to set up a Node.js process to handle the process asynchronously. You then have to get this code into your extension code and handle keeping them in sync. The sample GitHub repo does all of this out of the box, and everything is documented, but it is really a lot to do at one time. I can say that every time I screwed up, it was because I didn't see the &quot;Common Questions&quot; part at the end of doc. My strongest recommendation is that you go to the bottom of the page before you do anything and quickly check what issues they call out there. You will run into them probably.\nI get why this process is complex, but it is so complex that there is no way I'd work on one while on my laptop screen. You need three instances of the VS Code open (yes, three, but again, this is for a linter, not all extensions) and managing that on a small screen would be pretty difficult. And again, to be fair, the experience of debugging a Bracket's extension was pretty similar.\nThe biggest tip I can give involves logging. While you can debug the server portion of your extension, I'm a big fan of just using a crap ton of console messages. Your client code can just use console.log, but your server code can't. However - your server code has access to a connection object that links back to the client code. That means you can do:\n\nThis will then show up in your debug console. And I kind of like that both parts of my extension can share one log.\n\nSo - I did publish it to the Marketplace, and that too was a bit complex, but I think - eventually - it will show up and be available. You can find the detail page here: https://marketplace.visualstudio.com/items/raymondcamden.CSSLint. It is pretty bare bones though. You can find the Git repo here: https://github.com/cfjedimaster/vscode-csslint.\nLet me know what you think!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Deals on my Cordova book and JavaScript videos",
		"date":"Wed Dec 16 2015 02:08:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450231702,
		"url":"https://www.raymondcamden.com/2015/12/16/deals-on-my-cordova-book-and-javascript-videos",
		"content":"Just a quick note to let folks know about two deals regarding my content. First, you can get half off of my Apache Cordova book with the following code: dotd121615au. Other books are included in that deal including their Ionic book.\nThe other deal I want to share is that two of my JavaScript videos (JavaScript Templating and Client-Side Data Storage) are now bundled in a larger collection called Introduction to the Modern Front-End Web. This bundle includes 5 different videos and over 10 hours of content for just 99 dollars. That's virtually free! (Ok, maybe not...)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using Ionic Creator with MobileFirst 7.1",
		"date":"Tue Dec 15 2015 02:35:14 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1450146914,
		"url":"https://www.raymondcamden.com/2015/12/15/using-ionic-creator-with-mobilefirst-7-1",
		"content":"A few months ago I wrote a post discussing how to use IBM MobileFirst 7.1 with Ionic (Developing Ionic Apps with MobileFirst 7.1). Recently, the Ionic folks have done some darn good improvements to Ionic Creator. Not only is the app more powerful to use, but more importantly, the output of Ionic Creator is, in my not so humble opinion, a heck of lot better. I'm still struggling to become &quot;Angular-literate&quot; so I'm not sure if I'm the best judge of Angular code, but I find the output from Creator to be a lot easier to work with, and more importantly, closer to the default code you get when you create Ionic applications with the CLI. So in this post, I thought I'd quickly demonstrate how to go from an app designed and created in Ionic Creator to a MobileFirst-enabled hybrid application.\n\nFirst, I assume you've got a project up on Ionic Creator already. How it looks, what it does, etc. doesn't really matter. But it has to include a picture of a cat.\nTo begin, you'll want to grab the zip download. First hit the export link:\n\nThen select the zip tab:\n\nExtract the zip someplace - it doesn't matter where, we'll be moving it in a second.\nOk, next, create a new MobileFirst Cordova application with: mfp cordova create. Just name it whatever you want and accept the defaults. (Or change them if you know what you're doing.)\n\nNow - I assume you've already got a working MobileFirst development server, but I like to be sure. So before going any further, go ahead and push the app to the server (mfp push) and then send it to your emulator (mfp cordova emulate -p ios).\n\nOk, so now lets get in your Ionic Creator code. Open the directory containing your MobileFirst Cordova project. Find the www folder and either delete it or rename it.\n\nThen, copy the assets from your Creator zip export into a new www folder.\n\nOk, so you're almost done actually. First, be sure to add in Ionic's keyboard plugin: mfp cordova plugin add ionic-plugin-keyboard. NOTICE:  At the time I write this, a bug in the mfp CLI will report Error adding plugin &quot;ionic-plugin-keyboard&quot;. But if you mfp cordova plugin ls you will see that the plugin was added. This bug is known and will be fixed in a future release.\nNext you need to prepare the app to &quot;speak&quot; to MobileFirst. I covered this process in depth in my earlier article: Developing Hybrid Mobile Apps with IBM MobileFirst 7.1. But if you want to quickly just see your app running, open up app.js and simply add this to the end:\n\nThen simply emulate. (Note, previously you needed to mfp push before every emulation. Now that is unnecessary.)\n\nAnd that's it. Let me know if you've got any questions about this process by leaving a comment below.\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Parsing RSS Feeds in JavaScript - Options",
		"date":"Tue Dec 08 2015 10:24:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449570290,
		"url":"https://www.raymondcamden.com/2015/12/08/parsing-rss-feeds-in-javascript-options",
		"content":"For a while now I've used the Google Feed API to parse RSS feeds in JavaScript. It did a good job of converting various RSS flavors into a simple array of entries you could easily work with. Unfortunately, Google has deprecated the API and while it still worked the last time I used it, I would strongly recommend folks migrate their apps away from it as soon as possible. While this makes me sad, you have to move on.\n\n\nSo what kind of options do you have?\nParsing Manually\nSo remember that RSS is just XML, and XML is just a string, and string parsing is easy, right? Of course, there are 2 major flavors of RSS, and multiple versions of both flavors, but if you're just parsing one known RSS feed then you can write to that particular flavor and version. (More about the different versions can be found on Wikipedia.) Unfortunately, if you try to simply XHR to a RSS feed you'll run into the lovely cross origin browser doohicky that prevents you from making requests to another server. Of course, if the RSS is on the same domain, that isn't a problem. And of course, if you are building something in Apache Cordova, then it isn't a problem either. (Just don't forget to update the CSP!) And finally - if you control the RSS, you could add a CORS header to it so modern browsers could use it. Unfortunately, if none of those apply, you're out of luck trying to do it completely client-side. (Well, until we get to the next options!) Let's pretend that none of the roadblocks apply to you and look at a simple example. (As a quick note, none of my sample code will actually render anything. It will just get crap, parse it, and log data. I'm assuming folks know how to manipulate the DOM. I've heard there's a good library for that.)\n\nSo all I'm doing here is using jQuery to request my RSS feed. I then use jQuery's built in XML parsing to iterate over the &lt;item&gt; blocks in my RSS feed. As you can see, I'm using sample code from a StackOverflow answer that I modified a tiny bit. Specifically the answer iterated over &lt;entry&gt; blocks, not &lt;item&gt;. Remember when I said there were different flavors of RSS? That's an example of the issue right there. If you must write code to handle both cases, you would need to look for &lt;item&gt; first and then &lt;entry&gt;. But that's basically it. If your curious, I tested this in Canary with --disable-web-security as a command line flag.\n\nYQL\nRemember YQL (Yahoo Query Language)? The last time I blogged about it was way back in 2010 (Proof of Concept 911 Viewer). As a gross simplification, YQL acts like a &quot;query language&quot; for the web. You can literally run SQL like content against URLs and get formatted data out of it. They provide a powerful testing console and wouldn't you know it, one of the examples is a RSS parser:\n\nJust in case that screenshot is a bit too small, here is what the YQL statement looks like:\nselect title from rss where url=&quot;http://rss.news.yahoo.com/rss/topstories&quot;\nI've got one word for that. Bad ass! Like, kitten in armor bad ass!\n\nI tested with two different RSS flavors and YQL had no issue handling either. Note the REST query URL at the bottom. I copied that into a new file:\n\nAnd it worked like a charm. Note the use of JSON/P to sidestep needing CORS. And here is the result:\n\nA big thank you to Addy Osmani from Google for reminding me that YQL was still around. Google, I forgive you for killing the Feed API now.\nFeednami\nLast but not least is a brand new service, Feednami, created just in time for the death of the Google Feeds API. To use it you simply add a new script to your code and then use feednami.load() to get your feed. Here is an example:\n\nThat's also pretty darn easy to use. Here is the result:\n\nSummary\nSo - you've got options. Which one is best? Honestly I don't know. YQL requires the least amount of code from what I can see, but I kinda dig Feednami's look a bit more. If you've used any of these in production, drop me a comment below with your thoughts!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Feeling bored? Want to hurt your brain a bit? Play the Advent of Code",
		"date":"Mon Dec 07 2015 04:34:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449462862,
		"url":"https://www.raymondcamden.com/2015/12/07/feeling-bored-want-to-hurt-your-brain-a-bit-play-the-advent-of-code",
		"content":"I absolutely love code puzzles which is why I'm both happy - and pissed - that I discovered Advent of Code. Every day the site releases two Christmas-themed code puzzles (the second one is heavily based on the first one and can - typically - be solved much quicker). You can solve the puzzles in any language you want (I'm using JavaScript of course) and alternate between loving them and thoroughly hating them as well. Ok, maybe not hate, but they can get pretty frustrating, although mostly in a good way. I've already learned a few things so I'd suggest checking it out if you want a good exercise. There's a reddit thread in case you get stuck (I've had to hit it up twice when I ran into a brick wall) and the posts there are pretty good in terms of trying to 'spoil' as little as possible.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Review: Destiny and Star Wars Battlefront",
		"date":"Sat Dec 05 2015 04:12:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449288738,
		"url":"https://www.raymondcamden.com/2015/12/05/review-destiny-and-star-wars-battlefront",
		"content":"It isn't Sunday but here are two video game reviews for you. Next will be Fallout 4 which should be at home within the week.\nDestiny: The Taken King\n\n \nI wasn't planning on getting Destiny because every time I've picked up a MMORPG I end up not playing and then feeling guilty when I pay for a month's usage that I didn't - well - use. There's no monthly fee for Destiny though and after seeing reviews of the Taken King DLC praising it for incredibly improving the game experience, I thought I'd check it out.\nDestiny is a shooter with RPG elements. You've got 3 classes with 3 subclasses each. You've got equipment that adapts to different play styles and levels up as well. To be honest, it is more shooter than RPG, but that's fine with me. Unlike a typical Call of Duty game where your character doesn't change throughout the story, you really get a good feeling of progression as you go through the game.\nThe story is - unfortunately - a hot mess. There's actually a good story about how the game's story got so messy (The Messy, True Story Behind The Making Of Destiny), but to be honest, it didn't really bug me too much. As I went through the various chapters, I figured out pretty early on that things weren't going to make sense and I just went with it. There's a wealth of online material in a 'codex' that I could make time to read if I cared, but frankly I didn't want to be bothered. You've got - basically - a space opera with all kinds of weird names thrown at you with loads of depth that ends up making no sense because there's never any context given to what you're being told. It is a huge failure imo, but a testament to the game that I still enjoyed the heck out of it.\nThe game is also a bit confusing if you've never played a MMORPG before. The idea of a central hub where you can get quests and deal with factions and earning reputation and all that made sense to me, but if you've never seen these things before you're going to be pretty confused. Maybe Destiny can't &quot;fix&quot; the story problems, but I think they could do more to make some of the 'mechanics' of the game itself more familiar to players.\nAnother issue with the game is that because it is online only, there is no real concept of pause. Now - to be fair - this is true of all MMORPGs. But at least in Warcraft I could hop on a winged mount, lift myself above the ground, and essentially be untouchable while I take care of something. You can find quiet places in Destiny during a mission, but for the most part, once you start something you have to assume 20-40 minutes of play without the ability to pause.\nAs I said though, the game play is incredibly fun. It takes the shooter genre and really makes it much more interesting. Some of the battles were amazing. On more than one occasion you have to solve a minor puzzle in terms of how in the heck you're supposed to harm the boss. All while the boss is raining death upon you at the same time. These fights were some of the best I've seen in shooters. Ever.\nI've finished the main story and am now in what would be called the &quot;end game&quot; content. Things here get a bit slower and the difficulty jumps a bit all of a sudden. I'm not quite as excited about the game now as it feels like you get a bit less reward per game session, but I'm definitely going to keep at it for a while longer.\nThe only aspect I really didn't care for is PVP. Maybe I didn't dedicate enough time to it but every match I was in was an exercise in frustration. I did win a few matches, but never scored well.\nThe game has a cool mobile app companion as well as a web-based profile. Here's my character - not the prettiest one in the world but she's mine.\n\nStar Wars Battlefront\n\n\nAnd now for something completely different... Star Wars Battlefront is another online only (well, mostly) game focused on PVP. Unlike Destiny, there's no story. The entire game is literally various different types of PVP matches with some training and 'fend off waves of attacks' solo play as well. (It feels like I'm dismissing the offline/solo matches. They are fun. They just aren't really the meat of the game.) But since most of us know the Star Wars story by heart, it isn't something that bothers me. What makes me happiest of all is that the setting is entirely within the original series. While I certainly do not hate the Prequels, it seems like it has been a long time since we've had a good game in that setting.\nGame play is pretty vanilla for a shooter. Probably the biggest difference is the lack of ammo. Since your weapons are all energy based (except for rockets and grenades), you instead have to worry about overheating your gun. Another interesting aspect is the use of hero classes in some matches. Playing as Darth Vader or Luke is pretty cool and while powerful, the hero classes can be taken down with effort.\nOf course, you've got an entire mode based in space ships as well. (Well, they are 'space' ships but all matches are in atmosphere.) I enjoy the mat",
		"tags":[
	        
		],
		"categories":[
            
                "video games"
            
		]

	},

	{
		"title": "Swift goes open source, and loads of new resources",
		"date":"Fri Dec 04 2015 02:44:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449197070,
		"url":"https://www.raymondcamden.com/2015/12/04/swift-goes-open-source-and-loads-of-new-resources",
		"content":"Yesterday was a big day for Swift developers - Apple announced the open source release of the Swift language. There's a new home for this initiative, Swift.org, and the complete source for the language is up on GitHub: https://github.com/apple/swift.\n\nSwift is an interesting language. I learned ObjectiveC about a year or so ago when I was still working for Adobe. I respect the language, but it never felt like one I'd like to use on a day to day basis. I've just played a bit with Swift, and it feels a lot more approachable then ObjectiveC, especially for those of us who may be coming from a web background and have pre-existing knowledge of JavaScript.\nBy open sourcing Swift, Apple has really broadened the environments where Swift can be used. You could now consider using Swift on the server, which is a powerful combination if you're already using Swift in your mobile application. Having one language for both the front end and back end is pretty darn compelling!\nSo where does IBM come into play? We've partnered with Apple to help the Swift developer community. You can read more at our developer center:\n\nAt the Swift@IBM, you'll find articles, links, and even cooler, a web-based Sandbox that let's you test Swift in your browser:\n\nMake sure you use the drop down on the upper left side to try out the different samples. I missed that at first. XCode itself provides a way to test code, but I found this web-based sandbox a lot easier to use.\nIn 2016, I plan on focusing on two things - Node.js and Swift. I had already planned on this before the announcement, but this move by Apple just cements my choice.\nFinally, for a good perspective on this announcement and what it means for development, I suggest this article: https://developer.ibm.com/swift/2015/12/03/swift-moves-to-open-source-a-development-perspective/\n",
		"tags":[
	        
            "swift"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Ionic/Cordova Demo: Where did I take that picture?",
		"date":"Thu Dec 03 2015 04:38:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449117511,
		"url":"https://www.raymondcamden.com/2015/12/03/ioniccordova-demo-where-did-i-take-that-picture",
		"content":"Every now and then I think of an idea for a cool (aka useless and pointless but fun) app that I think will take me one hour and let me grow my small little empire of demos. Sometimes those &quot;quick little demos&quot; end up turning into multi-hour sessions as I pull my hair out trying to find out why this or that isn't working. That's frustrating as heck while I'm working on it, but in the end it makes me as happy.\n\n\nWhy? Because if I run into problems with my little &quot;toy&quot; demo, most likely you, the poor reader who has to put up with my silly demos, will run into it in a production app. And if my pain helps you avoid issues, then this blog will earn its keep. Ok, so what was the idea?\nA few weeks ago I was shopping with my wife. It was the type of store where pretty much nothing in it interests me so I was just kind of mindlessly following along. But when my wife pointed out something she liked, I discretely snapped a picture of the item so I'd remember it as a possible present for her birthday or Christmas. Unfortunately, I couldn't remember the name of the store. I knew roundabout where it was, of course, but not the actual store.\nTurns out that many pictures automatically include data that relates to the location where the picture was taken. You can - with a few clicks - get the latitude and longitude of the picture. That's nice - but frankly, I can't translate those values into a 'real' location off the top of my head. I'm sure web apps exist to help with that, but I thought, wouldn't it be nice if I could just select a picture and have it tell me where it was taken - in English? For example:\n\nFor my demo, I decided to build the following:\n\nLet the user select a picture.\nAttempt to read the EXIF data and get a location.\nTry to Foursquare the location. I figured that would work great for businesses.\nIf that fails, try to reverse geocode it to an address at least.\nIf that fails too, show it on a map at least.\n\nRight away I ran into some interesting issues. First, I needed to read the EXIF data. I found a Cordova plugin for it, but it had not been updated in two years, and I saw multiple issues reported that were not being addressed. So then I simply Googled for &quot;exif javascript&quot; and came across this project: exif-js. This project was also old with outstanding PRs, but I thought it might be safer to try.\nFor the most part, it just works. Here is a snippet showing it in action:\n\nFirst thing I discovered was that when you select an image in Cordova, the EXIF data is stripped down to about 4 or so different tags. Turns out this is a known bug (CF-1285) due to the fact that the plugin copies the original image and in that process removes the data. The bug is marked resolved, but obviously it isn't. However, if you switch the camera source to NATIVE_URI then the problem goes away.\nSo far so good. To work with the code, you need to point it to an image in the DOM, and wait for the image to finish loading. That by itself isn't hard, although I feel dirty when I use the DOM in Angular controllers. (I got over it.) I then discovered an issue with the library. When it loads the EXIF data, it copies the values to the DOM item for caching. I'm using the same image every time you select a new photo, so this meant the tag data was cached. I filed a bug report and in the meantime I simply edited the library to remove the cache check. That's bad - but I got over that too.\nThe next thing I had to work with was the location stuff. As I said, the idea was to first check Foursquare, fall back to reverse geocoding, and fall back again to a static map. Let's look at the controller code first.\n\nNot too complex, right? I just run my service and deal with the result. The service is a bit complex, but really just makes use of the various APIs I'm hitting.\n\nIn both cases, I'm assuming the first result from the API is the best result. That may not always be true, but it works for now. You've seen an example of Foursquare working, here is an example of the reverse geocode.\n\nAnd here it is with the last fallback. Yes, this is the same picture, I just temporarily disabled the Geocode service for a quick test.\n\nAll in all, this was a fun little app to build, and as I said, I'm glad I ran into the EXIF issues. I know I'll need that in the future. You can find the complete source code for this demo here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/photolocate\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Book Review: Ionic Cookbook",
		"date":"Wed Dec 02 2015 03:20:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1449026400,
		"url":"https://www.raymondcamden.com/2015/12/02/book-review-ionic-cookbook",
		"content":"\n\nI've mentioned now multiple times that I'm a huge fan of &quot;cookbook&quot; style technical books. After I've learned the basics of a language, I love to see real, if small, examples of applications built in a language so I can get a feel for what it's like to actually develop with a particular platform. I was happy to get a review copy of what I believe is the first such book for the Ionic framework, Hoc Phan's &quot;Ionic Cookbook.&quot;\nIt has over 250 pages of recipes covering a huge range of topics, demonstrating everything from different UI components of the Ionic framework to handling data storage for proper offline support. To be honest, much of what is covered in this book would be incredibly useful outside of Ionic as well. For example, the discussions on Firebase would be useful in a vanilla Cordova application.\nTypically a &quot;cookbook&quot; style book assumes you know the platform already, but this book spends time at the beginning introducing you to the basics of Ionic as well, so it would actually help someone who is new to Ionic as well. (Although I'd try to have some basic Cordova knowledge first.) At the end, Hoc even discusses how to publish your apps to the app stores, which is yet again a topic that will be useful to folks using Ionic or some other UI/UX platform.\nWhile reading, I did find a few things I thought were done wrong, but honestly, those were more &quot;I'd do it this way&quot; type things than real disagreements. Even better, I learned a few things as well. I definitely recommend picking it up!\nIn case you're curious, here is the table of contents:\n\nCREATING OUR FIRST APP WITH IONIC\nMANAGING STATES AND NAVIGATION\nADDING DEVICE FEATURES SUPPORT\nOFFLINE DATA STORAGE\nHANDLING GESTURES AND EVENTS\nAPP THEME CUSTOMIZATION\nEXTENDING IONIC WITH YOUR OWN COMPONENTS\nUSER REGISTRATION AND AUTHENTICATION\nSAVING AND LOADING DATA USING FIREBASE\nFINALIZING YOUR APPS FOR DIFFERENT PLATFORMS\n",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Adobe loses its (web) edge...",
		"date":"Tue Dec 01 2015 00:50:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448931012,
		"url":"https://www.raymondcamden.com/2015/12/01/adobe-loses-its-web-edge",
		"content":"Well, consider me not surprised: Update about Edge Tools and Services. If you have no idea what this is about, then I'm not surprised. A few years ago, Adobe released an incredible set of web tools called the Edge suite. This included a responsive design tool (Edge Reflow), a web animation tool (Edge Animate), a mobile testing tool (Edge Inspect), and an editor (Edge Code), a 'branded' version of Brackets. Brackets was already on life support* (Update from the Adobe Brackets Team) so it isn't too surprising to see the rest of the web related stuff get killed off as a well.\nAgain - not surprising - but certainly disappointing. The Edge tools were incredibly cool. Small, light-weight, useful to both developers and designers alike, and a big change from what Adobe normally did. I loved talking about them to audiences and it was a great initiative, especially along with the rest of the stuff Adobe was doing regarding to web. I can remember attending conferences and hearing folks in the web community praise Adobe - typically with a bit of surprise - for what they were doing to help people working on the web.\nA lot of good will was earned - rightly so - and it has simply been thrown away. Truly disappointing. I'm sure there were valid business reasons for this - developers especially are pretty cheap - but I think there is value in good will that can be (almost) as good as profit.\n\nTo be fair, Brackets isn't on life-support, just a temporary hiatus. But development has slowed down quite a bit. Personally, performance for me has been really bad (but it may just be the extensions I use as I don't see others having the issues I do) to the point where I switched to Atom, and then Visual Studio Code, and I've had much better luck since then. Every time a new version comes out I check it out, but I pretty much only use Brackets now when I need to fix a reported bug with one of my extensions.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "How to tell if a Cordova application is running in the simulator",
		"date":"Mon Nov 30 2015 10:04:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448877859,
		"url":"https://www.raymondcamden.com/2015/11/30/how-to-tell-if-a-cordova-application-is-running-in-the-simulator",
		"content":"Just a quick note here but the most recent plugins release included a cool little update to the Device plugin. If you've never used it before, the plugin provides basic information about the app's current working environment, including operating system and device model. In the most recent version, a new property was added: isVirtual.\n\nAs you can probably guess, this property will tell you if you're running on a simulator or a real device. Now while I wouldn't recommend shipping code that uses this normally, during testing it could be real useful. As an example, here is code that simply toggles what kind of camera should be used - the device camera or the photo gallery:\n\nNot rocket science, but useful. Just to be complete, here is a screen shot of the same code running on my device and simulator.\n\nAnd if you want, you can grab the source for this demo here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/checkforsim.\nFor folks curious, running this on Genymotion actually shows that it is considered a simulator, not a real device, even though you run it from the command line like a real device. Surprising.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Where I want to go with Node next...",
		"date":"Mon Nov 30 2015 02:52:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448851942,
		"url":"https://www.raymondcamden.com/2015/11/30/where-i-want-to-go-with-node-next",
		"content":"This post probably isn't terribly useful to anyone else but me, but I wanted to write this down to help direct me and keep me on target. I've been spending a lot of time with StrongLoop lately and while I feel like I've covered the &quot;API stuff&quot; pretty well, I want to turn my attention to performance stuff - both in StrongLoop of course and generically across the Node.js ecosystem. Let me give some context.\n\nAs folks may know, I spent most of my development life working with ColdFusion. I'm mostly moved away from that and outside of side work for clients I don't write any new code in it. It should come as no surprise that I now recommend Node.js to developers looking to build server-side applications. It is easy to pick up, for the most part, and free and open source. There's one thing that ColdFusion did really well though that I'd like to replicate on the Node.js side.\nOut of the box, you could enable server-side reporting of performance metrics in your ColdFusion application. This was reported in an easy to read format at the bottom of a CFM page. This report would break down all kinds of useful information:\n\nHow long did the entire request take to process?\nGiven your request called various component methods, how long did they take to execute?\nGiven your request called a database, what queries were used, how many records were returned, and how long did they take to process?\n\nAs an example, I could see when a request wasn't performing well because it was calling the database for the same data multiple times in one request. That seems like a silly mistake to make, but as we all know, an application grows over time and is touched by many developers. Sometimes you may not realize you're repeating the same method calls and basically refetching the same request every time.\nAnother example - you may see that a database query is performing slowly when it doesn't make sense that it should. That could be a number of things - but probably a bad index. Yes - this is something MySQL itself can tell you, but you may not even realize you have a problem till you see the numbers in front of you. Or perhaps your returning N rows when you realize you should be returning 1. Again, a &quot;simple&quot; mistake that can be overlooked.\nThe service ColdFusion provided was also configurable in terms of what was reported and you could modify it to add your own flair to it.\nAll in all - it is a darn good feature. While it doesn't cover everything and doesn't replace &quot;deep&quot; tools like Fusion Reactor, it is good for getting a quick look at your site performance and making some immediate fixes. That's where I want to go with Node next. I'm assuming it may not be as easy, but I can hope, right?\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Happy Thanksgiving!",
		"date":"Thu Nov 26 2015 01:59:29 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448503169,
		"url":"https://www.raymondcamden.com/2015/11/26/happy-thanksgiving-3",
		"content":"I'm spending my day with my family and am incredibly thankful for them. I wanted to quickly thank everyone who reads this blog, comments, and helps make me a better developer. Thank you to the Apache Cordova team, the Ionic folks, and everyone else who helps make the tools that let me get stuff done.\nThe photo above is by Stephen Hayford and is part of an incredibly cool collection of Star Wars miniature art.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Important update for Apache Cordova and Android",
		"date":"Wed Nov 25 2015 02:58:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448420339,
		"url":"https://www.raymondcamden.com/2015/11/25/important-update-for-apache-cordova-and-android",
		"content":"Those of us who work with Apache Cordova are well aware of the issues surrounding the Android simulator. Calling it &quot;slow&quot; does not properly describe the painful experience of actually trying to use it. In fact, every now and then when I accidentally launch it via the Cordova CLI, I say something out loud that I don't usually include in my blog posts. (Hint - it rhymes with duck.) How bad is it? I've actually recommended folks go out and buy cheap Android devices instead of using the simulator. Of course, you should always test on real devices. My point is, I'd suggest using a real device instead of the simulator since it was actually slower than going to the real device. Of course, even when I have a real device, half of my USB cables don't properly let adb connect to it. Apparently I'm using the &quot;wrong&quot; USB cables. This is why I use iOS about 99% of the time for my Cordova work. I also recommend Genymotion - a free/commercial Android simulator.\n\nAnyway, this past week Google announced the beta release of a new version of Android Studio. Android Studio is an IDE specifically for building Android applications. It is free, and kinda nice in the few times I've used it. As part of the update, the emulator was also improved. I didn't pay much attention to this because &quot;much improved slow crap&quot; doesn't really entice me, but then I came across this article: The new emulator in Android Studio 2.0 is 50 times faster than before. 50 is a big number so I figured, let me check it out.\nFirst off, it was a bit hard for me to actually find the beta download. You can find it here on Android Studio's Canary channel. I downloaded and installed the bits. I fired up the app and went to Tools/Android/AVD Manager.\n\nI had one pre-existing AVD that was marked as needing repairing, so to make things easy I just deleted it and created a new one. Over all, the UI is a bit refresher and easier to use, but I pretty much just took defaults for my new device.\n\nAnyway, I then dropped down to the CLI, made a new Cordova project, added Android, and told Cordova to emulate it. Surprise surprise, the new emulator popped up just fine. I suppose that is to be expected, but it was nice to see it work well.\n \nThe first launch is still not exactly speedy compared to iOS, but as I've seen it take 5 or so minutes in the past, it was a heck of a lot faster. The second launch was really speedy - I'd say less then 10 seconds - on par with Genymotion. (I haven't done scientific testing, but I figure once you get below 10 seconds, it doesn't much matter.) Genymotion though has additional benefits on top of the simulator though so you still want to check it out.\nThanks go to Mike Hartington for letting me know the new emulator worked well with Cordova, and even more thanks to Mike for sharing a video of this in action.\n\nAs an additional FYI, it can be a bit difficult to control the Android simulator in terms of rotation and stuff like that. The program menus for it are pretty much completely blank. I Googled around a bit and found results to help: Android Emulator Shortcuts. But I'm surprised the app itself seems to provide no help.\nAnyway - if you've avoided the Android emulator like the plague over the past few years, it's time to finally give it a shot again.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "HarpJS GUI in Beta",
		"date":"Tue Nov 24 2015 03:06:45 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448334405,
		"url":"https://www.raymondcamden.com/2015/11/24/harpjs-gui-in-beta",
		"content":"So this is interesting. Harp is my &quot;go to&quot; static site generator when presenting on the topic and building new static sites. (I also really dig Jekyll and I go back and forth between which I like best.) The Harp team is now testing a new desktop application called Harp GUI. You can find the GitHub repo here: https://github.com/alexgleason/harp-gui. Right now there's only builds for Linux but you can generate builds for OSX and Windows. What does it do exactly?\n\nAfter opening it, you get a simple screen:\n\nGiven you have a Harp project already (and remember, technically, any folder can be a Harp project), you can then drag it onto the app to activate it:\n\nAt this point, you can click to view the site in your browser or click to compile it. In case you're curious, compiling it will create a subdirectory called _build in your project:\n\nSo... not a lot to it yet, but one of the things that hinder the use of SSGs in general is that they really aren't terribly user friendly for non-technical folks. (I wrote an article about this topic for Telerik: Merging Dynamic and Static Sites) Initiatives like this could go a long way to making it easier for normal people (yes, I called non-devs 'normal'). What do my readers think - can this help increase usage of SSGs?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "TIL - Autocomplete and forms",
		"date":"Mon Nov 23 2015 09:34:15 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448271255,
		"url":"https://www.raymondcamden.com/2015/11/23/til-autocomplete-and-forms",
		"content":"As it is Thanksgiving week here in America and my brain has already kinda checked out, I decided to take a quick look at a particular aspect of the input tag - autocomplete.\n\nAs you may, or may not know, most modern web browsers will make an attempt to remember form fields of a &quot;similar nature&quot; such that entering your name on one site means that when you go to type in your name on another form it will offer to automatically complete the field for you.\nIf you don't like this, or perhaps you're using your own autocomplete, you can add autocomplete=&quot;off&quot; to either the form tag or to an individual input field. The default behavior (most of the time) is to default to on.\nSimple enough. But if you read the spec, you discovered that the autocomplete attribute can also provide a &quot;hint&quot; about what field it is. So for example, maybe you've named your form field f_name, or firName, or usersGiveName, each of which is meant to represent what we commonly consider a first name, you can actually tell the browser to consider each of those variations to be the first name.\nThe spec includes support for a large number of &quot;hints&quot;, including:\n\nname (full name)\ngiven-name (first name)\nfamily-name (last name)\nhonorific-suffix (Mr, Dr, etc)\nnew-password (oh my god don't use this, why would you want to recommend re-using the same password???)\naddress-line1(-3), address-level(-4) (address-level2 is city, of course)\ncountry\ncountry-name (um)\n\nAnd so on. If you read the spec closely, it is also supposed to support &quot;grouping&quot;, such that I can say this is my street-address for shipping versus billing. So with that in mind, I decided to do a bit of digging. I was curious about a few things:\n\nWhen would the browser prompt me to fill in one field versus an entire form?\nCan I use crazy field names if I use the right autocomplete value as a hint?\nWhat happens if I mix in a datalist in just to be crazy?\n\nSo first - a simple form.\n\nNothing special about that. On Chrome, once I enter a value once, I will get prompted to autofill the field, but only one field at a time. It didn't remember that last time I did &quot;Raymond&quot; that &quot;Camden&quot; was an associated value.\n\nNote the lovely pee-yellow CSS Chrome uses to signify an autocomplete field. I honestly don't know why it does this when it requires user action in order to fill. Maybe the thinking is that I'll forget where the name came from? (FYI, apparently you can tweak it: http://stackoverflow.com/questions/2781549/removing-input-background-colour-for-chrome-autocomplete)\nFirefox has the same behavior (without the CSS pee) as does MS Edge (but with pee).\nSafari lets you use either other forms or your local contact info for form data. You can actually use both if you want:\n\nHowever, Safari will not begin suggesting a value until you type one letter. To me, that's a mistake, because at the point I'm typing, I can finish typing my name in less time it takes for Safari to draw a list of names. The UI for filling from a contact card is different from 'regular' autofill:\n\nOk, how about some more tests. I was first curious about why/when a form would completely fill out. I tried this test:\n\nIn Chrome, while I could autocomplete fields in the first form, only the second form let me completely fill the entire form. I'm guessing it is the number of fields that matter here. Again, the UI is slightly different in each case. First, Chrome offering to fill just one field:\n\nVersus the entire form:\n\nTo be honest, the second screen shot doesn't really imply that it will fill the entire form, but it is certainly different. Maybe the fact that the street address there is supposed to be the clue.\nFirefox does not fill out the entire form, but both Safari and Edge filled out the entire form (the one with three fields).\nOk, so what about the autocomplete=X feature? In theory, it should let me provide a clue such that my form field names won't matter. Here was my first test.\n\nIn theory, this should work, but it completely failed to note that I've given both names previously. However, all the browsers remembered previous entries when viewing the form again. Then I added another field:\n\nAnd oddly - Chrome finally got it working right:\n\nNo other browser changed though in terms of its behavior.\nFinally - I thought - why not see what happens when you add in a datalist:\n\nI love datalist - and support is good if you ignore Safari (sigh). Firefox and Chrome will render both past entries and autocomplete values at once, which is kinda nice:\n\nUnfortunately, while Edge supports datalist, it doesn't handle rendering autocomplete and the list at the same time - you can see them overlapping here:\n\nIt gets weird if you add more values and the field is towards the bottom of the screen:\n\nBut give it enough space at the bottom and it messes up again:\n\nI need to remember to file a bug report on this, of course, since I can't expect it to be fixed if I don't bother to report i",
		"tags":[
	        
		],
		"categories":[
            
                "html5"
            
		]

	},

	{
		"title": "Using the Meetup API in Client-Side Applications",
		"date":"Fri Nov 20 2015 07:06:04 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1448003164,
		"url":"https://www.raymondcamden.com/2015/11/20/using-the-meetup-api-in-client-side-applications",
		"content":"This isn't new, but was something I discovered a few weeks ago and I'm finally making the time to blog about it. I've used the  Meetup API in the past with ColdFusion and for the most part, it just works, but like many APIs today it requires authenticated calls to get data. Unfortunately, even a simple search against public data also required an authenticated call. This means using the API in a purely client-side application won't be secure because your code will contain your secret keys.\n\nOr so you would think. It isn't obvious at first (or wasn't to me), but the API actually supports &quot;Key signatures&quot;. What this means is that you can design an API call, like, find me the meetups around area X that use the word &quot;kitten&quot; in the description and Meetup will give you a &quot;signed&quot; version of the URL to use in your application. Now here's the killer part. That URL is 100% safe to use because it will only do precisely what you told it to: Search for groups around area X with kitten in the name. Even better, this &quot;lock&quot; on the URL does not apply to anything related to paging or JSON/P callbacks. So outside of not being terribly obvious (at least in my opinion), this is a killer feature of the API and one worth considering for your next application.\nIn order to test this, you will need to have an account with Meetup and be logged in. Then simply go to the API Console and begin testing. Here is a screen shot of that in action:\n\nYou'll want to use the console to get your API calls down exactly as you want them and once you're satisfied, just copy and paste that URL into your code. I went ahead and whipped up a quick demo that performs a search for Ionic meetups around the world. Here's the code - and to be clear - this was quick and dirty, not nice.\n\nI created a simple recursive function, fetchGroups, that lets me pass the initial signed URL in and deal with aggregating multiple pages of results. When finished, it then calls a callback function with the results. I should have used Promises perhaps, but, I was being lazy. The results are rendered out somewhat simply, but that could be improved of course.\nYou can view a live demo of this here: https://static.raymondcamden.com/demos/2015/nov/20/. Let me know what you think!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "CFLib converted to Harp and on Surge",
		"date":"Thu Nov 19 2015 02:00:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447898450,
		"url":"https://www.raymondcamden.com/2015/11/19/cflib-converted-to-harp-and-on-surge",
		"content":"Every now and then I code something that seems like a really bad idea, but I just do it and don't tell anyone and I'm totally fine with that. I've decided to push my luck and actually blog about what I coded even though I think it is - probably - a pretty bad idea.\n\nCFLib was first released back in the Jurassic period and was running ColdFusion 5. After the initial release I updated it a few times over the years. Early this year I went ahead and converted it to Node.js (CFLib moves to Node.js). This was a pretty simple conversion with the only real complex part being the editor. (In fact, for a while, I had no admin and I edited/added UDFs via a Mongo client. Yes, seriously.)\nRecently I found that my Node service was migrating to a new system that would no longer support Mongo. With submissions to CFLib dried up, I figured it was time to go ahead and make it static. (Although I've already added one UDF since this conversion and will be adding another next week.) Back in August, I wrote a blog post about how I used a Node.js script to act like a &quot;generator&quot; for Harp.js: Using Generators with Harp. The purpose of this script was to read in raw data from a Mongo export and give me the files Harp.js would need to generate a static version. For the most part, this is just simple grunt work - reading and writing JSON and plain text files, but here is the current version:\n\nOut of all these lines, the only one I really want to call out is this:\n\nNotice that third argument to the stringify call. This is the &quot;spacer&quot; argument and allows you to format the resulting JSON. Normally you don't really care what the JSON looks like, but in my case, the file represents all the UDFs for CFLib and I need to edit it add/edit/delete data. Having line breaks in the file makes it a heck of a lot easier to work with. You can find out more abut this feature from MDN: The space argument.\nOk, so now for the part that I really think is a bit freaky. My data exists are two core JSON files - one for libraries and one ginormous one for UDFs. This isn't a database - remember - we're static now - so simple things like, &quot;Tell me the number of UDFs in library X&quot; aren't really simple anymore. To make it a bit simpler, I built a file that acted like a function library for my Harp.js templates. I called it _udf.js. (Try not to get confused by the fact that the site itself hosts content called UDFs too.;) Here is an example of how the home page template looks:\n\nFirst - note the include. That's where my utility library is loaded into the template. After it is loaded, I have access to a library of functions in the name space, public.udfs. You can see a few calls - one that gets the last updated value for the library and one that gets the count. Let's look at the library.\n\nSo what am I doing here? Harp.js has a Public scope. This scope contains data about the file system of my Harp app as well as any metadata. I'm abusing this scope by writing directly to it. This lets me define functions I can reuse in my template via the public.udfs namespace.\nThis is the part I think is a bad idea! But it works - and it made the rest of the site a heck of a lot easier to build.\nThe final part was deploying via Surge, which I've praised numerous times here on this blog. My site was live in approximately 120 seconds after I was done. All I had to do was update my DNS for the site and wait for it to propagate.\nAs a quick aside - I also needed access to a MD5 library in JavaScript. I used this one, https://github.com/blueimp/JavaScript-MD5, by simply cutting and pasting the code into my UDF file. I removed it from the code above as it was rather large. That's how I built in support for generating the Gravatar links.\nThat's it - let me know if you have any questions about the site. I don't really have any plans on putting the Harp.js code up on GitHub, but if folks want to see more of the CFLib site, I'll share it.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using Authorization Tokens for IBM Watson services",
		"date":"Fri Nov 13 2015 04:53:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447390428,
		"url":"https://www.raymondcamden.com/2015/11/13/using-authorization-tokens-for-ibm-watson-services",
		"content":"This is a handy little trick I discovered last week. It is definitely documented (Using tokens with Watson services), but I had not run into the feature until I was investigating a Watson service. Way back in February I wrote up a blog post that discussed how to use the Visual Recognition service with a Cordova application: Using the new Bluemix Visual Recognition service in Cordova. While this worked fine, it had a big problem.\n\nIn order for my mobile application to talk to the remote service, I had to embed the username and password in my source code. That's Bad(tm) of course, and I finally got around to correcting that a few months ago: A real world app with IBM Bluemix, Node, Cordova, and Ionic. The solution was to setup a Node.js server that acted as a proxy between the mobile applications and the Bluemix services. That certainly wasn't hard to do - especially since we've got a kick ass npm package, watson-developer-cloud, that makes it rather trivial to speak to services.\nTurns out - there's an even simpler way. Bluemix services support the idea of authorization tokens. Instead of having your mobile app hit Node.js to simply proxy to the remote service, you can have your mobile app hit Node.js and request an authorization token. The token is good for one service so you would need to return multiple tokens if you're using multiple services. Once you have that token, the good news is that you can then skip hitting the Node.js and instead speak directly to the remote service. Let's look at an example. (And I highly encourage you to read the two blog entries I linked to above as the app and it's features are described there.)\nFirst, let's show the server.\n\nThe first part of the code handles defaulting my credential information. I get my username and password from the Bluemix console but when I deploy my code to Bluemix, it will pick up on the environment variables instead.\nNow take a look at the authorization section. For the most part this probably makes sense, but there is something that I guarantee will trip you up. It certainly tripped me up. Look at this section of code in particular:\n\nThe last setting there, url, is not the URL of the API itself. We'll get to that in a minute. Rather, it works kind of a like a &quot;group&quot; in terms of what kind of service you are using. Services are either &quot;regular&quot; or &quot;streaming&quot;. A regular service will use the URL you see there: https://gateway.watsonplatform.net/authorization/api. A streaming API will use https://stream.watsonplatform.net/authorization/api.\nOk, so your next question is, if it isn't obvious, how do I know what type of service I'm using? The answer is in the URL for the service itself. So for example, here is the one I'm using for visual recognition: https://gateway.watsonplatform.net/visual-recognition-beta/api. See &quot;gateway&quot;? Yep, that's your clue. Compare that to the endpoint for text to speech: https://stream.watsonplatform.net/speech-to-text/api. You can see it has &quot;stream&quot; in the domain. This is all probably pretty obvious, and as I type it certainly looks obvious, but as I said, it tripped me up. Also, I discovered this entire feature by looking at the docs for another service, I did not have the nicely written feature docs open in my browser.\nThat's pretty much it. I set up a /getToken route and I call the authorization API. I then just return the token to the caller.\nNow let's take a look at the JavaScript code. As I mentioned before, I won't be going over the entire application, instead I'll just focus on the aspect related to this change.\n\nSo the first change is that I immediately call my server to get a token. Since my entire app is &quot;take a picture and identify crap in it&quot; I've bootstrapped the button itself to that load event.\nThe next change is to the FileTransfer object. I have to add a header with the token, and obviously change the URL. Finally, I have to massage the result a bit. Previously my Node.js app did that for me. Now I'm working with the raw result from the remote service so I do that in the result hander.\nAnd voila - that's it.\n",
		"tags":[
	        
            "bluemix",
            
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with the Clipboard in Cordova apps",
		"date":"Wed Nov 11 2015 08:14:51 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447229691,
		"url":"https://www.raymondcamden.com/2015/11/11/working-with-the-clipboard-in-cordova-apps",
		"content":"Earlier this week a friend of mine on Facebook noticed something odd. Facebook recognizes when you have URLs in your clipboard:\n\n\nNow - I think we can have a good discussion about whether or not this is creepy (I have some thoughts at the end!), but I was curious about how one would do this in a Cordova application. Turns out - it's quite easy. VersoSolutions has a Cordova plugin that provides both read and write access to the clipboard for Windows Phone, iOS, and Android devices: https://github.com/VersoSolutions/CordovaClipboard. Even nicer, ngCordova supports it too. So with that in mind, I built a simple demo.\nThe app is just one textarea and nothing more:\n\nBut if you copy a URL into your clipboard, the app will recognize it when your return:\n\nAnd clicking the button will insert the text:\n\nNot exactly rocket science, but you get the idea. So here's how I built it. First, the index.html page.\n\nThere's nothing particularly interesting here outside of the button which only shows up when a URL is available. Now let's look at the code.\n\nI begin by setting up an interval that runs every 4 seconds. That was somewhat arbitrary and actually a bit slow. Also, it runs forever and should be cleared when the user navigates away from this particular view. (If the application had more than one view of course.)\nThe function called every 4 seconds, checkForURL, uses the ngCordova wrapper for the clipboard plugin and simply grabs the text. If it can find any, and it is a URL, then we enable the button.\nThe pasteURL function simply handles adding the URL and clearing the clipboard. Finally, it hides the button.\nYou can find the complete source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/copypaste/www.\nSo... let's spend a minute or so talking about the privacy aspects of this. As I mentioned earlier, my friend was surprised by this and a bit put off as he didn't even know Facebook was doing this. I can certainly see that point. I kind of figure that if the operating system itself allows for apps to read the clipboard than it must be &quot;Ok&quot;, but I wonder how many people know this? Obviously it would be trivial to take that clipboard content and Ajax it up to a server. For &quot;research&quot; purposes of course. What do you think?\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Article: Advanced Image Editing in the Browser",
		"date":"Wed Nov 11 2015 02:41:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447209706,
		"url":"https://www.raymondcamden.com/2015/11/11/article-advanced-image-editing-in-the-browser",
		"content":"Just a quick FYI that I've released a new article for the Telerik Developer Network: Advanced Image Editing in the Browser. The article discusses how you can add tooling to a web site to let non-technical users do basic (and sometimes advanced!) image editing directly in the browser.\n",
		"tags":[
	        
		],
		"categories":[
            
                "design",
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Demo of the Ionic Resources command",
		"date":"Tue Nov 10 2015 04:54:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447131279,
		"url":"https://www.raymondcamden.com/2015/11/10/demo-of-the-ionic-resources-command",
		"content":"One of the cool little &quot;side&quot; features of the Ionic CLI is the resources command. If you've never seen it in action, it lets you create a simple source icon and splash screen and then generate icons and splash screens for the 500 different variants supported by Apache Cordova. This isn't a new feature, but I've been meaning to create a video tutorial of this in action so folks could see what it does. Thanks go to Mike Hartington for helping me prepare this video!\n\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Cordova, iOS, and Orientation - wondering why it is locked?",
		"date":"Mon Nov 09 2015 04:49:07 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1447044547,
		"url":"https://www.raymondcamden.com/2015/11/09/cordova-ios-and-orientation-wondering-why-it-is-locked",
		"content":"I'm not sure if this is new behavior, but if it isn't, I haven't run into this till last week. I was working on a project with Ionic (Cordova Demo – Apple TV HD Video Viewer) and ran into something odd. When I rotated the device, the orientation did not change. I quickly made a virgin Cordova project to see if I could confirm it there as well - and I did.\n\n\nI knew that Cordova supports a preference to lock orientation, and I checked my config.xml to ensure there wasn't a &quot;lock&quot; there. Turns out, I was half-wrong.\nIf you check the docs (The config.xml File) you'll discover this little tidbit:\n\nOrientation (string, defaults to default): allows you to lock orientation and prevent the interface from rotating in response to changes in orientation. Possible values are default, landscape or portrait. Example:\n\nNote the &quot;defaults to default&quot; aspect - that's crucial.\nA bit later in the doc you then see this:\n\nFor iOS, to specify both portrait & landscape mode you would use the platform specific value all\n\nSo to be clear, for iOS, default is portrait only. For Android, default allows for all orientations. In order for your application to support both (well, all four technically) orientations in iOS, you will want to specifically allow that:\n\nNote how the preference is wrapped in a platform tag. Don't forget you can set values just for particular platforms within your config.xml file.\nSo as always - when I post stuff like this I'm always curious to know if everyone else knew this but me. Let me know in the comments below. Thanks to @riddlerdev in the Ionic Slack for helping me find this last week.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Cordova Demo - Apple TV HD Video Viewer",
		"date":"Thu Nov 05 2015 11:27:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446722859,
		"url":"https://www.raymondcamden.com/2015/11/05/cordova-demo-apple-tv-hd-video-viewer",
		"content":"So a few days ago, someone on Twitter (sorry, I forget who) mentioned that the new Apple TV has some pretty stellar screen savers. Turns out - the data for those screen savers was all driven by a public JSON file. It didn't take long for someone to notice and then build a cool demo: Watch All The Apple TV Aerial Video Screensavers. You should check it out. Seriously. Absolutely beautiful stuff.\n\nNow it looks like everyone is playing with it. You can even get a OSX and Windows screen saver of the videos. I'm sure Apple is going to kill this off sometime soon - I mean - they have to I imagine - but in the meantime they are some darn pretty visuals to look at.\nWhile exercising today, I thought I'd quickly whip up a demo of this using Apache Cordova and Ionic. Here it is in action. And yes - for the life of me I couldn't get it to be 100% of the canvas. I'm sure there is some way in CSS to say, &quot;Stretch this so it covers everything and I'm OK if parts of it are off screen&quot;, but such CSS Wizardry is beyond me.\n\nSo the code isn't anything special. The front end is pretty much just the &quot;pull to refresh&quot; widget and a video tag:\n\nAnd here is the JavaScript:\n\nSo the controller simply sets up a call to my service and updates the DOM with the proper HTML. I'm always unsure about how to do DOM manipulations like this with Angular. I'm guessing I should have used ng-model or something here, right?\nThe service isn't too complex either. We load Apple's JSON once and parse it into a list of day and night videos. We then figure out what time it is, and arbitrarily decide that 7AM to 6PM is &quot;day&quot;. Obviously your world may differ. Then we can just select a random video and return it.\nAnd really that's it. I could add a label to the display so folks knew what it is. I could also add support for knowing when you are offline. But I won't. I will, however, share all the code: https://github.com/cfjedimaster/Cordova-Examples/tree/master/arialscreensaver.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Checking for platform and plugin updates in your Cordova project",
		"date":"Wed Nov 04 2015 08:31:55 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446625915,
		"url":"https://www.raymondcamden.com/2015/11/04/checking-for-platform-and-plugin-updates-in-your-cordova-project",
		"content":"Earlier today the Cordova team announced an important update for the iOS platform (Apache Cordova iOS 3.9.2). I thought it might be worthwhile to discuss how you can check your platforms and plugins for updates. It isn't a complex process, but it is probably something to make part of your routine management in your organization. As I don't manage one application but build lots of silly demos, I don't necessarily have to worry so much about this. Despite that, I was curious so I did a bit of digging.\n\nLet's discuss platforms first. In a Cordova project, you can type cordova platforms to get a list of installed and available platforms. Here is an example:\n\nNotice how at the end of each installed platform the current version is printed. Cool. But given that you aren't following the Cordova blog, how would you know a newer version of the iOS platform existed?\nShaz (from the Cordova team) pointed out that the CLI supports a &quot;check&quot; command - this was something I had missed! According to the CLI docs, running cordova platform check will &quot;list platforms which can be updated by cordova platform update&quot;.\nCool. Unfortunately, in my testing, it was pretty broken. I tested against three or four projects and only once did it see an update and it never reported that my iOS platform could be updated. From what I can tell with conversations with Shaz and others, this feature hasn't been properly tested yet so it needs some work. However, if you are reading this in the future, try this first as it is the most direct way of reporting on your platforms. If your curious about the bugs I reported, you can find them here: CB-9951 and CB-9953.\nThe alternative for now is to use npm. The platform code all exists on npm and all you need to do is figure out the package name of the platform itself. This is rather easy to guess for iOS and Android:\n\nIf you choose to update, you can simply cordova platform update ios and if you decide you made a huge mistake, you can install an earlier version by doing cordova platform update ios@X where X is a version. To be honest, in the past I've also remove and re-added a platform. That's silly, but I've done it.\nSo - what about plugins? Running cordova plugin ls will report on installed plugins and their versions:\n\nUnfortunately, there is no &quot;check&quot; command like we have with platforms (broken or not), so you'll need to use npm info again to see if new versions exist:\n\nThere is no upgrade command either, but you can rm and add a plugin in a few seconds so just do that and you're set.\nBut wait! There's more. Don't forget your CLI also has a version. It is easy to check both your version and the latest release:\n\nSo now that you've chewed on that a bit - let's hear from Steven Gill, also from the Cordova project:\n\nBtw, the plan is to move towards stop advising users to update platforms independently (except patch releases like this one). Instead we will only tell users to update cli and add a command (cordova update) that would update necessary platforms and plugins based on new pinned versions in cordova-lib. (Plugins will start to be pinned soon). That way we can verify the mix of plugins, platforms and tools have been tested together.\n\nSo my translation is - it's going to get simpler. That's goodgreat.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "In defense of the Reset button...",
		"date":"Tue Nov 03 2015 10:28:08 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446546488,
		"url":"https://www.raymondcamden.com/2015/11/03/in-defense-of-the-reset-button",
		"content":"Earlier this morning, in a fit of intense silliness - I tweeted an observation about reset buttons and forms:\n\nSpeaking of forms, why are we still adding Reset buttons? When was the last time you *intentionally* reset a form?&mdash; Raymond Camden (@raymondcamden) November 3, 2015\n\nNow, to be clear, I can't remember the last time I accidentally cleared a form, but it still surprises me when I see the element on a page. I honestly cannot remember ever wanting to reset my form and it just feels like a bit of wasted space.\nBut of course - as soon as I tweeted this I got some pretty interesting responses that made me re-examine my thoughts on the feature in general. Here they are in no particular order.\n@raymondcamden @zeldman for CSS-only drop-down nav controlled by radio inputs, where a reset closes the active menu https://t.co/qAtmqPDazl&mdash; Radoslav Sharapanov (@radogado) November 3, 2015\n\nOk, so that's an interesting demo there. To be honest, whenever I see CSS doing weird stuff with forms, I get a bit concerned. It seems cool to &quot;twist&quot; stuff that way but something about it just seems wrong to me. That being said, I can't CSS my way out of a paper bag so what do I know? In the end though, his demo/usage doesn't really match my initial statement about the &quot;typical&quot; use of the reset button.\n@raymondcamden Only on a form used to filter search results (to reset it back to the default filter criteria).&mdash; Dan Skaggs (@dskaggs) November 3, 2015\n\nI replied back to Dan to clarify that he was talking about a form using Ajax and in that respect - I think it makes sense. As long as you change the value of the reset button to something like &quot;Clear Search&quot; and as long as you clear the results too, then I think this is actually a pretty darn valid use of the reset button.\nAnd yes - you can listen for a reset event. I never knew it existed but it makes sense that it does. Here it is in jQuery:\n\nAnd yes - if you return false from this event you can block a reset event. Why in the world would you do that? I have no idea. But you can.\nAs an aside - if you listen to the change event on a form field, even though reset technically changes the value (or may change the value), you will not get an event fired. I guess this makes sense, but if you are listening for change events and have a reset button, you'll want to listen for the reset event as well.\n@raymondcamden I see them more used (and labelled) as &quot;Cancel&quot; which typically takes you back a page vs just clearing.&mdash; Jordan Kasper (@jakerella) November 3, 2015\n\nThis examples makes sense too - and you would need to listen to the reset event to handle it. But as with my issue with the fancy CSS drop down menu thing, this feels like a small violation of the purpose of the button. Not that the W3C Police will come after you, but it seems wrong.\nDon't forget that modern browsers support the formaction attribute. You could literally do this something like this:\n\nThis only works on submit buttons though. Support is actually pretty good, and an article over on Wufoo documents this: The formaction attribute.\n@raymondcamden Password-protected pages. Happens during testing and in daily use cases for our clients (work at a web dev. company)&mdash; Sarah Jedrey (@sejedrey) November 3, 2015\n\nSo my take away from this is that Sarah's customers are asking for this on secured pages. I can't see why a customer would ask for this - but at the same time - I've got intimate knowledge of browsers that a casual user would not have. Seeing a way to remove form data with a single click could be reassuring. And in fact, a bit later Ben S said the same thing:\n@raymondcamden @zeldman Funnily enough we just added them. Our users felt safer (in testing) knowing they could revert &amp; restart their work.&mdash; Ben S (@beseku) November 3, 2015\n\nI guess I can see this helping users feel safe. As a reminder though, don't forget that the reset button doesn't &quot;clear&quot; forms, it literally resets it. So if your form is using hard coded values, perhaps on a &quot;Edit Profile&quot; page, the reset button isn't going to clear anything off screen. Rather it will just return the form back to its original values.\nAny comments on this?\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5"
            
		]

	},

	{
		"title": "Quick Tip - Ionic apps and touch events",
		"date":"Mon Nov 02 2015 08:48:14 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446454094,
		"url":"https://www.raymondcamden.com/2015/11/02/quick-tip-ionic-apps-and-touch-events",
		"content":"TL;DR: Ionic handles touch versus click for you. Don't worry and carry on.\n\nThis afternoon I was working on some code that began life as a quick example in a desktop web app and than began to transition to an Ionic application. My desktop app had a button with a click event in it and when converting this to use ng-click, it suddenly occurred to me. How do you switch from a click event to a touch event? We all know (or hopefully know) why that is important for mobile web development, but I have to be honest. As much as I'm aware of that and try to always use it in my code, when it came to Ionic's code, I just always used ng-click.\nAll of a sudden I realized that I had completely forgotten about using touch in my Ionic apps. Obviously things worked, but I had been using the wrong event in my demos and presentations. I was pure evil.\n\nI assumed that Angular would just support touch built-in, perhaps via ng-touch, but surprisingly, this isn't the case. You have to grab angular-touch.js to use ngTouch. Given how important mobile is, I'm kinda surprised that this isn't baked in directly.\nI was about to switch over to using it (I even ran bower, ewww....) when @breakingthings on the Ionic slack channel told me something that surprised me. Ionic fixed this already. In fact, if you go to the docs for Tap &amp; Click, you'll find this:\n\n\nOn touch devices such as a phone or tablet, some browsers implement a 300ms delay between the time the user stops touching the display and the moment the browser executes the\nclick. This delay was initially introduced so the browser can know whether the user wants to double-tap to zoom in on the webpage. Basically, the browser waits roughly 300ms to see if the user is double-tapping, or just tapping on the display once.\n\n\nOut of the box, Ionic automatically removes the 300ms delay in order to make Ionic apps feel more \"native\" like. Resultingly, other solutions such as fastclick and Angular's ngTouch should not be included, to avoid conflicts.\n\n\nSo yep, no need to worry about it (and you can disable it too if you want), Ionic has your back. And yes - this is yet another reason why I need to make the time to read the docs from start to end. I've been telling myself I'd do that for a while now but I think I need to make it a priority for this month.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "November is National Adoption Month",
		"date":"Mon Nov 02 2015 04:07:14 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446437234,
		"url":"https://www.raymondcamden.com/2015/11/02/november-is-national-adoption-month-3",
		"content":"Every year or so (although sometimes later than planned), I like to remind people that November is National Adoption Month. My wife and I have adopted six times over the past fifteen years. We adopted three times from South Korea and three times from China. It is an incredible experience - and &quot;incredible&quot; in every way possible. Joyous, scary, exciting, nerve-wracking, etc. My wife and I both advocate for adoption (both international and domestic) and highly encourage people to look into if they are in any way interested. Here are just a few random links for more information and if you have any questions, just drop me a comment below. (And if it is something you would rather discuss in private, you can drop me an email too.)\n\nNational Adoption Month\nNational Adoption Day (November 21st)\nAdoption.org\n",
		"tags":[
	        
		],
		"categories":[
            
                "adoption"
            
		]

	},

	{
		"title": "Building a hybrid mobile app? Avoid using CDNs for your libraries",
		"date":"Fri Oct 30 2015 04:50:21 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446180621,
		"url":"https://www.raymondcamden.com/2015/10/30/building-a-hybrid-mobile-app-avoid-using-cdns-for-your-libraries",
		"content":"I'm reading an (otherwise fascinating) article now on Ionic and came across this snippet of code:\n\nWhat's wrong with the above code? (And yes, the title mostly gives it away.)\n\nBy using a CDN for libraries in your hybrid mobile app, you've essentially ensured the application will be completely broken when offline. This is obvious, but like most of us doing client-side work, we've become accustomed to using CDNs for libraries.\nNow - you may ask - what if the core functionality of the application requires you to be online? In that scenario, you still want to ensure you gracefully handle the user being offline. If your CDN libraries fail to load, most likely your application is going to crap the bed. If those libraries were stored locally within the application, you can at least still load up, detect the offline state, and then tell the user they can't do anything while offline.\nEven better, you can add a simple event listener for when they get back online and then start the application up again. (And of course, you'll have an event listener for when they go back offline. Because that happens. A lot.)\nFor an example of this in action, see my earlier blog posts: PhoneGap Online/Offline Tip and PhoneGap Online/Offline Tip (2).\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "StrongLoop, Ionic, and IBM Bluemix",
		"date":"Thu Oct 29 2015 07:26:02 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446103562,
		"url":"https://www.raymondcamden.com/2015/10/29/strongloop-ionic-and-ibm-bluemix",
		"content":"Over the past few weeks I've been digging deep into StrongLoop and rather enjoying the heck out of it. As I said in my earliest post - I'm not necessarily a fan of tools generating code for me or lots of &quot;automagical&quot; stuff at the framework level, but after working with the LoopBack framework and models I got over it pretty darn quickly. I'm definitely sold on the concept and am exciting about digging into the other parts of StrongLoop's offering. But before I went too much further in that direction, I wanted to write up a complete example that covered a fully functioning server and mobile app running on Bluemix. To the end I've created a project and a set of videos to help guide you through the process. Let's get started!\n\n\nGetting the Code and Testing\nYou can find all of the code on GitHub: https://github.com/cfjedimaster/StrongLoop-Bluemix-Ionic. While this will give you the raw code, obviously it won't give you all the tools you need to run through everything. For the server-side, you'll need:\n\nNode.js\nStrongLoop (installs via npm, you also want to register at the site)\nSign up at Bluemix and grab the cf command line tools here: Deploying your app with the Cloud Foundry command line interface\n\nFor the client-side, you'll want:\n\nApache Cordova and some mobile platform to test on. You may be able to get buy with the browser platform though.\nIonic (installs via npm)\n\nThat's a lot, but I assume if you are a developer you probably already have Node and hopefully you have Cordova done too. There are no requirements for editors but I strongly recommend Visual Studio Code. Ok, so let's get started!\nAn introduction\n\nIn this video, I go into detail about what is being built and what components are being used. To be honest, this blog post itself explains most of that so I won't be offended if you skip this, but I also demonstrate the final app so you can see everything come together.\nServer-Side Setup\n\nIn this video, I walk you through creating the Node.js application using the StrongLoop command line. I then show StrongLoop Arc Composer visually designing a simple model. I then show you the API in action and quickly create a few objects to test that everything is working.\nBuilding the mobile app in Ionic\n\nIn this video, I create the application with Ionic. I don't walk you through every line of code, but rather show the completed source code and explain how I did it. Angular's $ngResource made this incredibly simple. Shockingly simple actually.\nDeploying to Bluemix and adding Cloudant\n\nIn the final, and longest, video, I walk you through pushing the application up to Bluemix and then adding Cloudant to the mix. As I said, this is the longest part, so let me know if anything isn't clear.\nWrap Up\nAll in all, you've got about 20 minutes of video, and in that time a server is created and hosted live and a front end application is setup to speak to that server via an API. That's power. Incredible power. Obviously I'm pretty biased towards all the technologies used in the stack here but frankly I think I have reason to be. They kick butt. I hope you think so as well!\nEdit\nJust a quick FYI - after posting this article, I discovered that the StrongLoop folks actually had a four part series on the same topic! I haven't read it yet, but part one is here: Part 1: Ionic &amp; LoopBack Frameworks – Building a REST API.\n",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "A quick look at debugging Node.js with StrongLoop and Visual Studio Code",
		"date":"Wed Oct 28 2015 09:01:28 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1446022888,
		"url":"https://www.raymondcamden.com/2015/10/28/a-quick-look-at-debugging-node-js-with-strongloop-and-visual-studio-code",
		"content":"As I mentioned in my last entry on StrongLoop, I've decided to move on from API building and begin looking at what else is available when working with StrongLoop. Today I'm going to talk about debugging, and while it mainly &quot;just works&quot;, I ran into a few issues that I want to warn people about.\n\nSo first and foremost, the main docs for debugging with StrongLoop, Debugging applications, mention using the command slc debug to begin debugging your application. It wasn't entirely clear to me at first, but this command is also responsible for starting your application. You can connect to an application that is running, but I figure most people are going to test locally and will probably kill their app and then start it up again via the debugger.\nNow the first thing that happens after running slc debug is that your default browser will open up with the debugger. The debugger is only supported in Chrome and Opera. So if your default browser is Firefox, like it is for me, just copy and paste the URL into Chrome.\nHere is where things got a bit weird for me. I noticed that nothing seemed to be working. But then I saw that the debugger was actually paused:\n\nFrom what I know, the Node debugger wraps your code with - well - a wrapper - and for some reason it is automatically breaking at some point there. Clicking the blue arrow on the top right (Resume script execution) let's things carry on. And here is where another odd thing happened.\nApparently it takes a little bit of time for the debugger to get up and running. Certainly not a long time. I'd say about 30 seconds. But I was convinced the debugger wasn't working because I immediately tried to run some code with a break point and nothing seemed to work. What you want to do is watch your terminal for your app's start up message. So here is what I had initially:\n\nI then clicked Resume in the debugger, and back in my terminal prompt I waited for it to update:\n\nOk, so in theory, that's it. To test, I added this route to my code:\n\nIn the debugger, I then added a break point:\n\nI then opened up the URL in my browser (and while the debugger wants you to use Chrome, any request to the URL at all will work) and confirmed that the request was hung while the debugger was paused.\n\nNote that you can also edit values, which is pretty freaking cool. Just double click and enter a new value and resume. All in all - cool - and easy to use.\nSpeaking of easy to use, don't forget that Visual Studio Code also includes built in support for debugging Node.js applications as well. I won't repeat what their excellent docs say about their debugging support. As with slc debug, you do not want to start your server. Instead, you'll configure VS Code to run the proper script when you hit debug.\nOnce you've done that, you can go to your files view and add a break point:\n\nThen request something that fires the break point. Code will automatically take focus (which, I must say, I'm not sure how I feel about - in general, I never want my apps to take focus) and you can then look at variables and begin stepping through the code.\n\nYou can modify values too, but the Code docs don't make this explicitly clear in my opinion. You'll want to open the debug console and then modify the code using simple variable assignments.\n\nSo, there ya go, two interesting options for debugging Node.js applications, and yes, I know there are even more. To be honest, I'm kinda leaning more towards using Code for debugging as I like it in my editor versus my browser (which seems weird, I love dev tools), but I'll probably go back and forth.\n",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "\"Apache Cordova in Action\" final edition released!",
		"date":"Tue Oct 27 2015 10:37:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445942252,
		"url":"https://www.raymondcamden.com/2015/10/27/apache-cordova-in-action-final-edition-released",
		"content":"Well, this took a bit longer than I expected, but the final version of my Apache Cordova in Action book is for sale!\n\nThis book was incredibly difficult to write at times. I really hope that it helps people learn (and love) Apache Cordova. Please check it out and let me know what you think!\nFor folks curious, here is the table of contents:\nPART 1: GETTING STARTED WITH APACHE CORDOVA\n\nWhat is Cordova?\nInstalling Cordova and the Android SDK\nPART 2: CORE CONCEPTS\nCreating Cordova projects\nUsing plugins to access device features\nMobile design and user experience\nConsiderations when building mobile apps\nTools for debugging Cordova and other hybrid apps\nCreating custom plugins\nPacking options for Cordova projects\nUsing PhoneGap tools\nPART 3: APPLICATION RELEASE\nSubmitting your app\nBuilding an RSS reader app with Ionic\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "mobile"
            
		]

	},

	{
		"title": "NodeSchool for IBM Bluemix and Node.js",
		"date":"Tue Oct 27 2015 05:51:26 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445925086,
		"url":"https://www.raymondcamden.com/2015/10/27/nodeschool-for-ibm-bluemix-and-node-js",
		"content":"Nearly two years ago I blogged about the cool set of tutorials available at NodeSchool. I loved the interface of the system and thought it was an excellent way to learn Node. Since that time, there have been an incredible amount of lessons added to the core package, including WebGL, ES6, and React. I'm happy to say there is also a cool package for working with IBM Bluemix.\n\nSimply npm install -g bluemix-workshop and you'll get a set of lessons that help walk you through learning how to build Node.js apps on the IBM Bluemix platform.\n\nEach lesson walks you through a few simple sets and even provides a verification system.\n\nWhat I like about these lessons is that it is all command line driven. Most of my experience with Bluemix is with the web console. I knew the cf CLI did everything the web site did, I just didn't realize how easy it was. I've learned some new things already. I can say I ran into a few bugs around the debugging/trace area (see my issues on the GitHub repo), but they don't prevent you from moving on to the next lessons.\n",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Status of RIAForge",
		"date":"Sun Oct 25 2015 12:27:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445776074,
		"url":"https://www.raymondcamden.com/2015/10/25/status-of-riaforge",
		"content":"A bit over 9 years ago I launched RIAForge (Announcing RIAForge). The brainchild of Ben Forta, the idea was to make it easier for people using Adobe technologies to create open source projects. At the time, that made a lot of sense. I believe GitHub was around then, but it certainly wasn't as popular as it is now. Sourceforge was there, but at least for me it wasn't the easiest solution in the world. RIAForge made it incredibly easy. You could launch your project and get a blog, forums, a bug tracker, even a wiki.\n\nI was - and still am - rather proud of the site. Over thirteen hundred projects are hosted there and there have been over two million downloads since the site launched.\nThat being said - the benefits that RIAForge provided are not necessarily as valuable now in 2015 as they were back in 2006. While there are more options than GitHub, let's be honest, GitHub has &quot;won&quot; for the most part. If you are hosting an open source project that you want others to work with, there really is no reason not to be there. Also, Git in itself has become the most popular version control system and RIAForge's Subversion server really isn't up to date.\nAfter getting a few error emails from the site last week, I really began to think about the site and came to the conclusion that it is time for my involvement with the site to end. I reached out to Rakshith on the ColdFusion team and asked him if they wanted to take it over. While RIAForge isn't just for ColdFusion projects, ColdFusion (and ColdFusion Builder) dominates the site. If Adobe had said no, my plan was to set RIAForge to read only mode and then eventually move it to static, but, that is not to be. Rakshith, and Adobe, have agreed to take over management of the site. I handed over login info today and &quot;officially&quot; I'm no longer in charge.\nBen, and Adobe in general, deserve a lot of credit for launching this initiative and funding it. (Yes, I was paid to develop the site initially!) Going forward though I'd probably recommend people just use GitHub, even if you are launching a ColdFusion project. If you have projects on RIAForge now, don't forget you can edit the project to point to external (like GitHub) sites. I'd set up on GitHub and then edit your RIAForge listing to point to the new location. Or just stay on RIAForge; it's up to you. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Getting an error running a Cordova project to iOS?",
		"date":"Fri Oct 23 2015 08:15:34 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445588134,
		"url":"https://www.raymondcamden.com/2015/10/23/getting-an-error-running-a-cordova-project-to-ios",
		"content":"This morning when testing an Ionic project, I got an error trying to emulate iOS. I tested it again in the Cordova CLI and got the same error:\n\nsimctl was not found.\nCheck that you have Xcode 6.x installed:\nMy first thought was that it was an XCode 7 issue, but I had run builds earlier in the week. On a whim though I fired up XCode and...\nBam. I had to OK a license agreement change. Because... reasons. So if you ever run into an issue like this before, open up XCode and see if you've got a prompt waiting for you. Apparently you can script your way around this too, but just running XCode was faster.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Quick tip for Cordova and the Browser platform - Setting a custom port",
		"date":"Thu Oct 22 2015 03:10:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445483444,
		"url":"https://www.raymondcamden.com/2015/10/22/quick-tip-for-cordova-and-the-browser-platform",
		"content":"I'm not regularly using Cordova and the Browser platform because most of the time I'll use ionic serve instead. However, last night I was working on a Cordova project that - shockingly - didn't use Ionic. I needed to run it in a web page to do some quick testing. I quickly discovered that one thing the browser platform does not handle is enabling CORS for all requests. I had set up CORS for the server part of this application a while ago but it required me to use localhost:3333. By default, Cordova will use port 8000 for the port. There wasn't an obvious way to change that so I did some digging.\n\nThe first thing I did was go into the browser folder under platforms. Under platforms/browser/cordova I opened up the run file and saw that the script did include the ability to pass in a port argument. I couldn't figure out how to pass it though so I tried doing run -h:\n\nOk, that's simple. I confirmed it worked by doing cordova/run --port=3333. Sweet. But how do it via the &quot;main&quot; Cordova CLI? If you run cordova help run, you'll see this nugget in the docs:\n\nSo basically, this is all you need to do: cordova run browser -- --port=3333. Simple, right? Probably everyone but me knew this, but as I had to dig to figure it out, I thought it made sense to blog it.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with StrongLoop (Part Four) - Locking down the API",
		"date":"Wed Oct 21 2015 04:03:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445400239,
		"url":"https://www.raymondcamden.com/2015/10/21/working-with-strongloop-part-four-locking-down-the-api",
		"content":"Welcome to the latest in my series of blog posts on the StrongLoop platform. The last few blog posts have been focused on the API composer (part of StrongLoop Arc) built on top of LoopBack. As I've mentioned, there is a lot more to StrongLoop then just the API stuff and I plan on moving to those other topics soon. For today I'm going to discuss how you can lock down your API. Out of the box, all your models (and APIs) are 100% open. That makes it incredibly easy to quickly prototype and test adding, editing, and deleting data. But in a real application, you'll obviously want to lock down how folks can use your API. As before, the StrongLoop folks do a good job documenting this area: Authentication, authorization, and permissions. The focus of this entry is to summarize those docs and discuss some of the things that confused me personally.\n\nOk, so let's begin by talking about the security model at a high level. Security rules are defined at the model level (although you can also apply security to all models). You can apply a rule to a model method or property that sets an access value for a particular user. In terms of users, you can specify a specific user, or more likely, a role instead. LoopBack has various roles built in, like $owner, $authenticated, $unauthenticated, and $everyone. These are referred to as ACLs (Access Control Lists) and you can see them within a model definition.\nThe StrongLoop Arc Composer does not support visually defining ACLs so you have to either type them by hand, or use the command line. The actual definition is really simple so once you've done it a few times you can probably skip the CLI, but the CLI isn't too hard to use.\nIf you remember my previous blog posts, I defined a &quot;Cat&quot; and &quot;Dog&quot; model for my application. To test security I decided to lock down access to Dogs. My thinking was this:\n\nAnyone can get dogs, or an individual dog.\nOnly logged in users can modify dogs.\n\nThat's a fairly simple design and doesn't support the idea of different types of users. LoopBack definitely supports that but I wanted to keep it as simple as possible. I followed the guide (Controlling data access) and began by locking down all access to the Dog API:\n\nNext, I wanted to add anonymous access to get dogs and an individual dog. Here is where things get weird. When using the CLI, the prompt will ask if you want to modify access to a property or method. In my case I wanted to enable the REST API to let me read dogs. However, when you look at the API explorer, this is what you see:\n\nGetting all dogs corresponds to GET /dogs and getting one dog corresponds to GET /dogs/ID. But that is not what LoopBack wants in the ACL. Instead it wants find and findById. Ok, that kinda makes sense, but I was not able to find a good table that maps the REST APIs to various internal LoopBack methods. You'll have to figure these out one by one I suppose (and remember it of course ;). So here I am adding support for running find for anonymous users:\n\nAnd I simply did this again for findById. Finally, I added support for making new dogs:\n\nThe CLI is easy to use, but check out the Dog model. As you can see, these ACLs aren't too complex. I think after you've used the CLI a few times you won't need to generate them via the CLI.\n\nWoot! Ok, so how do you test? Again, the docs do a good job of walking you through this. Start here, Introduction to User model authentication, and just follow the directions to create a User via the REST API. Here is what confused me though.\nWhen you create your user, you'll specifically want to use the email property and password property. They document this (image stolen from their docs):\n\nBut I was confused since the password field as an argument does not map to a password property (that you can see on the right hand side). Also, I wondered they used email instead of username. In the end, I just used what they demonstrated and it worked.\nOnce you've made a user, you can login, get an application token, and then run your locked down methods in the API explorer. It just works... until you restart. By default, the User model is stored in the in-memory database. As a reminder, if you go to your server folder and open model-config.json, you can see this for yourself:\n\nI first attempted to move User to the MySQL datasource I created. In the web-base Arc Composer, they hide the &quot;built in&quot; models so you can't just migrate User. I tried to just set it in the JSON file, but then ran into the issue where the appropriate tables weren't made. You can do migration via JavaScript code, and I was beginning to work on that, until I discovered this nugget in the docs:\nThe User model represents users of the application or API. Typically, you'll want to extend the built-in User model with your own model, for example, named \"customer\" or \"client\".\nOh, that's easy. So back in Arc Composer I made a new model called appuser, told it to extend User, and pointed to the MySQL datasource, a",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Building my own iPhone Availability Web App",
		"date":"Tue Oct 20 2015 05:16:06 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445318166,
		"url":"https://www.raymondcamden.com/2015/10/20/building-my-own-iphone-availability-web-app",
		"content":"Before I begin, a quick disclaimer. What I'm building here is completely for fun and because I thought it might be interesting. I'm critiquing and improving a form that was built by people smarter than me and more than adequate for 99.99% of users. Basically, I saw something I wanted to build and I did it.\nI'm currently the owner of an HTC M8 phone - my foray back into Android after using an iPhone for a couple of versions. I like the HTC UI, and in general, the phone was pretty incredible, but after the most recent Android OS update, my phone began to get more and more sluggish. It got to a point where just opening up the phone to take a picture would take 30-60 seconds for it to respond. Phone calls, which I don't get many of, were even worse. When I missed a call because my phone's basic UI wouldn't respond I nearly threw the thing in the pool. I tried many things but eventually wiped the phone and restored from a back up. It &quot;helped&quot;, but the phone is still chunky. I decided it was time to switch back to iOS and I figured the iPhone 6S+ would be a great phone to pick up. I also decided that the new iPhone upgrade program would be a good fit. From what I've read it is better than ATT's Next program. The only problem is that you must go to an Apple store to sign up for the program. My nearest Apple store is in Baton Rouge, about an hour away. Worth a drive, but only if I know I'll have a device there to pick up.\n\nLuckily, Apple has a cool form you can use to see if your desired phone is available. You select your state, your store, your model, and then your carrier:\n\nAs you can see, none are available. (Sigh.) You can switch to SIM-free of course (and I checked, my HTC and the 6S+ use the same type of SIM). What bothered me about this form were a couple of issues.\n\nFirst off - you can't use it before 8AM. No, wait, stop laughing, I'm serious. It's a web based system with \"open\" hours like a retail store. There's probably a data reason for that. I spoke with an Apple rep last week and they mentioned they get new inventory data at 8. I'd like to imagine that Apple stores have some sophisticated real time hook into inventory but that's probably not the case. Still, it is kind of shocking to see a \"closed\" sign at a web site.\nWhen I was in CA last week, I tried to search around me. Every time you switch stores, the form rebuilds. So if I've selected 6S+ and ATT, I lose those selections. Now, the reason for this makes sense. It is possible that the other store doesn't have 6S+ or ATT available, but it still annoying. That's the kind of problem that intelligent front-end code could handle gracefully. There were 5-6 stores around me in South San Francisco and I checked every day there and those damn drop downs annoyed me every day. (As I said on top though, I'm probably not the target user here.)\nFinally, it would have been really nice if I could have simply said, \"Tell me when a 6S+ for ATT or SIM-free is available in gray or silver that has 64 GB since 16 is just plain stupid.\" But apparently Apple isn't having any difficulty selling iPhones so such a system probably isn't a high priority for them. (And to be clear, this is just for the upgrade program. Obviously the 'regular' store lets you buy right now.)\n\nSo - bored this weekend - I did what any self-respecting web dev does - I opened up dev tools while using the form. First thing I noticed was that the app was hitting JSON files to drive the drop downs:\n\nI then opened each of those files and took a look at the JSON. stores.json was a literal listing of all the stores with availability. Here is a snippet:\n\navailability.json was availability data of course. Here is a snippet from it:\n\nThe key there is the store and each line item (except for timeSlot) represents a model/color/carrier/size line item. So given that I could get the data (right click in dev tools and open them in a new tab, then save as), I began work on a web app that would let me parse the data my own way. Specifically I wanted a few things:\n\nLet me specify a store, and then multiple stores.\nLet me specify any model I want.\nLet me specify multiple carriers.\n\nI also wanted to ignore 16GB, but at the end decided against that. I began working on my own code that would suck in the JSON files (my local copy) and let me parse it myself. I'll show the result first and then talk about the code. And yes - mine is far less pretty than Apple's.\n\nOn top you can see a state drop down and store selector. As I said, my initial plan was to provide for adding multiple stores, but I never got around to that.\nBelow it you can see the carrier and model selections. Below it is the grid of options. I used CSS (woot) to gray/blur options that weren't available. How did I get the Apple iPhone colors? Did you know Firefox has a color picker builtin to their dev tools?\n\nThe circles on the Apple store actually have nice gradients as you move from the center of the circle to the outside. I just clicked &quot;in",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery",
            
                "mobile"
            
		]

	},

	{
		"title": "The New TFA Trailer",
		"date":"Mon Oct 19 2015 23:58:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1445299123,
		"url":"https://www.raymondcamden.com/2015/10/20/the-new-tfa-trailer",
		"content":"Sure, everyone has seen this probably and you don't need me to share the video here, but I'm going to do so anyway - enjoy.\n\nPersonally, I freaking loved it, but that's no surprise.\n",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Working with StrongLoop (Part Three)",
		"date":"Thu Oct 15 2015 06:23:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444890204,
		"url":"https://www.raymondcamden.com/2015/10/15/working-with-strongloop-part-three",
		"content":"So this week I'm continuing my look into StrongLoop. If you missed my previous entries, I'll include them in a list at the bottom. I'm kinda hopping around the technology picking and choosing what seems interesting to me so these posts may not be the best introduction to the platform, but I hope folks are finding it interesting. As a reminder, you can access the core documentation here to start learning about it yourself. As I mentioned in the very first post, StrongLoop's Arc product runs on top of the open source LoopBack framework. Today's post is pretty much entirely based on that.\nWhen I began this series, I discussed how you could quickly generate APIs for your data. You've got a CLI and a visual component (that's where Arc comes in) that allow for rapid development. In five minutes, I can create a model for my data and have a complete set of APIs to list, find, create, update, and delete that data. This got me thinking though - given you get a set of CRUD operations out of the box, how do you modify the API to add or alter those operations?\n\nFirst - let's examine how you would add new methods to your remote API. The StrongLoop docs cover this very well (Remote methods and I'm going to borrow from them liberally. To add a new method, you do two things. First, you define the method itself. Remember that every model item creates a model.js file and model.json file (where 'model' is replaced with the name of your model). I have a Cat model and I want to add a remote method that would let me return a cat by name instead of ID. (As an aside, the APIs generated by LoopBack already provide a way to filter by properties, so technically this is already done, but I wanted a specific method for this functionality.) I wrote a simple method that just returned a string.\n\nIn the code sample above, the null argument there is where you would use an error if something had gone wrong. We'll replace this logic with something real in a moment. That's step one. Step two is to expose the method remotely. Here is how you could do that.\n\nIn the code sample above, I'm defining that byName is remote and I'm defining how it is accessed (note the http section). I'm also defining that it accepts an argument and returns a string.\nWhen I restart my application (and you do not need to do this via the CLI, if you have StrongLoop Arc running, you can reload from the UI), I can see, and test this, in the Explorer:\n\nI then modified the code to do a real look up. I haven't really seen yet the CRUD methods you have available via code, but they are incredibly easy to use. Here is how I implemented it.\n\nSimple, right? Also note I updated the return type to specifically say I'm returning a cat. My code doesn't handle an unknown cat well, but you get the basic idea. Here is the updated result:\n\nAnother interesting aspect is the ability to work with remote hooks. These are functions that run in conjunction with other API calls. They let you do something before and after a remote call. They also let you hook into an error event. You can use this for logging or to sanitize inputs for methods or modify results for output. What's cool is that you can even use wild cards to match multiple (or all) remote methods for automatic logging of everything your API does. StrongLoop differentiates between static and model methods, so this particular example will only match the static calls (you can match the other ones too of course):\n\nNot really rocket science, but I love how easy it is to do stuff like this.\nFor the final bit of this post, how about blocking a particular method? You can hide a method following the guide here (Hiding methods and REST endpoints). This is actually from the security section and that's something I want to cover later on, but if you just want to blanket hide/block something, you can do it like so:\n\nAgain - super simple and incredibly quick to implement.\nSo - hopefully you are finding these posts interesting. I'm going to keep digging into StrongLoop and demonstrate more features over the next couple of weeks.\nPrevious Entries\n\nWorking with StrongLoop (Part Two)\nWorking with StrongLoop (Part One)\n",
		"tags":[
	        
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Adding localization to your Ionic application with IBM Bluemix",
		"date":"Wed Oct 14 2015 07:16:26 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444806986,
		"url":"https://www.raymondcamden.com/2015/10/14/adding-localization-to-your-ionic-application-with-ibm-bluemix",
		"content":"Localization is an important topic for mobile developers and one that is - in my opinion - not discussed enough. It is a difficult and complex topic, but like testing, it isn't something that should be ignored just because it isn't easy. Today I'm going to discuss one aspect of creating an internationalized hybrid application with Ionic - the localization of your UI. In my next post, I'll talk about formatting data values. To create the localized version of a sample application, I'm going to use two services. First, I'll use a beta Bluemix service for machine-based translation. Then I'll use an Angular library to employ the results of that service. Ready?\n\nPart One - Doing the Translation\nLet's begin by talking about the translation service. The service we'll use is a beta one which means you can only find it in the Bluemix Labs Catalog of services. We'll use the IBM Globalization service.\n\nTo be clear, we're talking about machine translation. This will not be perfect. However, this service is more than appropriate for development and testing. You can hire professional translators at a later time to come in and proof-read what SkynetBluemix provided for you.\nOnce you sign up for Bluemix (and hey, you know you can do that for free, right?), you can then add this service. You do not need to bind it to any application as it runs &quot;on it's own&quot; just fine. After you've added it, you can then begin working with it. You'll start off by adding a new project.\n\nIn the next page, simply enter a name for your project, and a source language. For now, this must be English.\n\nYou can also select the language you wish to support. You can add more languages later.\n\nOk, now that the project is created, you get a 'dashboard' view of your languages.\n\nAs you can see, my other languages have no data yet, so let's fix that. Clicking the little Upload icon by English prompts you to select a file. Note that they ask you for a format. What's cool is that you can upload as any format and then download as any format.\n\nWhile my first guess at what the JSON format required was correct, you can see all the formats documented in the Globalization docs. Here is the one I built for my initial test.\n\nAfter uploading it, the service parses it and shows you a set of keys and values:\n\nOnce back on the project dashboard, you'll get a status message about each language. In my testing this was instantaneous, but I'd assume it won't always be that fast.\n\nYou can click on a language and see how it translated, as well as provide your own edits if you know better. My &quot;expert&quot; on Chinese is my 12 year old daughter as I'm not home right now, I'll trust Bluemix.\n\nThe next step is to simply download the translation:\n\nAnd that's it! As I said, you should not expect perfect translations, but I was amazed at how easy this was and how quickly it worked.\nPart Two - Using the Translation\nIn my &quot;research&quot; in how to use translation files with Angular (and by research I mean some Googling and Slack conversations), I was pointed to two different libraries: angular-translate and angular-localization. (Hat tip to @northmccormick on Slack for the later.) angular-translate seemed powerful, but almost too powerful. All I really wanted (at least for this demo) was the ability to translate UI strings into a language-appropriate format. angular-localization did just that and worked well, however the documentation was pretty poor. I'll detail what I did to use it and point out what wasn't clear in the docs.\nTo begin with, you need to ensure you both name and store your localization files correctly. angular-translate expects a root folder for the files and beneath that a folder for each locale you support. Finally, and this was the weird part, what you name your file will drive how it is addressed in code. So for example, if you name your file cat.json, then &quot;cat&quot; acts like a grouping of translations. The idea is that you can have multiple different groups of key/value pairs for your translations. That's nice, but it wasn't clearly spelled out. To make it easier for me, I just used app.json. Here's my folder structure. (I didn't bother downloading the German translation. Sorry Germany.)\n\nNow let's look at the code. After installing the core library, I began by configuring the service. This is done via value objects.\n\nThe first portion is just high level configuration stuff. The library requires you to pass everything even if you are only tweaking one value. In my case, it was basePath being set to &quot;lang&quot;. The next value, localeSupported, is required, even though it isn't (from what I saw) documented that it is. If you don't tell the service what languages you support, then you can't change languages. (Which, by the way, also isn't documented.)\nOk, so next I set up some simple HTML using the format the service requires:\n\nNote how localiztion is done. You specify the key (and remember, 'app' is the file name of the translation JS",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with StrongLoop (Part Two)",
		"date":"Tue Oct 13 2015 05:55:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444715716,
		"url":"https://www.raymondcamden.com/2015/10/13/working-with-strongloop-part-two",
		"content":"Yesterday I blogged about StrongLoop and the ability to quickly generate APIs (Working with StrongLoop (Part One)). Today I'm going to write up a short post detailing how to switch from the in-memory database storage system to a 'real' persistent one. If you haven't read the previous entry, be sure to quickly scan it over or none of this will make sense.\n\nTo set up a persistence system with your StrongLoop-enabled app, you'll want to set up a datasource. In the screen shot below you can see five different data source types. Clicking one will open up the appropriate editor.\n\nFor my testing, I decided to first try MySQL. I've got a local MySQL server running so I made a new database and user just for testing my local application. I entered the appropriate details and tried to save. I then got this:\n\nThis was surprising. Since I saw the icons in the nav I just assumed it was already supported. Luckily the error message not only tells you what is wrong but gives you a link to correct it. I literally spent the 2 seconds to use npm to add the connector and that's all it took. The web admin also provides a test button you may miss if you aren't paying attention. It is at the very bottom of the data source form.\n\nThe next step is to go to your model, each model, and update the data source to point to your new source.\n\nRemember, you need to do this for each model. I imagine most folks will use one data source per API, but certainly it makes sense to allow for multiple. What's cool too is that you can have an existing site with various models and then you can use the in-memory data source to test something new.\nSo at this point, you're done, right? Nope. I went to the explorer to test creating a new cat. When I did my POST, I got:\n\nThis is easy to fix - when you change the data source, click that &quot;Migrate Model&quot; button I had not noticed before:\n\nAgain, you will want to do this for each model. StrongLoop also provides a JavaScript API for data migration if you want to programmatically handle moving from one data source to another.\nOnce I did that for my cat and dog models, I hopped back over to the MySQL GUI and confirmed it:\n\nI then ran my POST to add a cat and confirmed it persisted as well:\n\nAnd that was it! To be clear, everything I did visually with the StrongLoop web admin could also be done via the CLI or just pure files. In case you're curious, here is the datasources.json file I have for my application:\n\nSo that's MySQL, what about Cloudant on Bluemix? The good news is that is also easy. My coworker Andy Trice covers it well here: Getting Started with Node.js LoopBack Framework and IBM Cloudant. Essentially, once you've gotten your Cloudant service up, it is one more npm call to install a connector, and then you can edit your datasources.json file to include the relevant information.\nAll in all - pretty darn nice. I love how I can quickly go from a quick in-memory test to a 'real' setup in a persistence system, and I like how many different options I have as well.\n",
		"tags":[
	        
            "bluemix",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Important note for targeting iOS Emulators in Cordova",
		"date":"Tue Oct 13 2015 03:39:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444707556,
		"url":"https://www.raymondcamden.com/2015/10/13/important-note-for-targeting-ios-emulators-in-cordova",
		"content":"With the recent changes to iOS9, I've had to do more testing in iOS 8.4 versus 9.0 when working on Ionic/Cordova applications. It is relatively easy to switch which emulator you are using if you use the --target argument in the CLI:\n\ncordova emulate ios --target=&quot;something&quot;\nOf course, the question is, what value do you use for &quot;something&quot;? My coworker Carlos Santana has an excellent answer over on Stackoverflow. Basically if you run:\n./platforms/ios/cordova/lib/list-emulator-images\nYou will get a list of valid targets for your simulator. As an aside, how many of you ever dig around in your platforms folder? Did you even know this tool existed? Should I write up an exploration of this folder? Ok, stay on target, Raymond.\nRunning this command will give you output that looks like this:\n\niPad-Air, 8.4\niPad-Air, 8.4\niPad-Air, 8.4\niPad-Air, 9.0\niPhone-6, 8.4\niPhone-6, 8.4\niPhone-6, 8.4\niPhone-6, 9.0\niPhone-6-Plus, 8.4\niPhone-6-Plus, 8.4\niPhone-6-Plus, 8.4\niPhone-6-Plus, 9.0\n\nThe list above is about half of my list and your list will be different. Ok, problem solved, right? Not so fast. What happens when you try to target the 8.4 version of the iPhone?\ncordova emulate ios --target=&quot;iPhone-6, 8.4&quot;\n\nWtf? Confusing, right? If you keep reading on that StackOverflow page, you come to this answer by Ruslan Soldatenko. He points out that the platforms/ios/cordova/lib/run.js file has a specific list of allowed targets. I'm sure there is a good reason for this. Maybe the Cordova CLI doesn't want to keep asking the system for valid targets. Either way, if you open it up, you will find a line like this:\n\nAdd &quot;iPhone-6, 8.4&quot; to the end of the array and you are good to go... for this project only. You'll need to modify this line in every project you work with where you need to target different iOS versions.\nAs an aside, this (obviously) applies to Ionic and their CLI.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with StrongLoop (Part One)",
		"date":"Mon Oct 12 2015 04:26:21 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444623981,
		"url":"https://www.raymondcamden.com/2015/10/12/working-with-strongloop-part-one",
		"content":"A few weeks back when I was shuttling back and forth between this side of the planet and the other, IBM purchased StrongLoop. I'll be honest and say that before this purchase, I had never heard of them. A quick perusal of their home page will tell you this:\n\nBuilt on top of the open source LoopBack framework, StrongLoop allows you to visually develop REST APIs in Node and get them connected to your data. StrongLoop also features built-in mBaaS features like push and offline sync, plus graphical tools for clustering, profiling and monitoring Node apps.\nThis seemed interesting to me. Right now I'm at an interesting point in my Node.js development. I can write code. Not great code, and I need to copy and paste quite a bit, but I can build an application. On the hosting side, I've got multiple different ways of moving my application to production, including, of course, Bluemix.\nWhat I haven't really gotten yet is the ecosystem around tools to help me build Node.js apps quicker as well as debugging and performance tuning. I feel like I'm just now getting to the point where it makes sense for me to learn more about this area and the StrongLoop acquisition is the perfect opportunity for me to do so.\nStrongLoop has a number of features related to Node.js development, but for today's post, I'm going to focus on just one - the API Composer. At the simplest level, this is &quot;just&quot; a code generator, and I've got a bad history with code generators in general. I tried like heck to get behind Yeoman, but it simply never clicked for me and how I develop web apps. StrongLoop's tooling though works really well (as I hope you'll see) and so far I'm incredibly impressed.\nThe API Composer (and technically, I'm going to be showing the CLI as well as the graphic interface) is focused around building APIs. This is especially appealing to me because the more I work on the client-side, the leaner my server becomes. This is why I've been moving away from ColdFusion. I simply don't need my server to do much beyond simply proxying of API calls to various data sources. The more intelligent my front-end becomes the less intelligent (and complex) my back-end becomes. Let's consider a simple demo of what I'm talking about.\nAs a quick aside, if you are following along and actually doing these steps, you will eventually need to register at StrongLoop. You can do so here. This is free, and will be required to test the graphical stuff. I'm also assuming you have Node.js installed because, well, why wouldn't you?\nThe first thing you'll want to do is install StrongLoop itself. This can be done via npm:\nnpm install -g strongloop\nThis gives you the CLI tools as well as everything required to run the graphical portion as well.\nNow, we'll build a sample app. At the command line, run this:\nslc loopback\nLoopBack is an open source Node.js framework that StrongLoop created, and their tooling runs on top of it. Some of what you'll see below is available in LoopBack and some just within StrongLoop itself. Running the above command will begin the app creation process.\n\nAfter naming your app and entering a directory, the CLI will layout the app and end with this:\n\nFire up the application and you'll get two endpoints:\n\nThe home page just reports some startup info, but the explorer is where things get cool.\n\nWhat you are seeing is automatic documentation for a simple modal called user. This is baked into the sample code and obviously you can rip this out if you don't need it. Clicking User expands the full list of methods available on it.\n\nAnd you can then expand one particular method for more detail:\n\nNotice that not only do you get quite a bit of information, you also get the ability to test the API directly on the page too. This is all really slick and well done, but let's actually make a proper model for our new application. We'll use the CLI first.\nYou begin by typing slc loopback:model. You'll be asked for the name of the model. Be sure to use the singular version as a later question will be what the plural should be. After entering the name of the model, you'll be asked about the data-source. Out of the box, you can use an in-memory database for testing. This is slick, but remember that every time you stop the Node.js app, the data will be cleared. (Not your models, they are store as files, but instances I mean.) If you want to play with the models and keep your data around, you may want to use one tab to run the application and one to use the CLI. You'll be asked a few more questions that you can just accept as default. StrongLoop supports things like MySQL and Mongo, and can be extended to support other data providers like Cloudant. (You'll see this in the next post!)\n\nYou're next asked to enter properties. Obviously this will depend on what your data is exposing. In the screen shot below I added three properties - name, gender, and color. I set these as strings, but I could have used different data types.\n\n(Note - the deprecation warnings th",
		"tags":[
	        
            "bluemix",
            
            "strongloop"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Article: A Review of ContentTools – a Rich Content Editor",
		"date":"Fri Oct 09 2015 03:43:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444362210,
		"url":"https://www.raymondcamden.com/2015/10/09/article-a-review-of-contenttools-a-rich-content-editor",
		"content":"Just a quick note that I've published an article on the Telerik Developer Network: A Review of ContentTools – a Rich Content Editor. Enjoy. Go there to at least see the Wonder Woman picture.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Testing Ionic Push Webhooks with IBM Bluemix",
		"date":"Wed Oct 07 2015 03:45:33 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1444189533,
		"url":"https://www.raymondcamden.com/2015/10/07/testing-ionic-push-webhooks-with-ibm-bluemix",
		"content":"Disclaimer: Ionic Services are currently in Alpha status. While the feature I'm talking about will surely exist when Ionic's Push service goes Gold, the details of what I'm covering today will surely change. Please keep that in mind.\nI've been going over the various Ionic Services as I prepare for my presentation next week. One of the aspects of Ionic's Push service that I had not used yet is webhooks. Ionic lets you define a webhook (a URL really) that they will call whenever someone registers for push, unregisters for push, or simply has a device that becomes invalid. I thought I'd create a quick Node.js application so I could test this feature for myself. To host this application, I'll make use of Bluemix, our PaaS solution that makes Node.js hosting quite easy. As an aside, Bluemix supports Push by itself and you may wish to use that instead of Ionic's Push service. One of the nice things about Bluemix is the ability to mix and match services as you see fit.\n\nTo begin, I created a simple Push demo. You can find the complete source code for this demo here: https://github.com/cfjedimaster/IonicServicesPresentation/tree/master/demos/push1_user. I'm not going to show the code here in the blog entry as it isn't necessarily relevant. All it does is register the application for push and associate it with an Ionic User. That by itself is interesting as the docs don't show a complete example of this yet, but I'll save that for a blog entry later this week. The important thing is that I've got an application I can fire up on my device and do real push tests.\nNow let's turn our attention to the webhook feature. The main purpose of this feature is to give you the ability to know when a user registers (or unregisters) for push. You can use any server technology you want for this, which for me means Node.js. I began by going to Bluemix and signing in (which is free, by the way!) and creating a new app using the Node.js starter. I downloaded the sample code (which has gotten quite a bit simpler since I first began using Bluemix), and ran npm install to get things prepared locally. I've talked about how to use Bluemix for Node.js before, but in case you need a refresher, check out my article here: Hosting Node.js apps on Bluemix.\nFor my application, I decided that it will respond to Ionic's calls by storing the registration data in Cloudant. There's a great Node.js package for it so I knew using it in the application would be simple. You can easily add the Cloudant service to your Bluemix app from the catalog:\n\nOnce I added the service, I then went into the administrator and created a database called &quot;registrations&quot;. Now I opened up my code and started writing. Believe it or not, I wrote everything below in one sitting and I didn't make any mistakes. Seriously. (Ok, I may be off by a factor of ten or so.) Here is the code I used to handle calls from the webhook:\n\nBefore we dive into this, let me just clarify that I wrote the bare minimum here to get my tests working. This code could be organized quite a bit better. I'm sorry.\n\nLet's work our way from the top down. For the most part, the first few lines are just simple require statements. Notice that I get my Cloudant credentials either via an environment variable or via a hard coded value. Don't forget you can get your credentials in the Bluemix console by clicking on the &quot;Show Credentials&quot; link of the service itself:\n\nMoving on down - the next critical aspect is getRegistrationMode. If you read the docs for how Ionic will hit your URL, you will notice that it is bit difficult to tell one &quot;mode&quot; form the other. I spoke with the developer behind this service and he agrees it can be simpler, but for now, I wrote a simple function to look at the data and figure out the mode. In the /register route, we then use that function to figure out what in the heck we are doing.\nIf we are adding, then we get the tokens and figure out if they exist already in our database. If they do, we update, if they don't, we insert. I'm including a time stamp along with the registration. I could include other things as well. Both the 'unregister' and invalid status we end up removing the token.\nFinally, I built a /list route so I could quickly see if it was working. And that's it. As I said, I wrote this all in one fell swoop without any errors. (Actually I did something kind of spectacularly stupid - read the p.s. at the bottom for details.)\nI ran this on my local machine first, and to test, I copied the sample JSON packets from Ionic's docs and used the Postman Chrome app to test. Postman has been around for years, but I never got around to playing with it till yesterday. It is awesome. Here it is in action:\n\nI really can't praise this app enough. It certainly isn't a &quot;use every day&quot; type thing but it is incredibly useful. Once I had my app working fine, I pushed it up to Bluemix: cf push IonicPushRegistration. I waited for it to get setup, and then ran ",
		"tags":[
	        
            "bluemix",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Learn about Ionic at Appcamp",
		"date":"Fri Oct 02 2015 05:10:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443762658,
		"url":"https://www.raymondcamden.com/2015/10/02/learn-about-ionic-at-appcamp",
		"content":" Interested in another way to learn the Ionic framework? Recently they released an interesting little tool called Appcamp. Appcamp is a set of interactive tutorials that walk you through various different aspects of Angular and Ionic. Courses are divided into four sections:\n\n\nThe Basics: An introductory course covering basic Ionic components.\nAngular: A look at Angular itself. \nUser Interface: A look at Ionic components focused on UI and navigation.\nNetworking: How to call remote services.\n\nEach course contains multiple sections, and in each section, you are given a set of steps to walk through. You have a fully featured text editor with syntax checking and a preview in the right hand pane.\n \nAs you go through the steps you can cut and paste the code but I recommend actually typing it in. I feel like I learn a lot more when I'm actually typing each and every line of code.\nThe site itself is really well done, and covers some good material. In particular, the Side Menu stuff was interesting as that is a part of Ionic I haven't used yet in an application. On the negative side, I really don't think the Angular section is going to work well for beginners. I think it needs to be greatly expanded and simplified if the expectation is that it will be appropriate for people who don't know Angular yet. If that can't be done, I'd probably remove it, but keep the links to Angular resources as they already have in the Resources page.\nAs a reminder, don't forget about the Ionic Playground at http://play.ionic.io. It provides a free form web-based tool to test Ionic. It works really well with Appcamp.\n\nI like where this is going, and hopefully it can be fleshed out even more in the future.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Some cool things in Lucee",
		"date":"Thu Oct 01 2015 10:39:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443695979,
		"url":"https://www.raymondcamden.com/2015/10/01/some-cool-things-in-lucee",
		"content":"I really haven't spoken much about Lucee since the initial announcement a few months back. It seems like a lot of &quot;things&quot; are going on, and frankly, I figured I'd just see where things settle down later this year. It just so happened that I ended up on the cfapplication doc for Lucee and I was pretty surprised by what I saw. Here are some of the cool things Lucee is doing with Applications versus ColdFusion. This probably isn't everything, but here is what I found and what I thought was cool or interesting.\n\nBy the way, just in case it isn't obvious - this list comes from the documentation for the cfapplication tag and is presented as arguments for the tag. Each of these works as &quot;this&quot; scope values in App.cfc too. That is implied, but it may not be obvious from the docs themselves.\naction\nSo this falls into the &quot;interesting but not sure I'd use it&quot; bucket. Lucee provides an action attribute for applications that allow you to update settings for an existing application. Personally, changing application settings on the fly feels like a bad idea, but it feels like something that should exist and it is cool that it is supported. I'm sure folks will tell me plenty of reasons why this makes sense in the wild, and frankly, I don't really care, it jut makes me happy that it is supported. (Ok, I do care, and would love to hear some practical uses for this.)\nsessionStorage\nThis one is cool as heck. You can use a database for session storage versus just plain RAM. You can setup the DSN in the admin enable this feature by checking the &quot;Storage&quot; checkbox:\n\nWhile I was writing this, I was curious how this would be done in App.cfc code instead. I just now discovered that the Lucee admin will actually tell you how to define your DSN in code. THAT IS EPIC.\n\nOne cool thing this allows for is ad hoc queries against the database for things like checking the number of active sessions or gathering up session data in general. That's doable now with the Server Monitoring API in ColdFusion, but that's Enterprise only and requires admin-level access. This would require a database query.\nbufferOutput\nI had to ask for help on this one - thanks go to Harry Klein and Gert Franz on the Lucee google group. I'm going to quote Gert here:\n\nIf there is an error or abort inside the function or the silent tag and the setting bufferoutput is set to true any generated output will bee visible. If set to false it will not generate any output at all.\n\nInteresting. Apparently this helps in memory consumption. I don't see how - but I trust Gert and Harry.\nrequestTimeout\nYou know how you can set a request timeout value in the cfsetting tag? From what I see, this is the same thing, but for the application. Nice.\ncomponentPaths\nIt looks as if Lucee supports mappings for cfinclude, custom tag paths for custom tags, and component paths for CFCs. This seems like a nice separation of concerns. I dig it.\ntypeChecking\nNice - you know how you can disable type checking in the ColdFusion Administrator? It can be a good performance boost. I've seen it significantly help out Model-Glue applications. Well this is the same thing - but at the Application level.\ncompression\nThis allows you to enable GZip compression for the application. I kinda feel like this should be done on the web server and not the app server, but meh, it works well and is incredibly easy to enable. Here is a page with the setting turned off:\n\nI then added this.compression=true; to my App.cfc:\n\nThat's a pretty big change there.\nsuppressRemoteComponentContent\nSo yeah, this is a weird one. Imagine bad code like this:\n\nAs you can see, I'm both outputting stuff and returning a value. If you set the suppress setting to false, you will actually see &quot;MOO&quot; in the remote output. I see no reason why you would ever desire that.\nlocalmode\nThis one was pretty confusing. @nicholasc on Slack helped me understand it. Given this line in a method x=1;, by default this will write to the variables scope unless local.x was already set. If you use this.localmode=&quot;modern&quot; in your App.cfc, it will set to the local scope instead. This seems like a great idea.\ntag\nMy favorite change. You can specify default attribute/value pairs for tags across your application. Here is an example I took from the docs:\n\nI freaking love this. I kinda remember arguing against this in ColdFusion about 5 or so versions ago. At the time I was concerned about some setting somewhere modifying code and developers getting confused, but having it in app.cfc makes it easy to reference and notice.\nlocale/timezone/webcharset/resourcecharset\nThese all apply different language/locale type settings to your application.\nscopeCascading\nOk, I lied - this is my favorite feature. If you output a variable without scope, this defines the &quot;look&quot; up process. You have three values. &quot;standard&quot; is your standard lookup table and will hit variables, CFI, URL, Form, and Cookie. &quot;small",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Proof of Concept: Validating an HTML Snippet in a Form",
		"date":"Thu Oct 01 2015 03:18:07 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443669487,
		"url":"https://www.raymondcamden.com/2015/10/01/proof-of-concept-validating-an-html-snippet-in-a-form",
		"content":"On my blog, I write my entries in pure HTML. WordPress supports a Visual editor too, but I like to write my HTML tags by hand. That's how I've done it for the past twenty years and that's how God intended me to write HTML. (Joking, honestly.) What's nice is that I can leave out some tags, like paragraph markers, and WordPress will handle that for me. I typically just worry about italics, bold, and links.\nDespite having worked with web pages since dinosaurs used FrontPage, I still screw up from time to time. My typical mistake is either not closing a tag: &lt;i&gt;Foo&lt;i&gt; or closing the wrong tag: &lt;i&gt;Foo&lt;/b&gt;\nI've often wondered - is there some way to test for this type of mistake on the client side?\n\nObviously you could use regex to check for the cases I described above, but regex can get messy. That and as much as I appreciate the power of regex I'd rather avoid it. Also, it wouldn't handle a case like this: &lt;stron&gt;Bold!&lt;/stron&gt;.\nSomeone on Twitter (I forget who and I'm being too lazy to scroll through my Notifications panel) suggested DOMParser, but when I tried that I could find no way to detect invalid/broken HTML when passed to the API. It is entirely possible I was doing it wrong, but nothing really stood out.\nI then had an idea - what if I tried the W3C Validator service? I use it for my Brackets extension (which I'll be rewriting to Visual Studio Code the second they release extension support) and it works well enough there, maybe I could use it in code? Here is what I came up with as a proof of concept.\n\nThe form is pretty simple - just a textarea. Obviously a real form would have more values. Since the assumption here is that I'm validating a &quot;snippet&quot; of HTML, I create a 'full' doc by wrapping the form value with a doctype, html, head, and body tags. I then simply pass this to the W3C validation API. Finally, I check the results. I don't have any nice UI here, just a console dump, but let's look at some tests.\n\nIn the first test, I didn't close the italics tag correctly, and the service caught it. It reports it twice, which may be confusing, but I'd be fine with it.\n\nIn my second test, I just left it off completely, and it was also caught.\n\nFor my final test, I used proper wrapping with an unknown tag, and it also worked. Obviously this could be an issue if I'm embedding a Polymer example. (Actually, no, since it would have been escaped.)\nSo, what do you think? You can try a live version of this here: https://static.raymondcamden.com/demos/2015/oct/1/testjqm.html. Don't forget to open your browser developer tools of course.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionic 1.6.5 and updates to Services",
		"date":"Wed Sep 30 2015 09:34:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443605686,
		"url":"https://www.raymondcamden.com/2015/09/30/ionic-1-6-5-and-updates-to-services",
		"content":"As a quick aside, I worked with Eric Bobbitt of the Ionic team while writing this. I asked him a lot of questions very quickly and he never complained. Big kudos to him for being patient with me!\nA while ago I gave a presentation on Ionic Services, and as I mentioned earlier this week, I'll be speaking on the topic again next month. Today the Ionic folks released an update to the CLI that helps improve Ionic Service usage as well. This ties in with other improvements they have made since I last dug into them. The services are still in Alpha, which is bad news on one hand (I can see being concerned about using them in a production app), but the good news is that they have gotten a heck of a lot easier to use since I last played with them. In today's post I'm going to talk about those setup changes as well as show some updates they've done to make working with users easier.\n\nFirst and foremost, you want to update your local Ionic CLI. Type ionic -v at the command line and you'll probably see this:\n\nGo ahead and run the update and ensure you are at version 1.6.5. At the time I wrote this blog entry the change log had not yet been updated, but hopefully that will be corrected soon.\nNext, you'll want to ensure you have an account at Ionic.io. I'm not entirely sure if the CLI allows you to register, but since you're going to be using the site anyway, you should go ahead and register there.\n\nAt this point, I'm going to borrow directly from Ionic's excellent docs for their services. You can find them here: http://docs.ionic.io/. But I'm copying from them because I'm so darn excited about how simple things have gotten. Assuming you've made a new project, you begin by adding the core services library like so:\nionic add ionic-platform-web-client\nThis will automatically modify your index.html and app.js. Yes, if you rename your files this won't work correctly, but a) don't do that and b) you can still manually modify the files if you want.\nNext, you need to upload your application to ionic.io:\nionic io init\nThe first time you do this, the CLI will prompt you to login. After that it will remember your authentication information.\n\nAnd that's it. Seriously. Previously this involved you doing all the edits by hand. To be clear, it wasn't a lot of work - we're talking 5 minutes here - but I know I screwed it up more than once and I freaking love that the CLI is handling this for me now.\nJust to repeat - in terms of &quot;code setup&quot;, you're done in terms of loading the core support on the JavaScript side. In order to use individual services, two of them (Push/Deploy), require a plugin to be added. All three will require you to inject the service in your controller/service calls when you want to make use of them.\nAll in all, services are a heck of a lot easier now and that's pretty freaking cool. Now let's turn our attention to the User side. The docs on Users are a bit bare now. Personally, it feels like Ionic Users aren't necessarily a real &quot;service&quot; per se but more of a &quot;support system&quot; for the other services. That's my gut feeling now and obviously I reserve the right to change my mind later.\nIonic Users have a couple of different features:\n\nYou can easily define and create a new user for your application. If you don't want to manage IDs, you can use Ionic.User.anonymousId(); to have an Id assigned.\nYou can \"save\" your user so that Ionic.io knows about it as well. (More on that in a minute.)\nYou can set and get basic values on users. Ionic users will have a few internal properties like id, image, and is_deleted. These are not documented currently but will be. Also, the property API (next bullet) does not work with these values so it isn't something you have to worry about running into. (Although you will want to read the next bullet carefully.)\nTo get a value, you just do user.get(\"key\"). You can also provide a second argument for a default value. Setting works just as easily: user.set(\"key\", value). You can provide complex data like arrays and it will persist just fine. As I said, the internal values are not fetched this way. You may want to get the ID value. To do that, you simply get it on the core user object: user.id\nNow here is where stuff gets cool. You can also register your own data types to add complex support for user data. If you register an object that has a toJSON and fromJSON method, you can then create a user property of that type and store more complex data with additional rules. Ionic is shipping one by default, UniqueArray (and yes, I've told them to be sure to document all the defaults), that provides support for storing arrays of unique values.\nTo save a user, and you must save users if you want the data to exist on Ionic.io, you simply do user.save(). This is an async call but you don't have to handle the results unless you care too. Note that users are saved to LocalStorage. If you are offline, the save happens, but does not persist to the server (obviously). In that scenar",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "New ColdFusion docs (and some tips for handling it)",
		"date":"Tue Sep 29 2015 23:55:33 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443570933,
		"url":"https://www.raymondcamden.com/2015/09/30/new-coldfusion-docs-and-some-tips-for-handling-it",
		"content":"Yesterday, the Adobe CF team announced an update to the ColdFusion docs: ColdFusion documentation update. The summary is that the wiki we have used for a while now is gone and the docs are back in a non-editable, and non-commentable (not really a word, sorry) form. Apparently this isn't a permanent change and in the future sometime it will move to something that allows for more collaboration. You may also run into an SSL issue while browsing the docs (yes, seriously), and if you want to know how to get around that, Adobe as posted a blog entry on that as well (although personally I think they could have fixed it in the time it took to write the blog entry) - SSL certification issue with wikidocs link.\nYou can find thew new home here: https://helpx.adobe.com/support.html#/product/coldfusion.\n\nAs an FYI, you can still download the docs here and find the older versions here.\nYou can also find much quicker versions of the tag/function docs at Pete's site: http://cfdocs.org/. But note that this is just tags and functions - nothing about Application.cfc or building ColdFusion apps in general. But the search will be much better there. As an example, I searched for cfabort on the Adobe docs, and the CF11 reference for the tag/function didn't show up in the first page of results.\nYou can also use the CF docs via Dash, a commercial, OSX-only doc viewer that I really recommend. I hope these changes don't impact the availability of docs in the app.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Speaking on Ionic Services at Ionic-SF",
		"date":"Tue Sep 29 2015 04:03:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443499430,
		"url":"https://www.raymondcamden.com/2015/09/29/speaking-on-ionic-services-at-ionic-sf",
		"content":"The title pretty much says it all. I'll be speaking at the next Ionic-SF meetup on October 14th. I'll be talking about Ionic Services, which luckily hasn't changed at all since the last time I spoke on the topic. (Sigh...) You can RSVP for the meetup here: Introduction to Ionic Services. Hope to see you there!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Switching platforms with Ionic Serve",
		"date":"Sun Sep 27 2015 23:36:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443397010,
		"url":"https://www.raymondcamden.com/2015/09/28/switching-platforms-with-ionic-serve",
		"content":"Yesterday a question came up in an Ionic session I was attending involving ionic serve and platforms. When you use ionic serve, it will fire up the project in your Chrome browser. You may not be aware of it, but by default it uses the iOS CSS styles. This becomes more obvious when you use the lab option. Here is the tabs template by itself:\n\n\nAnd here it is with the lab option - you can now see that the iOS one was used by default.\n\nOk, so the question was - how do you ionic serve to just Android? Turns out, you can use the platform attribute with ionic serve. This was added ([Request] ionic serve --platform=&quot;android&quot;) back in May. Simply do ionic serve --platform android:\n\nIn case you're curious, this works without needing to actually add the Android platform. (And on the flip side, if you don't have iOS, the default behavior for ionic serve is to still show the iOS CSS.)\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Assets from my NCDevCon Preso",
		"date":"Sun Sep 27 2015 08:34:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443342896,
		"url":"https://www.raymondcamden.com/2015/09/27/assets-from-my-ncdevcon-preso",
		"content":"Today's presentation was a bit rough without power, but I want to thank everyone who attended and worked with me to - well - make it work. I really appreciate it. The slide deck can be found here: https://github.com/cfjedimaster/Static-Sites. If you want to watch a recorded version of this preso (slightly older), you can view the one I did for O'Reilly here: Static site generators: Why use them and how they work\nPhoto Source\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "New book on Static Sites Generators",
		"date":"Fri Sep 25 2015 03:40:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1443152458,
		"url":"https://www.raymondcamden.com/2015/09/25/new-book-on-static-sites-generators",
		"content":" As you know, my current passion tech-wise is static site generators. I'm speaking on it Sunday at NCDevCon. My buddy Brian Rinaldi, another SSG fan, released a free e-book today on SSGs that you can download from O'Reilly right now:\nStatic Site Generators: Modern Tools for Static Website Development\nI did a tech review on this book and it is a great introduction to SSGs. You can read it in about an hour, and it is well worth your time.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "\"Beginning JavaScript\"",
		"date":"Wed Sep 23 2015 02:23:57 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442975037,
		"url":"https://www.raymondcamden.com/2015/09/23/beginning-javascript",
		"content":"As I've mentioned before, I've got a few videos available at O'Reilly (JavaScript Templating and Client-Side Data Storage). You can now get my &quot;JavaScript Templating&quot; video as part of a bigger series called &quot;Beginning JavaScript&quot;:\n\n\nThis video series consists of three parts. First is Semmy Purewal giving an introduction to JavaScript. Next up is my video. Then it ends with Douglas Crockford giving a &quot;Master Class&quot; in JavaScript.\nFrankly, I'm the smaller/lesser cool part of this series, which is why I say I'm honored to be a part of it. But it looks like a great package in general for folks looking to get a great set of content for learning JavaScript.\nAs always, I'm happy to take any feedback on my videos, positive or negative. Just drop me a line.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Another Cordova Mashup - Pixelatize",
		"date":"Tue Sep 22 2015 07:35:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442907356,
		"url":"https://www.raymondcamden.com/2015/09/22/another-cordova-mashup-pixelatize",
		"content":"One of the things I love about Cordova is how you can take existing client-side libraries and mash them up with the device features Cordova provides. The example I typically give of this is a demo I built a few years ago that mashes up the camera and a library called Color Thief. A few days ago I saw Jenn Schiffer (who is a pretty cool individual and someone you should follow on Twitter) release a library called Pixelatize. As you can probably guess by the name (which, by the way, is freaking hard to type right multiple times in a row), it takes an image and pixelates it. She has an online demo here so can you test it in your browser. I thought it would be fun to connect this to the device camera with Cordova. Here is how I did it.\n\nFirst, I created a new Ionic blank template. For my UI, I decided I'd include a button to take the picture, an image for the original picture, a slider that lets you determine how pixelated the result should be, and the result image. To be honest, this is a bit much and could be layed out better. I think I could remove the original image since you probably don't care about that, but whatever, this was just a fun demo. Here's the code for the index.html page.\n\nThere's nothing fancy here. Note though that I'm using a canvas for the second image. This comes directly from Jenn's demo. Now let's look at the application logic.\n\nThere isn't much here. I basically just shell out to the Camera API when you hit the button. Once the image is loaded, I then call Jenn's library. During testing I just used the photo gallery on the iOS Simulator but switched to the real camera when I was done. If you were building a &quot;real&quot; app for this you could easily use two buttons to let the user decide between their existing photos and a brand new one.\nThe final bit of code is Jenn's library, which I modified a bit to fit the JavaScript module pattern. I feel smart when I do crap like that, but any bugs here are from me, not her code.\n\nIt is surprisingly small and simple for what it does - but there's no way in hell I would have figured this out. So how about some samples?\nFirst, a scary one:\n\nThen a cooler one:\n\nYou can find the complete code for this here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/pixelatize. Enjoy!\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Finally! A good (darn good) Star Wars book",
		"date":"Sun Sep 20 2015 08:19:37 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442737177,
		"url":"https://www.raymondcamden.com/2015/09/20/finally-a-good-darn-good-star-wars-book",
		"content":"I make no secret of my Star Wars obsession - and yes - I'm counting the days till the next movie. (88 days if you're curious.) As the new trilogy approaches, Disney is firing up the marketing machine quite heavily. I'm fine with that. Sure - some things are a bit silly. (Star Wars themed coffee - um... ok.) But there's some pretty cool stuff too - including twenty new Star Wars books. I've read three of these new books and while I wasn't impressed with the first two, the last one I read was pretty freaking incredible. Here is a short review of the ones I've read so far.\n\n\n I'm a fan of the \"Rebels\" TV show, but this book was only so-so. I like seeing the early beginnings of the Rebellion, and the villain was pretty compelling, but overall though it just didn't do much for me. It was an OK book but forgettable. This would be a good book to borrow from your local library but I'd probably skip picking it up. \n\n\n So while \"A New Dawn\" was so-so and forgettable, this one was a book I wish I could forget. It has a great premise: Luke right after \"A New Hope\" - a hero - but still not a Jedi - is sent on a mission to extract a cryptographer working for the Empire. But then you end up with scenes where Luke uses the force to move noodles. Yes, seriously, noodles. I mean, I get that at this point he has the bare minimum of exposure to the Force but the multiple (yes, multiple) scenes of him trying to move bits of food with the Force were painful to read. The ending was pretty bad too. About the only thing I really found interesting about this book was that after \"A New Hope\", Luke was a Lieutenant. Yes, I found that interesting.\n\n\n And finally - a really, really good book in this new set. \"Aftermath\" takes place after the events of RotJ and we finally get a look at the universe leading up to the new movie. (To be clear, we had this years ago with the epic Thrawn series, but that's the past. ;) As to be expected, blowing up the second Death Star and killing Palpatine didn't just wipe out the rest of the Empire. In \"Aftermath\", the Empire is hurt, hurt bad, but not destroyed. The New Republic (the formal government power headed by the Rebellion) is in the process of setting up and trying to bring the galaxy together in peace. Those details were fascinating but alone wouldn't be enough to make a good book. Luckily, the book has some great characters. While older, familiar names are there, all of the main characters are new. (Ok, Wedge isn't new, but he felt more like a side character than a main character.) Sinjir, a former Imperial loyalty officer, is easily one of my favorite characters in the Star Wars universe. Before I began this book I heard a lot of people complaining about this book. The writing style is unique - but frankly - didn't bother me. (And by \"unique\" I mean a bit different, not crazy weird like \"The Road\" for example.) Also, apparently, some folks had a problem with the fact that a major character is gay, and apparently the female characters outnumber the male. Yeah, whatever. I mean, obviously the only reason to have a strong female character is to make some kind of liberal point, right? Ugh. I'm just glad I ignored the negative reviews and read it with an open mind because I thoroughly enjoyed the hell out of this book and strongly recommend it.\nFinally, while they aren't books, I'm really enjoying all of the new Marvel comics right now - even the Lando one. If you read comics I'd definitely suggest picking them up.\n",
		"tags":[
	        
		],
		"categories":[
            
                "books"
            
		]

	},

	{
		"title": "Integrating the Calendar into your Ionic App",
		"date":"Fri Sep 18 2015 05:50:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442555448,
		"url":"https://www.raymondcamden.com/2015/09/18/integrating-the-calendar-into-your-ionic-app",
		"content":"For today's demo, I decided to try something I've been meaning to make time for - integrating with the calendar on the mobile device. Luckily there is a great plugin for this - Calendar-PhoneGap-Plugin. This plugin provides all types of hooks into the local calendar including the ability to search and add events. With that plugin in place, I whipped up a quick demo.\n\nI began by building an application that simply returned events from a list and displayed them as is. Here is the view:\n\nLet's look at the code behind this. First, the HTML. Since this application is so simple, I'm not using routes and templates.\n\nThis should all be fairly boiler plate. I simply loop over the events and create a card UI for each. Now let's look into the controller code.\n\nYep, just call the service and render the events. Trivial. Now let's look at the service.\n\nOk, so this is a bit more complex. I've got a set of fake data that creates four events in the future. The service then returns those fake events. Ok, so let's kick it up a notch. Given that our Calendar plugin can check for events, I'm going to update my code to display if an event has been added to the calendar or not. Here is an example.\n\nIn this screen shot, you can see buttons to add the event to your calendar. Notice that the third event though is recognized as being in the calendar. To make this work, I updated the service call for events to handle checking the calendar. It was a bit complex since each call is asynch, but $q makes it easy to handle that.\n\nI set a status value on events to represent whether or not the event exists. Back on the display side, I handle this like so:\n\nFairly simple, right? Now let's look at the add code. I'll skip the controller code as all it does is call the service and update the scope.\n\nAnd just to prove it works - here is the event I just added:\n\nI've put the full source code for this demo up on GitHub: https://github.com/cfjedimaster/Cordova-Examples/tree/master/calendarionic. I want to give big thanks to Eddy Verbruggen for helping me use his plugin, and for fixing a bug I encountered!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Creating a custom display for Google's Analytics Embed Library",
		"date":"Thu Sep 17 2015 03:44:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442461451,
		"url":"https://www.raymondcamden.com/2015/09/17/creating-a-custom-display-for-googles-analytics-embed-library",
		"content":"I've blogged a few times now about Google's Analytics Embed library. I really dig it as it makes working with their Analytics API pretty darn simple. It really opens up the ability to create interesting mashups and custom dashboards on your sites with just plain JavaScript. Recently, a reader asked me a question about how he could have more control over the data displayed by the library.\n\nHis question involved formatting of the &quot;TABLE&quot; display supported by the library. This is a format I had not used myself yet so I built up a quick demo (and as the reader supplied some code it made it a bit easier). Let's look at the code and then I'll show the result.\n\nI'm assuming you are somewhat familiar with my older posts, but if not, the basic idea here is that the Embed library will handle authentication and it provides rendering capabilities. You can see the DataChart call there handling both a query (what data to fetch) and how to render it (a table). Here is the result:\n\nNice, but what if you wanted more control over the rendering? Specifically, the user wanted to change the seconds column into a report that showed the minutes instead. Unfortunately, you don't get the ability to modify the format of the table. Fortunately, Google makes it easy to get the data manually and do whatever the heck you want. Let's look at an updated version of the script.\n\nThe biggest change here is that now I ask for the data so that I can do whatever I want with it. You can see this in the gapi.client.analytics.data.ga.get call. Once I have the data, I'm free to format it as I want. I decided to get a bit fancy.\nFirst, I made use of Intl to format the dates and numbers nicely. This is a cool web standard (see my article here - Working with Intl) that is incredibly easy to use. It doesn't work on iOS yet, but luckily you can do 'snap to' with CSS in iOS which is far more important than internationalization.\nTo handle the minute display, I made use of the awesome MomentJS library. It has a duration API that makes showing the parts of a span of time very easy. I'm not terribly happy with how I showed it - but obviously you could modify this to your needs. Here is the result.\n\nThere ya go. Hopefully this helps. I'd share an online version but it is currently tied to my web property so it wouldn't work for you. To be clear, you can easily change the property, or add support for letting the user select their property.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionic Example: ion-slide-box",
		"date":"Wed Sep 16 2015 04:58:20 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442379500,
		"url":"https://www.raymondcamden.com/2015/09/16/ionic-example-ion-slide-box",
		"content":"One of my favorite parts of the Ionic framework is the ion-slide-box. It is a simple directive that allows you to create a pretty handy little widget for your mobile application. (Widget isn't really the best word.) The ion-slide-box directive lets you embed a set of images (or random HTML) and then display one item at a time. Their docs have a great little animated gif that I'm going to steal to demonstrate exactly what this looks like:\n\n\nWhat makes this feature so cool is how darn easy it is to use. For example, here is the sample code to create a slide box:\n\nThis is incredibly simple. I thought I'd build a simple demo of this feature that tied the slides to a dynamic result. I thought I'd use the Bing Image Search API since it worked well for me in the past (Adding voice-based search to a PhoneGap app). I set up a simple view that included a form field and button top.\n\nWhen you enter a term, it will then display the results in a slidebox:\n\nNotice the little gray balls at the bottom - they provide a way for you to know where you are in the slide list (and you can turn that feature off if you want). Now let's take a look at the code. First, I'll show the HTML.\n\nMost of this is boilerplate Ionic code, but you can seem my ion-slide-box is using a dynamic ion-slide list. That's really all there is to it. I could include more in the slide, like the image title, source, etc., but I wanted it simple. Now let's look at the code.\n\nThe Bing API changed a bit since my last demo, but for the most part is still relatively easy to use. Their documentation wasn't always very direct. Outside of that I had no real issues. And yes - that's my appid included in the source code. This is a perfect example of where I could use a MobileFirst HTTP Adapter. I described this process here:\nWorking with MP3s, ID3, and PhoneGap/Cordova – Adding IBM MobileFirst. Using the adapter would also let me modify how Bing returns results. I could use lowercase and I could return just the URLs, making my mobile perform better since less network data would be going back and forth.\nOutside of the service it is a simple matter of updating the scope - but I ran into an interesting issue. I noticed that if I was on slide X and searched for something else...\n\nthen the &quot;current slide&quot; remained at where you were before. That's where $ionicSlideBoxDelegate.slide(0) comes into play. But doing so introduced a weird bug involving AngularJS and digests. I hate those things. Mike Hartington from the Ionic team helped me out on Slack and recommended the timeout/$scope.$apply() solution you see above. That made it work perfectly.\nAll in all, a simple demo, but I hope this is useful for folks. You can find the complete source code for this demo here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/ionicslidebox1\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Want to learn about client-side storage?",
		"date":"Tue Sep 15 2015 02:52:14 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1442285534,
		"url":"https://www.raymondcamden.com/2015/09/15/want-to-learn-about-client-side-storage",
		"content":" I'm happy to announce that my latest video series for O'Reilly is now available: Client-Side Data Storage for Web Developers. This series covers everything from cookies to IndexedDB and all the fun stuff in between. The link there is for Infinite Skills, with ORA bought a while back. If you find my video via oreilly.com itself it still shows up as not available so there may be a bit of a lag there between web site updates.\nI think this is a great video series, and I'm also working on an e-book version of it as well that will be released pretty soon. As always though, if you have feedback, I want to hear it.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Working with Directories and HarpJS",
		"date":"Fri Sep 11 2015 14:22:23 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441981343,
		"url":"https://www.raymondcamden.com/2015/09/11/working-with-directories-and-harpjs",
		"content":"First off - a quick apology. The blogging here has been extremely slow over the past few weeks. Two weeks ago I was in Australia and New Zealand. This week I've been in Malaysia, Singapore, and Manila. I'm writing this post in Hong Kong as I wait to board my 14 hour flight back to Texas. (Yes, I'm looking forward to Texas, imagine that.) I don't normally travel this much so I've been pretty much exhausted for the past three weeks. It has been an incredible experience, and I got to really work on some presentations too, but I'm definitely ready to go home and stay home for a while.\nOk, so with that out of the way, let's get to the issue. A HarpJS user asked (List files in directory) about how to work with directories in their static site. Essentially, given that you have subdirectories, how can you access them and do - well - something with them?\nWorking with files is easy. You get access to a variable, _contents, that includes all of the files in a directory. (You can find the documentation for that here: http://harpjs.com/docs/development/contents.) But what about subdirectories?\nIn order to create a simple testing environment, I set up the following folder structure in a Harp project:\n\nI want to point out a few things. Note that there is a root level _harp.json file for data. Then notice the articles subdirectory has one as well. Beneath that are three &quot;category&quot; directories each with some random files in it. Ok, so given that, let's look at the data. Your templates have access to a variable, public, that represents pretty much all of the data in the current Harp application. There is a Harp recipe (How to print out all available data for debugging) that details how to quickly print out variables to the page and console:\n\nHere is how it looks for my sample structure above:\n\nSo let's pick this apart. At the top level is a _contents structure. This contains the files, and only the files, for the root level folder. Then you have _data. This represents - well - data. And then you have one key per subdirectory. You can see the same pattern represented under articles as well.\nTherefore, to get access to subdirectories, you work with the keys of the object minus the _contents and _data values. To get the subdirectories of some subdirectory /foo, you would use public.foo.\nIn theory, you could write up a simple utility function that would return directories for you, or perhaps even files and subdirectories given a particular root node. Anyway, I hope this is useful!\n",
		"tags":[
	        
            "harpjs"
            
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Node 4 and libsass issues?",
		"date":"Wed Sep 09 2015 13:32:03 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441805523,
		"url":"https://www.raymondcamden.com/2015/09/09/node-4-and-libsass-issues",
		"content":"Yesterday I updated to Node.js 4 and today I noticed problems with two apps, Harp and Ionic. Harp was totally broken but Ionic only had issues with the Gulp script. I tried various things, like uninstalling and reinstalling node-sass and Harp itself, but nothing worked. Finally, I found this bug report:\n\nError: libsass bindings not found when using iojs\nI can confirm that by following the steps here I was able to get Harp working, and I reached out to the team at Harp and they said a proper fix will be out very soon. Update: Harp fixed it.\nAs for Ionic, it wasn't an issue with Ionic but with gulp-sass. Luckily there is a document on how to fix that too: Update to the latest Gulp Sass. I can confirm this fixed my issues with Ionic's gulp script.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working around Ionic's cached views",
		"date":"Mon Sep 07 2015 19:58:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441655899,
		"url":"https://www.raymondcamden.com/2015/09/08/working-around-ionics-cached-views",
		"content":"Just a quick tip here as it came up in the presentation I did today - how do you handle running code when a particular view runs in Ionic? Let me begin with an example so you get what I'm talking about. Given a new Ionic application using the default tabs application, I'm going to modify the Account controller to add a random number to the scope:\n\n\nIn the view for this view I added a quick display for the number: Random number: {{number}}\n\nYeah, not the most exciting demo, but you get the idea. What you'll notice right away though is that as you click back and forth between different tabs, the value doesn't change. And if you open up your remote dev tools, you'll see the console message run only once.\nThis is due to the default caching system built into the ion-view directive. Luckily it is rather easy to fix with one of the life cycle events. The docs say you can listen for these events but don't demonstrate how. Here is an example:\n\nAnd that's it. For me the tricky part was $scope.$on, something I've not used in Angular before. You can read more about the events at the docs.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Is it time to stop using ColdFusion for remote APIs?",
		"date":"Fri Sep 04 2015 10:55:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441364113,
		"url":"https://www.raymondcamden.com/2015/09/04/is-it-time-to-stop-using-coldfusion-for-remote-apis",
		"content":"So, let me begin by saying I'm a bit frustrated, and so this blog post may be one I regret later on, but, I try to be as honest as possible here and right now, I'm kinda ticked off about something and I want to get it off my chest. For a long time now I've had an incredible amount of respect for how ColdFusion makes it easy to access data from client-side code (or remote servers). As much as I'm digging Node.js these days, the fact that I can write up a CFC and get an API that can be used by JavaScript is pretty darn powerful. This feature has come a long way. When it first came out, the only option for output was WDDX. You can now output anything, from WDDX, to SOAP, to XML, plain strings, and of course, JSON.\n\nIn fact, I like this feature so much that I proposed a topic for it at this years ColdFusion Summit. My session will be an overview of how to generate output for remote consumption and cover everything from the beginning (ColdFusion MX and earlier) to the upcoming release (12).\nHowever...\nSince the beginning of native JSON support in ColdFusion (ColdFusion 8), there have been consistent issues with serialization. This all drives from the fact that ColdFusion variables are typeless and therefore the server has to (or does it?) make guesses as to how to convert values into JSON. Over the past three releases, I've seen multiple bugs, and multiple fixes, and while I had no real proof (more on that in a minute), my gut told me that things had simmered down a bit and that ColdFusion 11 had this problem licked.\nOr so I thought.\nTurns out bug 3337394, created nearly three years ago, oh one marked closed and fixed, is still very much an issue for serialization. If you have data in a struct, and it has the string value &quot;No&quot;, ColdFusion will convert it to false. Here is a sample:\n\nWhich gives you:\n\n{\"name\":false}\n\n[{\"ID\":1,\"NAME\":\"ray\"},{\"ID\":2,\"NAME\":\"No\"}]\n\nAs you can see, the struct is broken, the query works fine. (As reported in the bug itself.)\nAnother issue involves strings that contain numbers. Consider these two examples:\n\nThis returns:\n\n{\"productkey\":89900909130939081290830983019819023}\n\n{\"COLUMNS\":[\"ID\",\"NAME\"],\"DATA\":[[1,\"ray\"],[2,\"89900909130939081290830983019819023\"]]}\n\nAs you can see, productkey is now a number, and one that will be converted to 8.990090913093909e+34 in JavaScript.\nSo.... yeah. In all cases, there are workarounds. But let me ask you this. Would you use a database that randomly changed values on you?\n\nIt seems ridiculous that this is still a problem now. It may be incredibly difficult. Heck, I won't pretend to be able to solve it. But here are some suggestions:\n\nObviously the ColdFusion team has a unit test for this. They must. So when the unit test is updated for the items found in the bug, share it with us. I know there are multiple people in the community who would give their time to help flesh out the unit test, or heck, just have it locally and test when new versions come up. So that's my first request - let us see the tests for JSON serialization. Speaking of unit tests, the last time this bug was fixed, why not share the test immediately? I mean, we won't see it working, but I promise you people who have asked why a bug involving data serialization had a test that only tested queries (and CFCs I believe). I don't want to harp on the engineer who fixed this - we all make mistakes - but why not post the test when you write it and share it with us? \nOf course, one could write essays on the lack of communication that goes on sometimes on the bug tracker, which is a real shame. You know, I get that some people don't like to engage or are basically shy. But there's no excuse for it anymore. I'm very shy too. When someone speaks to me, I have to work hard to respond to their questions with questions of my own. I recognize my lack of engagement in basic human communication and force myself to hack around my lack of social skills. Make a list and put it on a PostIt next to your monitor. &quot;When I fix a bug, respond with details about how I fixed it, how I tested it, and what I may be concerned about. The reporter may have good input!&quot;\nGiven that the \"glue\" aspect of ColdFusion is one of its greatest selling points, make JSON serialization a priority for ColdFusion 12. Obviously the issue isn't licked yet, which, ok, fine, it's been years but fine. The ColdFusion 12 road map says this: \"Ability to manage, monitor, regulate, secure REST and SOAP web services – API management\". Before I manage my APIs, I want 100% certainty that they actually process data correctly.\nAnd hey - how about the nuclear option? If the very nature of ColdFusion variables means the problem is not 100% solvable, then remove the feature. Seriously.  Ok, maybe that's overkill, but there are things in ColdFusion now that haven't been updated in a while, look abandoned, and maybe should be dropped. So if they can't fix it, remove it.\n\nSo I started this blog entry with a somewhat bold title - is i",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "New MobileFirst Slack!",
		"date":"Fri Sep 04 2015 04:53:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441342430,
		"url":"https://www.raymondcamden.com/2015/09/04/new-mobilefirst-slack",
		"content":"If you are interested in learning more about MobileFirst or are already working with it, come join our new public Slack channel to discuss problems, questions, Star Wars trivia and cats with other developers. Simply sign up at the form here: Come chat with us!\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Speaking on Cordova, Ionic, and MobileFirst/Bluemix in Manilla",
		"date":"Thu Sep 03 2015 05:30:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441258209,
		"url":"https://www.raymondcamden.com/2015/09/03/speaking-on-cordova-ionic-and-mobilefirstbluemix-in-manilla",
		"content":"So I mentioned my other presentations in Asia next week, but here is the final one - an event in Manilla covering Apache Cordova, Ionic, and MobileFirst/Bluemix. Free for all - attend just to see how crazy tired I am by this point.\nBuild your Hybrid Mobile App with Ionic, MobileFirst and Bluemix Workshop\n",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Recording of my ORA Static Site Presentation",
		"date":"Wed Sep 02 2015 23:53:41 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441238021,
		"url":"https://www.raymondcamden.com/2015/09/03/recording-of-my-ora-static-site-presentation",
		"content":"Here is the recording of the presentation I gave yesterday - enjoy: http://w.on24.com/r.htm?e=1001065&amp;s=1&amp;k=DAD5EBAFFBD67B61E8DAF6719D546A2A. Note that you will need to register with O'Reilly if you have not done so already. You also have the opportunity of seeing this live if you attend NCDevCon later this month.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5"
            
		]

	},

	{
		"title": "Looking for mobile developers who want to test something cool...",
		"date":"Wed Sep 02 2015 04:55:28 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441169728,
		"url":"https://www.raymondcamden.com/2015/09/02/looking-for-mobile-developers-who-want-to-test-something-cool",
		"content":"One of the reasons I joined IBM was because I discovered they were doing some pretty cool things. I mean, I knew IBM did cool stuff, but as a developer, I wasn't really aware of what they offered and how they could help. I know I'm supposed to say crap like this, I work here, but I can honestly say I began my job here impressed with what we offer and I continue to be really freaking excited about what I'm seeing. We're working on something interesting and we need folks to help us bring it to life.\n\nWhat if you could build a mobile application that wasn't static? An app that would actually modify its behavior in an intelligent manner depending on the person who was actively using it? For example, imagine a shopping app that can recognize your location, not just the longitude and latitude, but the nature of where you are, and selectively target different products or services? As a concrete example, imagine being at a stadium and the application recognizes this and suggests sports-related items to you, but is intelligent enough to only do this if you've shown a prior interest in sports products. It is this combination of factors, not just one simple property, where true intelligence could do some pretty useful stuff.\n\nOn the flip side, the development experience is quite simple. A developer can initialize support for this service, but then let a non-technical person actually design these rules. They can decide what factors matter most (location, history, time of day, etc) and visually design the rules for how your app responds.\nI know this is a bit vague, but hey, we're talking about an experiment here and this is where you can help out. If this sounds at all interesting to you, you can read more, and contact the team, at http://adaptiveexperience.mybluemix.net/. In case you're curious, both native and hybrid solutions are being considered here so mobile developers of all stripes are welcome to reach out.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Ripple is Reborn (Again!)",
		"date":"Wed Sep 02 2015 02:54:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441162498,
		"url":"https://www.raymondcamden.com/2015/09/02/ripple-is-reborn-again",
		"content":"  Almost two years ago I blogged (Ripple is Reborn) about the relaunch of the Ripple emulator. Ripple lets you test Apache Cordova applications in the browser. It mocks many of the built in plugins (device, geolocation, accelerometer and more) and can be a good way of debugging your hybrid mobile applications. Unfortunately, about a year or so ago something on the Cordova side changed that had a negative impact on Ripple. Whenever I would use it, I'd get an infinite loop in the console that would effectively kill the browser tab.\n\nI pretty much gave up on Ripple, but still paid attention to the Apache project (Apache Ripple) to keep up to date with its progress. They recently had an update so I thought I'd check it out again. I'm happy to say it is working again! In general, it just plain works. I still see the issue where it defaults to Android and if you only have iOS you'll need to switch platforms to not get an error initially. I do not see the issue where I had to rerun the emulation CLI to refresh the code which is really freaking nice. Note that... oddly... ctrl+r did not refresh the view but hitting the reload icon in the browser did work.\nAs a reminder, if you have the old Chrome extension, kill it. Ripple does NOT use a browser extension anymore - it is entirely setup via the CLI. You can find out more, and how to install it, at the site: http://ripple.incubator.apache.org/.\np.s. There is a new Twitter account for the product: @ApacheRipple.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Calling remote services from Ionic Serve",
		"date":"Tue Sep 01 2015 08:27:47 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441096067,
		"url":"https://www.raymondcamden.com/2015/09/01/calling-remote-services-from-ionic-serve",
		"content":"A coworker pinged me today with an interesting question:\n\nI am sure you may have encountered this error when using IONIC SERVE locally. \nXMLHttpRequest cannot load http://something.mybluemix.net/rest/audit/list. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.\nEssentially I am calling the URL via the $http.get() method from a factory. Ionic appears to have multiple ways to call REST services (1. $http or 2. ngResource).\nSo my question is do you have a workaround for this problem or a preferred way of dealing with / calling REST services from within Ionic? Any sample code would be appreciated.\n\n\nThis is a great question as there are multiple ways you can handle this. Before we begin, let's do a refresher. What is this error and why do we get it? Browsers have a security model in place that prevents an Ajax request from one domain to another. This is what led to the creation of JSON/P, which is essentially a workaround for calling remote services.\nWhen working with Cordova, you don't have to worry about it. But when using Ionic Serve and running the application in the browser, you have to figure out a way around it. Even though using JSON/P is simple, I tend to try to avoid adding code to my Cordova/Ionic apps that are just for the purpose of making it run on the desktop browser. To be clear, I do it from time to time, but I try to avoid it. (And in fact, one of the answers below will involve just such a modification.) So given that, what are some options?\n\n\nEnable CORS\nModern browsers support CORS, which is a way for a remote API to say it is acceptable for your code to call it from another domain. If you have access to the API (so you're building both the mobile side and the server side), than it is trivial to enable CORS for your API. How you do that depends on the language of course. I described how to do it in ColdFusion and you can enable it with Node.js/Express pretty quickly.\n\n\nHack with an extension\nYou can find a Chrome extension (Super Long URL to it here) that will modify CORS headers on the fly in your application. Obviously this is Chrome only, but I like that it works with zero changes anywhere at all.\n\n\nIonic Serve Proxies\nDid you know Ionic Serve itself has a proxy feature? In their Testing in a Browser documentation page, they discuss how you can modify a setting in the ionic.project file to add a proxy. Here is an example.\n\n\n\nOnce you've done that, a request to /v1 will be redirected locally through a proxy to api.instagram.com/v1. This is a cool feature, but it does mean your code has to be changed. Note that you can simplify this a bit by checking for the browser platform as described here - Platform Classes - or by using ionic.Platform. Oops, scratch that! I naturally assumed if a CSS element was added to mark the browser platform then ionic.Platform would support it as well, but from what I can tell it does not. I filed issue 4306 for this.\nNote - I did some quick testing on this, basically console.logging:\n\nOddly, inside Ionic's run block, even inside $ionicPlatform.ready, the class does NOT exist. It only exists in my first controller. This seems... wrong to me.\nOk, so more testing. In the docs for ionic.Platform, they mention a platforms array. You can do: ionic.Platform.platforms.contains(&quot;browser&quot;) to see if you are on a browser. So my issue 4306 there is a bit wrong. You can check - but it seems like an isBrowser helper utility should perhaps be available.\nSo - to wrap this up - you've got multiple different ways of handling this - some require some changes to your code, some do not. Find the one that works best for you and roll with it. :)\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Upcoming Ionic/Bluemix Presentations in Asia",
		"date":"Mon Aug 31 2015 07:05:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1441004759,
		"url":"https://www.raymondcamden.com/2015/08/31/upcoming-ionicbluemix-presentations-in-asia",
		"content":"After spending last week in Australia and New Zealand (NZ wins for best food and beer), I'll be heading around the planet again next week for more sessions on Apache Cordova, Ionic, and Bluemix. Next week I'll be hitting Kuala Lumpur, Singapore, and Manilla. I've got details for my sessions for the first two cities and will post the Manilla links when I can.\n\n(A quick note - the details on these sessions may change a bit.)\nThe first session is possibly not titled entirely well, but here it is: Zero to Hero with IBM Bluemix. While the title doesn't mention it, I'll be spending about one hour on Ionic and then the next part on Bluemix and how you can use it with Ionic.\nThe next session, Zero to Hero with IBM Bluemix is a longer block (9AM - 2PM with lunch included I believe - don't quote me on that) where I'll be running a lab where you can bring your laptop and learn Cordova, Ionic, and Bluemix.\nAfter Kuala Lumpur I'll then be in Singapore. My first session is Bluemix SG Meetup #4: Rapid Mobile Development with Ionic Framework. This is a multi hour event due to food and stuff, but is mainly an hour of me talking about Ionic.\nThen I have a three hour block: [Workshop] Build your Hybrid Mobile App with Ionic, MobileFirst and Bluemix. This will be another session where you can bring your laptop along.\nPlease note that all of these sessions are 100% free and I'll be brining some schwag with me to give away.\n",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Upcoming Presentation: Static site generators: Why use them and how they work",
		"date":"Tue Aug 25 2015 16:27:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1440520078,
		"url":"https://www.raymondcamden.com/2015/08/25/upcoming-presentation-static-site-generators-why-use-them-and-how-they-work",
		"content":"Sorry for the lack of posting this week - I'm &quot;Down Under&quot; for some presentations and client meetings and trying my best to stay up past 8PM. I'm mostly losing on that front. Anyway, next week, on September 2nd at 12:00 PM CST, I'm giving a free, online presentation for O'Reilly - Static site generators: Why use them and how they work. I'll be explaining why in the heck you would ever consider such a thing, discussing and demonstrating one as an example (Harp), and then I'll talk about how to add dynamic aspects back in and where you can publish your static sites. This will be thrilling. Seriously. I'll also have some snippets from the yet to be released next Star Wars trailer*!\n* This statement may or may not be absolutely true.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using Generators with Harp",
		"date":"Mon Aug 24 2015 18:58:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1440442712,
		"url":"https://www.raymondcamden.com/2015/08/25/using-generators-with-harp",
		"content":"I've blogged (and presented) on Harp before as well as Jekyll. In general, I think Harp is much simpler to use, but Jekyll is more powerful. One of the ways Jekyll surpasses Harp is with generators. This is a script you can write that automates the creation of files during the generation phase. I blogged about this a few months back (My experience working with Jekyll), but in case you don't feel like reading the entire blog post, let me explain what problem a generator can solve.\n\nMost static site generators have a one to one mapping between the templates and final pages on your site. So I may have about.handlebars, for example, that maps to about.html when the site is generated. Your generator may create about.html by taking about.handlebars, layout.whatever, and various includes, but you have at minimum one file associated with one URL.\nThis works fine except in a few cases. Categories is a great example of this. If you want to represent categories in your static site, you'll need to create one file for each category, so for example: bluemix.handlebars, phonegap.handlebars, kittens.handlebars. You'll probably include the exact same code in each (or better yet, use an include), but the point is you need to create a physical file for each item.\nThis isn't horrible, and to be honest, you probably don't want to willy nilly change categories (or tags, or topics, etc) on the fly, but it is kind of a pain to remember to have to update these files. As I wrote about in the other blog post, Jekyll resolves this problem by letting you use a generator. I was able to use Ruby to look at my data and create category pages on the fly.\nI was thinking about this in regards to Harp recently and it occurred to me that there was a rather simple way for me to get the same behavior. I tend to think of Node.js in terms of web apps/site, but you can also use Node.js for simple scripts. Yeah, everyone else knows this, I know this, I just don't think about it very often. So given that I'm using Harp and have my data available in a JSON file, there's no reason why I can't use a simple Node.js script to do much of the same stuff Jekyll's generators do. It won't be automatic, in other words I have to run it manually, but I could make it part of a build script (Grunt, Gulp, etc) so I can automate the entire thing.\nHere is a real example. In my test, I was working on a Harp version of CFLib. It has various categories that I've encoded into JSON:\n\nI'm using this data on the front page to dynamically list out libraries. But I can also use it in a Node.js script:\n\nSo all this does is read in my JSON, read in a template I built to represent a specific library page, and then write it out in physical files so Harp can use it when generating the site. In my case, my template is just an include:\n\nIn which I've built in the code to handle generating a library display. Yeah, so this is pretty obvious, and I'm pretty sure some Harp users are already doing this, but I thought it was kind of neat, and it made me re-evaluate when I'd use Harp compared to Jekyll. (I just wish they would support more than Jade and EJS. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "Chrome, console, and URLs - watch out",
		"date":"Thu Aug 20 2015 09:02:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1440061356,
		"url":"https://www.raymondcamden.com/2015/08/20/chrome-console-and-urls-watch-out",
		"content":"Ok, this isn't necessarily a huge bug, but it surprised me and was very subtle so I want to make sure folks know about it. Honestly, I don't care if Google corrects this (I can think of other dev tools things I wanted tweaked), but as I said, I want to make some noise in case other people run into it as well.\n\nToday I was working on some client-side code that involved a URL value. In my testing, I needed to change the URL slightly based on a regex. So I did this:\n\nI then opened up my dev tools and saw this:\n\nIt may be a bit small in the screen shot, but what my eyes saw were two URLs of the exact same size. Looking in the middle, you can definitely see a difference, but since I was removing stuff and saw two strings of the same length, I assumed my regex was wrong. (Which, let's be honest, we all expect our regexes to be wrong at first.)\nThen I noticed the oddities in the middle of the string. When I moved my mouse over the URL and waited, a tooltip showed up with the complete URL. Apparently, Chrome's dev tools decided to help out here and change my display.\nNow - I don't want to say that's always a bad idea. I've noticed that if you have a large array and dir it, Chrome will break the dump into pages. That's helpful. But I think this is a case of going a bit too far. If I print a string, even a long one, I probably know what I'm doing, and Chrome shouldn't mess with it. As it stands, it is inconsistent. I can print out any other long string just fine, it is just URLs that are shortened.\nI did a quick Google (hey, I can complain about them and use them at the same time, right? ;) and found this Stack Overflow post: Disable URL Shortening/ Formatting in Chrome's Console. The SO post mentions using console.dir to print it out, and that does indeed seem to work. I also did nextUrl.replace(&quot;https://&quot;,&quot;&quot;) and that worked as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Speaking Down Under Next Week",
		"date":"Thu Aug 20 2015 02:31:26 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1440037886,
		"url":"https://www.raymondcamden.com/2015/08/20/speaking-down-under-next-week",
		"content":"Tomorrow I hop on a plane and head down under. I'll be going to both Australia and New Zealand. In Australia, I'll be speaking at two multi hour events. I'll be covering Apache Cordova, Ionic, and MobileFirst. Both sessions are a three hour block with an hour focused on each topic.  I'll be bringing some Ionic schwag with me as well as the spirit of America, so please attend if you can!\nThe first event is in Melbourne on August 24th: Cordova/Ionic/MobileFirst.\nThe second event is in Sydney on August 26th: Cordova/Ionic/MobileFirst\nBoth events are completely free, so please show up!\n",
		"tags":[
	        
            "cordova",
            
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Developing Ionic Apps with MobileFirst 7.1",
		"date":"Wed Aug 19 2015 09:27:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439976450,
		"url":"https://www.raymondcamden.com/2015/08/19/developing-ionic-apps-with-mobilefirst-7-1",
		"content":"Time for the last in my series of blog posts on hybrid development with MobileFirst 7.1. Obviously I've got more to say about MobileFirst, but this last post will complete the picture so to speak about the development is like in 7.1. I want to give a special shout out to my coworkers Carlos Santana and Karl Bishop. They helped quite a bit with the first two blog posts and this one in particular is thanks to Carlos. Both are smart folks who make my job of telling yall stuff quite a bit easier. With that in mind, before going further, be sure to read my introductory post (Getting Started with Mobile Development and IBM MobileFirst 7.1) and my follow up (Developing Hybrid Mobile Apps with IBM MobileFirst 7.1).\n\nFor this post, I'm going to speak specifically about Ionic development and MobileFirst. In general, you can follow much the same path as what I described in the last blog entry. Basically make a new MobileFirst project, make a new Ionic project, and then copy over the www assets. But you will also want to make sure you include Ionic's keyboard plugin: com.ionic.keyboard. Finally, you want to include the code I mentioned that makes use of wlCommonInit. Remember, this is the &quot;MobileFirst is ready to go&quot; event.\nIn general, that would work fine, but there's a way to make it even easier. As I mentioned, my buddy Carlos has been working on this issue and has already made something that will help quite a bit - a set of Ionic templates: https://github.com/csantanapr/mfp-ionic-templates. These templates make it easier to work with MobileFirst and Ionic. Assuming you've checked out the repo, you can simply provide the path to the repo when creating a new MobileFirst hybrid project:\n\nOnce you've created the project, you then need to initialize Ionic library values and other settings. Luckily Carlos made this easy - just run: npm install. This will read in the dependencies defined in package.json and run bower as well. (This means that Carlos didn't need to include a specific Ionic JavaScript library - you'll always get the latest.)\nIf you crack open the code, you'll see that app.js has been updated to include MobileFirst specific chores including wlCommonInit. He also includes a bit of code to ensure the app will work in our Mobile Browser Simulator (Using the MobileFirst Mobile Browser Simulator) and Ionic serve as well. To be honest, I kinda felt like it was a mistake to include code that just works in those situations, but I'm glad he included it. He clearly marked them in app.js and if you're worried about the 'waste' of 15 lines of unnecessary JavaScript code in production, it will be easy to remove it. (And since this is a repo, you can always just check it out and modify it yourself.)\nHere's an incredibly cool animated Gif of the blank starter in action.\n\nAnd there you have it. Let me know what you think in the comments below!\n",
		"tags":[
	        
            "cordova",
            
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Developing Hybrid Mobile Apps with IBM MobileFirst 7.1",
		"date":"Tue Aug 18 2015 08:40:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439887232,
		"url":"https://www.raymondcamden.com/2015/08/18/developing-hybrid-mobile-apps-with-ibm-mobilefirst-7-1",
		"content":"In yesterday's blog post (Getting Started with Mobile Development and IBM MobileFirst 7.1), I discussed what MobileFirst was and why it could be beneficial for mobile developers. In today's post, I'm going to discuss how hybrid mobile development works with MobileFirst. This is something I've discussed before (for MobileFirst 7.0), and while the process wasn't difficult, it was definitely a few steps away from the &quot;typical&quot; Cordova development workflow. MobileFirst 7.1 really improves this process and makes it somewhat simpler for hybrid developers. In this post I'll talk about the process both for new projects as well as how an existing project can be migrated to MobileFirst. I will not be discussing specifics for Ionic until tomorrow, but most of what I say today will apply there as well. Ok, let's get started!\n\nPrereqs\nBefore I begin, I'm assuming you've already downloaded and installed the CLI as I described in yesterday's post. You'll also want to have a server up and running, either locally or on Bluemix. I also assume you have the &quot;normal&quot; Cordova prereqs like the iOS or Android SDKs.\nCreating a Project\nTo create a new hybrid project, you begin by running mfp cordova create. You'll be prompted for the name and given a default package ID and version:\n\nNext you'll be prompted to select platforms. Like any other Cordova project, you can change this later.\n\nNext, the CLI lets you know that some plugins are installed by default. These plugins are required for Cordova apps running with MobileFirst:\n\nNow the CLI prompts you about other plugins you may want to install. Note that you can easily add, remove, and list plugins later on so don't stress too much over this.\n\nFinally, the CLI prompts you to select a template to use for your app. You can pass in other templates via the -t argument and you'll see this in action tomorrow when I blog about Ionic:\n\nAt this point, the CLI will start generating your project as well as push a copy to your MobileFirst server. If everything went well, the last thing you'll see is: &quot;MFP Cordova project created successfully.&quot; Let's look at the folder created by the CLI.\n\nFor the most part, this should look very similar to a regular Cordova project. Notable differences include:\n\napplication-descriptor.xml: This allows you to tweak some settings for the application under MobileFirst. In general, you won't need to tweak this and when you do, do not edit it by hand, use mfp config.\nmobilefirst: The files in this folder are what get pushed up to the MobileFirst server. You won't need to mess with this.\n\nAnd that's it - the rest of this is vanilla Cordova stuff.\nWorking with the MobileFirst/Cordova Project\nSo now that you've got a project, how do you use it? The MobileFirst CLI wraps calls to the Cordova CLI, much like Ionic does. So for example, to add a platform, you would do: mfp cordova platform add android. In general, the commands are very similar, but sometimes there are small differences. So for example, to emulate, you need to pass a -p flag: mfp cordova emulate -p ios. In this case, -p stands for platform. You can easily see the syntax by typing mfp help cordova:\n\nSo the process to code/test is pretty similar. You can open up the www folder, edit, and then see your changes by doing: mfp cordova emulate -p ios:\n\nAt the time I write this blog post, we have a small bug with the CLI that impacts this process. When working with a MobileFirst server, you need to deploy the bits to the server so it is aware of it. (There's more reasons than that, but let's keep it simple for now.) That command is: mfp push. The emulate command is supposed to do a push automatically, but right now it does not. Again, this is a bug, and a known one that is already being worked on. (I'll try to remember to edit this post once the fix is released.) For now, I recommend doing both commands at once. In OSX, this would be: mfp push &amp;&amp; mfp cordova emulate -p ios. You could automate all of this with Grunt/Gulp of course.\nOutside of that - you're done. Build your app. Make use of the cool features of MobileFirst, iterate, deploy, and be successful.\nMigrating an Existing Application\nSo what do you do if you've got an existing application? First, begin by creating a new MFP Cordova project as I outline above. You'll want to match the ID and application name. You can also tell the CLI to install the plugins your app needs, but if you forget, you can always add the plugins later. You can then simply copy the www folder from your existing project into the new MFP www folder.\nOk, so at this point, you need to make one very small tweak to your application code. As you know, Cordova applications need to wait for the deviceready event before they do anything related to the device itself. Most folks treat deviceready as their main application &quot;bootstrap&quot; - i.e., they don't really do squat till after it has fired.\nIn a MFP Cordova application, you have another event as w",
		"tags":[
	        
            "cordova",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Coming Soon: The Ionic Market",
		"date":"Mon Aug 17 2015 06:42:21 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439793741,
		"url":"https://www.raymondcamden.com/2015/08/17/coming-soon-the-ionic-market",
		"content":" While not technically released yet, you can now take a gander at the new Ionic Market a market.ionic.io. The Ionic Market contains Starters (things to help you jump start a new project), plugins, and themes. \nAs an example, I turned my Ionic RSS Reader into a project: http://market.ionic.io/starters/rssviewer\n\nMarket assets are both free and commercial and anyone can submit a resource for the store. As I said, it isn't really released yet, so now is a great time to check it out and provide feedback to the Ionic folks!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Getting Started with Mobile Development and IBM MobileFirst 7.1",
		"date":"Mon Aug 17 2015 05:05:29 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439787929,
		"url":"https://www.raymondcamden.com/2015/08/17/getting-started-with-mobile-development-and-ibm-mobilefirst-7-1",
		"content":"A few days ago I blogged about the release of MobileFirst 7.1. At the time, I really didn't have a lot of time so I didn't say much outside of, well, that it was released. Now that I'm back home (for a few days anyway), I thought I'd write a bit more about MobileFirst 7.1 and how hybrid mobile developers can make use of it. This is going to stretch out over a few posts, mostly written today, so for now I'm just going to cover installation and basic setup.\n\nBefore I get into installation, I know some of my readers still don't quite get what MobileFirst is. Hitting the home page will give you a bunch of pretty pictures and good information for managers, but as developers, we typically like things a bit more boiled down. What follows is my personal take on what MobileFirst is and why it is cool. Obviously I'm not a marketer so you will forgive me if what follows is a bit more casual than typical IBM marketing. ;)\nAt a high level, MobileFirst is a support system for mobile apps. It is a server product that integrates with your mobile app (and to be clear, this doesn't impact offline support at all) and provides various different services. The server can run in multiple locations (more on that later) and includes a CLI for working with mobile projects. In terms of how your mobile app is built, MobileFirst supports native, hybrid (Cordova), and mobile web sites. The services MobileFirst provides includes:\n\nThe ability to provide proxies for HTTP, SQL, and other services. So imagine your application makes use of an API that returns weather information. In a typical mobile app, your app makes a HTTP request to the API, parses the response, and then does something with it. In MobileFirst, I can create an \"adapter\" that represents that API. My mobile app calls the adapter on the MobileFirst service which then proxies the call to the remote API. Right away I get some freaking cool benefits. One, if the remote API returns a bunch of crap I don't need, I can reduce the result payload to just what I want. Along with reducing the result, I can do whatever I want. Imagine the weather API only returns data in that weird format the rest of the planet uses. I could 'fix' that bug by returning Fahrenheit instead. Along with acting as a proxy, I also get reporting on the back end server so I can see how often the API is being called and how much data is going back and forth. Finally, I can completely replace my API provider on a moments notice by updating the adapter. My mobile app wouldn't need to know at all.\nThe ability to setup Android/iOS Push for your application. A REST API is also provided to so you can dynamically send push notifications to your mobile apps. Oh, and full reporting on your use of push as well.\nThe ability to manage multiple versions of your application as well as deploy new ones. So being able to deploy an update without going to the app store is pretty cool. But you can also do things like send an application message (for example, warning folks if part of your app will be down, or if a new version is coming soon, etc) as well as disable a version (imagine version 1.1 has a bad bug fixed in 1.2, you can disable and warn folks on 1.1).\nThe ability to send logs to a central server (with awesome support for handling your app being offline) so that you can search them later. So if all of a sudden folks on Android begin complaining about bugs, you can go to your MobileFirst server and begin searching on Android logs to see if you can figure it out.\nYou get a set of utilities to use within your mobile application. These utilities cover various things that might be useful. So for example, geolocation related utilities like \"am I in a polygon\" or \"how far am I from a polygon\". Or setting badge icons. Or detailed network information (like is airplane mode on). These utilities aren't something you would use on every project. They aren't \"oh my god, I've waited all my life for these\" type things, but they are useful items that you'll use from time to time and appreciate they exist.\nI've already mentioned analytics in regards to the adapters, but you get more much more analytics of course.\nSupport for working with existing security systems. This lets you tie in things like adapter calls to an authentication framework. I'll be honest and say I've not yet played with this aspect of MobileFirst.\nFinally, you can also use MobileFirst to handle your enterprise app store.\n\nSo that's what MobileFirst is to me, and to be clear, there's more I haven't covered, but these are the aspects that have most interested me as a developer. If you want to check it out, note that you can do so for free. This isn't a &quot;trial&quot; edition that times out, but a complete developer edition that you can use forever while testing/learning/etc.\nSo let's talk installation.\nInstallation\nWorking with MobileFirst requires two things - a server and a command line interface. There is one main download for MobileFirst and it includes both the se",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "A quick Pokemon demo, because, Pokemon",
		"date":"Sat Aug 15 2015 07:45:25 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439624725,
		"url":"https://www.raymondcamden.com/2015/08/15/a-quick-pokemon-demo-because-pokemon",
		"content":"So, I'm not really a Pokemon person (and yes, I know there's an accent in the word, but I'm not even going to bother trying to type that), but my son came to me last night with an interesting request. He is quite the artist, and he decided he wanted to start an incredibly ambitious project: Every day he is going to sketch a Pokemon. All 700 plus of them. His request was rather simple. Given that Pokemon had a number, he wanted me to generate a random list from 1 to 721.\n\nI told him I could do that - but I had a better idea. I knew there was a Pokemon API (Pokéapi) and I thought I could probably whip up a quick list for him using that data. Unfortunately, the API doesn't support the ability to return all the Pokemon at once. But the API itself is 100% open source (https://github.com/phalt/pokeapi) and it includes the raw CSV data behind the API. So I cloned a copy of the repo locally and built the following quick demo. As a warning, this is not optimized. I wanted to build something super quick (it was last night, I was tired, etc. etc.).\n\nI begin by simply Ajaxing the CSV file that contains all the Pokemon data. I strip off the first line (it just contains headers) and then filter out rows containing &quot;non-default&quot; Pokemon. My son can explain why that is important - frankly I didn't get it. Then I just pick a random item from the array and remove it.\nThe GitHub repo also contains images (sprites) so I include that in the table display. Here is a quick snapshot of some of the report:\n\nIf you want to run the demo yourself, you can do so here: https://static.raymondcamden.com/pokemon/\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "IBM MobileFirst 7.1 Released",
		"date":"Fri Aug 14 2015 01:18:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439515089,
		"url":"https://www.raymondcamden.com/2015/08/14/ibm-mobilefirst-7-1-released",
		"content":"I'm happy to announce that today we've released MobileFirst 7.1!\n\nNote - that is not official IBM MobileFirst branding. Also note - this is why I'm not in branding.\nThis release includes a pretty significant update to how hybrid apps are developed. You can read more about those updates here: Integrating IBM MobileFirst Platform Foundation SDK in Cordova applications. You can also peruse the Getting Started docs if you've never looked at MobileFirst at all.\nEvaluating MobileFirst is 100% free. You can download the developer edition here: http://www.ibm.com/mobilefirst/us/en/downloads/. It is not terribly clear where to go next on this page. Scroll down till you see this:\n\nAlong with improving support for hybrid mobile apps (and yes, that includes Ionic), MobileFirst will be available on Bluemix as well. This will make setting up the server even easier then it is now. (I'm not seeing it available right now but it should be up relatively soon.)\nI gave a presentation on 7.1 yesterday and I plan on turning that content into a series of blog posts and videos... but not today. I'm flying home today so I'll be in the air for most of the day.\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Processing multiple simultaneous uploads with Cordova",
		"date":"Mon Aug 10 2015 11:29:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1439206170,
		"url":"https://www.raymondcamden.com/2015/08/10/processing-multiple-simultaneous-uploads-with-cordova",
		"content":"I forget where, but a user on one of my posts recently asked about handling multiple uploads with Cordova, so I thought I'd whip up a quick demo. As always, the code below is all on GitHub (link will be at the bottom), so you can skip my post completely and just grab the bits if you want.\n\nFirst off, the only reason this is even a little bit complex is because the upload method of the File Transfer plugin is asynchronous. Luckily there is an easy (heh, ok, kinda easy) way to handle multiple asynchronous responses - Promises. If you are using Ionic, then I'd strongly suggest using ngCordova. It includes a &quot;Promise-fied&quot; version of the File Transfer plugin already. But I didn't want to assume Angular so I decided to skip ngCordova and instead simply use the promise support from jQuery. (Reminder, I've got 2.5 hours of jQuery training, including promises, here: https://www.youtube.com/playlist?list=PL_z-rqJYNijrtVAc5qQbkzHnDELANGiOn)\nFor my demo, I simply used the Camera plugin to let you select multiple images from the device. Each image is added to the DOM. Here is that code:\n\nI assume this stuff is rather simple, but if not, just let me know in the comments below. Here is a screen shot of the app in action.\n\nAs you can guess, the upload button at the bottom there will begin the process. For my testing, I set up a ColdFusion script to simply save the uploads to a temporary directory. To add a bit of randonmness though, it will randomly reject images by outputting 0. On successful uploads, it will output 1.\nHere is the remainder of the code:\n\nSo from top to bottom, what I'm doing is creating an array of promise objects. Or more specifically, the jQuery version of them. I then run an upload call for each image selected by the user. I check the result from the server and either resolve the promise with 0 or 1 based on what the server said.\nFinally, I've got a call to $.when to handle waiting for all these asynch processes to finish. I don't actually show anything, I just console.dir, but you could imagine checking the results and doing - well - whatever makes sense.\nI hope this is useful for folks, and as always, let me know if you have any questions. You can find the complete source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiupload\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery",
            
                "mobile"
            
		]

	},

	{
		"title": "More information on restoring Ionic projects",
		"date":"Thu Aug 06 2015 02:35:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1438828522,
		"url":"https://www.raymondcamden.com/2015/08/06/more-information-on-restoring-ionic-projects",
		"content":"Back in April I blogged about a new feature that was added to Ionic (Ionic adds a new State feature). Yesterday I had a conversation with Mike Hartington of the Ionic team about another version of this feature - the app.json file. It was pretty confusing to me at first, but after talking with Mike for a bit I think I have a handle on it and wanted to share this with others. Everything below is thanks to Mike and any mistakes in transcription are my fault.\n\nOk, so what exactly is app.json support? The app.json file lets an Ionic project define plugins, bower packages, and SASS support, for an Ionic project. That sounds like package.json and the &quot;State&quot; feature, right? There is an important difference though.\nYou use Ionic's State feature when you have an existing Ionic project. It can restore both platforms and plugins.\nYou use Ionic's app.json feature when creating a new Ionic project. In other words, when you do: ionic start directoryname remoteurlOrlocalpath and the remote URL or local path contains an app.json file, it will be executed as part of the creation process.\nThe app.json file is a bit different from the package.json feature. The app.json feature can enable plugins, can download bower packages, and enable/disable SASS support. You can see an example of this in the starter Push template:\n\nIn this sample, four plugins will be installed and SASS will not be enabled. What about bower support? You would just add a bower key to the JSON:\n\nIn case your wondering why you may not have heard of this yet - that's because this feature isn't actually documented yet. (I've filed a bug for it: 556.)\nHopefully this makes sense for folks. It took me a bit to wrap my head around it. Just to be sure I'm clear, I'm going to repeat myself. The package.json/State feature is for an existing project and lets you restore plugins and platforms. The app.json feature is for setting up a new project.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "A real world app with IBM Bluemix, Node, Cordova, and Ionic",
		"date":"Wed Aug 05 2015 11:01:10 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1438772470,
		"url":"https://www.raymondcamden.com/2015/08/05/a-real-world-app-with-ibm-bluemix-node-cordova-and-ionic",
		"content":"I'm still working on my large SauceDB project, but during a meeting at work earlier this week my coworkers and I came up with a simple project that may be a nicer introduction to working with Bluemix and Ionic. What follows is a complete application (both back and front end) that is also somewhat simple. There's multiple moving parts here so it does require some setup, but I think this guide would be a good introduction for developers. Of course, the entire thing is also up on GitHub (https://github.com/cfjedimaster/IonicBluemixDemo) with the instructions mirrored there as well. Alright, let's get started!\n\nWhat are we building?\nBefore we get to the code, what are we actually building? We're building an application that makes use of the Watson Visual Recognition service. We'll create a mobile application that lets you select a picture and send it to the Watson service so it can try and find what's in the picture. If this sounds familiar, it should. I blogged about this back in February. However, back then I built a simple Cordova-only demo with the service credentials hard coded into the code. That was bad. This version is &quot;proper&quot; with a Node.js server running as a proxy to Watson on Bluemix. Here's a screen show of the mobile app on start:\n\nClicking the button brings up a prompt to select an image. Note - it would be trivial to make this use a real camera - but by using the photo gallery it is easier to run on a simulator. And obviously you could use two buttons so the user could choose.\n\nAfter you select the image, it will be uploaded to the Node.js server, sent to Watson for processing (I imagine Watson as millions of tiny minions), and the results returned to the mobile app. Watson includes both labels for things it believe it found as well as scores, but for this app, we'll just display the labels.\n\nPrereqs\nIn order to build this project, there's a few things you'll need to get started.\n\nApache Cordova should be installed, and at least one of the mobile SDKs. I tested with iOS, but this should work fine in Android and other platforms as well. In theory, you could try the Ionic View application, but there is one part that I'm fairly certain will not work well. I'm going to test that a bit later.\nIonic.\nA Bluemix account. Remember, this is 100% free. Yes you will be asked for a credit card after 30 days, but even then you can run Bluemix, and every service on there, at a free tier appropriate for testing. I think our verbiage is a bit unclear on this, but you can run it for free. Free. Did I say it was free? Yes, free.\nNode.js installed so you can test locally.\n\nSet up\nLet's begin by creating the application on Bluemix. Assuming you've logged in, begin by clicking Create App under Cloud Foundry Apps.\n\nThen select Mobile for the type of app you are creating. To be clear, this will only set some default services. You can, and we will in this project, also create a web site via your Node.js application.\n\nNow select the Mobile option that supports hybrid. To be clear, even though you aren't picking iOS 8, you can still deploy to iOS 8. All we're doing is driving what's automatically added to our application in Bluemix.\n\nClick Continue and then give this bad boy a name. I like to name my applications optimistically:\n\nClick Finish and let Bluemix set stuff up for you. When done, you'll get a confirmation screen with some tips for where to go next.\n\nJust hit continue, and then select the Start Coding link in the left hand nav. This next page has a few important links on it:\n\nThat first item, &quot;Download CF Command Line Interface&quot;, is a one time download to get the command line tool. The command line tool, cf, lets you push up your code to the Bluemix server. You'll do this when you want to deploy the app live to the Internet. For our project here you won't ever need to do that, but can if you want to show your app to others.\nThe second item, &quot;Download Start Code&quot;, gives you the Node.js code to start your server. Normally you could download this to get started on a new application. But our project exists up on GitHub already. Before diving into the code, let's go ahead and set up the service our application will load. Click &quot;Overview&quot; to return to the main application home page, and then &quot;Add a Service or API&quot;.\n\nBluemix offers quite a few services, and while I can see &quot;Visual Recognition&quot; there clearly, you may not. You can use the search field on top to quickly narrow down your search. When you click on the Visual Recognition service it will give you a confirmation of the price (free, well, beta, but free!) and where the service will be installed. For now you can accept the defaults.\n\nRecap\nOk, just to recap. We create a new application in Bluemix and added one new service to it, Watson Visual Recognition.\nNow it's time to crack the code!\nThe Server\nAt the command line, check out the repo: https://github.com/cfjedimaster/IonicBluemixDemo\nThis will give ",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Review: REST Web APIs: The Book",
		"date":"Sun Aug 02 2015 03:34:35 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1438486475,
		"url":"https://www.raymondcamden.com/2015/08/02/review-rest-web-apis-the-book",
		"content":" Before I begin, a quick disclaimer. I've known Adam for a long time, so my review may be a bit colored by the fact that I'm friends with him. Of course, the flip side to that is that on more than one occasion I've fought with him over something or another on IRC so this could be my chance for revenge. ;) That's my official disclaimer out of the way. \nAdam's REST book is a good example of a trend I'm seeing lately in smaller, more concise technical books. I've read, or pretended to have read, huge tomes on programming languages before, heck I've even contributed to a few, but frankly, I'm finding that the shorter, more direct books are a heck of a lot more effective. His book is right over a hundred pages and as it is all on the theory of REST, you can read it in one or two sittings.\nI say &quot;theory&quot;, but to be clear, this is an incredibly practical book. Adam doesn't just explain REST, but goes into details and gives you real nuggets to chew on when thinking about why REST does things a certain way. He is also not shy about calling out what isn't practical and being clear about why and when he breaks the rules.\nThis comes through especially well in his best practices chapter. Obviously this is an opinionated section, but it is also something you don't see often in books of this type. Frankly, I want to see more of this. You may not agree with the author, but it is helpful to have someone with real world experience tell you their opinion on what works best with them.\nI really enjoyed the book and definitely recommend it. To be honest, REST has always felt like a &quot;nice&quot; idea, but not necessarily worth the effort. After reading Adam's book, I'm more convinced now that it is worth my time and part of what should be considered a requirement for building a decent API.\nYou can pick the book up for 19 American dollars at the official book page here: REST Web APIs: The Book. You get a PDF, Mobi, and ePub version. You can also grab a sample chapter to see if it looks worth your time.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Cordova/Ionic Sample App: My Sound Board",
		"date":"Thu Jul 30 2015 04:18:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1438229896,
		"url":"https://www.raymondcamden.com/2015/07/30/cordovaionic-sample-app-my-sound-board",
		"content":"Today's demo is something I started working on Sunday &quot;for fun&quot;, but when it turned into an unholy mess (see Recording and saving audio in Cordova applications), it took me a bit longer to wrap than I expected. The idea was simple. &quot;Sound Boards&quot; are apps that contain a collection of sounds, typically related to a movie or TV show. My coworker Andy built a cool sound board themed around Halloween a few years back: Halloween Fun with PhoneGap. I wanted to build a sound board too, but instead of shipping it with a set of sounds, I wanted it to be completely customized. The idea being you could record your own sounds. In a fit of extreme creativity, I called it - &quot;My Sound Board&quot;.\n\nLet me share some of the screens behind the app and then I'll dive into the code. On launch, the app will present you with a list of sounds you currently have or prompt you to record a new one.\n\nClicking record brings you to a new UI:\n\nOn this screen you can record a sound, play it back to test, and name it. The recording interface will be device specific on Android, but on iOS it is a standard UI created by the Media Capture plugin itself. Here it is on my HTC M8.\n\nAnd here it is on an iPhone.\n\nOnce you've saved a few sounds, you can see the list grow.\n\nI then used a cool Ionic feature that makes it easy to add delete buttons to a list. If you swipe right, you get a button:\n\nAll I had to do then was wire up the logic to handle deleting. So - about that code - let's take a look. I won't cover the workaround I mentioned in this weeks blog post, but I strongly suggest reading it. I'll begin with the controller code for the home page:\n\nThere's nothing really interesting here except for the $ionicView.enter handler. That's how I handle getting my sounds every time you hit the home page. Technically, that is a bit wasteful. I should only care when I modify my sounds. But this was simpler, and as it stands, if you aren't recording and leaving the home page, then there is no real performance issue. Note that delete is a bit complete. I decided that my Sound service would not worry about files. My RecordCtrl for example handles the file copying that I mentioned in the earlier post. So when I delete, I have to kill the file myself, and then tell the service.\nOk, let's look at the view.\n\nRight away, I can say I don't like the two divs there. I have to think Angular has a way of doing an ELSE type condition. Can anyone suggest an improvement? I absolutely love how Ionic adds the &quot;swipe to show button&quot; logic. I literally just used can-swipe and then defined a button.\nNow let's look at the service.\n\nI decided to use LocalStorage for my persistence. Normally I recommend against LocalStorage when working with user generated content like this. But I decided that since the user data would be fairly small (a short array), then it was safe. The ease of use of LocalStorage is what sealed the deal. I felt kind of bad using outside stuff like LocalStorage and the Media plugin, but I got over it.\nAnd that's pretty much it. Here is a video with some sounds I recorded of my kids. The whole reason I even thought of this app was that one of my boys wanted to record his younger sisters being silly, so naturally I made use of them.\n\nFinally, you can find the complete source code for this application here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/mysoundboard. I need to add a readme to the folder, and I promise I'll get to that. Eventually.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "SauceDB: Writing data back",
		"date":"Wed Jul 29 2015 11:28:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1438169328,
		"url":"https://www.raymondcamden.com/2015/07/29/saucedb-writing-data-back",
		"content":"Welcome to another post on my ongoing series exploring building an Ionic hybrid mobile app making use of Node.js running on IBM Bluemix. Today I'm going to talk about writing data back to the server. Spoiler - this won't be quite as cool as bringing sexy back. In my last post, I described a few minor updates to help flesh out the views of the mobile app. This included building the &quot;Sauce view&quot; (sauce plus reviews) for the application. Today I built another major aspect of the application - actual review writing.\n\nGetting review writing working is actually a multistep process. Before you add a review, the application asks you to name the sauce you're going to review. The idea was to autocomplete on the name so you can quickly select one that already exists. I had built this functionality on the client side already (SauceDB – Working on the front end), so at this point, all I needed to do was actually get the server-side version of it working.\nI began by creating a Cloudant Search Index. This is a pretty darn powerful tool. You simply create an index in your database of the field you want to search and their API will use a Lucene search engine to interface with the data.\nHere is the index I created. My data consists of Sauces with names, so I simply index the name. I want to get the name back in searches, so I tell the index to include the value.\n\nThis worked well enough, but it took me a good hour of banging my head against the wall to get search working. Why? In the term area, you need to include a field. So for example, this returns nothing:\n\nBut when I added the actual field name, &quot;name&quot;, it worked:\n\nOk, so that's cool. I then needed to work with this on the server-side in my Node code:\n\nYou can see I do a bit of manipulation on the input and then just use the Search API provided by the Cloudant Node module. So back in the client-side code, I then removed my mock code in the service layer and replaced it with Cloud Code calls back to Node:\n\nAnd that was pretty much it. (I did change the view template a tiny bit.) Here it is running in the emulator with real data powering the autocomplete.\n\nOk, so at this point, we load up a form to let you write your review. I had already built this out and included logic to recognize a new sauce via an existing one. So for example, an existing sauce just asks you to write the text and select the rating.\n\nWhereas a new sauce requires a name and company:\n\nAlrighty... so... here comes the fun part. We now need to communicate back to the server. That's a simple post. But remember that we need to include information in the post that signifies that the user is logged in. I already described how I'm using OpenFB for Facebook integration and I've got a login token I copy to $rootScope. I also blogged (Combining client-side social login and server-side authorization with Cordova and Node) about how you can combine client-side login with server-side authorization in Node. All I had to do was bring those parts together.\nFirst - I built in my login middleware. This will check the Facebook token sent by the user to ensure it is valid. I also want to get user information including their name and profile picture. I store all of this in a Node session so I don't have to fetch it again.\n\nNow let's look at adding a review. This has to handle two cases - adding a new sauce with a review as well as adding a review to an existing sauce. My code does not validate that a sauce name is unique. I figure... that's a bit too much for now.\n\nAnd that's it. Here's my last review:\n\nThere's still plenty of rough edges in this code. For example, going back to the feed doesn't get a fresh copy. I'm going to fix that with a cool Ionic widget in my next post. There's also some view caching going on that needs cleaning. You get the idea. But it's getting there!\nRemember, you can view the source code here: https://github.com/cfjedimaster/SauceDB.\n",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Recording and saving audio in Cordova applications",
		"date":"Mon Jul 27 2015 08:02:15 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437984135,
		"url":"https://www.raymondcamden.com/2015/07/27/recording-and-saving-audio-in-cordova-applications",
		"content":"Ah, looking at that title there you probably think, &quot;Surely this is a simple matter in Cordova and surely this is Raymond, once again, blogging something is incredibly obvious and simple just to drive people to his Amazon Wishlist.&quot; Yep, that's what I thought too. This weekend I began work on a simple little Cordova app for my son. I thought it would be a great blog post too. But while working on it, I ran into an issue with audio recordings that drove me bat-shit crazy for a few hours, so I thought I'd better share so others don't have to bang their heads against the wall too.\n\nBecause this problem turned into a royal cluster-you know what, I've decided to blog it about it in detail here and talk about the app itself later this week. For now, consider this use case:\n&quot;You want to allow the user to make an audio recording. Later, the user can play that recording.&quot;\nSimple, right? So I began working on a form that would let the user make the recording as well as name it. I wanted them to be able to do the recording as well as play it back to ensure they liked it.\n\nClicking Record fires off a call to the Media Capture plugin:\n\nThis is boiler-plate Media Capture usage here. I'm storing both the URL and file path in $scope so I can save it later. The Play feature is also pretty trivial:\n\nThis worked fine in Android and iOS. But I noticed something weird. When I looked at the Media Capture object in captureSuccess, I saw this in Android:\n\nSee the portion I called out there? Persistent. Cool. That gives me the warm fuzzies. However, in iOS, I saw this:\n\nAs you can see, it is being stored in a temporary location, which is not good. Unfortunately, there is nothing in the Media Capture plugin that you can change to modify this behavior. Therefore - the answer was clear.\nThe File System!\n\nSo in theory, this should be easy. First, we pick a persistent location that covers both Android and iOS. The File plugin provides such an alias: cordova.file.dataDirectory\nWe have a folder, right? So literally all we need to do is copy a file from one location to another. Copy. A damn. File.\nBut we've got an issue. First, we have to give the file a unique name. To handle that, I just used time and the existing extension.\n\nI then spent about an hour trying to get the copy command to work. I began by adding numerous console.log messages with the F word in it. If you don't know what that word is, ask your teenagers. I thought that with a file path, I could just do this:\n\nBut nah, that would be too easy. You need to use window.resolveLocalFileSYstemURL first. And since the File copy command requires a path, you have to do this twice. Here's the code I ended up with. I removed a few console.log messages that may offend some readers. If you're curious, I went way beyond just saying the F-word.\n\nAll in all, it isn't that bad. A few nested callbacks, and nearly half my code there is related to my app, not the actual issue, it just took me a while to get there.\nLuckily, everything worked perfectly.\n\nSo at this point, I've saved the location of my audio file so I can use it in the Media api, but the new location doesn't work in the Media plugin anymore. Why? I don't freaking know. I blogged last year about how when you use the Media plugin and a relative path, you have to do something funky on Android, basically prefix your relative URL. In my case, I'm using a file:// url and I just assumed it would work.\nAnd here is where things got awesome - it worked perfectly in Android but not iOS. Because - reasons. I brought this up on the Cordova Slack channel and @purplecabbage mentioned that a relative path may work. In my dev tools, I tried to manually create a Media object via the console. I discovered that - given the file name - I could access the file with the Media plugin by using this as the root: &quot;../Library/NoCloud/&quot;.\nSo now my play code looks like so:\n\nSo... yeah. That's it. My app now works. I can record and test audio, and I can persist it to a permanent file system. As I said, I'll share the real app later this week.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "SauceDB - Handling the Sauce view",
		"date":"Fri Jul 24 2015 03:03:27 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437707007,
		"url":"https://www.raymondcamden.com/2015/07/24/saucedb-handling-the-sauce-view",
		"content":"&quot;Handling the Sauce view&quot; - how immature would I sound if I said that title made me laugh? Today's post isn't terribly exciting. I'm basically going to cover how I got one more screen in my app working. But I'm ok with this being kind of boring. As I document this process, somethings will be complex and some will simple. This is one of the simple items that I think is still useful to cover.\n\nIf you remember, in the last post, I detailed the setup of the back end using IBM Bluemix. I got a Node.js application up and running (both in the cloud and locally) and I connected my first data view from Cloudant, to Node, to my Ionic. The mobile-side was especially simple as all I had to do was update my service to call Node.js instead of creating random data.\nIn today's update, I'm building the &quot;Sauce view&quot;, or what you see when you click on review. It should show information about the sauce and all the reviews. I began by building the server side. I've already got a connection to Cloudant, so I literally just needed to add a route and a call.\n\nUnlike the call I did to get reviews, I'm not modifying the result. In theory, this is kind of bad because I'm passing data back to the client that i don't need, specifically a _rev key that my front-end doesn't need:\n\nBut for now, I don't care. I just mention it as something to consider. JSON is small, and Ajax is awesome, but we still shouldn't be wasteful about the crap we send back and forth, right?\nOn the client-side, the change was simpler. Here is the getSauce method:\n\ncc is an instance of the Cloud Code API I'm sharing in the service. I do modify the code a bit here, changing _id to id. I also slightly modify the result in getFeed too. Thinking about this, I believe it may be best for me to do all these modifications on the server so that my client-side code can use it as is. I don't think that's a super crucial modification so I won't worry about it now.\nFinally, I can run this baby in the emulator and see my live data:\n\nOk, that's it for now. Don't forget you can see the complete source code here: https://github.com/cfjedimaster/SauceDB. Next, I'm going to try building the Add Sauce view. This will require a valid login as well as security on the Node side.\n",
		"tags":[
	        
            "bluemix",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Some initial thoughts on building desktop apps with Ionic and Electron",
		"date":"Thu Jul 23 2015 05:34:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437629696,
		"url":"https://www.raymondcamden.com/2015/07/23/some-initial-thoughts-on-building-desktop-apps-with-ionic-and-electron",
		"content":"Earlier this week I was working with a desktop app (which I can't talk about... yet) that had an Ionic-look to it. On a whim, I opened up the package contents and discovered it was an Electron app. If you've never heard of it, Electron is an open source project that lets you build desktop apps (for Mac, Windows, and Linux) using web technologies. The last time I did anything in this space was with Adobe AIR, which was years ago. I've played with Electron a tiny bit, but I had not tried to use Ionic with it so I thought I'd give it a shot. Before digging in, I want to bring up two very important points.\n\nFirst, I've spent maybe three hours looking into this subject, and obviously have not built a production application yet. This post focuses on some of the things I ran into while testing things out, but it is safe to assume more issues probably exist. As time goes on I'll probably blog other things to consider and I hope my readers will share their own discoveries.\nSecond, while Electron makes it easy to build a desktop app, I cannot stress enough that you need to remember that app development is not the same as web development. When I talk about Cordova and how it makes it easy to use the web to build mobile apps, I try very hard to remind people that building a mobile app is nothing like building a web page. Repeat that after me: &quot;Building a mobile app is nothing like building a web page.&quot;\nOk, so with that out of the way, let's talk shop.\nThe Basics\nThe first thing I did was to see what would happen if I took a generic Ionic app and just plain ran it under Electron. To do that, I created a new Ionic app and used the -no-cordova flag. If you aren't aware, you can tell the Ionic CLI to create a new project and skip including all the Cordova bits. You still get a bunch of extra stuff like the bower file and gulp script, so I simply copied out the www folder.\n\nThen, following the Electron quick start, I added a package.json file and main.js. For my main.js, I copied their code exactly, but removed the bits to open dev tools. (More on that in a bit.)\n\nHere's how my directory structure looked. And again, this is the result of taking the www contents from Ionic's default template (tabs) and adding in the files Electron requires.\n\nAt this point I went and just tried running it with the Electron CLI. Surprisingly, it just plain worked. In fact, even the $ionicPlatform.ready fired. As far as I can tell, it noticed that cordova.js didn't load and assumed I was running the app on a desktop browser.\nSo as I said, it just worked, which was cool, but then I began digging in and figuring out bits and pieces of code I needed to rip out and modify.\nThe first thing I did was remove cordova.js since - obviously - this was no longer a Cordova application. Here's the index.html for my app with that modification. I also added a title value. This is going to come into play in a bit.\n\nNext, inside app.js I completely emptied out the run block. We don't need to test for the keyboard plugins, and ionicPlatform.ready does not make sense in this content. Electron lets you do 'Desktop stuff', but doesn't make you wait for an event in your code.\nI then noticed something odd. Here's the app running:\n\nAnd here it is with another tab selected:\n\nDid you notice the title? The title of the app changes as I change my view. Now, that could be nice, but to me, it doesn't make sense. The app should have a core title, like &quot;My App&quot;, the one I used in html earlier, and the header could continue to be more context-driven. Unfortunately, the code that updates the title is built into Ionic itself and can't be disabled. I raised the issue in an Ionic chat room, and Leandro Zubrezki spent some time helping me out. The following solution is 100% his idea.\nIn order to prevent the title from updating, you can listen for the $ionicView.afterEnter event and stop it. So for example:\n\nThis can be used in your controllers, but quickly gets repetitive. While talking with Leandro, he suggested using the root level state to define a controller and run it from there. Here is that top level state from the Tabs demo:\n\nThe change here is the addition of TestCtrl (not the best name). I then added this to the controllers.js file:\n\nAnd that was it! It worked fine at that point.\nOne more tip. Don't forget you can enable dev tools for your app from the menu.\n\nBy default this will appear in the app. Don't forget you can 'pop it' out using the icon highlighted below.\n\nThat icon actually has three states - right, bottom, and popped out. So if it looks like this:\n\nClicking will just send it to the bottom. Click and hold to open a menu and select the icon I showed earlier. Using this, I was able to have my dev tools nicely separate from the app which made debugging easier. I also made use of the Reload option too so I didn't have to restart the app from the command line.\n\nSpeaking of the command line, it is worth noting that console.log messages will show up",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Using JavaScript to integrate with the EventBrite API",
		"date":"Wed Jul 22 2015 07:44:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437551040,
		"url":"https://www.raymondcamden.com/2015/07/22/using-javascript-to-integrate-with-the-eventbrite-api",
		"content":"Almost a year ago I blogged about using the EventBrite API with ColdFusion. At that time, I was under the impression that all uses of the EventBrite API required a private OAuth token. This means it would not be possible to use the data on the client-side. (Unless you used a server to proxy the API calls for you of course.) But after speaking to Mitch Colleran from EventBrite, I was happy to discover I was wrong.\n\nTurns out, when you create an app on the EventBrite developer portal, you actually get both a regular OAuth token and secret as well as an anonymous oauth token:\n\nBy using the anonymous oath token, you can read any public data you want, all without having to use a secure key. This means you can use the EventBrite API in a JavaScript app (desktop, mobile, hybrid, etc.) without worrying about keeping your key secret. In fact, their API is so easy to use you can actually pass in the anon key right in the URL.\nAs an example, given that token represents your anon oath token, this will get all public events:\n\nObviously you probably want your own events, not the entire worlds. While it is trivial to restrict the results to your own organization, oddly it is still a bit awkward to get your organizer id. As I said in the old post, go to your profile and then your organization settings. You'll see your organizer URL with the ID at the end:\n\nYou can then pass it to the URL:\n\nIn case you're curious, the expand=venue option there just tells the API to return venue information. Without it, you get the ID of the venue and could do more HTTP calls to get it, but returning it all at once is simpler.\nOk, so why bother? EventBrite already has embed options, right? Well they do - and those are nice - but if you want more control over the embed experience then you'll want the ability to write out the data as you see fit.\nHere's an incredibly simple example that fetches events and displays them in a list.\n\nAs I said, this is simple, so simple I assume it just makes sense, but if not, let me know in the comments below. After building this for an organization, I then worked on making it a bit nicer. For example, I formatted the Date using Moment.js:\n\nI love Moment.js. By the way, that code to display the event is a bit brittle. For example, it assumes a venue exists when it may not have that data. It could also show more of the venue too. Basically, remember that EventBrite events are complex so you'll want to use conditionals and the like to determine what to display. And yeah, generating the layout in JavaScript like that is ugly. Check out my video series on JavaScript templating for ways to make this nicer.\nI then kicked this up a notch. The person who had asked for help mentioned they wanted to display events from multiple organizations. So I built a method that lets you pass in either an org, or an array of orgs, and your key, and then use a promise to return a sorted array of events when done:\n\nAnd to use it, you can do code like this:\n\nOf course, I don't have any error handling in there, but if you don't tell anyone, I won't. What's nice is you could take this code and easily use it with something like FullCalendar to create a nice, large calendar on your site.\nOk, so I was going ot go ahead and post the blog, but then I figured, why not go ahead and build a FullCalendar demo. All I had to do was modify the array of events to match what FullCalendar wanted:\n\nYou can run that demo here: https://static.raymondcamden.com/demos/2015/jul/22/test7.html.\nEnjoy!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "SauceDB - Building the back end with IBM Bluemix",
		"date":"Mon Jul 20 2015 08:40:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437381636,
		"url":"https://www.raymondcamden.com/2015/07/20/saucedb-building-the-back-end-with-ibm-bluemix",
		"content":"Welcome to another post detailing my efforts to build an Ionic-based mobile app backed by Node.js and Cloudant on IBM Bluemix. In my last post, I focused on the front end of the application. I talked about the various screens I built and how my service layer used mock data to generate data. In today's post, I'm going to setup, design, and connect a back end server to start replacing some of that mock data with real information. As a reminder, you can find the initial post in this series here,\n\nAlright - so as I mentioned, I'm going to use IBM Bluemix to host the application. I've talked about Bluemix before, but let me give you a quick refresher on what it offers.\n\nBluemix is a PaaS offering. If you aren't up to date on the latest acronyms the cool kids use, this is &quot;Platform as a Service&quot;. In short, it lets you set up applications and services on the cloud all via a simple dashboard and command line. There's a variety of different services, some I've already blogged about here before, and a large library of code you can add to your application to make using those services even easier. I've already set up my application in Bluemix, but let me walk you through what the process was like.\nAfter signing in (and you can sign up for a trial for free), I clicked on Create App:\n\nThen selected Mobile:\n\nAnd then the option for Hybrid:\n\nI clicked continue on the detail and then named the app. Note that in the screen shot below I used a slightly different name so as to not conflict with the app I already created.\n \nAfter you hit finish, your application is going to be staged and a number of default services assigned to it.\nAt this point, you have a few options. You can take an existing Node.js app and configure to work with Bluemix services, or you can take their boilerplate code and work with that. Since this is a new app, I recommend getting the boilerplate. You can find that by clicking &quot;Start coding&quot; in the left hand nav:\n\nThis leads you to a documentation page where you can download both the command line interface, something you'll only need to do once, and the boilerplate. As I said, I recommend getting the boilerplate as it has the required modules to work with Bluemix already defined in the package.json. It also includes some sample code that you'll probably want to remove. For example, I'm not using the Mobile Application Security service so I don't need the package. But it is a good place to start.\nIn case your curious, the &quot;Enable Node.js app&quot; you'll see on top...\n\nis just another link to documentation that you can read later. Whenever I see this UI I assume that is a required link, but it is not. To be clear, your Bluemix app itself is ready to kick butt right away, you don't need to flip some toggle. You do need to upload your code using the cf command line. That &quot;Start coding&quot; link I just mentioned explains how to do that. It's fairly simple and I won't repeat it here.\nFor SauceDB, I needed to modify the services set up by Bluemix. Here is what you get by default:\n\nSince I'm not using them, I removed both Mobile Application Security and Mobile Data. You can do that using the little gear icon in the upper right side of each service. To add in support for Cloudant, you simply click &quot;Add a Service or API&quot; and find the Cloudant service on the next page. As I said, Bluemix provides access to a bunch of services, so you can use the search form on top to filter them out:\n\nOk, so that was a lot, so let me quickly review. I use Bluemix to setup a new mobile application, gave it a new name, and then added a Cloudant service. I can now start building my Node.js app (again, using their boilerplate as a starting point), and then push to Bluemix when I want to deploy. For everything I've done with SauceDB so far, I have yet to actually push my app to Bluemix, which is good because I can work much quicker locally during development.\nSo, enough with the consoles and crap, let's talk code. First off, everything I'm showing below is available up on the Github project: https://github.com/cfjedimaster/SauceDB.\nOne of the first things I did was look for a npm module for cloudant. Here's the one I used: https://www.npmjs.com/package/cloudant. After adding it to my package.json and installing, I added the library to my app.js code and pointed to a database.\n\nThe URL value you see there, which comes from a credentials.json file that will not be in Github, comes from the service description in the Bluemix console. You can see it by simply clicking the &quot;Show credentials&quot; link:\n\nAlright... so I'm connecting to a database called sauces, which doesn't actually exist yet. If you click the Cloudant service ... um... &quot;box&quot; I suppose, the detail page will include a link to launch the Cloudant console:\n\nAt this point, you can use the &quot;Add New Database&quot; button to add a new database. I did this for &quot;sauces&quot;:\n\nMy goal now was to create some data so that",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Review: \"Build an HTML5 Game\"",
		"date":"Sun Jul 19 2015 10:22:10 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437301330,
		"url":"https://www.raymondcamden.com/2015/07/19/review-build-an-html5-game",
		"content":" I've said on more than one occasion that it was gaming that initially got me into programming. Initially just cheating at games (I hacked my Bard's Tale save and I modified the source code in Lemonade Stand), but what I really wanted to do was create my own games. That dream pretty much went away when I left the computer science program in college and discovered the web, but I've toyed with the idea of building my own web-based games from time to time. A while back, I was lucky enough to get a copy of &quot;Build an HTML5 Game&quot; by Karl Bunyan.\n\nAs you can imagine, the book walks you through the process of building a game using web standards. The game, a simple bubble shooter you can play here, is built iteratively throughout the chapters. You start building each piece one at a time, gradually adding more features and logic to the game. It felt like a good pace to me and I only really struggled during some of the mathematical parts. (I'm a bit ashamed to say that, actually. I was pretty good at math growing up but since I haven't really used complex math since my early college career it's all wasted away.)\nEven cooler, the author actually has you create the game with multiple different approaches. You get to experience using JavaScript to animate the DOM and then compare this to working with Canvas. I liked how the author compared and contrasted these approaches in a very practical manner.\nFinally, the book wraps up by talking a bit about more advanced topics, like performance tuning, WebGL and deployment issues. This is a good section, but pretty slim. I think in the next edition this could be expanded significantly. The book's definitely packed with information now, but making this last portion even larger could make a great book even better.\nIf you have any interest at all in learning how to build games with web standards, then I definitely recommend checking it out. You can find out more about the book, including seeing the table of contents, at the book's web site: http://buildanhtml5game.com/\np.s. As a slightly off topic aside, this was my first introduction to Modernizr. While I had certainly heard of it before, I had never actually used it. The game you develop in the book makes heavy use of it and I'm happy I had a chance to actually use it. I didn't know it had a loader feature with fallback support. That was pretty darn neat.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "games"
            
		]

	},

	{
		"title": "Using JavaScript, IndexedDB to cheat at WordBrain",
		"date":"Fri Jul 17 2015 02:45:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437101109,
		"url":"https://www.raymondcamden.com/2015/07/17/using-javascript-indexeddb-to-cheat-at-wordbrain",
		"content":"Warning - what follows is a complete waste of time. Do not spend time reading this blog post. Still here? Of course you are. For the past few days I've been addicted to a cool little game called WordBrain. It is a simple idea. You're presented with a grid of letters and must find two words within it by drawing a 'path' from one letter to the next. I like word games, but oddly have never really played any on my mobile devices before. Now I know why - they're incredibly addictive. While playing a few days ago I found myself stuck on one particular puzzle.\n\n\nI've obscured the hints in case you want to try to figure it out yourself before reading on. The obscured area though does provide you with a clue. You get the length of each word. In this case, the first word is five letters long and the second one is four letter.\nOne thing you run into very early in the game is that there will almost always be valid words that don't match. So looking at the puzzle above you'll see FONT, PINT, PITA, and possibly more words. They are valid, but not what the puzzle wants.\nAs I stared at this puzzle and got more and more frustrated, I naturally thought - I bet I could cheat at this! I'm absolutely pro-cheating in games. Heck, I learned to program because I wanted to cheat. I figured out hex so I could edit my Bard's Tale characters (and it worked, so hah).\nGiven that we know the length of a word, and we can figure out a 'path' through the grid, and assuming we can find an English word list, in theory, it should be possible to figure out all the possible words, right?\nHeck yes!\nI began by finding a good English word list: The English Open Word List. The web site describes the data like so:\n\nThe EOWL currently contains about 128,985 words. To make EOWL more usable for computer word games there are no words longer than 10 letters, and no proper nouns or words requiring diacritical symbols, hyphens, or apostrophes.\n\nSounds perfect, right? The source data consists of one file per letter, so I combined them into one big text file with a word per line:\n\nWhile some of the words were a bit questionable (like that last one), I figured it would be a good source set. I didn't want to keep all of this data in memory, so I decided I'd used IndexedDB to store the words. Here is how the application starts up:\n\nTaking it from the top, and ignoring globals variables and the such, I start off with a call to open up and setup my IndexedDB. This is fairly boiler plate, but I will point out the compound index was based on an initial idea I had for getting the solution. I ended up not using it. The seedData function just does an XHR and loads the word list. The file is 1.1 megs which is large, but not horribly so, and we only need to load it one time. I loop over the words and store each value in my IndexedDB object store. (And again, I'm storing the length because of some plans I had originally that changed while I was working. I'll detail that in a sec.)\nSo at this point, I've got a database of words. Now I need to ask the user for the length of the word they want to find and have them input the grid. I could have made something fancy, but I just used form fields:\n\nHow in the heck do we solve this? Considering we have a grid, we can iterate over every letter, and find every possible N-lengthed path from there. The game allows a path of any direction and you can't go over a previously used square. My initial thought was this:\n\nStart at the upper left and find the paths from there.\nWe'd have: RF, RN, and RO.\nWe can search the IDB for words that are N characters long that begin with RF, etc. If any match, then find paths from RF.\n\nIn theory, using the data above, RF and RN would &quot;die&quot; as possibilities but RO would not. I began to work down this path but had difficulty with the asynch nature. I thought about using promises, but... it just didn't click. To be honest, someone smarter could probably figure it out. I decided to take another approach.\n\nFor each letter, get all the N length paths possible.\nGiven a monster list of N length words, check to IDB to see if it is a valid word.\nProfit\n\nHere is my ugly, incredibly ugly, solution. Note I probably have multiple unused variables and bad practices here. Also, I just output to console. Oh, and I also assume a 3x3 grid. I'm pretty sure I could make the 'find paths' portion handle any square sized grid, but for now, I'm keeping it (somewhat) simple.\n\nGot all that? Nice and simple, right? FYI, I could have used a oncomplete for my transaction to tell the user when I'm done, but since I'm simply spitting out to the console, it doesn't matter. Also, since my code fires asynch, my &quot;don't repeat words&quot; code could fail too. Again, using an oncomplete would let me handle that better. Who cares! Let's see the results:\n\nWoot! Cool, right? I went through the words and then discovered something weird - none of them worked. So... I gave up and used the hints system the game doles out at certain ",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Safari and HTTP Caching",
		"date":"Thu Jul 16 2015 03:38:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437017916,
		"url":"https://www.raymondcamden.com/2015/07/16/safari-and-http-caching",
		"content":"Well, this was a weird one. Yesterday I was working on a project where I made multiple requests to the Random User API. It worked perfectly in Chrome, and Android, but in Safari, I noticed something odd. Even though I made multiple requests, every result was the same, not unique. Here is a simplified version of what I built.\n\n\nMy code was somewhat more complex (it had Angular, Promises, even kittens thrown in), but this gives you the basic idea. Running this in Chrome I get what I'd expect, 10 random users in the console:\n\nAnd ten requests in the Network panel:\n\nSo far so good. However, in Safari (well, Mobile Safari at first, but today I tested in Safari), something odd happened. Instead of ten random users, I got the same one again and again. (And before someone asks, no, it isn't the for loop or anything like that.)\n\nNaturally I thought - ok - Safari is caching the response. But here is what threw me for a loop. I went into the Timelines panel, turned on Recording, and this is what I saw:\n\nLooking at this, you can see Safari made one network request, which I suppose makes sense, but here is what ticks me off. Nowhere in this panel is any indication that it simply ignored my Ajax calls and used a cache result.\nTo be clear, I'm totally fine with Safari ignoring my request to an API that is random and deciding it knew better and should cache. Fine. What upsets me is that the dev tools do zero to let the developer know what's going on here. It should report the other 9 requests and flag them as being from a cache or some such. I guess I'll go file a bug report for this (no, I will, because that's the right thing to do), but damn was this frustrating.\nFor folks curious, I simply added &quot;?safaricanbiteme=&quot;+Math.random() to the URL - just like I used to do for older IE.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Article: Merging Dynamic and Static Sites",
		"date":"Thu Jul 16 2015 02:01:05 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1437012065,
		"url":"https://www.raymondcamden.com/2015/07/16/article-merging-dynamic-and-static-sites",
		"content":"The Telerik Developer Network has published another article from me: Merging Dynamic and Static Sites. The impetus behind this article was simple. I've been very interested in Static Site Generators (SSGs) lately but one issue you have with them is the lack of a way for non-technical users to work with them. In this article I create a proof of concept system where a Node.js app within some private customer network can be used to integrate with a SSG and publish to a cloud-based file system. Let me know what you think over on the article.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "SauceDB - Working on the front end",
		"date":"Wed Jul 15 2015 10:04:07 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436954647,
		"url":"https://www.raymondcamden.com/2015/07/15/saucedb-working-on-the-front-end",
		"content":"Yesterday I blogged about a new project I'm building to demonstrate both Ionic and IBM Bluemix. I've made some progress on the project so I thought I'd share what I've built so far. My thinking is that as this project goes on I'll continue to share these updates so folks can see how I approach projects. Feel free to comment, criticize and make suggestions!\n\nBefore I begin, note that I've checked in my code to the Github repo: https://github.com/cfjedimaster/SauceDB. You can find the bits I'm covering today in the mobile folder.\nThis release focuses on the front end. I've decided to try to get as much of the mobile app done as possible. By using services in my Angular app, in theory, I can mock everything up with fake data and then - in theory - just replace with calls to the Node.js server. I'm not going to pretend that I think this will happen perfectly, but I find that if I focus on one aspect of the project at a time I can be a bit more productive. So let's look at the screens done so far.\nLogin\n\nThis has a lot of wasted space - and I'd imagine some nice logo above the button - but as I don't have a logo yet (any volunteers) I've let it simple. The Facebook button is going to use the OpenFB library I mentioned before. Let's take a look at the controller.\n\nThe first thing you'll notice is a &quot;skipAuth&quot; flag. As you can guess, this lets me skip login while testing. Since I'm focused on building out my views and basic integration, I didn't want to have to relogin every time the page reloaded. When that little hack isn't enabled, you can see how I use OpenFB to both authenticate and then call the API to get information about the current user. My thinking here is that I want some basic details about you (email, name, profile picture) so I can greet you by name.\nFeed\nNext up is the &quot;Feed&quot;/Stream/etc view. This is meant to show all the most recent sauce reviews. Here is how it looks - and yes - it definitely needs some love. This also handles the &quot;Add Review&quot; logic of writing your own sauce reviews. For now though we'll focus on the feed.\n\nLet's take a look at the code. First, the controller.\n\nThe part that gets the feed simply speaks to the dataService I mentioned earlier. I'll talk more about 'Add Review' in a bit. Here's the service method handling the feed:\n\nFor absolutely no good reason, I decided I'd make use of the Random User Generator API for my mocked data. On reflection that seems kind of stupid and a waste of time, but it was fun to check out the service and get 'real' pictures/names in the feed. Plus, it made the service a bit slow which felt a bit more life like. Each feed item consists of a sauce object (just the name, company) and a review object (user, rating, etc). As I write this, I see that &quot;avgrating&quot; really should be part of the Sauce object (something I naturally did in other code) so I'll have to fix that soon.\nClicking on Add Review pops open a modal. The idea for the modal is to let you search for a sauce to see if it exists. If it does, you can select it and then just write your review. Otherwise you need to include the sauce name and company when writing the review. I built a very simple &quot;search as you type&quot; service in the modal window:\n\nI showed the search code at the controller layer up above. The search service itself is hard coded:\n\nAdding a Review\nMy static list is short, but useful enough for testing. For now, I don't support adding new sauces, so I just bound the list to a new review form:\n\nThe fancy star widget there came from https://github.com/fraserxu/ionic-rating. It's rather easy to use. You can see it in the view below:\n\nFor now, clicking the Add Review button just returns the user to the sauce.\nSauce View\nSpeaking of - you can also view a sauce and all its reviews. This really needs some formatting love:\n\nFor yet another completely silly reason, I changed the user pics here to kittens. Because kittens. Here's the service method that &quot;fakes&quot; a sauce retrieval:\n\nSo there ya go. It isn't pretty - but it is coming together. Tomorrow I'm going to switch to the server side and start setting up both my Cloudant database and the Node.js application.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "FYI - Cordova events must be run after deviceReady",
		"date":"Wed Jul 15 2015 02:53:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436928816,
		"url":"https://www.raymondcamden.com/2015/07/15/fyi-cordova-events-must-be-run-after-deviceready",
		"content":"In all of my work on various Cordova projects, I've only rarely needed to make use of the various events supported by the platform. Last night I needed to add some code to handle the back button. The docs clearly tell you to register your handler after deviceReady has fired:\n\n\nTo override the default back-button behavior, register an event listener for the backbutton event, typically by calling document.addEventListener once you receive the deviceready event.\n\nBut obviously I know better. I mean - it's an event, right? So it shouldn't matter when we add the listener. Sure, if the user hits the back button before deviceReady fires, I assume my handler won't run, but it should be safe to register it whenever, right?\nNope.\nAfter bringing this up in the Slack channel, @devgeeks pointed out this little snippet from the Cordova JavaScript library:\n\nEssentially, Cordova modifies the default event listener in your web view so it can actually handle some of those special events. So, I guess the point of this post is - yes - it really does matter where you add your event handlers in regards to the events Cordova gives you access to!\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Quick Tip: Navigating in Ionic without History",
		"date":"Tue Jul 14 2015 07:06:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436857571,
		"url":"https://www.raymondcamden.com/2015/07/14/quick-tip-navigating-in-ionic-without-history",
		"content":"Before I start, just a quick note. What I'm describing here is clearly documented, but as I keep reminding myself I've yet to read 100% of the Ionic docs and I really need to. A big thank you goes out to @breakingthings on the Ionic Worldwide Slack channel for letting me know about this. So here's the question. Imagine you have an Ionic app with a login screen:\n\n\nAfter logging in, you want to automatically move the user to a new state:\n\nBut when you do, you end up with this in your header:\n\nThat link back to the Login view comes from how Ionic handles view history and the header. Most of the time you probably want that, but in this case, I definitely do not want it. Luckily it is rather simple to fix using $ionicHistory:\n\nYep, that's it. Nice and simple. And just in case it isn't clear, this modification only impacts the next change.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "New Demo Project: SauceDB",
		"date":"Tue Jul 14 2015 04:27:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436848076,
		"url":"https://www.raymondcamden.com/2015/07/14/new-demo-project-saucedb",
		"content":"Today I'm kicking off a new project for the purposes of demonstrating Ionic and IBM Bluemix. While I'm not sure I'll actually release this application (I'm building something I actually want, so I kinda want to), my intent here is to build a full application demonstrating multiple facets of Ionic with Bluemix handling the back end. Let me start off by talking about what this application actually does.\n\nThe App\nI'm a huge fan of Untappd and Goodreads. For me, these apps help me organize two of my favorite passions in life - good beer and good books. (And hey, Budweiser, you can officially take your anti-micro-brew campaign and shove it where the sun doesn't shine.) Both sites/apps follow the same basic principle - they allow you to track and rate things are you consuming. I have pretty crap memory, so I find both apps to be incredibly useful. Both also include a social aspect to them that - honestly - I don't really care about. I do like to know what beers my friends are enjoying and what books they've read, but I typically talk about that in the real world. I can't honestly remember the last time I used either app to check what other people are doing.\nMy application, SauceDB, is basically a BBQ Sauce version of Untappd. The features will be:\n\nSauce stream (um, that sounds kinda gross): Essentially a 'feed' of the most recent sauce ratings. As I mentioned, I don't necessarily care much for the social aspect, so this app won't have \"friends\". Basically you see what everyone has posted. (And since this is a 'proof of concept' with few users, it will make it easier to see content.)\nAdd a sauce review: Sauces include a name, a company, your rating, and a description of the sauce. Optionally a picture as well. The app will recognize existing sauces and not add a new record for the sauce if it already exists.\n\nI whipped up a quick prototype on paper first. Here is my completely incomprehensible attempt at drawing screens.\n\nI'm actually pretty impressed by how bad my drawing is. If I was a D&amp;D character and Drawing was a skill, I'd have a -5 in it. In case you can't read my chicken scratch, the screens are:\n\nLogin\nFeed\nFeed item (ie, a sauce review)\nSearch to add a new review\nNew item (ie, adding a new sauce + review)\nNew review only (adding a review to an existing sauce\n\nI don't create prototypes often, but I definitely see the value. Just typing out the list there makes me realize I don't have a page that is Sauce-centric, ie a Sauce with all the reviews. I went ahead and set up a &quot;real&quot; prototype using Ionic Creator.\n\nI essentially built all the screens you saw from the pen drawing above and added some basic interactivity. So for example, the login button goes right to the feed. Doing this was also helpful. For example, the 'feed' page (see the shot above) used a list view, but I'm pretty sure I'll switch to a card view to give the reviews more space.\nIonic Creator has multiple export options, including a way to use it as a seed for a new project.\n\nI find the output of Ionic Creator to be a bit undesirable, so I used this in a new project just for the Creator output. I then Surged it up to a real site: http://jittery-bait.surge.sh.\nThe Tech\nSo what does my stack look like?\n\nObviously, Ionic. I've made no secret of my opinion that Ionic is the absolute best tool to use for building hybrid mobile applications with Cordova. \nOpenFB, a Facebook API library by Christophe Coenraets. I've used ng-cordova-oauth in the past, but I just need FB now and I need a way to use their API as well.\nNode.js running on Bluemix. This will essentially be a proxy to...\nIBM Cloudant for the database. \n\nSome &quot;possible&quot; stuff I want to add too - time permitting:\n\nIonic Analytics\nPictures (no idea where I'll store the file blobs yet)\nPush (maybe a notice for every new review - again - this will be a pretty low used app)\n\nI'll also be sharing everything I've built up on Github. My code base is currently a bit messy, so the repo is empty, but it will be here: https://github.com/cfjedimaster/SauceDB.\n",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Review: Dave vs the Monsters",
		"date":"Sun Jul 12 2015 05:58:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436680738,
		"url":"https://www.raymondcamden.com/2015/07/12/review-dave-vs-the-monsters",
		"content":"I've been a fan of John Birmingham since I picked up the first book of his Axis in Time series. It felt like a more sci-fi infused Clancy series. The concept was pure geek day dream - send a fleet of modern day warships back in time and see how they impact history. But Birmingham did a great job of of showing the cultural impact of this transfer as well as the providing epic battle scenes.\n\nI first heard of his new series, &quot;Dave versus the Monsters&quot; a few months ago, but had to wait until his novels became available in America. The premise is simple - an oil rig digging deeper than any before it opens a portal to another world. Take all the bad guys from a typical fantasy setting (orcs, demons, dragons, insect ninjas) and you've got the UnderRealms. (Yes, no space. My only complaint in the entire trilogy. It felt like the bad guys came from a Silicon Valley start up - which probably is how the world will end.) The denizens of this realm used to have contact with us and considered us nothing more than cattle. Obviously in the thousands of years since they last saw us we've changed a bit.\nAs the portal between our worlds opens up, we meet Dave, the main character of the series. As a hero he leaves a lot to be desired. In fact, he is a complete and utter ass. I've read through books before with a main character who is essentially an asshole, but I never found myself bothered by it in this series. As much of a disappointment this guy is, you can't help but root for him as the story progresses. Early on, he does something that grants him - essentially - super powers - which helps him fight against the untold millions of creatures from the other world.\nOn one hand, you've got your basic geek fantasy - what happens when very strong fantasy creatures encounter a modern military force? You can probably guess. But things take a rather interesting turn after the first few massacres. The bad guys wise up and react in a way that I would never have guessed. I think any genre nerd could write a few cool battles between orcs and SEALs - but Birmingham takes this story in a much more intelligent direction that made the series very satisfying.\nWhat was truly surprising though was the humor, especially after the first book. I've read and enjoyed multiple books by Birmingham but I don't think any of his previous books ever made me laugh at loud. There was one scene in the second book that I distinctly remember having a stupid big grin on my face as I read it.\nI definitely recommend picking it up (and the Axis of Time series while you're at it). Birmingham is also on Twitter (@JohnBirmingham) and one of those author's who actually respond to questions:\n@raymondcamden the talons of the Lore Keepers kept missing the space bar. So they just typed it as one word&mdash; John Birmingham (@JohnBirmingham) July 8, 2015\n\nYou can get all three books below:\n\n\n\n \n\n\n\n\n\n\n\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "books"
            
		]

	},

	{
		"title": "Combining client-side social login and server-side authorization with Cordova and Node",
		"date":"Fri Jul 10 2015 07:57:20 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436515040,
		"url":"https://www.raymondcamden.com/2015/07/10/combining-client-side-social-login-and-server-side-authorization-with-cordova-and-node",
		"content":"I believe this wins the title for the longest blog title ever. So what in the hell am I talking about? It isn't too difficult to add a social login aspect to your Apache Cordova application. I've used a variety of plugins/libraries in the past and for the most part - it just plain works. Recently however I ran into an issue that I didn't know how to get around. Given that your mobile client authenticates a user against some OAuth provider, and given than you then allow that user to interact with your server, how do a) ensure that the person running the server API is authenticated and b) how do you uniquely identify that user? That's a bit abstract, so how about a real example?\n\nImagine you are building a mobile app that lets a user write notes. While you could store the notes on the client, you really need to store them in your own back end storage. In order to ensure that only authenticated users access your server APIs, you could build your own user system, have the user login (over https of course), store a session variable with their account info, and when they create data (or attempt to read data), use a primary key of some sort to get the right data.\nThat's fairly boilerplate. But what happens when you mix in social login on the client-side? In that case, the mobile app handles the OAuth request to the provider and the provider returns a token. So on the client-side you know the current user is kosher. But when she makes a request to your Node (or ColdFusion, PHP, etc) server, how do you handle knowing the user was logged in - and even better - how do you get a unique identifier for that user so you can properly handle their data operations.\nI did a bit of searching and came across this Stackoverflow post/answer:  iOS &amp; node.js: how to verify passed access token?. User Gijs responded that different OAuth providers have different ways of authenticating an access token. So the idea would be to pass the token to your back-end server, verify it, and then get a unique id.\nI decided to give this a shot and build a simple proof of concept. This is very rough, but gives you a basic idea of how you could do this. For my POC, I decided to use Christophe Coenraet's OpenFB library. It is a plugin-less JavaScript library that handles OAuth. Most recently I used ng-cordova-oauth as well, but I wanted to try something different. I created a quick Ionic application with two simple buttons:\n\nThe first button fires off the OAuth process. I followed Christophe's directions and created my Facebook app first of course:\n\nThe code behind this is pretty simple:\n\nNote I store the access token so I can use it later. Next - we need to call the server. If I remember right, Angular provides a way to modify all HTTP requests to include stuff. For now, I'm keeping it simple and just including the token as part of a form post.\n\nOk, so far so good. Now let's turn to the Node side. As mentioned in the StackOverflow post I linked to above, you can make a request to graph.facebook.com with the token and if you get the proper response, you know you're good to go. I wrote a function I could use for middleware later on. It caches the test in the session scope which seems safe, but maybe that isn't a good idea.\n\nSomething to note here: I'm specifically asking for the email address back. My result looks like this:\n{ email: 'raymondcamden@gmail.com', id: 'abigassnumber' }\nIn theory, the ID is unique enough. However, if I use multiple different oauth providers, I can instead use the email as a primary key. That would let you login via Facebook or Twitter and have the same data as long as you have the same email address being used for both accounts. Note that I do not actually store that email address. I would in a real app. Finally, here is the route I called from the mobile app:\napp.post('/test1', secure, function(req, res) {\n\tconsole.log('attempting test1');\n\tvar msg = req.body.msg;\n\tconsole.log('msg was '+msg);\n\tres.send(\"1\");\n});\nIt is pointless, but at least demonstrates using the secure function to wrap the route.\nAs I said - this is rough - but it seems to make sense to me. How about you?\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using friendlier URLs for a HarpJS Static Site",
		"date":"Wed Jul 08 2015 03:38:45 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436326725,
		"url":"https://www.raymondcamden.com/2015/07/08/using-friendlier-urls-for-a-harpjs-static-site",
		"content":"Ok, &quot;friendly&quot; is in the eye of the beholder, but honestly, I spent about five minutes trying to think of the &quot;best&quot; title for this post and just gave up. What I'm really trying to describe here is how to use &quot;year/month/date/slug&quot; type URLs for a HarpJS static site. As an example, the URL for this blog entry includes the year, month, day, and a 'slug' or &quot;URL-friendly&quot; version of the title. In general, Harp doesn't care what folder structure you use, but the issue I ran into was how to handle metadata about the blog. So for example, how do you generate a list on the home page of the most recent articles and create an RSS feed?\n\nBy default, HarpJS asks you to place a _data.json file in your folder to associate metadata with content. That works fine if you have one folder for your entries, but for my demo, I've got a bunch of folders. One for the year, one for the month, one for the day, and finally, one for the article. Heres an example of just how deep/complex this can get:\n\nYou could, if you wanted, create a _data.json file in every single folder. That wouldn't be horrible per se, but if you ever change the structure of your metadata, to add categories for example, you would need to update a butt-load of files. So I wanted to figure out another solution that would make handling the metadata easier. I also wanted a solution that emphasized ease of content creation. I didn't necessarily mind if the core solution itself was complex, but the actual act of writing a blog entry should require minimal work/direction/etc in order to publish. Here is what I came up with.\nFirst, I created a _data.json file for the root of my site. Within it, I create a key called &quot;articles&quot;. Within it, I'm going to list my articles. The idea is to be similar to the default metadata examples Harp provide, but using an array instead of an object. Here is a sample, with some of the data removed to make it shorter:\n\nEach article instance contains a title, a posted value, and a path. Why the path? One of the things Harp provides when using _data.json is an automatic &quot;sync&quot; between your current URL and metadata. In order words, if you are at url /article/foo.html and you had a &quot;foo&quot; key in the article folder's metadata, Harp will copy that data to the global scope automatically. As a practical example of that, you can use &quot;title&quot; in your layout and have it default to a global value but then automatically switch to the article's title when viewing that page.\nThat behavior is nice, but we won't have it because we're skipping the &quot;put a data file in every folder&quot; thing. I'm ok with that. The article writing process now is:\n\nMake your folder:\n\nyear, if the first entry of the year\nmonth, if the first entry of the month\nday, if the first entry of the day\n\"slug\" - friendly version of the title\n\nWrite your blog article.\nAdd to the list of articles in the root _data.json file.\n\nEverything outside of step 2 there is &quot;process crap&quot; that I want to minimize, and I think that's pretty good.\nSo how about working with the data? How do we create a list of articles on the home page? I began by creating a file called _article_parser.ejs. The idea behind this file was to do various &quot;parsing&quot; things for my article data so I had easier access to them in my templates. (As a reminder, files that begin with _ in Harp are automatically ignored when generating the static version.) Let's take a look at the code and then I'll explain what it does.\n\nThere's two main blocks here. The first grabs the article array and performs a sort on it. I wanted to ensure that when the author entered data, they didn't have to worry about putting things in a particular order. Most likely they will always just add to the end or the bottom, but this code ensures I don't have to worry about it. Note the use of var for articlesRaw versus articles. Var scoping articlesRaw keeps it to this file, while not scoping articles means I can use it outside in my template.\nThe next block is a bit weird. As I mentioned, when we don't have a &quot;url to path&quot; match in data files, we don't have an easy way of having global variables, like title, automatically switch when viewing a particular file. The second code block handles this by looking at the current scope. The current scope provides information about where you are in the site. I simply assume that if we're at a place with 5 levels, we're viewing a blog entry. I could make this a bit tighter. For example, I could look at the first part of the path and see if it is all numeric and 4 characters, but that wasn't necessary now so I kept it simple. I compare the path from the current data to the path in my JSON file and if there is a match, I have new title to use.\nSo, how do we use this? Check out my home page:\n\nFor the most part, this is pretty similar to other Harp demos. The only difference is that I loaded up my article &quot;lib",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "jamstack"
            
		]

	},

	{
		"title": "Using the Google Analytics Embed API to Build a Dashboard",
		"date":"Tue Jul 07 2015 03:29:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436239758,
		"url":"https://www.raymondcamden.com/2015/07/07/using-the-google-analytics-embed-api-to-build-a-dashboard",
		"content":"About a year and a half ago I created a demo (Proof of Concept – Dashboard for Google Analytics) of a Google Analytics Dashboard. This demo was entirely client-side based and made use of the API libraries Google created. For the most part, the process was fairly simple. After I figured out how to authenticate the user and query the data, I spent more time making it look pretty than actually working with the API, which is a good thing. More recently I discovered the new Analytics Embed API. The cool thing about the Embed API is that it greatly simplifies the authentication/authorization aspect of getting analytics data and even provides built in charting capabilities. I blogged an example (Quick example of the Google Analytics Embed API) and I thought it might be fun to revisit my dashboard concept using this simpler API.\n\nBefore I show the code behind this demo, let me share a screen shot so you can see it in action. I switched to a new charting library for this demo (ChartJS) mainly because Google used it themselves in one of their demos. I didn't spend much time making this pretty - I was going for 'quick impact' in terms of visuals. Obviously though it could be better.\n\nEach chart represents one of my properties. Each line represents page views per days, with the darker blue being the most recent page seven days and the lighter gray being the seven days before that.\nThe code is made up of several parts. The authentication, as I said, is handled almost entirely by the embed API.\n\nIn order to get profiles for my account, I make use of a management API. getProfiles handles fetching, and caching, this result. (I use caching as I had planned, still plan on, adding a few additional filtering options to the report.)\n\nNote that I do not make use of promises in this block and that's a mistake. I make use of it a bit later in another function so I need (well, I want) to be consistent. Now for the fun part. For all of my properties, I need to fetch data for each site. I was able to rip code from one of Google's demos but I quickly ran into rate limit issues. To get around this, I single thread the calls and add a slight delay.\n\nAnd that's basically it. As I said, I'd like to add a few options to this. Specifically, the ability to compare the current and past 30 days as well as this year versus the past. I'd also like the ability to dismiss/hide some sites I don't care about. I could use LocalStorage to remember those properties and hide them automatically.\nWant to see the complete code and test it yourself? Check out the online demo here: https://static.raymondcamden.com/ga_embed/test10.html\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Recording from Static Sites Presentation",
		"date":"Mon Jul 06 2015 01:36:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1436146571,
		"url":"https://www.raymondcamden.com/2015/07/06/recording-from-static-sites-presentation",
		"content":"If you missed my presentation on static site generators last week, you can now watch the recording here:\n\n\nYou can view a copy of the slides here:\n    Static Sites - Bringing Web 1.0 Back  from Raymond Camden \nAnd finally, you can find the source code for the slide deck and demos here: https://github.com/cfjedimaster/Static-Sites\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Using Angular and a Content Security Policy? Watch out for this...",
		"date":"Fri Jul 03 2015 02:57:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435892236,
		"url":"https://www.raymondcamden.com/2015/07/03/using-angular-and-a-content-security-policy-watch-out-for-this",
		"content":"Edit on July 6, 2015: Kevin H, in the comments below, pointed out that the docs for ngShow actually talk about this! I missed this completely. The solution the docs recommend, including an additional CSS file, worked fine for me, and feels like a \"better\" solution than mine, so I recommend following the docs lead.\nI've been working on a Cordova app that uses AngularJS. It has been working fine for a while now but I needed to do a bit of cleanup. After I pushed up my changes, my client noticed something odd. We have a button that only shows up under certain situations. Basically, if some scope variable is true, we display it.\n\n\nAll of a sudden, the button began showing up in all cases, even when the variable was false. I began to debug. I double checked to ensure my variable wasn't being set true when it shouldn't be. That wasn't the problem. I then added some simple tests to my view.\n\nAs you can see, I checked both the negation of the variable an the variable itself. I also output it, and the negation, as well as the JSON version of the variable. Finally I did two tests using ng-if.\nHere is where things got freaky. Both of the first two ng-show tests showed up! Even though the value in the third line was exactly what I expected - false.\nAnd to make things even more weird - the ng-if's worked perfectly! At this point, I was just considering switching to ng-if, but then I punted and asked over on StackOverflow. I had a long &quot;conversation&quot; with user thsorens and he reminded me that ng-show/ng-hide will add/remove CSS classes based on the condition.\nThen I paused and thought a bit. When I did my 'cleanup' of the app, I made a few different changes. For example, I had been using a remote Angular JS library and I switched it to a local one. I double checked to ensure it wasn't a version issue. I also made use of a Content Security Policy. I talked about this on my blog a few months ago. As a reminder, it is a way to lock down the resources your Cordova app, or any web app, can make use of. On a whim, I renamed the meta tag that defined the CSP and bammo - things worked!\nI didn't want to just remove the CSP, so I dug a bit deeper. It occurred to me that JavaScript was being used to create and manipulate CSS, so I looked at the script-src I had defined:\nscript-src 'self'\nI then though... I bet Angular is using eval. So I added this:\nscript-src 'self' 'unsafe-eval'\nAnd that did it. Here is a code sample that demonstrates the issue. First, the HTML:\n\nAnd here is the JavaScript:\n\nIf you remove 'unsafe-eval' from the meta tag, it stops working. If you would rather just run a live demo of this, view this Plunker I created: http://plnkr.co/edit/Hqo4G2NqwwAQU3Tu001J?p=preview. You can only see this in Chrome as it appears Firefox doesn't support CSP yet. The issue shows up in Safari as well.\nUpon running the Plunker, you will see the ng-show's failing to correctly work. Just go into the meta tag and add 'unsafe-eval' to the script-src area and it will work correctly.\nAt the end, a very understandable issue I suppose. The lesson here is that while CSPs are a powerful tool to lock down your web app, you're going to need to look out for side effects like this.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionic Push example supporting State Changes",
		"date":"Thu Jul 02 2015 08:51:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435827079,
		"url":"https://www.raymondcamden.com/2015/07/02/ionic-push-example-supporting-state-changes",
		"content":"Please note that I'm writing this blog post while Ionic Push is still in Alpha. Everything I discuss here may change before 1.0 is released. Use the following code with caution. One of the more interesting features of Ionic's Push feature is the ability to recognize a push message with state data. You can craft a push packet that not only includes a state value but state params as well. This is discussed in the FAQ:\n\n\nI want my app to open to a specific state when I receive a notification.\nIn addition to handling this in the onNotification function described here, you can specify which $state a notification should open your app to using the push payload. Below is an example JSON object highlighting this.\n\n\n\nThis works well, but I ran into a few gotchas while playing with it so I thought I'd write up a quick demo of it to help others. For my demo, I created a new Ionic project using the Tabs default. I then followed the directions in the quick start to get Push setup. One thing that may trip you up right away is that you have to ensure your application always runs the push registration call. The quick start guide has you build a demo where registration is only run when a button is pushed. In some cases, that may make sense. You may not always want to register for push notifications. Most likely though you will. So to start off, here is my modified run() block from app.js that includes push registration.\n\nI also want to point out two important settings. First, you probably want canShowAlert to be false. If not, then every notification will fire an alert in your app. Most likely your app will respond to notifications in different ways. Sometimes with dialogs, sometimes not, but probably not always one way or the other. Secondly, to actually enable &quot;respond to state so and so&quot;, you want to set canRunActionsOnWake to true.\nCool - so almost there. I then create a Push notification via Curl that included the state info. The Tags application includes a state called tags.chat, so I decided to use that as my 'target':\ncurl -u appkeyThingy: -H &quot;Content-Type: application/json&quot; -H &quot;X-Ionic-Application-Id: 6aef0d7b&quot; https://push.ionic.io/api/v1/push -d '{&quot;tokens&quot;: [&quot;a device token&quot;],&quot;notification&quot;:{&quot;alert&quot;:&quot;ray&quot;,&quot;ios&quot;:{&quot;payload&quot;:{&quot;$state&quot;:&quot;tab.chats&quot;}}}}'\nYou want to be careful when crafting the JSON of your notification. The server is pretty good about noticing mistakes, but on more than one occasion I screwed that part up. Note that you include the state information in the payload section. Also note that I didn't bother sending state params either.\nSo... this worked! And it was cool! Then I tested again and it failed. Sad kitty. Why? Did you notice the preference back there in register was canRunActionsOnWake? Specifically - &quot;OnWake&quot;. So this feature only works automatically if the application is woken up - not if it is active.\nIf you think about it, that makes sense. As a user, I don't want the app automatically shifting focus when a notification comes in. However, you may be curious as to how you could possibly handle this. I modified my notification handler to add in some new logic - just for this:\n\nThe modification is simple. I had added the Dialogs plugin and then made use of it in my handler. If I see a $state parameter I simply ask the user if they want to view the chats. If they say yes, I switch to it.\nNow - to be clear - my logic could be a bit tighter. I don't actually look at the $state value. I should see if it exists and if is tabs.chat, but you get the idea. Here it is in action:\n\nYou can grab the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/push2.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Happy 20th, ColdFusion",
		"date":"Thu Jul 02 2015 03:12:42 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435806762,
		"url":"https://www.raymondcamden.com/2015/07/02/happy-20th-coldfusion",
		"content":"\nToday is the 20th anniversary of the release of ColdFusion. While I don't do nearly as much ColdFusion as I used to, I'll always be appreciative of how much ColdFusion has helped me in my career. To this day, ColdFusion remains one of the most powerful, easy to use application servers out there. It also has one of the best communities you can ask for. I can still remember the first time I used ColdFusion. I had been using Perl CGIs for web apps for a while, but the company I worked for got a client that needed integration with SQL Server. At that time I believe ColdFusion 2 or 3 had been released. I downloaded the software and had a working prototype in about thirty minutes. I was sold. I gradually dropped Perl and began doing all my web development in ColdFusion. I began to share my code (via the Allaire Tag Gallery, remember that?) and present at conferences. In fact, I was lucky enough to speak at the first ColdFusion conference in Fort Collins:\n\n(Thanks go to Cameron Childress for sharing that pic - his collection of shots from the conference may be found here.)\nI can also thank ColdFusion for giving me an opportunity to get published. I think ever avid reader dreams of writing a book, and while I had always hoped to be the next Stephen King, I can still remember the excitement of seeing my name in a real live book.\n\nIt would take a few more revisions before my contribution was enough to get my name on the cover. ;)\nAdobe is currently working on ColdFusion 12, and I'm excited to see what will be added. I'm also excited about the upcoming ColdFusion summit this fall. To learn more about ColdFusion's history and this anniversary, check out the official ColdFusion blog: Twenty years of making web happen – Happy Birthday, ColdFusion!\nAlso, for the next nine hours or so, you can get a free copy of Matt Gifford's excellent Object-Oriented Programming in ColdFusion book!\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Online presentation - Working with Static Sites",
		"date":"Thu Jun 25 2015 04:38:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435207119,
		"url":"https://www.raymondcamden.com/2015/06/25/online-presentation-working-with-static-sites",
		"content":"Next week (July 1st), I'll be giving an online presentation to the Boston CFUG. This presentation will be given via Google Hangouts and is open to anyone and everyone. I'll be talking about one of my new passions, static sites. If you can't make it, the recording will be available on YouTube afterwards.\nHere is the Hangouts on Air link: Forget Dynamic - All the cool kids are doing Static Sites!\nIf you think you can make it, please RSVP at the link above.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Keyboard Tip for the iOS Simulator",
		"date":"Wed Jun 24 2015 09:01:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435136513,
		"url":"https://www.raymondcamden.com/2015/06/24/keyboard-tip-for-the-ios-simulator",
		"content":"I've been using the iOS Simulator for a few years now and never noticed this trick before. Thanks go to Tommy-Carlos Williams for teaching me this. Have you ever noticed that sometimes when you type in a form field inside the simulator that the virtual keyboard doesn't slide up? For example:\n\n\nNotice the cursor in the field? I can type there, but the virtual keyboard isn't up. It just so happened I wanted the virtual keyboard to show up. I was testing a bug in an app where users hitting the Go button were ending up opening an action that was tied to another button. In order to actually get that keyboard, you need disable &quot;Connect Hardware Keyboard&quot; in the Hardware menu:\n\nDoing so gets you the virtual keyboard - and obviously requires you to use it:\n\nI'll add that I've seen some gremlins with this. Just a few minutes ago I was testing with a web page search form and it consistently let me type with the keyboard and showed a virtual keyboard, but now I can't reproduce it so... yeah... gremlins. Any way, hope this tip helps.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with Ionic, Box, and IBM MobileFirst",
		"date":"Wed Jun 24 2015 03:02:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1435114950,
		"url":"https://www.raymondcamden.com/2015/06/24/working-with-ionic-box-and-ibm-mobilefirst",
		"content":"Earlier today IBM announced a new partnership with Box. Box is a cloud storage provider much like Dropbox, OneDrive, and other services, but also provides some pretty cool workflow features as well. While it is still early, you'll soon see some interesting collaborations between IBM and Box. I decided to see how easy it would be to integrate Box into a hybrid mobile application using both Ionic and IBM MobileFirst. This is just a simple proof of concept, but it demonstrates how you can use all these different pieces together in one application.\n\nBefore diving into the code, let's look at a few screen shots of the application in action. On loading the application, you will see a button prompting you to login with Box.\n\nClicking this button will begin the authentication process. You need to have an account with Box.com of course.\n\nAfter logging in, you have to allow the application access to your data:\n\nAfter you've allowed the app to access your Box account, you can then begin working with your data. For my demo, I simply let the app upload images from the device to the Box account. (You could modify the code to allow new pictures to be taken with the camera too. Since I was testing with the simulator, I limited it to existing pictures.)\n\nAfter selecting the image, I display a thumbnail and then upload it.\n\nIf you have your Box account open in a browser (they have a desktop client as well), you can see the image appear.\n\nAnd that's it. The Box API allows for full access to Box content, more then just uploading. You can even use a special View API to display renditions of Box content. It is a pretty great API and I encourage you to read more about it on their developer site. So - let's talk about the code.\nIn order to handle the OAuth, I used a great library from Nic Raboy called ng-cordova-oauth. It provides OAuth support for a butt load of different services, with Box being one of them. How simple is it? Here is the code behind the button you saw in the screen shot above.\n\nYep, that's it. Then using the API itself is rather simple. I first wrote some code to just test hitting the API, in my case, requesting folders at the root of the account. Here is how I did it using Angular's $http service:\n\nThe only real interesting part here is setting the OAuth token in the header. You can see that is one simple line before the get. Technically I only need to do this once and should set it after logging in - but as I said - I wrote this just as a test of the API. File uploads were a bit more complex. Instead of using $http, I used Cordova's FileTransfer plugin. This let me upload the image file selected by the user. Here's the entirety of the operation including the camera selection and upload.\n\nAnd that's it. I then mixed in MobileFirst - specifically the logging service. I blogged about this a few months back (). It is a rather simple API I can make available via a service in my app:\n\nThen when I inject Logger into my controllers, I can just do Logger.log(&quot;some message&quot;). There were a few examples of that above. Then when my application is out in the wild, I can look at my analytics in my MobileFirst server:\n\nWant to see all of the code? You can see all the code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/boxdemo_mfp. Note that I ran into two small issues with Nic's OAuth plugin. The first is that after authenticating with Box, you will see a 404 error temporarily. Nic already has a fix for this in the dev branch of his library. It is harmless and can be ignored. The second issue was specifically involving his code running in MobileFirst. Plugins act a bit differently there and his code to check for the InAppBrowser didn't work. (To be clear, that one is absolutely not his fault.) The workaround was a quick mod to his code and is in the GitHub repo. You can see a video of the app in action below.\n",
		"tags":[
	        
            "cordova",
            
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "An update to my RSS Reader built with Ionic",
		"date":"Mon Jun 22 2015 02:12:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1434939131,
		"url":"https://www.raymondcamden.com/2015/06/22/an-update-to-my-rss-reader-built-with-ionic",
		"content":"A few days ago a reader posted a comment to a blog post I wrote that demonstrated a simple RSS reader built with the Ionic framework. Looking over the code I had written for that demo I realized there was a lot of room for improvement. I'm still no Angular expert, but I've learned a few things over the past few months and decided to update the code base. I thought it might be interesting to point out what I changed (especially if people better at Angular want to correct me) so folks could compare the differences.\n\nI made three major changes to the code base. The first was to tweak how I handle Cordova's deviceready event within the Ionic code. Previously I had the default Ionic &quot;run&quot; code inside my controller. That was messy and not necessary, so I moved it back to the run method in app.js:\n\nThat cleaned up my controller a bit. Next, I added a service. When I first started working with Angular, I did almost everything in controllers because that was simpler, but it also made my code messy (imo). I moved all the logic of RSS parsing into its own service.\n\nNote the use of deferreds here to handle the async nature of Google's RSS parsing. The end result of these two changes is a much simpler controller.\n\nThe final change was how I set the title and RSS URL of the app. I had used rootScope variables before, but now I'm using Angular Constants, which is something I just discovered about a month or so ago:\n\nAll in all, not a huge amount of changes, but it &quot;feels&quot; a heck of lot better architected now. As I said in the beginning, I welcome comments/criticisms about the techniques here - just post a comment. You can find the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/rssreader_ionic\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Presentation: Leveling Up at JavaScript",
		"date":"Tue Jun 16 2015 17:22:45 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1434475365,
		"url":"https://www.raymondcamden.com/2015/06/16/presentation-leveling-up-at-javascript",
		"content":"So last week - right before I left on vacation, and yes, I shouldn't be posting now - I gave a presentation to the Chicago ColdFusion User Group on my thoughts on how to improve your skills at JavaScript. It is a topic I'm pretty interested in and I  think the presentation is a good one. However, Adobe Connect kept crapping out on me. I don't know if that was Connect's fault or my laptop, but, it caused problems. I believe the recording is still worthwhile though so I encourage you to check it out and let me know what you think:\nhttp://experts.adobeconnect.com/p869m59cacq/\nYou can see the slides here: http://www.slideshare.net/raymondcamden/leveling-up-at-javascript\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Vacation Notice",
		"date":"Wed Jun 10 2015 14:32:17 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433946737,
		"url":"https://www.raymondcamden.com/2015/06/10/vacation-notice",
		"content":"Blogging and responding to email will be somewhat slow over the next ten days as my family and I will be on a well deserved vacation. See you on the flip side!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Quick example of the Google Analytics Embed API",
		"date":"Wed Jun 10 2015 07:31:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433921496,
		"url":"https://www.raymondcamden.com/2015/06/10/quick-example-of-the-google-analytics-embed-api",
		"content":"In my blog post from earlier this week, I mentioned how Google has a new Analytics Embed API. While it still requires a bit of programming, it is a greatly simplified version of the code you needed before in order to work with Google Analytics. As you can guess, the primary use case (at least in my opinion) for this will be to embed charts on a web site so you don't have to go to the Google Analytics dashboard to see how well your site is doing.\n\nOddly though the docs don't show an actual example of that use case. Instead, all of the examples rely on your own particular Google Analytic site data and each demo asks you to select a property first. Now, I get that that makes it possible for us to actually see the demos. However, it doesn't give us an example of how you would embed this on a site and ensure that the report just shows that site's particular data. It's really relatively simple, but as I said, I was a bit surprised this wasn't demonstrated.\nTo see how you could do this, let's start with a demo that lets you pick a property, like the demos on the site. (And to be clear, this is a demo from their site, I didn't write it.)\n\nThat's nice, right? As I said, when I was building my own Google Analytics mashup it was quite a bit more complex than this. Here is a screen shot of the result - don't forget you can see this, and other examples, on the demo site for the API.\n\nI've called out two things in the screen shot above. The first is the &quot;You are logged in as&quot; message. The Embed API does not let you remove this. You can change the text in front of the username, but even if you set the property to an empty string, the username will still show up. To me, this is useless information if I'm embedding in my site admin portal. And obviously the site picker itself is something we want to get rid of.\nIn order to make these changes, we just need to do a few things. Here's the updated code.\n\nSo what did I change?\n\nI set userInfoLabel to an empty string in my authorize code. As I said, Google will still show the username in the div it is associated with, and we're going to fix that too, but this will at least make the text size smaller.\nIn the DataChart embed, I hard code the ID for my property.\nNext, in my authentication success method, I hide the div that contains the authentication info.\n\nAnd that's it. Here is the result - which would make more sense in an admin portal of some kind but I assume you can use your imagination:\n\nBy the way, you can customize the chart quite a bit as well. For example, here is a modification that adds a simple animation as the chart loads:\n\nSo, where did I get my ID value? Well for me, I simply modified the first code listing to console.log the ID value. Another way to get it would be to go your property settings, then to the View, and copy the View ID. Remember to add ga: in front.\nI hope this helps. To be clear, this will only work if the authenticated user has access to this particular ID. You may be wondering how to handle that. You could add an error handler to the auth code, but the user may authenticate just fine and not have access. Luckily you can also add an error handler to the timeline - as simple as this:\n\nYou wouldn't use the console to report the issue of course. You could easily add a message like, &quot;Please contact Bob in IT so he can add you to the list of people who can access this GA account.&quot;\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Using Cloud Code, Mobile Application Security, Node.js and Bluemix",
		"date":"Tue Jun 09 2015 07:35:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433835313,
		"url":"https://www.raymondcamden.com/2015/06/09/using-cloud-code-mobile-application-security-node-js-and-bluemix",
		"content":"So, first off, forgive the somewhat long, rambly title. I'm working on a new project that involves quite a few moving parts - many of which are new to me. I ran into some trouble along the way (well, a lot of trouble), and this morning I finally broke through and got things working. I want to give huge thanks to my coworker David Cariello for helping me out and not losing patience with me.\n\nMy application is a hybrid mobile application that makes use of a Node.js app running on Bluemix. My Node.js app is going to make use of Cloudant for data storage and here is where the wrinkle came in. I wanted to make use of a specific feature of Node.js running on Bluemix, Mobile Application Security (MAS).\nMAS provides a basic framework for locking down resources in your server application. The docs seem to imply it only works with the Mobile Data and Push services, neither of which I'm using. Turns out though you can also secure ad hoc routes in your Node.js application. What's cool is that it isn't an all or nothing solution. You can have some routes open and some closed, depending on whatever your app needs are. So how do you use this?\nLet's start on the client side. You can find all the documentation for working with Bluemix services here. It begins by asking you to add a core library using bower:\nbower install https://hub.jazz.net/git/bluemixmobilesdk/ibmbluemix-javascript/.git\nYou then have to add the libraries you'll use. I already said I wasn't using Mobile Application Data and Push. There's another feature though that I will use, Cloud Code. You can think of Cloud Code as a quick way of &quot;speaking&quot; to your Node.js application. So while normally you may do something like so:\n$.get(&quot;the location of my server/my route&quot;, etc etc\nCloud Code simplifies this down to calls that look like this:\ncc.get(&quot;/my route&quot;)\nSo not a big savings, but you then get to add security to your calls automatically and toggle between development and production as well. All in all, it's a nice library. You would add it with bower as well:\nbower install https://hub.jazz.net/git/bluemixmobilesdk/ibmcloudcode-javascript/.git\nAnd once you've got both libraries installed, simply address them in your code:\n\nOk, so now comes the question of using CC. If you aren't using security, or have an unsecured route you want to run, the code looks like this:\n\nThe initial block configures your use of Bluemix. All three values can be found by clicking the Mobile Options link on your Bluemix app dashboard:\n\nYou can keep the instance variable around obviously. Also, you get methods for each HTTP type as well, so cc.post, cc.delete, etc. During testing when your Node.js app is running locally, you can switch to hitting your local instance:\n\nThat's calling unsecured routes, but what about routes you want locked down? Mobile Application Security supports two types of authentication - Google and Worklight. Google will be easier to use in my mobile application so I selected that. To use Google, you need to use OAuth to log clients in. Nic Raboy has a very simple Cordova plugin for this, ng-cordova-oauth. His library supports a butt load of different OAuth providers, including Google. To add Google authentication to my app, I added his plugin, and then used this simple code. Note that I do not store the result from authentication in this code block. That's something I'll be adding later.\n\nThat big value up front there is my Google project client ID. Believe it or not - that's the entirety of the code. The plugin handles popping open a window and running the entire OAuth flow:\n\nOnce OAuth is done, we can use the access token returned by that process to &quot;sign&quot; our calls to Cloud Code:\n\nNow whenever I use Cloud Code calls, they will pass along the token value to the Node.js app.\nThat's it for the client-side code. Obviously there's a lot more in the details, and when I get the application I'm really building ready to share, I'll be sharing the complete code base then. Now let's turn our attention to the server-side.\nNode.js will require two different packages - first a generic Bluemix library and then the security one. You can install both to your package.json by doing:\n\nnpm install ibmbluemix --save\nnpm install ibmsecurity --save\n\nYou then configure your application at startup. Here is an example from the boilerplate:\n\nNow - let's discuss the routes I mentioned earlier using Cloud Code. In the first example, you saw me run a route called /allfree. In order for Cloud Code to access this route, you must modify the route path to include an IBM Bluemix context root. This is fairly simple though:\n\nIf you forget this, then you'll get errors running Cloud Code calls from your mobile application. To be clear, a regular HTTP call to /allfree would work, but not the &quot;wrapped&quot; call using Cloud Code.\nSo the next question is - how do you enable security for routes? To do this, you first physically separate your routes that need sec",
		"tags":[
	        
            "bluemix",
            
            "cordova",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Speaking at Ionic Dallas next month",
		"date":"Tue Jun 09 2015 02:10:20 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433815820,
		"url":"https://www.raymondcamden.com/2015/06/09/speaking-at-ionic-dallas-next-month",
		"content":"Next month I'll be speaking at the Dallas Ionic meetup. I'll be there on July 7th and I'll be talking about the services Ionic provides. As you know, Ionic provides a great set of UI/UX components, but they are also working on services you can add to your application to make them more powerful. I'll be discussing Push and a new service that hasn't (as far as I know) been shown anywhere else yet. You can RSVP here: http://www.meetup.com/ionic_dallas/events/223045527/. I hope to see you there!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Google Analytics and RSS Report",
		"date":"Mon Jun 08 2015 03:07:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433732829,
		"url":"https://www.raymondcamden.com/2015/06/08/google-analytics-and-rss-report",
		"content":"(As just an FYI, while working on this demo I discovered the Analytics Embed API. It looks like a powerful way to create Google Analytics mashups and I plan on looking at it deeper in the next week or so. Note that the code I use here could be much simpler with the Embed API.)\nThis past fall I switched from a custom blog engine I had written to Wordpress. While in general I'm really happy with the move, there's one thing my old blog had that I really miss. Whenever I logged into the admin, I'd see my last five blog posts and how many views they had received so far. For me, this was a great way to gauge how my current articles were doing. I obviously checked my Google Analytics often as well, but the report of my current entries was a great way to see how my most recent articles were being received.\n\nA year ago I worked on a Google Analytics dashboard that made use of their client-side API. This dashboard simply gave me a birds-eye view of all my properties and how well they are doing. This demo gave me the basics of working with both authentication and the Analytics API, so I began working on a new mashup. The idea was simple:\n\nAuthenticate the user to Google.\nLet them select a property.\nLet them enter a RSS URL that matched the property.\nUse the RSS entries as queries against the Analytics API and report the stats.\n\nIn order to make this work, I needed the Core Reporting API and the Feed API. The Feed API actually supports a method of searching for a RSS feed, but it oddly didn't return the RSS feed itself - just matched entries. Because of this my tool needs to ask you for the RSS URL. Before digging into the code, let me share some screen shots so you get an idea of how it looks.\nFirst - the application recognizes if you've recently authenticated with Google. If you have not, it will prompt you to:\n\nClicking that button pops open a new window where you'll begin the authentication/authorization process with Google. I've set up a project for this demo and specified what scopes my demo needs. This is what controls the list of requests seen here.\n\nAfter you've authenticated, the code then uses the Analytics API to fetch the web sites you have access to for that account:\n\nNotice how the RSS field is empty. I added code so that once you enter a value here it will remember it. Next time you load the tool and select your property, you won't need to fill in the values again.\nAnyway, once you do enter the value, the Feed API takes over. It parses the RSS and gets your most recent entries.\n\nAnd then magically - the stats show up in the table:\n\nOk, so how does it work? I won't go over the authentication part as I used the same code from my previous demo. The analytics part was a bit different. I moved it into a module pattern and exposed two APIs - getProperties and getStatsForPropertyURL. getProperties returns a promise which makes using it somewhat simple:\n\nBehind that simple call though was some complex rejiggering of the previous demo. I make use of LocalStorage to cache values and the Analytics stuff is multi-step, so it took me a few iterations to get it right. Getting stats for a URL was pretty simple. Given you know the property ID and the URL path (you don't include the full URL), here is the call:\n\nNote the use of 2005-01-01 as the start date. This is the earliest date that the API supports.\nSo, want to check it out? Remember - you must have a Google Analytics account and the property you want to test must use a RSS feed. I tested in Chrome, Firefox, and Safari and it seemed to work well, but obviously you can let me know (via an issue or comment) how it works for you:\nhttps://static.raymondcamden.com/rssanalytics/\nWant to see all the code, or report an issue? I created a Github repo: https://github.com/cfjedimaster/Google-Analytics-RSS-Parsing\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Cordova Sample: Capture and Display Video",
		"date":"Fri Jun 05 2015 04:16:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433477769,
		"url":"https://www.raymondcamden.com/2015/06/05/cordova-sample-capture-and-display-video",
		"content":"Just a quickie - a user on Stackoverflow asked how to both capture video and display it in the app. This is fairly easy with the Media Capture API so I thought I'd whip up a quick demo.\n\nFirst, I created an HTML page with a button and an empty div to hold the video later on:\n\nAnd then I wrote up the following JavaScript:\n\nSo I begin by simply listening for a touch event to the button I create. I then use the Media Capture API to start the video process. In the success handler I just take the result's fullPath property and put it in HTML. I was kinda surprised the path worked as I had thought I'd need to use a URL form, but it worked just fine.\nHere it is running on my iPad.\n\nIf for some reason you want to try this yourself, I uploaded it to my Cordova demo repo: https://github.com/cfjedimaster/Cordova-Examples/tree/master/videotest1.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "My first video course for O'Reilly - JavaScript Templating",
		"date":"Fri Jun 05 2015 01:30:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433467818,
		"url":"https://www.raymondcamden.com/2015/06/05/my-first-video-course-for-oreilly-javascript-templating",
		"content":" I'm proud to say my first (but not my last) video course for O'Reilly was just released: JavaScript Templating. This course covers how to use JavaScript templating (duh) and goes deep into four different products: EJS, Handlebars, Dust, and Nunjucks. I also talk a bit about ES6 template strings. What makes this course great (imo) is that at the end of each section, I use a common sample application and show how to use that particular product. This gives you a great way to compare each of these products against each other. Anyway, I think this is a great course (again, duh) and I hope you do too!\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Online presentation next week - How to Become An \"Intermediate\" JavaScript Programmer",
		"date":"Thu Jun 04 2015 03:20:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1433388009,
		"url":"https://www.raymondcamden.com/2015/06/04/online-presentation-next-week-how-to-become-an-intermediate-javascript-programmer",
		"content":" For the past five or so years, my biggest focus (development-wise) has been on JavaScript and web development. Luckily nothing has changed much during those years. (Ahem.) I've discovered that I'm probably not smart enough to reach the upper levels of JavaScript development. I've got a lot of respect for those who do, but frankly, I'm happy enough if I can simply be competent as a JavaScript programmer.\n\nI've been thinking about what skills/traits/tools/etc make up a good intermediate JavaScript developer and next Wednesday (June 10) night I'll be giving a presentation to the Chicagoland ColdFusion User Group on the topic. I'm not flying to Chicago, but instead will be giving the presentation online via Adobe Connect. The meeting opens up at 6:30 CST and I'll probably go for an hour. If you can't make it, I'll share the recording later. (I'm actually going on vacation the very next day, so I may be a bit slow in getting that post up.) The meeting URL is: http://experts.adobeconnect.com/ccfug-06-10-15/.\nObviously, this will be my opinion on what practices developers should follow and I highly encourage folks to give their own opinions as well. In general, when I do presentations for user groups like this I give priority to questions from the local group, but I'll also try to answer questions from folks just in the Connect room as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "A look at New Relic Browser",
		"date":"Sat May 30 2015 09:14:05 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432977245,
		"url":"https://www.raymondcamden.com/2015/05/30/a-look-at-new-relic-browser",
		"content":"While at Fluent Conf this year, I was walking by the New Relic booth when I noticed something interesting - a product called New Relic Browser. Back when I converted my blog to Wordpress, I ran into a lot of problems. My server went down, WordPress crashed, it was a bit frustrating. (Much like how lemonade in a paper cut is a bit frustrating.) One of the tools I used to help diagnose my server was the New Relic Server monitor. Outside of a few issues installing, I was really impressed with the level of detail the monitor provided. While it wasn't the final solution for fixing my problem, it definitely helped me pinpoint what was sucking up all the RAM on my box, and helped me check my changes to ensure things were going well. Best of all, this was entirely free. I'll give them huge props for offering such a powerful tool for no up front money. Because I had such a good experience with them on the server side, I thought I'd give their Browser product a try as well.\n\n\nAs you can guess, this tool is meant to help you gain insights into how well your web application is performing. I decided to try it on my blog, which, admittedly, is probably not the best use case for this product. WordPress isn't something I need to hack up and outside of the performance issues I had on the server side, I figured the client side was pretty much good enough. It certainly seemed good enough to me. But at the same time, my blog gets quite a bit of traffic so I figured it would also provide a good set of data to dig into as well.\nSetup is relatively simple. You begin by selecting a deployment method:\n\nI selected Copy/Paste as I figured that would be simpler. On the next page, I said I was not using APM, even though I guess I kinda was. I was trying to test this as someone who was not also using the server side product, so there may be things I missed out on. Typically when I try products like this though I try to keep things as simple as possible.\n\nThe final step was copying a ginormous JavaScript string into my WordPress template.\n\nSo that was that. I copied in the code, cleared my WordPress cache, and then promptly forgot about it for a week or so. I then took a look back at my stats. There's a tremendous amount of information you get right on the front dashboard.\n\nFirst off - note the browser load times. I'm averaging 6.8 seconds or so which is quite high and not what I expected. For over ten years I ran my blog with very precise knowledge of what I had going on within my templates. With WordPress, I've kinda gotten lazy about it and have given up being so deeply involved. This gives me a clue that maybe I need to take a closer look at my template and plugins and see if I need everything I've got.\nAlso note the error graph. On average, 2% of my pages have JavaScript errors. The real question is - how often do those JavaScript errors impact the core thing people need to do on my site - read a blog post.\nAs I said, the dashboard is pretty packed, but let's go deeper. First, the Page views report.\n\nThis report shows recent pages and which page requests are consuming the most load time. You can mouse over each line item for a detailed view:\n\nYou can also switch the &quot;Sort by&quot; to show average page load time:\n\nAnd yes - that twenty plus second item on top there made me crap my pants. Honestly I'm not sure why that page averaged so high as it is relatively simple, but it does give me something to dig into deeper.\nThe next link, Session traces, is not what you may think. I had assumed this was a report of a 'session' for one visitor to my blog. Instead, it is a deep look at one particular web page. And when I say deep, I mean deep. Here is a top level report for one session:\n\nNote all the detail in the chart. You can then scroll through the session and look at every particular darn thing in the one request. For example, here I can look at what Google Analytics is doing.\n\nThe next report shows you the AJAX requests your web app is making. You get details on what is making requests as well as throughput and data size. I can say from experience that the data size chart could be really useful. Back when I first learned Ajax I made the mistake of not considering the size of my packets and my applications suffered through it. This is a rather long page so I've split the screen shot into two parts.\n\n\nI'm thinking that in 'real' web app the Ajax report will be the number one place you'll find nuggets of information. Of similar importance is the JS errors report.\n\nClicking on a particular item will give you details:\n\nIf you click on the instance details, you can see the line number where this error was thrown.\n\nWhile not this particular error, earlier I found an issue with Gravatar. I didn't think I was using Gravatar, but it turned out one plugin was making use of it and throwing an error. I modified the plugin and the error went away.\nThe Browsers report gives you details about what types of browsers are hitting your site and how wel",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Finding the owner of a file with ColdFusion",
		"date":"Fri May 29 2015 02:30:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432866653,
		"url":"https://www.raymondcamden.com/2015/05/29/finding-the-owner-of-a-file-with-coldfusion",
		"content":"A reader asked me last night if it was possible to find the owner of a file using ColdFusion. You may think getFileInfo would return this but it does not. Luckily it is relatively simple to get to it from Java.\n\nThe first result I found via Google sent me to a Stack Overflow answer that worked fine once I converted it to work within ColdFusion:\n\nYou could easily wrap this up into a UDF so it is easier to call. Ok, Ray, don't be lazy. Here is a UDF version with an example call:\n\nAnd because I was bored, I rewrote the UDF as one line that would be a pain in the rear to debug.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Best Voice Recognition Fail(?) Ever...",
		"date":"Wed May 27 2015 00:15:05 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432685705,
		"url":"https://www.raymondcamden.com/2015/05/27/best-voice-recognition-fail-ever",
		"content":"So yesterday I flew, or, rather, attempted to fly, since apparently the weather forecast was apocalypse and I didn't now it. I actually made it to Texas, but my flight to Madison was canceled there. American Airlines rang me with a robo call to my Google Voice number, and Google helpfully parsed their audio into text for me:\n\n\nCalling with the flight update this call. Maybe reported. Bye. Yeah unfortunately. Bye. Yeah like 3376. Bye yo operated by lawyers, american eagle, bye Dallas Fort Worth to Madison, is experiencing a delay and is now expected to depart at. Bye, yeah 9:20 PM, you within arrival. Love, bye yo 11:42 PM, bye yo line updates can be obtained by calling our automated flight system at 1(800) 242-4444 to hear the message again say repeat 8. Bye. Yeah. Otherwise, feel free to hang up. Yanks for choosing American Airlines. Bye bye.\n\nI especially appreciate the &quot;love, bye&quot; part.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Using the Marvel API with IBM Watson",
		"date":"Tue May 26 2015 03:33:51 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432611231,
		"url":"https://www.raymondcamden.com/2015/05/26/using-the-marvel-api-with-ibm-watson",
		"content":"A little over a year ago I blogged about my experience working with the Marvel API (Examples of the Marvel API. It's been a while since I took a look at it and I thought it might be fun to combine the Marvel data with IBM Watson's Visual Recognition service. The Visual Recognition service takes an image as input and Watson's cognitive computing/computer vision intelligence to identify different items within it.\n\nI reviewed this service back in February in context of a Cordova app (Using the new Bluemix Visual Recognition service in Cordova). At that time, I wrote code that directly spoke to the API via code in my hybrid mobile application. For this though I wanted to make use of Bluemix and a hosted Node.js application.\nMy setup would be simple - let the user search against the Marvel API and return matching comic books. Since I'm planning on scanning the covers, I'd just show them:\n\nSelecting a comic will load it and then begin the call to Watson's Visual Recognition service. The service will use the cover as input and try to find things within it.\n\nLet's take a look at the code. You can download the entire thing at my GitHub repo: https://github.com/cfjedimaster/marvelcomicrecognition. First, the main app.js:\n\nOf note here are the following lines:\n\nI've split out my Marvel and Visual Cognition code in their own files to keep my app.js nice and slim.\ncfenv is a helper library for Node.js apps on Bluemix to make it easier to use environment variables and oservices.\nYou can see some code I commented out for Dust support. I ended up not using any template engine as my entire app was one HTML view and two Ajax calls.\nAnd you can see those two Ajax calls at the end. Easy-peasy.\n\nOk, let's look at the Marvel code. To be fair, this is pretty much the same as I built for my &quot;random comic site&quot; (see the first link) but modified to return all the results for a search.\n\nAnd yes - poop on line 74 is my lame reminder to add proper error handling later on. Honest, I'll get to it. Now let's look at the Visual Recognition stuff:\n\nI made use of the Watson Developer Cloud npm package to speak to the service. For the most part this makes it easy, but right now it expects a physical file. (I mentioned this and the team behind it is already working on improving this.) So to make it work I had to download the file from the Marvel API and then send it up to Watson. I don't have &quot;cleanup&quot; code yet, but it gets the job done. As I said, once the Watson team adds  support to talk to a URL, nearly half of this code will go away.\nFor the final part, here is the JavaScript I use on the front end:\n\nNothing too terribly interesting here besides some basic jQuery DOM manipulation. If any of this doesn't make sense, just let me know, but obviously the heavy lifting here is really being done on the back end.\nSo what else is there? As I said, this is all hosted on Bluemix. I've blogged about Bluemix and Node.js apps before, but in case your curious about the process...\n\nCreate a new app with Bluemix and select the Node.js starter.\nAdd the Visual Recognition service.\nIf you haven't installed the command line, do it one time.\n\nThe entire process is roughly five minutes. I can then use the command line to push up my code to Bluemix. You can see the final app yourself here: http://marvelcomicrecognition.mybluemix.net/.\n",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Important information about Cordova 5",
		"date":"Mon May 25 2015 02:51:57 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432522317,
		"url":"https://www.raymondcamden.com/2015/05/25/important-information-about-cordova-5",
		"content":"In the most recent update to Apache Cordova, there was a rather important change that could really confuse you if you aren't paying attention. This is exactly the type of thing that I would have warned my readers about, but I mistakenly thought it would not impact most users. I'll explain later why I screwed that up, but I want to give huge thanks to Nic Raboy and his post, Whitelist External Resources For Use In Ionic Framework. Nic is a great blogger that I recommend following, and it is his post that led me to dig more into the changes in Cordova 5 and do my own research.\n\nI won't repeat Nic's post here, but the summary is that how you whitelist in Cordova has changed from earlier versions. Previously whitelisting was done via an &lt;access&gt; tag in config.xml. The default application created by the CLI would use a * to make everything available. To repeat, by default you could use any resource in your Cordova app.\nIn Cordova 5, this was changed. Specifically, this was changed for Android and iOS. You can begin by looking at the whitelist guide from the Cordova docs, but this will lead you to the docs for the new whitelist plugin.\nSo let's talk about this plugin. If you create a new project using the default template, then this plugin is automatically added whenever you add a platform. What does this plugin do? For modern Android (KitKat and above) and all iOS versions (all supported) it uses a new security system called Content Security Policy (CSP for short). The best place to read about CSP is at MDN (https://developer.mozilla.org/en-US/docs/Web/Security/CSP). I'll do my best to explain it here though.\nCSP is implemented via a meta tag in your HTML. Again, not your config.xml file but your actual HTML. This is what you'll see in the HTML file from the default template:\n\nThat's pretty weird looking, right? What you are basically seeing is a set of rules that dictate what resources can be loaded and how. You can split the above content by semicolons:\n\nThe beginning of each part represents a &quot;policy directive&quot;, basically &quot;what my security rule applies to&quot;. So for example, media-src represents audio and video tags. style-src represents style sheets. There's more policy directives (including script-src) to give you really fine grained control over all aspects of your application. You can find the complete list here. default-src represents a default value but it only applies to policy directives that end with -src. If that sounds confusing, wait, I'm going to make it a bit more confusing in a bit.\nSo that's the policy directive, what about the values after it? These values dictate what locations particular resources may be loaded from. You can use a combination of keywords, like 'self', and URLs. Let's talk keywords first. The keyword 'self' means you can use any resource served from the same location as the current document. I can't imagine a case where you wouldn't want that, but it's possible. You can use 'none' to say nothing at all is allowed. A complete list of keywords may be found here.\nURLs can be of the form &quot;scheme&quot;, ie &quot;http:&quot; or a scheme and domain, like http://www.cnn.com. In my testing, I was not able to use a domain by itself nor was I able to use a wildcard for the scheme. Curious about gap? This is a special scheme for iOS and must be left there.\nunsafe-eval isn't really a location but instead represents being able to use eval() within code. I've seen some JavaScript frameworks that require this so it is probably good that it is there by default. One more that isn't in the default template is unsafe-inline. This is a big one. Without this being in your CSP you can't use JavaScript code in your index.html file.\nNow - I know all of us are good JavaScript developers and always put your code in JS files, but I know I've used inline JavaScript from time to time. Heck, on this blog I'll do it a lot just to keep the code a bit simpler. Well, this will no longer work unless you specifically modify the CSP to add unsafe-inline. To be honest, I'd skip that and just move your code into a JavaScript file. Note that the default CSP does allow inline style sheets.\nLet's consider a simple example. I created a new application and then added a CDN copy of jQuery:\n\nTo be clear - I do not recommend this. If you do this and your app is offline than your entire application is screwed.\nI then used this code in my deviceReady block:\n\nOut the gate, none of this will work. You can see this yourself in your remote inspector:\n\nFirst, I need to update my CSP to allow a script src at code.jquery.com:\n\nNotice I added 'self'! I had thought that default-src including 'self' would cover this, but it does not. As soon as I added script-src, I needed to also add 'self' here to let local scripts work.\nCorrecting that lets jQuery load - but guess what - there's more:\n\nWhat's nice is that the error is actually pretty descriptive. In a lot of security things in the browser I've seen thi",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Getting initial console messages you missed with remote debug",
		"date":"Thu May 21 2015 04:09:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432181358,
		"url":"https://www.raymondcamden.com/2015/05/21/getting-initial-console-messages-you-missed-with-remote-debug",
		"content":"This falls in the &quot;I'm sure it is obvious and no one will find it useful&quot; category, but typically those are the posts that end up being useful so here goes nothing. Imagine you are using remote debugging (and if you don't know how, here are two articles - Part One and Part Two) and have a few console messages that appear early in the app cycle. For example:\n\n\nIf you send this to your device, or even the simulator, and then pop open your remote debugger, you'll not see the console message as it has already been passed. There's a quick way around this. In the iOS Web Inspector, click on &quot;Resources&quot;, and then the reload button:\n\nThen, obviously, switch back to the console:\n\nYeah, lame, but as I tend to spend most of my time in the console and network tabs, I kinda forget about the DOM areas and this is something useful. You could get the same effect perhaps by using something like window.location.reload() (and I'm typing that from memory so I'm probably wrong), but clicking twice is a bit faster for me.\nIn case your curious, Chrome does not have this issue, so it really isn't a concern. Also, GapDebug will also have this issue too. While GapDebug will save you from having to reopen the darn Safari debug window again (that by itself makes GapDebug useful as heck), you will still miss early console messages.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Looking at the JavaScript API in Hybrid MobileFirst Apps (2)",
		"date":"Tue May 19 2015 09:19:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432027156,
		"url":"https://www.raymondcamden.com/2015/05/19/looking-at-the-javascript-api-in-hybrid-mobilefirst-apps-2",
		"content":"A few weeks back I blogged about some of the JavaScript APIs you have available when building hybrid mobile applications with IBM MobileFirst. I had meant to follow up on this a bit sooner but recent trips got in the way. Today I took some time to look at a few more of the APIs.\n\nWL.Badge.setNumber\nAs you can guess, this API lets you set the badge number for your application icon. It only applies to iOS and Windows Phone. The API is incredibly simple. You set a number. It shows up in the icon. If you set it to 0, the number is cleared. Yep, that's it.\nI wired up two buttons in an Ionic app for quick testing of this. Here is the JavaScript code I used:\n\nHere is a screen shot of the icon updated:\n\nUnfortunately, it appears as if iOS 8 has new requirements for apps that try to add badges. In my testing with the iOS 8.X simulator, I got an error in my console stating that permission was required for this action. I'll try to get back to this later, but for now, keep that in mind.\nWL.BusyIndicator\nYou can probably guess what this one does too - show and hide a &quot;busy&quot; indicator. The docs explain the full API, but the general usage is to create an instance of the indicator with the appropriate options and then call show on it. Where things get interesting is on Android. iOS supports an option to automatically hide the dialog, Android does not. So if you really want a &quot;timed&quot; dialog then you'll need to set a timeout:\n\nThe options you see above are just some of the options, mainly the ones I was curious about. bounceAnimation (which defaults to false) was a bit weird when enabled so I don't see using it often. fullScreen also seemed to a bit too much on iPad, but could be ok on iPhone. In a real app the hide() call would be in the async callback of whatever thing you are waiting for.\nHere is how the indicator looks in iOS:\n\nI think it looks nice, outside of the red text which I specifically asked for. (Remember, I'm where good design goes to die.)\nAnd here it is in Android.\n\nThat's it for today. As a reminder, if you want to see all of the hybrid API, check out the client-side API docs.\n",
		"tags":[
	        
            "cordova",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with the new PhoneGap/Cordova ContentSync Plugin",
		"date":"Tue May 19 2015 05:38:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1432013891,
		"url":"https://www.raymondcamden.com/2015/05/19/working-with-the-new-phonegapcordova-contentsync-plugin",
		"content":"Yesterday at PhoneGap Day EU (sooooo sorry I'm missing it!), someone (I forget who) announced two new plugins for PhoneGap development - Push and ContentSync. Push is what you would expect - a way to deal with push messages easier. ContentSync is another beast altogether. The plugin makes it easier to update your application after it has been released. The API gives you a simple way to say, &quot;Hey, I want to fetch this zip of crap and use it.&quot; It handles performing the network request to a zip, downloading it, providing various progress events, unzipping it, and then telling you where it stored stuff. All in all a kick ass plugin, but I had some difficultly understanding it so I worked on a few demos to wrap my mind around it. Before we get started though, let me clarify some things that were confusing to me. (And yes, I've filed some bug reports on where I got confused for possible documentation updates.)\n\n\nThe first example shows this: var sync = ContentSync.sync({ src: 'http://myserver/assets/movie-1', id: 'movie-1' }); What you may not realize is that URL you point to must be a zip file. So obviously a zip file need not end in .zip, but it wasn't clear at first that this was a requirement. \nThe plugin will unzip the file for you. Again, this is probably obvious, but it wasn't to me. The id value provided in the example above actually ends up being a subdirectory for where your assets will be stored.\nThe docs say that when the sync is complete, you will be given a path that is \"guaranteed to be a compatible reference in the browser.\" What you're really given (at least in my testing in iOS) is a complete path to a directory. So if your zip had 2 files, a.jpg and b.jpg, to you could get the full path to a.jpg by appending it to the value. But this is not a 'browser compatible' reference imo. Rather you need to change it to a file URI if you wish to use it with the DOM. (To be clear, I could be wrong about this, but that's how it seemed to work for me.) \nBy default, the plugin will always sync if you tell it too. You can pass an option to specify that it should only cache if a local copy doesn't exist, but for more complex logic like, \"sync if the remote source is newer\", then you need to build that logic yourself. That seems totally fair, just want to make it clear.\n\nOk, so how about an example? I decided to build a sample that would fetch a zip of kitten images. Here is where I made my first mistake. I took my folder in OSX, right clicked, and selected compress. This created a zip of one item (actually two, one was a system file) where the one item was the folder. That was not what I intended. What I should have done is select all the images, right clicked, and created a zip from that. I then put up the zip on my S3 bucket at https://static.raymondcamden.com/kittens.zip. For my first example, all I wanted to do was sync the zip and display them in the DOM. Here is the JavaScript code for this version:\n\nSo for the most part, I assume this is self-explanatory. My zip file had seven images named kitten1.jpg to kitten7.jpg. Since I knew exactly what they were, all I needed to do was iterate and create img tags for each. This worked perfectly. I really don't need to share a screen shot of this. You already know it's seven pictures of cats. But you know me. I've got to share cat pictures.\n\nPretty darn easy, right? In case your curious about handling a zip of unknown images, you could use FileSystem APIs to iterate over the entries:\n\nOk, so what if you only wanted to sync once? That is incredibly difficult unfortunately. You have to change\n\nto\n\nYeah, that's it. Nice, eh? Now I'm not sure how often you'll have a sync strategy that simple, but it's great that the plugin makes it that simple. But what about a more real world example? Consider the code block below:\n\nIn this one I'm using localStorage to remember both the last modified value for the zip as well as I where I stored the assets. I perform a HEAD operation against the zip just to get the last modified value and if it is different from my last request (or doesn't exist), then I do a full sync. Once done we just run a simple function to iterate over the items in the local copy. Since I store the path I'll have access to it when sync operations can be skipped.\nYou can find all three examples over on my GitHub repo: https://github.com/cfjedimaster/Cordova-Examples/tree/master/contentsyncexamples. Also note that the plugin does even more than I touched on today so be sure to read the docs to see more.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Tracking and notifying geolocation status with Ionic",
		"date":"Mon May 18 2015 02:42:04 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431916924,
		"url":"https://www.raymondcamden.com/2015/05/18/tracking-and-notifying-geolocation-status-with-ionic",
		"content":"On Twitter, Snehal reached out to me with an interesting question. Given a location X, he wanted to track a user's location and know when they were within a certain distance to X. By itself, that's not really a difficult task. You need to:\n\n\nTrack the user's location - which is easy with geolocation and an interval.\nGet the distance from the user's location to your target, which is also easy. (Ok, I lie. It's bat-shit crazy math but you can copy and paste a solution so let's call it easy.)\nTell the user when they are close - again easy.\n\nWhat wasn't particularly easy for me to wrap my head around how to build this within Ionic, or specifically, within Angular. As I've said on multiple occasions now, I can write Angular, but I'm still struggling with how best to organize and coordinate various different aspects of my application. In this case, I was particularly confused by how I'd handle the interval process. I also needed something that would run all the time, not just for a particular view/controller.\nI was stuck - but then I figured - if I know I'm probably going to do this wrong, let me just take a stab at it and let my smarter readers tell me what I did wrong. ;)\nI began by creating a new Ionic application. I let it use the default template, Tabs, so I'd have a &quot;real&quot; app with multiple views in it. I then created a new service in services.js called GeoAlert. GeoAlert would have a simple API:\n\nbegin: This would initiate tracking and would be passed a target location and a callback to fire when the user is \"close enough\". I ended up hard coding what \"close enough\" was, but that could have been an argument as well. Ditto for how often it checked the location.\nend: This simply stops the tracking.\nsetTarget: A method I built and abandoned, but I thought it made sense so I kept it in. This lets you change the target.\n\nHere is my service:\n\nSo a few notes about this. Since Geolocation is async, I used a variable, processing, to detect when it was active so that my heartbeat function (hb) wouldn't request it again. I could have switched from setInterval to setTimeout as well. I'd simply call the setTimeout in the success function of the geolocation request. I'm not necessarily sure that makes a big deal, but it is something I'd possibly change in the future. As mentioned, the &quot;how far away&quot; logic is something I just cut and paste from a good StackOverflow answer. As I said above, both the interval and minimum distance for a match (10 kilometers) are hard coded, but could easily be arguments instead.\nAt this point, I've got a service that will basically run every N seconds and determine when I'm within X kilometers of a target. How do I use it?\nI decided to inject the service in the run method of my main Angular module. I don't know why it felt weird to work there, but it did. Normally I use services in my controllers, but obviously you can use them here too. Heck, Ionic does this with the $ionicPlatform service. As for how to let the user know something happened, I had a few choices. I could have used an Ionic Modal or Popup, but they felt wrong to me. I don't have a good reason for why they felt wrong, but I decided to use the Dialogs plugin. Obviously that's a personal choice. My thinking was that the dialog would let the user know they are close to some target and give them the choice to view information about that target. For my demo though I just record what button was clicked and leave the implementation of that to the reader. Here's the code.\n\nPretty simple I think. As a quick note, the button index passed to onConfirm is 1-based, which is good, but don't forget. For testing, I fired up my iOS Simulator. What you may not know is that it lets you change your location. This can be found under the Debug menu.\n\nNotice it has a &quot;Custom Location&quot; item. You can select this, enter a location, and it will remember it. I entered my values (which are the same as the static values in the code) and in the next iteration of the heart beat, it matched:\n\nIf you want to play with this, check out the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/geoalert.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Fascinating cftry/cfcatch/cfoutput bug with ColdFusion",
		"date":"Mon May 18 2015 00:08:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431907730,
		"url":"https://www.raymondcamden.com/2015/05/18/fascinating-cftrycfcatchcfoutput-bug-with-coldfusion",
		"content":"This was reported to me by Brian Paulson and I have to admit I was pretty surprised when I saw it. Consider the following code block:\n\n\nRight away you may think, that cfoutput should be inside the cftry, and you're right, but it doesn't have to be, except for the fact that when you run this code, you get: Variable CDATA is not defined. Since CDATA has special meaning in XML, I thought perhaps it was a bug with ColdFusion's parser, but renaming it doesn't help. Note the cfdump of variables there. If I remove the outputTest UDF call I clearly see CDATA as a variable. In the UDF call I tried variables.cdata and it didn't work. Using &quot;variables&quot; by itself though did work. I also tried making a new structure, s, with cdata as a value inside and then passed s to the UDF and that worked fine too.\nThe main issue seems to be the placement of the cfoutput. Moving them inside makes it work properly:\n\nBrian filed a bug report for it you can view here: https://bugbase.adobe.com/index.cfm?event=bug&amp;id=3989302. I tested it in Lucee and it works as expected.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "A simple Cordova task runner for Visual Studio Code",
		"date":"Sun May 17 2015 04:32:35 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431837155,
		"url":"https://www.raymondcamden.com/2015/05/17/a-simple-cordova-task-runner-for-visual-studio-code",
		"content":"I've been really liking Visual Studio Code lately, so much so that it is now my primary editor. It is still definitely a pre-1.0 release, but it performs really well and I just dig its coding style. I had to step away from Brackets a few weeks ago due to a bug (details) that impacts AngularJS/Ionic apps. Since that's what I work with most of the time, Brackets simply became too frustrating to use. One of the cool features of Visual Studio Code is the ability to define Tasks for a project. You can support one or more different tasks for a project simply by adding a tasks.json file to the .settings subdirectory. VSC will even create a good default with lots of examples for you to modify. Even nicer, since VSC supports intellisense for JSON schemas, you get code hints while building the file. This morning I thought I'd write up a quick example of adding support for emulating Cordova app.\n\nI began by following the suggestion in the Tasks doc, I typed Shift+Command+P and then Configure Task Runner, which is slightly different than what the docs say. This created the JSON file with an initial default task that I immediately commented out.\nWhile you can do a lot with the Cordova CLI, I really spend most of my time doing cordova emulate. VSC supports multiple tasks, but you can specify just one and doing so let's you treat it as a build command. That means you can simply type Shift+Command+B to execute it. Here's my first draft:\n\nNotice I'm not passing a platform. If you have multiple platforms defined than all will be sent to the emulator. This will be a problem that I'll cover in a second.\nAlso note the showOutput command. You can specify three different values here:\n\nnever - means what you think it does\nsilent - will show output if you don't configure problem matchers - you can see an explanation of that here: Defining a Problem Matcher\nalways - means what you think it does \n\nHowever, in my testing, showOutput:never didn't actually work. I just made the output window a bit smaller and later today I'll file a bug report on it. On a larger screen (i.e. not this laptop) I wouldn't even care. Here it is in action.\n\nSo what if you want to specify the platform to emulate? You can define multiple tasks like so:\n\nIn this case, I've defined an ios and android task to pass to the emulate command. Note that I've still set ios to be the build command so I can use it as a default. One thing I don't like about this is that I can't provide a 'label' for the tasks. So when I do Shift+Command+P, Run Tasks, I see it like so:\n\nThat's not the end of the world, but I'd like to have a label so I could see &quot;Emulate iOS&quot; and &quot;Emulate Android (please wait 2-3 hours for the emulator to start)&quot;.\nThis works, but has a problem. When emulating Android, the CLI hangs while the emulator is running. If I run Android via VSC, it works (after the emulator finally loads up), but VSC will not let me run another tasks until this one ends. I have to kill the emulator to continue.\nOk, so I worked on this a bit more. I don't actually emulate Android as it is way too slow, what I do normally is use Genymotion and use the run command instead. Yes, this is still emulating, but the command line call is tweaked. I just tried this and it worked!\n\nSo I can now Shift+Command+B to send to iOS as my default, or do Shift+Command+P, Run Task, to select android if I need to.\n\nHope this helps, and I'm going to keep an eye out for other ways to help VSC work with Cordova.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Suggestions for Database Syncing with PhoneGap/Cordova",
		"date":"Fri May 15 2015 01:55:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431654954,
		"url":"https://www.raymondcamden.com/2015/05/15/suggestions-for-database-syncing-with-phonegapcordova",
		"content":"Shai asked me an interesting question that I thought would be good to discuss here, and get feedback from my audience. Their question was:\n\nHello Ray,\nI'm a long follower of your blog. I am also a MobileFirst &amp; PhoneGap developer.\nI wanted to consult with you, if I may, what is your suggestion about developing a PhoneGap app (not MobileFirst) which has to be synched with a remote DB. The user populated data on the app, and if the app is offline than it is stored locally. When the network returns, the app must upload the data to the remote server and it has to be done seamlessly. The amount of data in terms of records and/or size could become quite &quot;big&quot;. I don't suppose it could 5 MB but we have to provide a solution in case it does, as part of it is pictures taken by the user.\nAny suggestion would be most appreciated. Do you have a &quot;reliable&quot; plug-in you could recommend?\nThanks and BR's,\nShai.\nI have no idea what BRs are, but you are welcome. ;) So first off, this can definitely be done, &quot;by hand&quot;, if you would like. You can use the Network plugin to detect when a user is on or offline. You can use regular old Ajax to send the data, being sure to only send what you need to send, i.e. data that is new. Storing data locally you've got a few options as well. WebSQL is baked in, but has limits. You can also use the SQLite plugin. I haven't used this yet myself but plan on playing with it soon. As far as I know though it follows the same concept as WebSQL. Back in 2012, I blogged on this topic myself, Adding database synchronization to your PhoneGap project. (Hard to believe I've been blogging about PhoneGap/Cordova that long.)\nHow about libraries? As far as I know, Parse's offline support is only for Android now. I recently played with PouchDB (Connecting PouchDB to Cloudant on IBM Bluemix) and I really, really like it, but haven't used it in production yet. Firebase has offline support too, but I haven't yet tried their product.\nAll of these solutions assume that you are ok with using their own database of course and not your own. If it must be your database than you'll want to go with the manual approach I described above.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "My speaker's self post-mortem",
		"date":"Thu May 14 2015 02:56:45 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431572205,
		"url":"https://www.raymondcamden.com/2015/05/14/my-speakers-self-post-mortem",
		"content":"I've got a good hour and a half before I board (yes, I'm one of those people who arrive early at the airport still), and while driving to the airport I was thinking about the presentations I gave yesterday. I spend a lot of time thinking about my presentation and trying to see how I can make them better. I thought it might be interesting for me to share some specific thoughts I had about my most recent presentations and demonstrate the kind of thinking that goes behind updating a presentation. Obviously this will be very specific to what I just gave, but I'm hoping the &quot;inside look&quot; may help others who are new to presenting or are just looking to improve their game. (And honestly, I could be completely wrong about what I need to improve, so part of hope behind this post is that other more experienced speakers may help me out as well!)\n\nSession One - Ionic\nThe first of the presentations I gave yesterday was an introduction to Ionic. This is a session I've given about five times now. It is one I don't have to prepare too much for as I've got the basic gist of it down pretty well. But Ionic is a fast moving product. This week it released 1.0. A few days before that they released a new feature (the playground). In general, they move quickly. As a speaker, I need to weigh my desire to let the audience know about all the cool crap they do versus being aware of how much stuff I'm throwing at them.\nFor example, I currently mention http://ionicons.com, which is a nice part of the global Ionic product line, but seems like something I could possibly cut. I also discuss ngcordova, which is very cool and useful, but, every time I bring it up in a presentation it feels like a misstep. It feels awkward. So here is something that I think is a good feature, but contextually, it bothers me tone wise and I'm going to remove it from the presentation. I've got five slides in this section. One just bullet points the feature and then I have 2 pairs of before and after slides showing regular Cordova code versus the ngcordova version. The idea being to show how much better (for some) the ngcordova version may be, but it is a lot of code and frankly - if you aren't using promises then it kinda goes right over the head.\n\nThis leaves me with some space to fill. I had recently taken out a demo I did demonstrating some of the CSS stuff. Why? I feel like most people &quot;get&quot; what CSS frameworks do. Most folks have seen Bootstrap and know that adding class=&quot;so and so&quot; makes a pretty bar. Ionic does the same, and me showing this in an editor and browser feels like overkill. I switched to using a slide that shows the code and a screen shot of the result. This kinda minimizes the coolness of it but at the same time, folks expect Ionic to be pretty. When I show them this I don't need to spend a lot of time on it because it is assumed.\n\nIn general though I don't spend a lot of time in the code base. I demonstrate pull to refresh, which works well I think, but I'd like to spend a bit more time with a proper, but simple, Ionic demo. At the end of the presentation, I discuss an app I built with Ionic that is tailored for people adopting in China. Now, I'm biased, but I think this works damn well. It's got an emotional tag (my wife and I adopted from China), it has some funny jokes (yes, I plan my jokes ahead of time), and it just &quot;feels&quot; like a great way to show Ionic while discussing a topic I'm passionate about. I don't normally spend a lot of time in the code, but now I'm going to expand that section a bit.\nSession Two - Static Sites\nMy second session was a brand new one. I know some folks recommend it, but I don't do a practice session by myself before giving a presentation. I mentally go through the slides many times, but I don't give the talk in front of a mirror or to an empty room. That may work for others, but frankly, I'd feel stupid doing that. So I went into this session not exactly sure how well it was going to work. I felt pretty safe timing wise - I've given enough presentations over the past ten years to have a good gut feeling for how much material is necessary. What I wasn't sure of was how well the content would work.\nFor me, the goal of this presentation was to introduce people to the idea of why using a static site generator may work well for them. I divided the session into the following sections:\n\nWhy\nHow - Generally\nHow - Specifically\nBringing Dynamic Aspects Back\nHosting options\n\n\nThe two &quot;How&quot; sections are where I was most concerned. The first How is basically stating that there are editors (like Dreamweaver) that can help build static sites and then there are generators - the main point of the talk. I then go into how generators work generally. I spent a lot of time in this section because my audience in this case were primarily people doing back end work. Like me, I think they had been used to a mindset of needing an app server to solve most problems. In my experience, whe",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Sorry for not responding...",
		"date":"Thu May 14 2015 00:51:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431564706,
		"url":"https://www.raymondcamden.com/2015/05/14/sorry-for-not-responding",
		"content":"So apparently, FormKeep had a bug where on the free tier, I was sent emails for every form response on my contact form. It was supposed to be limited to 10. About 1.5 months ago they &quot;fixed&quot; this bug, and, yeah, I didn't know. I'm not too pleased with that, but, at least I've got my data in a CSV file. If you used my contact form and wondered why I never got back - that's why. Going to start responding to items over the next week or so. I'm also going to see if I can get SendGrid integration working here.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Ionic hits 1.0",
		"date":"Tue May 12 2015 10:31:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431426716,
		"url":"https://www.raymondcamden.com/2015/05/12/ionic-hits-1-0",
		"content":"The title says it all - and I assume most folks heard about this on Twitter or via the Ionic blog, but on the wild chance you did not, today Ionic 1.0 was announced.\n\nDon't forget you can easily update an existing Ionic project using ionic lib update. Check out this real example:\n\nBe sure to read to the end of the announcement post as they go into detail about what the future holds for the Ionic framework and platform.\nFinally, if you want an introduction to Ionic, you can always check out my presentation tomorrow at devObjective, or watch the recording of my ORA presentation here: http://www.oreilly.com/pub/e/3407\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Fluent 2015",
		"date":"Mon May 11 2015 09:35:07 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431336907,
		"url":"https://www.raymondcamden.com/2015/05/11/fluent-2015",
		"content":"\nI had meant to write up my thoughts on Fluent 2015 as soon as I got back home, but, life has a way of - well - being life. And as I'm about to leave for another conference tomorrow I figured I'd better write up some thoughts about Fluent 2015 before I head out. I should point out that these nuggets are not in any particular order (heck, the first one is probably the least important), and that I've been to a couple Fluent conferences so part of my what I'm responding to are changes from previous years (and in every case - a good change).\n\n\nOne of my memories of the first Fluent was how awful the food was. Yeah, I know that isn't the most important thing, but if I can still remember that then you know how bad it was. (Or I'm just really preoccupied with food.) This year it was great. \nAnother memory of an earlier Fluent was having difficulty going from one room to another. I seem to remember a twisty path going up flights of stairs, entering a blue police box, etc. You get the idea. This year everything was nice and tightly packed. It was very easy to go from one room to another. I will gladly take a location that is half as fancy as another if it means a conference with easy to find rooms. Of course, this year the location was both fancy and well setup, so location-wise, it was perfect.\nWifi was a problem. Not always, but enough to stand out. That's the only black mark I can give this conference, and frankly, if they told me that wifi would be just as bad next year, it wouldn't make me reconsider going for one second. I'm going to include it here because it should be corrected and checked thoroughly for next year, but it certainly didn't take away from my enjoyment and education.\nContent has always been a strong point of Fluent and this year was no different. This is one of the few conferences where if I wasn't elected to speak, I'd beg my employer to send me as an attendee. (And thankfully now I have an employee that would actually do so.) So yeah, the content kicked ass, but I think what stuck out the most was how the content topics seemed to have shifted quite a bit since my first Fluent. That's to be expected, of course, but I noticed a dramatic lack of Angular. In fact, I think this one session (Accessibility in AngularJS and Beyond) was the only one with Angular in the title. (I'm not going to review the entire schedule, I'm probably wrong.) It wasn't that I heard any Angular-bashing as well, it just seemed like Angular was everywhere previously and now... now it isn't. Of course, I used Angular in my session on Ionic and it wasn't called out in my title. So maybe that's the thing - it's just kinda assumed now?\nOn the flip side, there was a big honking batch of ES6. I've been paying attention to ES6 but not actually writing any yet as it just feels a bit too early. Of course, I knew I could write ES6 now with transcompilers, but I have to be honest. I feel uneasy using them. It's the same feeling I had with CoffeeScript (along with not liking the syntax either). My gut felt like if I was writing code that would be completely transformed before it hit the browser than I was giving up some essential link to my environment. That's how I felt.\nThen I sat in on Brian Holt's React session where a transcompiler was part of a gulp script where it just made it all work automagically and you know what? Writing ES6 with React was pretty freaking cool. Cool enough for me to maybe think I need to get over my apprehension and start considering using it sooner rather than later. Parts of ES6 are - frankly - confusing as hell, but writing classes for React felt right to me.\nWhile I'm raving about content, I'll also give props out to my friend Brian Rinaldi and his practical web audio session. It had no code - just discussions of how one could use web audio in the real world (outside of games) and what would work and what would not. This type of content is something I wish conferences would push a bit harder to cover. It is something I've tried to focus on as well in my career. I've sat in too many &quot;OMFG UNREAL GAME ENGINE IN JAVASCRIPT&quot; sessions that were cool but useless to my development.\n\nAnyway - if you missed out - you can still purchase a full pass to watch videos of all the sessions. At the time of this review it wasn't yet available, but this form would let you sign up to be notified when they became available.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Online ColdFusion Meetup Back in Action",
		"date":"Mon May 11 2015 00:01:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431302471,
		"url":"https://www.raymondcamden.com/2015/05/11/online-coldfusion-meetup-back-in-action",
		"content":"After a hiatus, the Online ColdFusion Meetup is now back! As you can tell by the title, this is a user group for ColdFusion developers that meets online only. Run by Charlie Arehart, it has a long history of great presenters and content, and since it is 100% free with no travel costs, it is definitely worth your time checking out. The next meeting will be on Thursday, May 28, and will be Charlie himself discussing updating ColdFusion servers: &quot;Updating/Hotf­ixing ColdFusion: Tips and Traps&quot; with Charlie Arehart.\nYou can also browse all the old meetings here: http://www.meetup.com/coldfusionmeetup/pages/Recordings_of_the_ColdFusion_Meetup/\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "IBM and Ionic",
		"date":"Fri May 08 2015 03:15:55 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1431054955,
		"url":"https://www.raymondcamden.com/2015/05/08/ibm-and-ionic",
		"content":"So I don't typically post links to press releases (I think my readers know I'm not that kinda guy), but I wanted to share one that I thought was pretty important. As you know, I've been doing a lot of posts recently involving Ionic and MobileFirst. You're going to see even more of those over the next few months. Today the I'm sharing the news that we've (we as in IBM, I can speak for IBM, right? I've been here 3 months) are announcing a partnership with the Ionic company. You can read the complete press release here:\n\nIBM and Ionic Empower Business Users to Accelerate Mobile App Development\nAnd also the Ionic blog post here:\nIonic Creator Available for IBM MobileFirst Platform Users\nIn a nutshell, I think the takeaway from this is that IBM MobileFirst is an awesome product (admittedly I'm biased) to help support the back end services of your mobile app and Ionic is an awesome product (yeah, totally biased) for building the mobile app itself. Ensuring these two awesome things work together is a net win for developers. It is the very example of mixing your chocolate and peanut butter for development. Enjoy.\n\nTotally important picture credit: https://flic.kr/p/khUZLd by Mike Mozart\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with MP3s, ID3, and PhoneGap/Cordova - Adding IBM MobileFirst",
		"date":"Wed May 06 2015 09:44:15 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430905455,
		"url":"https://www.raymondcamden.com/2015/05/06/working-with-mp3s-id3-and-phonegapcordova-adding-ibm-mobilefirst",
		"content":"Welcome to the fourth and final entry in my series on using an ID3 reader for MP3s in a Cordova application. If you missed the initial entries (and I highly recommend reading these in order), they are:\n\n\nWorking with MP3s, ID3, and PhoneGap/Cordova\nWorking with MP3s, ID3, and PhoneGap/Cordova (2)\nWorking with MP3s, ID3, and PhoneGap/Cordova (3)\n\nIn this series, I described how we could use a JavaScript library and the Cordova File plugin to get ID3 info from MP3 files. In the last entry I made use of the last.fm API to fetch album covers. Their API was simple to use, but you may have noticed something - my API key was embedded in the code:\ndefs.push($http.get(&quot;http://ws.audioscrobbler.com/2.0/?method=album.getinfo&amp;artist=&quot; + encodeURI(artist) + &quot;&amp;album=&quot; + encodeURI(album) + &quot;&amp;api_key=5poo53&amp;format=json&quot;));\nIf my app was out in the world, anyone with two minutes to spare could fire up remote debugging and take a look at the code to find the API key. This is where something like MobileFirst can save the day. Back on April 8th (which was my birthday by the way, do I look older?) I blogged about using HTTP Adapters with MobileFirst.\nThe basic idea is simple:\n\n\nYou define your adapter at the command line. For me, I called mine lastfm.\n\n\nYou edit the adapter XML as needed. For me, I modified the domain for my service and specified a procedure name:\n\n\n\nIn the code snippet above, ws.audioscrobbler.com is the last.fm API domain and getAlbumCover is the procedure name.\n\nYou then write your implementation code. Since the API is simple - pass in an album and artist - my code is simple.\n\n\nMy function takes the arguments and generates a URL pointing to the API. Of special note - notice how we return only the image. Not only have we abstracted out the service from the client, allowing us to switch to a new provider if we need to, but we've also dramatically reduced the network packet sent to the client. How much so? Here is the full result of a 'regular' last.fm API call:\n>\nAnd here is what is returned now:\n\nThat's a rather significant reduction.\n\nThe final piece is to update the client side code. I had to make two changes. First, instead of using $http, I used WLResourceRequest. You can see a good doc on this here, but this is how my new code looks:\n\n\nWLResourceRequest returns a promise, so it was pretty much a two second mod. setQueryParameter threw me for a loop though. If you try to use individual parameters, like so:\n\nThen it will not work. The doc I linked to above makes this clear, but it was easy to miss. The last thing I tweaked was the result handling code:\n\nAs I said above, now my API use is both agnostic, and a bit more secure. I'm not saying it is 100% secure - in my sample app I'm not using login so anyone could sniff the network request and try to hack it, but it's a heck of a lot more locked down then it was before.\np.s. I had to make one more small tweak, and I plan on calling this out in it's own blog post. When using the file system and assets under www, MobileFirst takes your www assets from common and puts them in www/default. I kept getting &quot;File Not Found&quot; errors trying to parse my MP3s and that explained why. I'll discuss this more in a future blog post.\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Article: The Epic, Awesome & Supremely Useful Data Attribute",
		"date":"Tue May 05 2015 02:38:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430793493,
		"url":"https://www.raymondcamden.com/2015/05/05/article-the-epic-awesome-supremely-useful-data-attribute",
		"content":"One of my favorite companies, Telerik, has published an article of mine on their developer network: The Epic, Awesome &amp; Supremely Useful Data Attribute. Yes, the title is a bit over the top, but hopefully it is useful to my readers. Check it out and let me know what you think. (Obviously you should comment there, not here. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Working with MP3s, ID3, and PhoneGap/Cordova (3)",
		"date":"Fri May 01 2015 08:40:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430469653,
		"url":"https://www.raymondcamden.com/2015/05/01/working-with-mp3s-id3-and-phonegapcordova-3",
		"content":"This week I've done a few blog posts (part one and part two) about MP3 and ID3 parsing in PhoneGap/Cordova applications. Today I'm updating the application again - this time to support album art. Let's look at the results in the simulator first and then I'll walk you through the code.\n\nFirst - I updated my sample music a bit. My 5 year old loves the Daisy Chainsaw track:\n\nAnd here is the detail view - now with album art:\n\n\nOk, so how did I do this? While ID3 data can actually include album art (see the docs for the JavaScript library I use), it didn't seem like any of my files actually had this data. I made the call that - probably - most files do not. I don't have any scientific data to back this up, but I decided to make use of the last.fm API. The API was super easy to use. Like - &quot;Wait, it worked on my first try?&quot; easy. Given that you know an artist and an album, you can use the album.getInfo call to fetch data about the album. This includes multiple different sized images.\nOf course, the issue is that each of these API calls is asynchronous. Our MP3 service is already handling the ID3 lookup asynchronously. If you remember, I had to single thread it due to memory issues. But the API calls are jut http calls so running multiple in parallel shouldn't be a problem.\nHowever...\nGiven that you may have multiple MP3s from the same album, we can improve performance by not requesting the same album cover once we've made one initial request for it.\nOk... so let's take a look at the new services file.\n\nNormally I trim out the console.log messages as noise, but I kept them in due to the complexity of this service. The important bits begin in the process(0, function(data)) callback. The general idea is this:\n\nLoop over all the MP3s.\nGet the album and artist. (This needs to be improved to see if the tags exist.)\nCheck the albums object to see if we have already fetched it. Note - at this moment, the cache object is just a flag. The initial request isn't actually done yet. But we want to know that we've already done a request for that album.\nIf we aren't, hit last.fm. Note that the API key should be stripped out and put into a constants block. I've temporarily changed the key above to a non-legit value.\nWe've created an array of deferred objects. These represent the async operations (and yes, some aren't async, which is ok, we can still use deferreds for them). I can then use $q.all to say, \"Do this crap when ALL of them are done.\"\nIn that handler, I see if I've marked this as an item that should use the cache. In theory, this will never be run before an item that used the cache, so I check the albums object, which now has a real value in it, and use that.\nIf this isn't a \"use the cache item\", I fetch out the image from the result data from last.fm and store the cache.\n\nAnd that's it. I then just updated the view to make use of the image. I've updated the GitHub repo with this version: https://github.com/cfjedimaster/Cordova-Examples/tree/master/mp3reader\n",
		"tags":[
	        
            "cordova",
            
            "ionic",
            
            "phonegap"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "PhoneGap CLI and Templates",
		"date":"Fri May 01 2015 03:33:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430451189,
		"url":"https://www.raymondcamden.com/2015/05/01/phonegap-cli-and-templates",
		"content":"A few days ago the PhoneGap team announced an update to the CLI: PhoneGap CLI 5.0.0 Released!. To be honest, I don't typically make use of the PhoneGap CLI as I mostly use either Cordova or Ionic. However, I missed an earlier update that I think is pretty cool (and it is one I was able to help out with) - template support.\n\nWhen creating a PhoneGap project, you can request a template to be used instead of the default PhoneGap project. You've been able to copy from a directory for a while now (using --copy-from or --link-to), but this new feature lets you specify from a set of templates that might be useful for seeding a new project.\nTo see what templates exist, you execute: phonegap template list (you can also replace template with recipe):\n\nThere are only four templates so far, but more will be coming in the future. I've said before that I'm not a fan of the default template used by both Cordova and PhoneGap, so I'm happy to see blank as an option. The jQuery Mobile template is one I built. To use a template, you simply use --template name when creating your application.\n\nMy template includes the latest jQuery Mobile assets, and includes JavaScript code that will fire a method when both jQuery Mobile and PhoneGap is ready to run. If you're curious, you can see the repo here: https://github.com/cfjedimaster/jQuery-Mobile-Starter.\n",
		"tags":[
	        
            "phonegap"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with MP3s, ID3, and PhoneGap/Cordova (2)",
		"date":"Thu Apr 30 2015 10:24:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430389464,
		"url":"https://www.raymondcamden.com/2015/04/30/working-with-mp3s-id3-and-phonegapcordova-2",
		"content":"Yesterday I blogged about using MP3s and ID3 information in a PhoneGap/Cordova application. Today I've taken the initial proof of concept I built in that demo and updated to make use of the Ionic framework. I've also a few other features to make the application a bit more applicable to real world usage. Finally, I've also uploaded it my GitHub repo (along with a copy of the last version) for you to use in your own applications. Before we get into the code, let's take a look at the visual updates.\n\nThe first update was the addition of a spinner dialog. I used the spinner from ngCordova.\n\nThis will display while the code is parsing the MP3s for their ID3 information. When done, a list is displayed:\n\nYeah, not very colorful, I really need to add something to the header to make it prettier. But you get the idea. Then when an item is selected, you get a nice Ionic card display:\n\nNow let's break down the code - and remember - you can download everything from the repo I'll link to at the bottom. First - the core app.js for the app:\n\nThe only thing of note here really is the use of $stateProvider to setup the various states of my app - which in this case is either a list of MP3s or a detail. Now let's look at the controller.\n\nOk, so this one is a bit more complex. The first controller, ListCtrl, handles asking a service to return a list of MP3s. It uses the spinner dialog to let the user know &quot;stuff&quot; is going on in the background. Once it has the data, it hides the spinner and the results are displayed. Note the deviceready listener wrapping the call. I forgot this initially and spent about an hour trying to figure out why my app wouldn't run until I did a reload in the console. Dumb, I know, but sometimes when I use Ionic I forget to remember I need deviceready in my controller.\nThe next controller handles fetching specific information about a MP3 as well as providing a way to play the MP3. I put that in a service as well so I could handle storing the state of the current MP3 being played.\nSo far so good? Ok, let's take a look at the service. Most of this is from yesterday's post.\n\nOk, there's a lot going on here. First - for this application I decided to ship the MP3s with the application. Now, in a real world app if you were going to do that, you wouldn't bother using an ID3 service. You would simply hard code it. That would be a heck of a lot quicker. But try to imagine an app where MP3s are downloaded after the initial install. This brings up another interesting issue. The area under www is read only, so technically you can't download there. But - and I'm not 100% sure on this - the Media plugin only supports remote URLs and local URLs under www. I could be wrong on that (and I've raised the question on the PhoneGap developer list), but... yeah. I'm not sure how the Media plugin would work with stuff outside of www. For now, I'm going to pretend it isn't an issue.\nAnother thing I didn't do here is caching. Since the service won't run again when you return to the app home page, I didn't need it, but I'd strongly consider adding a simple caching layer with LocalStorage. I think storing the tags for a path would be simple enough and would take maybe five minutes more work.\nAnd that's pretty much it. You can find the full source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/mp3reader. Tomorrow I'll have yet another iteration of this demo.\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "My Cordova book is on sale again",
		"date":"Thu Apr 30 2015 02:37:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430361452,
		"url":"https://www.raymondcamden.com/2015/04/30/my-cordova-book-is-on-sale-again",
		"content":"So a few weeks ago, my publisher ran an awesome deal for my Cordova book and their upcoming Ionic book. Today they are running the same deal. You can pick up &quot;Apache Cordova in Action&quot; and &quot;Ionic in Action&quot; both for half off. These are both MEAP versions, which basically mean an &quot;in progress&quot; copy of the book. You get the full ebook when done, and updates while new chapters are released. My book currently has nine chapters available, but I turned in the last chapter a few weeks back so you can expect to see the next three rather soon. (Yes, the end is in sight!) When checking out, use the code dotd043015 for your discount.\n\n\n",
		"tags":[
	        
            "cordova",
            
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with MP3s, ID3, and PhoneGap/Cordova",
		"date":"Wed Apr 29 2015 11:06:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430305604,
		"url":"https://www.raymondcamden.com/2015/04/29/working-with-mp3s-id3-and-phonegapcordova",
		"content":"As someone who remembers when MP3s became a de facto standard for audio files (*), I'm pretty familiar with the format used to embed metadata within them - ID3. If you've ever wondered how your favorite MP3 player displayed data about your music (artist, album, year, etc.), most likely it came from the ID3 tags embedded in the file. Almost ten years ago I even blogged about parsing them with ColdFusion. I thought it would be interesting to take a look at how ID3 parsing could be done within a PhoneGap/Cordova application.\n\nFor my testing, I decided to use an open source JavaScript ID3 project at GitHub: JavaScript-ID3-Reader. My biggest concern was performance. In the ReadMe for the project, they mention that if your web server supports the HTTP Range feature, it will only grab the bits it need. (If I remember right, the ID3 data is all at the end of the MP3 file.) Otherwise it reads in the entire file which - obviously - won't perform well. I decided to give it a shot anyway and see what my results would be.\nI decided to test this on my HTC M8. I've got a few MP3s there on my SD card and wrote a quick proof of concept that would scan one hard coded directory. One cool thing about the JavaScript-ID3-Reader is that it supports FileEntry objects and you can get FileEntry objects using Cordova's File plugin. Let's take a look at my first stab at this. I'm sharing just the JavaScript as the HTML is literally just a div block for me to write crap out too. In the next post I'll be adding a proper UI to this.\n\nOk, so let's take this from the top. I'm using a Cordova File plugin alias for the SD card. This is an Android only alias but as I said - I'm testing on my phone. Speaking of - I used an app to browse to one particular folder (in this case, disc one of Depeche Mode's excellent live album, 101). Once I convert that path into a directory object, it is then a simple matter of reading the entries from the directory. Once I've got the list of entries, I then loop over them and use the Javascript library's API to get the ID3 tags. If you check their docs, you can see additional options, but for the most part I just wanted all the tags so that what I did.\nThis promptly crashed and crapped the bed with an out of memory error. I then figured that the entry.file calls - being async - were running the ID3 parser for multiple mp3 files at the same time. I then rewrote the logic to handle the calls in order. This isn't necessarily pretty, but it worked right away:\n\nIn this version, I've used a simple counter and made a loop that calls itself until all the entries have processed. I then added simple code that writes out to the DOM the results.\n\nSo how well does it work? I fired up Chrome Remote Debug and watched it process. I'd say it takes about 1 second for each file to parse. That's not speedy - but you could easily cache these results so you aren't reparsing the MP3 on every request. You could also quickly display the file name (soandso.mp3) until you get the proper title from the ID3 info. That way a user could see the names, play the files, etc and then your code could update the display as it gets them.\nIn the next version of this project, I'll be adding an Ionic front end to the code and making it a bit prettier. I'll also make it more generic so it can work on iOS and Android. I'll be sharing the full source code, but I want to complete the second version before I push to my Cordova Examples repo.\n* I'm old enough to remember downloading music files in... AIFF format I think... back in 96 or so. I can remember thinking how cool it was (and illegal) that I could download 5-6 meg files of - if I remember right - Journey. Oh, and it took about 15 minutes for these files to download at my college's Sun something or another computer lab. Yeah - I'm old.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Can't find Cordova plugin docs?",
		"date":"Wed Apr 29 2015 02:33:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430274819,
		"url":"https://www.raymondcamden.com/2015/04/29/cant-find-cordova-plugin-docs",
		"content":"With the last big release of Apache Cordova, plugins have now moved to npm. (Details may be found here: Plugins Release and Moving plugins to npm: April 21, 2015. One temporary side effect of this move is that the documentation is no longer working. So for example, when you go to the Camera plugin page, you see this:\n\n\nThis is simply a bug and one that will be fixed soon. (And if you're curious, you can track the bug here: Markdown incompatible with GitHub flavour markdown) In the meantime, you've got a simple way to get to the docs.\nClick the repo link on the right hand side:\n\nThis takes you to the repo at Apache, which may be a bit weird to you if you've only used GitHub before. Click tree to go to the source tree:\n\nThen click the Raw link next to README.md. (Note there is also a useful RELEASENOTES.md file as well.)\n\nAnd there ya go - the docs. Obviously this won't be an issue for long, but for folks who may not be familiar with npm and Apache's repo system, I thought this might help.\n",
		"tags":[
	        
            "cordova"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Looking at the JavaScript API in Hybrid MobileFirst Apps",
		"date":"Tue Apr 28 2015 07:22:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430205759,
		"url":"https://www.raymondcamden.com/2015/04/28/looking-at-the-javascript-api-in-hybrid-mobilefirst-apps",
		"content":"I've been blogging lately about hybrid apps and MobileFirst, and today I thought I'd start investigating the JavaScript client-side API portion of the product. These are API methods you have available to you in your hybrid application. For today, I'm going to focus on the WL.App namespace.\n\nHere are the (non-deprecated) methods of WL.App, along with some thoughts and suggestions on how you could possibly use them in your application.\ngetDeviceLanguage/getDeviceLocale\nThe first returns the language code, not the name, so for me it would be en, not English. Locale will also be the code, so en_US for example. So how does this compare to the Globalization API? The biggest difference is that these are synchronous, which to me seems to make a bit more sense.\ngetServerUrl/setServerUrl\nThese get and set the MobileFirst server url. I don't imagine there are often times when you would want to set the URL, but perhaps for testing purposes you may want to switch the URL being used on the fly. I could see the getter then being used to provide feedback about which server is currently being used. Make note that the API here uses Url in the method names. Later on you will see a method using URL.\nhideSplashscreen/showSplashscreen\nI've already talked a bit about this in regards to bootstrapping an Ionic application under MobileFirst.\noverrideBackButton/resetBackButton\nOnly applicable to Android and Windows Phone 8, this lets you change the behavior of the device back button. Having a reset there is handy to quickly go back to the system default.\nopenURL\nSo yes - this is URL not Url! This opens up a new browser to a particular URL. There's options you can pass in (see full docs here) but they don't apply to Android and iOS. Note that this does not work like the InAppBrowser. This opens the system browser as a new activity. On Android you can hit Back to return to the app, but on iOS you would need to return to the app using the double click/select behavior. (That I don't think many users really know about.) I think in most cases you will probably want InAppBrowser instead, but this is another option.\nBackgroundHandler.setOnAppEnteringBackground / BackgroundHandler.setOnAppEnteringForeground\nNote that these methods are on the BackgroundHandler object (so the full API is WL.App.BackgroundHandler.etc). These two methods are iOS only but are really freaking neat. When an app is put in the background, iOS takes a snapshot of the current view. This could be a security issue since sensitive information may be on the screen. By using these events, you can hide/show sensitive information so it doesn't show up when the user is viewing running apps in the background. You can either specify a custom function (to hide specific items) or tell the handler to just blank it out.\nHere is a screenshot. Note that the scratch app is blanked out.\n\naddActionReceiver/removeActionReceiver/sendActionToNative\nNow - this is cool one. Typically when you want to use native code, you have to build a plugin. Plugins are necessarily difficult to write, but you may not necessarily want to go that far for everything you do. MobileFirst's client-side API provides a simpler solution. You can use sendActionToNative to send a message to your native code. Your native code can then do... whatever. There's a reverse to this as well. You can tell your hybrid app to listen in for actions sent from the native side and react appropriately. As an example, imagine this within your JavaScript:\n\nThen on the native side - you can listen for it and do something:\n\nAs you can see, you can listen for the action string and do something with it. You could also handle the args sent to it. In my example I just open an alert (which, to be clear, you do not need to do this way, just use the Dialogs plugin) but I could do pretty much anything here. And again - I could broadcast back to the JavaScript code as well. For times when you don't want a full plugin and just need to quickly talk to the native side, this is a pretty cool option.\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Testing Camera Quality Settings and PhoneGap/Cordova",
		"date":"Mon Apr 27 2015 07:03:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1430118211,
		"url":"https://www.raymondcamden.com/2015/04/27/testing-camera-quality-settings-and-phonegapcordova",
		"content":"As you know, when using the Camera plugin with PhoneGap/Cordova, you have an optional quality setting. It accepts values from 0 to 100 with 50 being the default. I was curious as to how much of an impact this setting had on the final result. Obviously quality can be subjective, but I thought it would be interesting to build a simple tool that would let me test the settings and compare the results.\n\nI began by building a simple Node.js application. Its sole purpose was to simply listen for a form POST and save attached files to a time stamped directory. While not really important to the discussion, I wanted to share the code because this is the first time I've done uploads in Node and Formidable made it freaking easy as heck. Like, I went from thinking I'd need 3-4 hours to figure it out to having the entire server done in about 30 minutes. Anyway, here's the code:\n\nAgain - don't spend too much time looking this over. It really isn't the important part. Now let's discuss the app. I created a quick Ionic app with one button. The idea would be that you would click the button, and the Camera API will take over. Each iteration of clicking would change to a new quality setting. I decided 20%, 40%, 60%, 80%, and 100% would be good testing points. I also decided to do the same tests for pictures selected from the device. My gut told me that the quality setting would have no impact there, but I wasn't sure. When I tested, I confirmed that quality does not impact existing pictures, so in the code below you will see some parts that are no longer applicable. (And I should stress, this code is an absolute mess. I was going for quick and dirty here, nothing more.)\nAnother interesting aspect of the code is that I decided to use XHR2 instead of Cordova's File-Transfer plugin. Why? Because the plugin doesn't support multiple uploads at once. With XHR2, I could create a FormData structure with all my file data and send them in one request.\nSo - the code - and again - this is a mess so don't consider this suitable production code. I'm only including the JavaScript as the HTML is a header and a button.\n\nOk - so what were the results like? In the following screen shot, 0.jpg represents the first image, which is at 20%, whereas 4.jpg represents the last one, at 100%. Note the file size differences:\n\nHow about the quality? I'll include all the images as an attachment to this blog post, but let's focus on the 20% and 100%. I'm resizing these too of course. First, the 20%:\n\nAnd now the 100%:\n\nThere seems to be a huge difference in the details of the wallpaper and the colors in the flowers.\nInterestingly enough - the 80% is over one meg smaller despite being just 20% less quality. Obviously theres a lot of loss going on - but I think if you look at the 80% - it looks really good:\n\nCertainly this isn't an incredibly scientific test. I left my default camera settings on and each click was a new picture. Many things could have impacted the result - how I held the camera - small changes in light - ghosts, etc. For folks curious, I tested this with an HTC M8. If folks want, I can give the iPhone a try as well.\nI've uploaded the zip of images here: https://dl.dropboxusercontent.com/u/88185/output_2015_4_27_12_26_22.zip.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Connecting PouchDB to Cloudant on IBM Bluemix",
		"date":"Fri Apr 24 2015 09:39:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429868383,
		"url":"https://www.raymondcamden.com/2015/04/24/connecting-pouchdb-to-ibm-bluemix",
		"content":"So, as always, I tend to feel I'm a bit late to things. Earlier today my coworker Andy was talking to me about PouchDB. PouchDB is a client-side database solution that works in all the major browsers (and Node) and intelligently picks the best storage system available. It is even smart enough to recognize that while Safari supports IDB, it doesn't make sense to use it and switches to WebSQL. It has a relatively simply API and best of all - it has incredibly simple sync built in.\n\nI tend to work with client-side databases with just the vanilla JavaScript APIs available to them, but honestly, after an hour or so of using PouchDB I can't see going back. (And yes, I know other solutions exist too - and I'm going to explore this area more.) Probably the slickest aspect is the sync. If you have a CouchDB server setup, you can set up automatic sync between all the database instances in seconds. For my testing, I decided to use IBM Bluemix. This blog post assumes you're following the PouchDB Getting Started guide.\nFirst, add the Cloudant NoSQL DB service to your Bluemix app:\n\nAfter you have added the service and restaged your app, select it, and then hit the Launch button:\n\nThis fires up the Cloudant administrator where you can do - well - pretty much everything related to setting up your database. But to work with that guide at PouchDB, select Databases and then &quot;Add New Database&quot;:\n\nThen enter todos to match the guide:\n\nOk, you're almost done. You then want to enable CORS for your Cloudant install. In the Cloudant admin, click Account and then CORS. Enable it, and then select what origin domains you want. For now, it may be easier to just allow all domains.\n\nWoot - ok - one more step. When using PouchDB and sync, they expect you to supply a connection URL. You can get this back in your Bluemix console. Select the &quot;Show Credentials&quot; link to expand the connection data and then copy the &quot;url&quot; portion.\n\nAnd voila - that's it. If you open your test in multiple browsers, you'll see everything sync perfectly. Remember you can also use PouchDB in Node.js, which, coincidentally, you can also host up on Bluemix, so yeah, that works out well too.\nEdit on June 3, 2015: Oops, that isn't quite it - but close. The URL you get from the console is a root URL for your CouchDB instance. In order to work with a database, like todos, you want to add /todos (or whatever) to the end of the URL you use in your code.\n",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "IndexedDB and Limits - IE",
		"date":"Fri Apr 24 2015 02:34:06 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429842846,
		"url":"https://www.raymondcamden.com/2015/04/24/indexeddb-and-limits-ie",
		"content":"Last week I blogged about maxing out the database size on your browser with IndexedDB, but I didn't test with IE. This morning I did, and unfortunately, it looks like IE does the same bad thing it does with LocalStorage (see my post for details).\n\nI discovered that IE11 would silently fail on adding LocalStorage data when the storage limit was reached. Even worse, you could set, and read data that wasn't actually stored. I slightly modified my code from the previous post to make it a bit more verbose. Here is the new version - I'll point out what's new below.\n\nThe first change was to set both the db and objectstore name to variables so I could quickly change them. I then added error/complete handlers to the transaction that handles adding data. Finally, I added a simple &quot;count&quot; method.\nTo test, I loaded the page up and clicked like crazy. I noticed that at 31 items, the count stopped updating. Here is where things got bad. Before this limit, my transaction complete handler did run. After I hit it, the success handler stopped running, but the onerror method never ran!\nI modified the objectStore name and confirmed that in a new store, I could still successfully add data (according to the event handlers) but count was stuck at 0. I had to clear my data in IE settings to get it to let me add data again.\nI'll file a bug report for this as soon as I can, but I honestly don't know what to suggest. You could add a timeout call in the add functionality to check for a global variable set in your transaction success. If not set, then it didn't fire and you can assume it failed. That feels quite hackish though.\nBut hey - this is still better (kinda) than mobile Safari, so, yeah, there's that. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Want to learn about Ionic?",
		"date":"Thu Apr 23 2015 02:56:40 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429757800,
		"url":"https://www.raymondcamden.com/2015/04/23/want-to-learn-about-ionic",
		"content":"On May 4th (hmm, that's a special day, isn't it?) I'll be giving an online presentation on the Ionic framework for O'Reilly Media. This will be at 12PM CST and will last for about one hour. You can sign up, for free, here: Using Ionic with Cordova/PhoneGap. I'll also have a special announcement during the presentation. Hope to see you there!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Big updates to Apache Cordova",
		"date":"Tue Apr 21 2015 16:46:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429634806,
		"url":"https://www.raymondcamden.com/2015/04/21/big-updates-to-apache-cordova",
		"content":"Over the past few days, there have been some pretty big updates to Apache Cordova. If you haven't been following their blog (they don't have a &quot;subscribe&quot; feature so I use a IFTTT email rule), then you may have missed out on the announcements. Here is a quick review of what's new:\n\nAndroid 4.0\nSo, what you may not know (and I'm honestly curious about how many people are aware of this) is that each platform (the actual bits you get when you add a platform to your Cordova project) is it's own separate project and has it's own version. I'm willing to bet most Cordova developers don't think about this very often, but sometimes pretty important updates happen to one platform that you should be aware of. On April 15th, Android 4.0 was released, with the biggest feature being the support of Crosswalk as a pluggable web view. Crosswalk gives you a way to provide a consistent, modern webview in your Android Cordova projects no matter what version of the Android browser they have may on their device.\nAnother big change is that you must use the Splashscreen plugin if you want to use a splashscreen with your Android application. Previously you could get by with just config.xml changes, but now the plugin is required.\nFinally, a new plugin (cordova-plugin-whitelist) is required to have whitelist support in your project. If you do not use this plugin, your app will act as if has no whitelist and will block all remote requests! Luckily the CLI now adds this by default, but be aware.\nPlugins move to NPM (and updated)\nAnother big change is that plugins are now being loaded from NPM. As a practical matter you may not care, but the IDs used to load plugins have changed from org.apache.cordova.something to cordova-plugin-something. So for example, you would switch from:\ncordova plugin add org.apache.cordova.device\nto:\ncordova plugin add cordova-plugin-device\nThe old IDs still work, and will work for some time, but you will want to get used to the new naming scheme. Want to search for plugins via the CLI? Use:\nnpm search ecosystem:cordova\n\nAll of the plugins have had minor updates and you can see a list of changes on the blog post.\nI'm scanning the list now, and there seems to be a huge amount of bug fixes. Things that stand out are:\n\n\"Added nativeURL property to FileEntry, implemented readAsArrayBuffer and readAsBinaryString.\" - This is nice!\n\"Unable to read android_asset directory through File API\" - this bug fix lets you use the file system API to read from android_asset. I ran into this as well, so I'm happy to see it fixed.\nBrowser platform support for File and File-Transfer\n\nTools Update\nAnd finally, the CLI has updated. You'll want to do a npm update to get the latest (version 5). Notable updates include:\n\nSupport for plugins via npm as described above.\nThe State feature I blogged about for Ionic is now supported in Cordova as well. It is a bit different though. Unlike Ionic, the Cordova CLI will not save plugins and platforms by default. To save the fact that a project uses a platform or plugin, you must include --save in the command line call, like so:\ncordova plugin add cordova-plugin-device --save\nBut if you forget, you can quickly save everything with two commands:\ncordova plugin save\nand\ncode>cordova platform save\nWhen you save, data is stored within your config.xml file:\n\nAnd to restore your plugins and platforms, you would do\ncordova prepare\n\n\nYou can read more details in the blog post.\nSo... thoughts?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Crazy cool Chrome extension (and how to dig into the source)",
		"date":"Tue Apr 21 2015 03:40:04 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429587604,
		"url":"https://www.raymondcamden.com/2015/04/21/crazy-cool-chrome-extension-and-how-to-dig-into-the-source",
		"content":"I'm learning some interesting stuff at FluentConf and I'll talk more about that when the conference wraps, but I want to share something I found yesterday that I thought was really freaking cool. It is a Chrome extension called Momentum. Apparently this was all over the place a few weeks ago so I'm late to the party, but it replaces your &quot;New Tab&quot; screen with an absolutely beautiful (and somewhat useful) design:\n\n\nYou can customize it a bit, but really, it's just that - a beautiful picture and a greeting. The text, &quot;Patience&quot;, comes from the app prompting you the first time you open a a new tab in the morning:\n\nWhich - ok - maybe it is a bit dorky - but I like it. I was also a bit curious about how it worked. Chrome extensions are built with web standards, so I decided to take a quick look at the code.\nTo find your Chrome extensions, you first need to find where your profile is. Open up chrome://version and you'll see a &quot;Profile Path&quot; folder. Get to that directory and then enter the Extensions folder. Here you'll see this wonderfully helpful list of directories (obviously different for each user):\n\nOk, wow, so, now what? Well next go to your Chrome extensions tag (chrome://extensions/), and find the extension. Make note of the ID value:\n\nYou would then enter the directory with the same ID as the extension. At this point, you can dig through the assets and see how they do - well - what they do.\n\nWhat I found out about Momentum is that the background images are stored locally, which makes sense because you could be offline, and they ship quite a few of them. I also took a look at their JavaScript libraries to see how they built it. It is a Backbone app which is cool, but not a framework I'm really interested in anymore.\nThe core logic was minified, but that doesn't really slow you down. I opened a new tab, and then opened dev tools. I went to the minified file and clicked Pretty Print:\n\nWhich then gave me nice, readable text:\n\nI then selected all and copied into an editor so I could go through it in a proper tool. For me, the real interesting tidbit I found was how they handle weather. They use YQL, a Yahoo API I've blogged about before, but I really don't see many people using. It is a very cool API so I'm happy to see it in action. If your curious how they use it, this is the block that grabs your current weather:\n\nYes - the YQL API is SQL like for data. Very cool stuff. Anyway, check out the extension, and don't forget you can dig into the code and learn from them as well!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Ionic adds a new State feature",
		"date":"Mon Apr 20 2015 03:31:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429500716,
		"url":"https://www.raymondcamden.com/2015/04/20/ionic-adds-a-new-state-feature",
		"content":"To be fair, this may not be very new, I've been working with an alpha version of the Ionic CLI while I test push (and other stuff ;) so my version has been a bit out of sync. Today I switched from the alpha to the release version and discovered a cool new feature - State.\n\nThe State feature lets you quickly save and restore plugins and platforms for an Ionic project. By default, when you add a platform or a plugin, Ionic will store this information in the package.json file. Here is an example of adding a platform:\n\n(And as an aside - did you notice it added icons and splashscreens? Freaking awesome.) And here is adding a new plugin:\n\nWhen done, here is how package.json is updated:\n\nTo skip this, you can use --nosave for both platform and plugin modifications. Now comes the cool part. To quickly load in plugins and platforms, you can simply do:\nionic state restore\nAnd Ionic will add in the appropriate plugins and platforms. This will be incredibly useful for folks checking out your Ionic project from a source control repository.\nYou can also do:\nionic state save\nTo store the current platforms and plugins to the package.json. I would imagine you would use this feature if you used the --nosave option to test a plugin you weren't sure you wanted to actually keep around.\nYou may want to remove everything. You can do this with:\nionic state clear\nBut be aware this really, really does remove everything, including the default plugins Ionic always install. Finally, you can do:\nionic state reset\nThis will remove everything then bring back what you have specified in the package.json file.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Some games to pick up",
		"date":"Sun Apr 19 2015 14:08:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429452492,
		"url":"https://www.raymondcamden.com/2015/04/19/some-games-to-pick-up",
		"content":"As it is Sunday and I'm sitting in a hotel in a San Francisco killing time till dinner, I thought I'd catch folks up on the games I've been playing recently. There's three in particular that are somewhat related so I thought why not share with my readers. These are in no more particular order than whatever comes to mind first.\n\nHeroes Generations\nThe first is probably the quickest to pick up and great for a quick casual game, Hero Generations. Hero Generations (HG) is a very interesting concept. You play a fairly stereotypical RPG hero, but every move in the game equals one year of your life. The \"generations\" aspect comes in in regards to how you make your game last longer - by finding a mate and having a child. As you play, you gain traits that may or may not pass on to your offspring. This makes a typical \"Kill the Big Ass Dragon\" quest much more interesting than normal. You actually find yourself planning for your grandchild, or great grandchild, to be prepared to attack the beast. \nFurther making the game unique is that it has a &quot;sim&quot; aspect to it. The towns you encounter can be improved with things that bring both money and strength (the primary combat trait) to your character. The improvements you add now will be appreciated by your children in the future.\nTo make things even more interesting, the game has no save slots (well, it saves your state) and death is permanent. In my first game my lineage died out when I simply didn't return to a town in time to marry and have a child. The game has a great casual yet intense mix to it and I'm really enjoying it. The game is rather cheap too ($15).\n\nCity: Skylines\nI spent many hours playing SimCity (cracked copies at that) at my university's computer lab back in the 90s. I've played pretty much every iteration of it except for the last. I wasn't so much bothered by the online requirement (Diablo 3 has that and I play the hell out of that), but the game just kinda rubbed me the wrong way. If I remember right, it had forced connections to other cities and I just didn't care for that. I wanted my sandbox to be... well... my sandbox. \nA few weeks ago I heard about City: Skylines and gave it a look. The first thing I noticed was that it was freaking beautiful.\n\n\n\nI picked it up and immediately felt like I was playing the good old SimCity I remembered from the past. The game play works well, but with obvious differences from classic SimCity. It seems like police and fire coverage don't matter quite as much, and health seems to be way more critical to infrastructure. Sometimes it can be frustrating (I've entered stages where dead bodies pile up in homes no matter how many cemeteries I build), but mostly in a good way. Even better, it has a built in cheat mode, which I really appreciate in my sim games. Sometimes I just want to build and not worry about reality. ;) Like Heroes, this game is rather cheap too - currently 29.99 on Steam.\nBeyond Earth\nOr more accurately, \"Sid Meier's Civilization: Beyond Earth\", is yet another sim but this time in the Civilization family. The last time I pulled an all nighter for a game was for Civilization - again - back in college - and again - playing cracked copies at my university. This game is - for all intents and purposes - the spiritual successor to Alpha Centauri, which was basically a sci fi version of Civ. I loved the game and so far I love this game too. Great graphics, interesting game play (like AC, the planet itself can be an adversary), and a good tech tree that actually makes sense for an advanced civilization. (It works more like a tree going in all directions than the more 'directed' ones used in the other games.) \nThere is an amazing amount of detail/story/etc to immerse yourself in to make the sim even more engrossing and out of the three games I listed today this is probably the most difficult and time consuming, so keep that in mind before you purchase.\n",
		"tags":[
	        
		],
		"categories":[
            
                "video games"
            
		]

	},

	{
		"title": "IndexedDB and Limits",
		"date":"Fri Apr 17 2015 09:05:42 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429261542,
		"url":"https://www.raymondcamden.com/2015/04/17/indexeddb-and-limits",
		"content":"Earlier this week I posted about hitting the limits of LocalStorage (Blowing up LocalStorage) and today I thought I'd do a bit of testing around IndexedDB. Unfortunately, I don't really have a simple &quot;if you do this, X happens&quot; type story to tell, but I did find out some interesting things about storage limits. I want to thank the following people for help in writing this post: Ben Kelly of Mozilla, Joshua Bell of Google,  Addy Osmani of Google, and Paul Irish of Google.\n\nSo before we begin, let's talk limits. This is what MDN has to say:\n\nThere isn't any limit on a single database item's size, however there is in some cases a limit on each IndexedDB database's total size. This limit (and the way the user interface will assert it) varies from one browser to another:\n\n Firefox has no limit on the IndexedDB database's size. The user interface will just ask permission for storing blobs bigger than 50 MB. This size quota can be customized through the dom.indexedDB.warningQuota preference (which is defined in http://mxr.mozilla.org/mozilla-central/source/modules/libpref/src/init/all.js).\n Google Chrome: see https://developers.google.com/chrome...rage#temporary.\n\n\nThis is - unfortunately - not quite correct. (But frankly, MDN being as awesome as it is gets a pass for not being perfect.) I mentioned to Ben Kelly that it was a bit weird that Firefox would only prompt for one big insert but be ok with a bunch of small ones. In fact, my test script (more on that below), inserted a 3 meg-ish Base64 image. I ran it about 100+ times or so and never got a prompt. Ben pointed out in this GitHub issue (Rethink about the storage model) that the prompting for &quot;one big blob&quot; had been removed.\nOne thing I'll point out before going further - storage as a general concept for browsers is in a huge state of flux right now. There is chaos. That's a bit frustrating, but it is really good that these conversations are happening now. In my mind this should have happened before Web Audio and Animation crap, but I'm a nerd who likes databases and IDB doesn't demo as well as Unreal in the browser. ;) If you want to see some of the current thinking in regards to storage, see: WhatWG Storage\nOk, so going back to that MDN quote - the link for Chrome is also incorrect. If you follow it, you will see that IndexedDB is described as temporary. Under persistent storage, it even says this:\n\nPersistent storage is storage that stays in the browser unless the user expunges it. It is available only to apps that use the Files System API, but will eventually be available to other offline APIs like IndexedDB and Application Cache.\n\nBut I got confirmation that this is no longer true. But... it is possible that Chrome may delete your IDB. If space on the host machine is low, then Chrome will clear out an IDB data based on a LRU policy. It will delete the entire local database - it will not trim. And to be clear, we are talking about one IDB database instance, not all of them.\nFirefox will also follow a similar procedure. If disk space becomes an issue, it will clear out IDB.\nSo given that there isn't a real good way to test quota, I was kinda curious to see what would happen if I abused IDB a bit. I wrote the following script which, on button click, would insert the base64 version of a 3 meg ish image. By the way, this code is pretty bad. I just noticed I convert the image on every click. I should cache the string in RAM while I test. But you get the idea - click a button - insert a bunch of crap.\n\nI did a lot of testing, and by testing, I mean I just clicked like crazy. It took a while, but I finally got an error in Firefox:\n\nBen Kelly and I spoke more on Twitter (like, a few seconds) ago, and he added some more information about Firefox:\n\nYes, it will evict (i.e. kill) an IDB by a LRU (Least Recently Used) policy.\nThe max is dynamic and based on your hard drive.\n\nHe had these details to add:\n&quot;Heurestic is roughly: all origin combined can take up to 50% available disk space, no one origin more than 20% available.&quot;\n&quot;Err... no one origin more than 20% of the total allowed for all origins.  So thats actually 20%*50%=10% of available disk.&quot;\nJonathan Smith wrote an interesting little JS snippet you can paste into your console to check the size of an IDB table: getIndexedDbSize.\nIf I read his results right, I got Firefox up to about 2.8 gigs of storage before it threw that error. My drive maxes out at 500 gigs. So if Firefox can take 10% of that and one origin can take 20%, then 2.8 feels certainly within the ballpark.\nFor Chrome, I couldn't get it to throw a QuotaErr, and eventually Smith's test script ended up crashing the tab. It is also possible I just gave up before I hit the upper limit.\nI didn't test in Safari because of how horrible they have screwed up IDB. I don't even want to think of it. Opera worked pretty much the same as Chrome.\nSo - take aways? IDB is still good &quot;persistent&quot; storage in terms of h",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "The Second Star Wars Teaser",
		"date":"Thu Apr 16 2015 10:04:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429178672,
		"url":"https://www.raymondcamden.com/2015/04/16/the-second-star-wars-teaser",
		"content":"I was supposed to write a blog post on IndexedDB today - instead I got royally distracted by the epic cool new Star Wars teaser:\n\n\nThere is so much to love in this. I love how the opening shot is reminiscent of ANH's landspeeder shot. I love the crashed Star Destroyer. And then things just get better and better. The new Stormtroopers and new Imperial logo (time for a new tattoo!), the new TIE fighters, and more shots of the big bad - Kylo Ren. A new - I think - Star Destroyer type as well. (Seems to have only one shield generator versus two.) And of course - the final shot of Han and Chewie. No - I'm not crying - something just got stuck in my eye. Honest.\nCan't. Freaking. Wait.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Speaking on Ionic next week at FluentConf",
		"date":"Wed Apr 15 2015 04:35:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429072512,
		"url":"https://www.raymondcamden.com/2015/04/15/speaking-on-ionic-next-week-at-fluentconf",
		"content":"After a very long dry spell (thank you again, Adobe, for consigning me to technical writing), I'll be back on stage next week at FluentConf 2015. FluentConf is easily one of the best technical conferences around. Last time I checked the schedule, I pretty much wanted to attend every single session. If you are in the area, or can make it, I highly recommend attending. And what about me? I'll be speaking on Ionic at 11:15. Full details of my session may be found here: http://fluentconf.com/javascript-html-2015/public/schedule/detail/38903. You can find the complete schedule here: Fluent 2015 Schedule.\nAnd best of all - and only because I really like you - you can use this SUPER SECRET CODE for a 20% discount. To make you work for it, I've hidden it here: FRIEND. If you can defeat my super secret hiding technique, you can have the code.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "ColdFusion Updates Released Today",
		"date":"Tue Apr 14 2015 11:14:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1429010093,
		"url":"https://www.raymondcamden.com/2015/04/14/coldfusion-updates-released-today",
		"content":"As the title says, multiple updates were released for ColdFusion today. One is an update to ColdFusion 10 (details may be found here) and the other is a rather big update to ColdFusion 11, Update 5.\n\nYou can see the complete list of bug fixes here: Bugs fixed in ColdFusion 11 Update 5. I want to call out one bug in particular that I'm happy is fixed: 3818767: Serialization of query does not respect case.\nColdFusion 11 added a new way to serialize queries - &quot;struct&quot;. You could pass this to the serializeJSON function or simply specify it as a default in Application.cfc. I discussed this in detail here: ColdFusion 11’s new Struct format for JSON (and how to use it in ColdFusion 10). While this was cool, the columns were all uppercased when the JSON string was generated. So for example:\n[{&quot;NAME&quot;:&quot;ray&quot;,&quot;AGE&quot;:30}]\nWhile still a better format, most people prefer lowercase. Now ColdFusion let's you specify a setting to tell ColdFusion to use the same case you used in the query. To enable this feature, add this.serialization.preserveCaseForQueryColumn=&quot;true&quot; to your Application.cfc file. Here is a complete example.\n\nNow your query will serialize and respect the case you used when writing your SQL.\nAdobe forgot to update the docs for this, but luckily I still have edit access to the wiki. You can see this, and the other Application.cfc variables, here at the reference: Application variables\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Blowing up LocalStorage (or what happens when you exceed quota)",
		"date":"Tue Apr 14 2015 06:00:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428991216,
		"url":"https://www.raymondcamden.com/2015/04/14/blowing-up-localstorage-or-what-happens-when-you-exceed-quota",
		"content":"Based on some discussion earlier today on Twitter, I wanted to take a quick look at what happens when you exceed the quota limit in a browser's LocalStorage system. I knew an error would be thrown, but I was curious about the type, message, etc. I built a quick test and threw some browsers at it. This probably isn't the most scientific test, but here's what I found.\n\nFirst, for my test I wanted a quick way to hit the &quot;typical&quot; limit of 5 megs per domain. To do that, I found an image of around one meg in size and then wrote code that would grab the binary bits, convert to base64, and then store it. Here's my test script.\n\nI'm using jQuery here, but I don't really need to. I loaded it up when I began but I didn't end up needing it. This isn't really important but I'm just trying to defend my somewhat shoddy test script. ;) The idea is simple - use XHR to fetch the bits of an image (hard coded to one on my test server), convert it to a DataURL and read in the base64 data. You can see that in the urlTo64 function. I then put this in a loop and tried to store the result. You'll notice my loop goes from 1 to 500. Originally it just went to 5. I'll explain the 500 when we get to Internet Explorer.\nSo - that's it. Read the binary, convert, store, and on error, print it out and stop working. Ok, so let's look at how different browsers handle this.\nChrome 41 (OSX)\nThrows an exception with the name QuotaExceedError. The code is 22. The message is nice as it tells you what key it was trying to set:\n\nFirefox 37 (OSX)\nThrows an exception, but with a completely different name/code. The name is NS_ERROR_DOM_QUOTA_REACHED and the code is 1014.\n\nSafari 8.0.5\nThrows the same exception as Chrome (name and code anyway):\n\nInternet Explorer 11 (Windows 10)\nSo, IE really threw me for a loop. When I ran my code (again, I had started with a loop of 5), it didn't throw an error. So I thought, ok, it has a bigger cache. So I added a 0. And then another 0. And another. I got it up to 5K calls and it still worked. That seemed... wrong. I did some Googling and turns out that IE supports a non-standard remainingSpace property. (Non-standard but a good idea imo. Client-storage does not help developers at all in terms of managing space.) When I inspected that value, it never seemed to change no matter how many additional 0s I added. I could inspect localStorage in the console and see all the values just fine.\n\nThen on a whim I tried something. I killed IE, re-opened it, and discovered that localStorage only had 2-3 of my images stored. From what I can tell, IE11 stopped storing items, but never threw an error! Which is really, really bad. Even worse, if you try to read the value, it reads it just fine, but on restart, it is gone. I'm not exactly sure what to think about that, outside of the fact that &quot;silent fail&quot; is the worst thing to happen to development since starting arrays at 0.\nI tried an interesting little test. I checked remainingSpace before and after a set, and when the set fails, you can clearly see the space does not change. In theory, you could use this (on IE11 anyway) to confirm a proper save.\n\nAs an aside, Jonathan Sampson tried the latest Spartan build with my code and saw the same.\niOS Safari and PhoneGap/Cordova\nIt throws the exact same error as desktop Safari.\nAndroid Chrome and PhoneGap/Cordova\nIt throws the exact same error as desktop Chrome.\nTakeaway\nSo, yeah, what's the practical takeaway from this?\n\nPretty much all client-side storage options suck really bad at quota. I love client-side storage (and have presented on it many times) but this is the biggest area of concern.\nTry to keep track of how you use client-side storage. So for example, if you saving the last 10 searches so you can display them to the user, know that and note it somewhere so that when you make use of client-side storage in the future (\"Hey, can we cache some fonts?\") you can check and see what you've stored already and see if you'll be hitting the limit possibly.\nWrap everything in a try/catch? Um - maybe. ;) The \"Super Strict Lets Do Everything Perfect\" side of me says yes, but the \"Practical I Live in the Real World\" says that would probably be overkill. Again, going to my last point, as long as you keep track of what you're doing then I think you will be ok. Obviously I think folks will disagree with me here. Let me have it in the comments.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Chaining multiple Cordova File Transfers with ngCordova",
		"date":"Mon Apr 13 2015 05:28:39 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428902919,
		"url":"https://www.raymondcamden.com/2015/04/13/chaining-multiple-cordova-file-transfers-with-ngcordova",
		"content":"One issue you may run into with the FileTransfer plugin is that it only lets you do one transfer at a time. You can get around this by using XHR2 (for uploads anyway), but I thought it would be nice to demonstrate how to work with multiple transfers using promises. The FileTransfer plugin does not use promises by default, but luckily you can simply use ngCordova and use the promisified (that's a word) version of the plugin.\n\nFor this demo I'm using Ionic and ngCordova, although in theory you can use ngCordova without Ionic. I created a new application with the blank template. I then used bowser to install ngCordova, which wasn't actually necessary since the CLI supports adding it:\n@raymondcamden @Ionicframework Already there! Just need to document it. ionic add ngCordova&mdash; uoʇƃuıʇɹɐɥ ǝʞıɯ (@mhartington) April 13, 2015\n\nI then whipped up the following demo. I don't like putting everything in one JS file for my Angular projects, but since this is just a demo, I guess that's ok.\n\nOk, let's take this from the top. First off - the config block is just there to allow for File based URIs in my dom. Angular is a bit anal (ok, secure) about what it allows for image urls. The next bit is my controller, Main. I've got 5 URLs for different kittens at placekitten.com. I create an array to store my promises, and then just loop over the URLs. For each one I call the ngCordova-wrapped file transfer download method. Since it returns a promise I end up with an array of promises.\nFinally, I use Angular's all method for their $q library to simply say, &quot;Do this when they are all done.&quot; I then push the final URLs into an array that is used in my view. Here's the index.html:\n\nAnd that's it. The result is a set of cat pictures, which, to be honest, is what all apps should end up with:\n\nSimilar code would work for uploads as well. Again, you do not need to use Ionic/ngCordova for this. You could create your own promises and do this by hand with a bit more work. (I've got a vide on deferreds and jQuery that may make this easier for you.)\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Front-End Interview Questions – Part 5",
		"date":"Fri Apr 10 2015 07:02:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428649333,
		"url":"https://www.raymondcamden.com/2015/04/10/front-end-interview-questions-part-5",
		"content":"Nearly two months to the day since my last post on this series, I'm finally starting it up again. I'll blame my new job for making me forget this for a while, but hopefully I can pick up steam again and finish all these darn questions.\n\nThis post is part of a series I’m writing where I attempt to answer, to the best of my ability, a set of Front-End developer questions. I expect/hope my readers will disagree, augment, and generally hash out my answers in the comments below.\nName 3 ways to decrease page load (perceived or actual load time).\nMinimize images. I use this grunt plugin before to automate it and it worked great.\nMinimize and combine JavaScript files. I haven't done this in grunt, but I'm sure plugins exist for that. The biggest issue I have with this is in development. How do I work on foo.js and goo.js such that I can test them in my browser, but in production, use all.min.js in my HTML code? That's a bit off topic I suppose, but the flip between &quot;development mode&quot; and &quot;production mode&quot; for a web application is not something I honestly have a good feel for yet.\nMinimize, combine, and &quot;prune&quot; CSS files. By prune I mean find unused styles and remove them. Addy Omsani has a good blog post on removing unused CSS and you can definitely automate this as well.\nIf you jumped on a project and they used tabs and you used spaces, what would you do?\nI'd follow the project norm. I personally prefer tabs, but when working on a project, I'm going to follow the standards/guidelines/etc that everyone is following. If it was for something important and not cosmetic, I'd bring it up for discussion of course.\nDescribe how you would create a simple slideshow page.\nOk, two answers here. If jQuery was already being used, I'd probably find a good plugin. I feel kinda lame saying that - but - honestly - a client wants me to get the best tool for the job and would probably not want to pay me to rebuild something that has been done a thousand times over. \nNow - assuming the question really wants to know how I'd build one - this is what I'd do. I'd use simple markup to list my images. Let's say div tags. I'd wrap the entire list with one div, we'll call it slideshow. I'd use CSS to default them to hidden. Finally I'd use JavaScript to get the divs, store them as an array, and make the first one visible. I'd build some form of simple navigation (and &quot;previous&quot; and &quot;next&quot; button for example) such that clicking them shows/hides the appropriate image. This is probably a bit heavy as all the images are loaded at once. I'm kind of assuming though you aren't building a 90 image slide show. You could do it entirely via JavaScript. Store the images in an array and on load show the first one. (You could preload all the images, but if you do, then why not just do it in the DOM instead?) If the slide show was the main part of the page (and not just part of the rest of the page), I'd consider using hashes in the URL so someone could bookmark a particular image. (As an FYI, I actually built a mini slide show thing to demonstrate keypress events here: Adding keyboard navigation to a client-side application)\nWhat tools do you use to test your code's performance?\nPrimarily just the Network tools in my browser dev tools. I'm not making use of profiling yet so I don't have a good grasp on testing the performance of my JS in terms of how it acts on the page once it is loading. I guess you can say I've maybe got half the picture (getting crap to the browser), but need to get better and understanding the rest (how crap runs in the browser). \nI can share a story though about how I learned about network performance. Like most things learnt - it was by screwing up. I had an existing &quot;Web 1.0&quot; app (I don't really like that term but I think it makes sense to most of us) that I decided to update and make everything loaded via Ajax. This worked wonderfully until the content became so large that every page load was taking 4-5 seconds because of the size of the XML packets (yes, I used XML, I was still learning) going back and forth. It made me realize that just switching to Ajax doesn't suddenly make your network any less of a concern.\n",
		"tags":[
	        
		],
		"categories":[
            
                "design",
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Using MobileFirst HTTP Adapters with an Ionic Application",
		"date":"Wed Apr 08 2015 08:03:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428480223,
		"url":"https://www.raymondcamden.com/2015/04/08/using-mobilefirst-http-adapters-with-an-ionic-application",
		"content":"Last week I blogged about how to use MobileFirst with Ionic. Today I'm going to show another example of an adapter - the HTTP adapter.\n\nAs you can probably guess, a HTTP adapter is simply a proxy between your mobile app and some other URL. So why would you bother? There's a couple of reasons why this may be beneficial.\nTracking and Logging: By using a HTTP adapter, you get a complete look at resources your mobile application is using. Imagine, for example, that you are using a service that charges per 1000 calls. By using a HTTP adapter, you can ensure that what the provider says your apps are using is accurate.\n\nThe MobileFirst back end will report on average response time, data size, and even let you filter by environment and version. (For example, maybe your iOS users are using more network resources than Android.)\nFiltering and Modifying: Your preferred back end service may return data that you don't necessarily need. By writing custom logic in your adapter, you can strip out, or modify, the data to match your app's needs. This is great for times when you are using an API you do not have control over. The default adapter code uses an example of converting an RSS feed XML result set into a smaller JSON result. This isn't even necessary as MobileFirst will convert the result to JSON anyway, but the example demonstrates returning a slimmer result set, dropping stuff not needed for the application.\nPortability: By using an adapter, you can swap out services at will. For example, you can switch from one stock quote provider to another based on costs and service reliability. Best of all - you can make these changes without having to push new versions of the application out to the market. You simply modify the adapter.\nFor my test, I decided to build a modified version of the default HTTP adapter code. I'd use their RSS XSLT code to parse my RSS feed into something sensible. As you saw in the last post, adding an adapter is rather simple. Use the mfp command line to add the adapter. Just give it a sensible name and select the proper type:\n\nSelect the defaults for the remaining prompts and you're good to go. The adapter creates three files: filtered.xsl, myHTTPTest-impl.js, and myHTTPTest.xml. As I stated in the last blog post, the names of the impl and xml files are based on the adapter name. The XML file lets you configure various defaults, but oddly, not the complete url. As far as I can see I could only specify the domain for my RSS feed. I also modified the procedure names to something simpler.\n\nNow let's look at the JavaScript implementation. As I said, the default code supports loading a RSS feed, but it had complexities I did not need. My version is somewhat smaller then.\n\nAnd that's it for the server-side. As I mentioned in my post on adapters, each adapter is session based. I could add some basic caching here, but it would only apply to one user at a time.\nNow let's turn our attention to the front-end. I'm going to use Ionic for the display and I'll use a simple two-page app like I had before. The home page will show all the items from an RSS feed.\n\nClicking an entry then sends you to the detail:\n\nBecause this was so similar to my last post, I literally just copied it and modified the services file.\n\nThe first thing I want to point out is that this version uses a simpler API to invoke the adapter: WLResourceRequest. I simply provide a path that includes my adapter name (not the same as what you see above) and procedure. And... that's it. I then just dumped out the result to console so I could see the proper key to return. I did have to modify my templates a tiny bit since the RSS data wasn't the exact same as my database of course, but you get the idea.\nFor fun, let's add a footer bar to the detail view with a button to load the full site:\n\nHere's the updated view code for that:\n\nThe addition is at the bottom. readEntry simply makes use of the InAppBrowser plugin. This is included automatically within MobileFirst projects.\n\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Form analytics with Formatic",
		"date":"Tue Apr 07 2015 07:06:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428390414,
		"url":"https://www.raymondcamden.com/2015/04/07/form-analytics-with-formatic",
		"content":"About a month or so ago I wrote a blog post discussing how you could detect when someone left a form (Warning a user before they leave a form). The idea being that you may want to notice/warn/gently nudge a user to complete a form before they try to leave it. One of my readers, Andrew Schwabe, reached out to me and asked if I wanted to demo a new service he is working on, Formatic.\n\n\nFormatic works by adding a simple JavaScript embed to your forms. Once added, you then get deep analytics about how users handle your forms. Let's take a look. (And note - the service was previously launched with another name, xForms. If you see that in the screen shots, that is simply the previous name and will be going away when Formatic publicly launches.)\nYou begin by adding your form in the console:\n\nThis step is actually optional and can be skipped. Next you then have to update your form. It's mainly one JavaScript embed, but you also have to do one minor change to your form tag.\n\nOnce you've done that, you're done and can simply wait for the data to begin rolling in. Here's where Formatic really begins to shine. About three weeks ago I asked folks on Twitter to start hitting my test form so I could start collecting data. It wasn't a terribly complex form and I made it clear that I didn't care if they finished the form or not. I just wanted people to hit it and play with it. Because I specifically said I didn't care if people finished it, I'm sure some folks intentionally did not complete the form, so the stats you see below may not be 100% realistic, but what I really want to point out is the complexity/depth of the data. First, let's look at the summary.\n\nThe Summary view shows form entries for the past three months. Since my form was just for testing there isn't a lot of data here, but you can see that there would - normally - be quite a bit of information here for the time frame. Now let's look at Failed Conversions. This is data about people who didn't successfully complete my form.\n\n\nPardon me for being too lazy to stitch the two images above into one shot. The service tells you what field was the last one worked with in failed conversions as well as which one had the most re-entries. Finally you can see just how many people are actually completing the form. To me, this detail is gold. You can immediately see which fields may be causing people to give up your form as well as which fields may be the most problematic.\nThe next tab, Time Spent, drills down into how much tme is being spent on the form as well as individual form fields.\n\nNext is Form Flows, which describes how people work through your forms:\n\nAnd then finally - Geography:\n\nOk - so by itself - this is an incredible amount of data - and all rather easy to use. However, Formatic actually has something that goes way beyond just stat collecting. As the amount of data it gathers becomes statistically significant, you can actually write code to listen for events within your form. What kind of events? You can notice, for example, when a user is spending more time on a field than the average user. That's freaking cool. Imagine you've got a field that you really want users to fill out but you notice a lot of people have trouble with it. You could add contextual help to that field that only shows up if the user seems to need it.\nAll in all, this seems like a service that will definitely be worth your attention. It isn't public yet, but check the site to register for a demo. If you end up using it, I'd love to hear what you thought about it and let me know if it helps you solve real problems.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Cool ColdFusion site on Kickstarter",
		"date":"Tue Apr 07 2015 02:31:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428373871,
		"url":"https://www.raymondcamden.com/2015/04/07/cool-coldfusion-site-on-kickstarter",
		"content":"SPY, a &quot;real world, live action&quot; game, is up on Kickstarter - SPY: Real World, Live-Action Espionage. I'm sharing this because the back end for the game is all built in ColdFusion. It looks like a pretty interesting idea!\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Some tips for writing JavaScript adapters for IBM MobileFirst",
		"date":"Mon Apr 06 2015 04:09:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428293399,
		"url":"https://www.raymondcamden.com/2015/04/06/some-tips-for-writing-javascript-adapters-for-ibm-mobilefirst",
		"content":"I've been doing a lot of playing lately with MobileFirst, and one of the cooler features it has is the ability to write adapters in JavaScript. I blogged about this last week and today I thought I'd share a few tips/notes for folks who may be new to this feature.\n\nFirst and foremost, it is important to remember that you are not using a full Node.js-style stack. You are working with a subset of the Rhino container developed by Mozilla. This is a JavaScript engine that runs within the context of a Java server. However, this is not a full Rhino implementation as some features, like load(), are not implemented. Unfortunately we don't document these differences (yet - I'm filing an enhancement request for this today).\nSecond, you cannot debug via console.log. Instead, simply use the WL.Logger API as shown below:\n\nAnd where do those logs show up? Type mfp logs at the command line to be shown where your logs exist:\n\nThen you can simply go to that directory and look at messages.log. I'd simply tail -f it while you work to see incoming messages. The log is a bit verbose, but you could use other tools to filter it out.\nThe third point to consider is that adapters are session-based. That means you can persist data by simply using a global JavaScript variable, but it will not be global to the server.\nFinally, and I've mentioned these before, but don't forget that you need to &quot;build/deploy&quot; when you edit your adapter files. You can use the bd shortcut for adapters just like you do for your web assets: mfp bd. You can also test your adapters directly from the command line using mfp invoke.\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Strategies for dealing with multiple Ajax calls",
		"date":"Fri Apr 03 2015 03:21:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1428031292,
		"url":"https://www.raymondcamden.com/2015/04/03/strategies-for-dealing-with-multiple-ajax-calls",
		"content":"Let's consider a fairly trivial, but probably typical, Ajax-based application. I've got a series of buttons:\n\nEach button, when clicked, hits a service on my application server and fetches some data. In my case, just a simple name:\n\n\nThe code for this is rather simple. (And note - for the purposes of this blog entry I'm keeping things very simple and including my JavaScript in the HTML page. Please keep your HTML and JavaScript in different files!)\n\nI assume this makes sense to everyone as it is pretty boiler-plate Ajax with jQuery, but if it doesn't, just chime in below in a comment. Ok, so this works, but we have a small problem. What happens in the user clicks both buttons at nearly the same time? Well, you would probably say the last one wins, right? But are you sure? What if something goes wrong (database gremlin - always blame the database) and the last hit is the first one to return?\n\nWhat you can see (hopefully - still kinda new at making animated gifs) is that the user clicks the first button, then the second, and sees first the result from the second button and then the first one flashes in.\nNow to be fair, you could just blame the user. I'm all for blaming the user. But what are some ways we can prevent this from happening?\nOne strategy is to disable all the buttons that call this particular Ajax request until the request has completed. Let's look at that version.\n\nI've added a simple call to disable all the buttons based on class. I then simple remove that attribute when the Ajax request is done. Furthermore, I also include some text to let the user know that - yes - something is happening - and maybe you should just calm the heck down and wait for it. The result makes it more obvious that something is happening and actively prevents the user from clicking the other buttons.\n\nAnother strategy would be to actually kill the existing Ajax request. This is rather simple. The native XHR object has an abort method that will kill it, and jQuery's Ajax methods returns a wrapped XHR object that gives us access to the same method.\n\nI use two variables, xhr and active, so that I can track active xhr requests. There are other ways to track the status of the XHR object - for example, via readyState - but a simple flag seemed to work best. Obviously you could do it differently but the main idea (&quot;If active, kill it&quot;), provides an alternative to the first method.\nWhen using this, you can actually see the requests killed in dev tools:\n\nAny comments on this? How are you handling this yourself in your Ajax-based applications?\np.s. As a quick aside, Brian Rinaldi shared with me a cool little UI library that turns buttons themselves into loading indicators: Ladda\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Using MobileFirst SQL Adapters with an Ionic Application",
		"date":"Thu Apr 02 2015 10:00:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427968844,
		"url":"https://www.raymondcamden.com/2015/04/02/using-mobilefirst-sql-adapters-with-an-ionic-application",
		"content":"As I continue my look at integrating MobileFirst and Ionic, today I'm going to look at the SQL Adapter. MobileFirst Adapters are server-side components that connect your hybrid mobile application to other things. Those &quot;things&quot; being generally broken down into a few categories, and a few specific adapter types. The SQL adapter is, as you can guess, a connection to a database.\n\nSo at this point you may be thinking - isn't that what I'd use ColdFusion, or Node, or heck, even PHP for? Sure - this is traditionally something an application server would handle. But MobileFirst lets you skip installing a complete application server where your needs may be met by a simpler adapter. If you've ever written server-side code that literally takes in a HTTP request, calls a simple SQL statement, and then just spits out JSON, then you really don't need a complete separate application server for that. The SQL adapter will handle that for you out of the box - and much easier.\nCreating an adapter is simple. Inside a MobileFirst project, simply type mfp add adapter. You'll be prompted to enter a name and then select the type:\n\nSelect the one you want (in our case, SQL), and then just accept the default for the next question.\n\nThis will create two files under your adapters folder: MyAdapterTest.xml and MyAdapterTest-impl.js. Both the directory these are created under and the names themselves are based on the name of the adapter you chose. Let's first look at the XML file.\n\nThat's a lot of markup, but you really only need to care about two parts. Under dataSourceDefinition, you can see a class, url, user and password defined. If using MySQL, you can leave the class alone, but do not forget to grab a copy of the MySQL Jar and copy it to your project! Modify the URL to point to your MySQL server and database. Finally, set an appropriate username and password.\nAt the bottom, note the procedure list. This is where you define the various different calls your mobile application is going to make. What those calls are obviously depends on your needs. Now let's look at the JavaScript file.\n\nYeah, the first time I saw this, and realized I could write my adapters in JavaScript, I was all like -\n\nTo be clear, this isn't a Node-engine, I'll be talking more about working with JavaScript and MobileFirst tomorrow, but if you can write JavaScript, you can probably handle this just fine. You can see two examples in the default - calling a simple SQL statement (and with a bound parameter) and calling a stored procedure.\nAnd that's really it. You do need to remember to build/deploy when you work on your adapter. When I worked on my demo, I kept one tab open for my adapters and one more my common (HTML, CSS, JS) code so I could build/deploy at will when I needed to.\nFor my application I created a simple table called &quot;content&quot; that had text entries, a bit like a blog. I wanted one procedure to list all the content and one to get all the columns for one entry. I began by creating two procedures in the XML:\n\nLooking at it now, getContent feels a bit vague since it can mean one or more things, but you get the idea. Now let's look at the code.\n\nSo I assume this probably makes sense as is. I'm using the MobileFirst server-side API to create two SQL statements. Notice that the second one has a bound parameter. I then build my two functions with names matching the procedures in the XML. The first simply executes, and returns, all the rows, while the second uses a parameter to return one row.\nAt this point, even before I try using it in Ionic, I can test it from the command line using mfp invoke. When run, it first asks you to select an adapter, and then a procedure:\n\nSelect the procedure, optionally enter parameters, and you can then see the result right in your command prompt:\n\nYou definitely want to make use of this tool as it will be much quicker to debug any possible issues with your adapters here. Alright, now let's turn to the client-side. I'm assuming you've read my earlier posts about using Ionic with MobileFirst. I created a new application with the blank template and set up two simple screens. The first just shows a list of articles:\n\nClicking an item takes you to the detail page:\n\nOk, so how do we use it? I created a service for my Ionic application and wrapped calls to WL.Client.invokeProcedure. This library exists for you automatically in a hybrid application running in the MobileFirst platform. Here is the complete service:\n\nThe calls to invokeProcedure return promises so I simply use a deferred to handle the result. Here is my controller:\n\nPretty simple, right? The result data is simply an array of plain JavaScript objects, or in the case of the detail view, one object. Using them in the view layer then is pretty trivial. Here is the list view:\n\nAnd the detail view:\n\nFor a video of this process, watch the embed below!\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using Remote Logging with Ionic and IBM MobileFirst",
		"date":"Tue Mar 31 2015 10:26:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427797618,
		"url":"https://www.raymondcamden.com/2015/03/31/using-remote-logging-with-ionic-and-ibm-mobilefirst",
		"content":"As the latest in my series of blog posts on Ionic and MobileFirst, today I'm going to demonstrate how to use the remote logging feature of MobileFirst with Ionic. I recommend you read my initial post as  quick guide on how to run Ionic apps inside the MobileFirst platform.\n\nThe &quot;Remote Logging&quot; feature is really just that - a logging service that stores data on your server (in this case, MobileFirst). What's nice from the client-side perspective is that it is incredibly simple to use. You send a log message and that's it. The API worries about when to send it and tries to wait till an opportune time before sending a bunch of log data. If you want, you can force it to send logs immediately, and by default, it will also send existing logs on application startup up. Let's begin by taking a look at the code.\nI created an instance of the Ionic tabs template, one of the default &quot;starter&quot; templates you can use with the Ionic CLI. Just to give you some context, here are some screen shots of the app in action. (And again, all of this comes from the template.)\n\n\n\nI decided to create a service that would log when each tab was activated for the first time. I also create a log event for loading a chat detail (see the second screen shot above) and for toggling the radio button (see the third screen shot). I began by modifying the services.js file from the template.\n\nOk, not exactly rocket science, but hey, easy is good, right? I began by creating an instance of WL.Logger. You can find the full docs for that here. I set autoSendLogs to true, which does not mean that logs are sent immediately, but rather that I don't have to manually send them. This may be a bit confusing at first, but it's really just the API being very conservative about network traffic. Me setting it to true means that the service will send the logs automatically, but it will still wait until it feels like it has enough data to make it worthwhile, or until you restart the app and detects existing data.\nYou can be fairly complex about the logging you do - both with the log level, the types of messages, and other metadata. If you plan on making heavy use of this service, you'll want to be pretty precise with your logs so you can search them easier later. I'm a simple guy so I used the simplest version possible.\nBack in my controllers.js file, I then added calls to this service in various places where it made sense for my demo.\n\nYou can see where I've injected Logger and then make calls to the log method. One interesting thing about this service is that when you use the Mobile Browser Simulator (see my post for a demo) the log messages will be sent to dev tools. That's handy! But this is only done in the Mobile Browser Simulator. When you test with XCode, for example, the messages are available in the XCode debug output but will not show up if you debug with Safari Remote Debugging. If you really wanted it to show up there then in my specific case I'd just modify the service to add a console.log.\nOk, so that's the code, how does the server side look? When you have the MobileFirst console open, you can go to analytics by clicking the pretty little chart icon in the upper right nav.\n\nIn this dashboard, click Search to bring up the Client Log Search form:\n\nAt this point, it is pretty much what you expect. Set your filters and go crazy:\n\nEach log entry can be expanded for even more data:\n\nSo - all in all - a fairly cool service - and only a small part of MobileFirst. Let me know what you think. I've also created a video demonstrating this.\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "An early look at Ionic Push",
		"date":"Tue Mar 31 2015 04:31:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427776278,
		"url":"https://www.raymondcamden.com/2015/03/31/an-early-look-at-ionic-push",
		"content":"Before I begin - please, please, please make note of the date of this post (March 30, 2015). As the title says, this is an early look at a new feature Ionic is adding. The feature is changing quite rapidly (it has already changed a bit since I wrote my demo!) so be sure to check the docs for the latest information. With that out of the way, let's take a look at Ionic Push.\n\n\nMy readers already know that Ionic provides a heck of a lot of awesomeness for Cordova developers. You've got UI elements, UX elements, AngularJS love (yes, that's a thing), and other features like live reload on device and multi-device preview. While this is cool, they are also working on creating an Ionic platform - a set of services that Ionic apps can make use of to better enhance the functionality of their applications. The first of these new services is Ionic Push. While not publicly released yet, you can sign up now and possibly get early access. They are slowly adding more testers to the beta over time.\nUsing Ionic Push requires making use of the Apps back end. If you have no idea what that is - don't worry. It's primary usage has been with the Ionic View service. You may have noticed that the Ionic CLI supports an &quot;upload&quot; command:\n\nUploading your app is required to use View, and also services like Push. So to begin, you have to upload your app at the CLI:\n\nAs a quick note, before you upload, be sure to add a platform first. There is a bug with the CLI where if you upload without a platform, it silently fails. You can track this bug here: https://github.com/driftyco/ionic-cli/issues/325.\nOnce you've done that, you can then go to apps.ionic.io to see your new app (and all your other ones as well):\n\nThe &quot;Ionic Services&quot; link will not show up for you unless you are in the Push alpha, or unless you're reading this in the future when the feature is actually released. Clicking that takes you to a dashboard for your app.\n\nAt this point, you can simply follow the docs to get working. I'll cover the high level details to give you an idea of the process, but again, this is early, and you can expect some things to change in the future.\nFirst - you have to install a core &quot;Ionic Service&quot; library. As you can imagine, this is a generic library that will support future services beyond Push. You use the CLI to add the library, ionic add ionic-service-core, and then include the JavaScript library in your html. Next - you identify your application (this code is right from the docs):\n\nYour API key is back at the dashboard, as well as the app id, and you can also find the app id in the file ionic.project at the root of your project.\nThe next step involves adding the Push specific code to your local project. You'll add a plugin, ngCordova, and then an Ionic Push library. This can all be done nice and quickly from the CLI:\nionic plugin add https://github.com/phonegap-build/PushPlugin.git\nionic add ngCordova\nionic add ionic-service-push\nYou then add ngCordova and the Push JavaScript libraries and then inject the Ionic Push dependency.\nNow - using Ionic Push can be a little bit tricky if you don't follow the guide closely. You must identify the current user before Push can be used. Now you may be wondering - what if you aren't using a login system? You can still register the user, but can do so anonymously. User identification is provided by the core services API and is relatively simple to call:\n\nThis call returns a promise and within that success, you could then register for push. The register API provides a few options including the ability to listen for events. These events include initial registration stuff and later push notifications too. Here is an example:\n\nThat covers the basics for the code - although is just the very beginning. You would need proper code to handle the user identification and notifications too. You also need to actually set up push at the provider. Ionic provides documentation for how to do this with Google and Apple. I recommend trying Google/Android first as it is the easier. If you do, be sure to note that you have to add a specific Google key to your config. (See the first code snippet above. It is one more key.) You then have to use the command line to register your Google API key with the Ionic service:\nionic push --google-api-key [your-google-api-key] \nOk - so at this point - you can test on a device (this may be obvious, but I swear I forget half the time - you can only test push on a real device). You want to use your Chrome Remote debug to pick up the registration ids sent back. This can take about 10 seconds or so.\n\nYou want to copy out that device token because testing, currently, is done via CLI. Ionic will be adding a proper dashboard soon, but at the moment, sending a push notification requires using curl:\ncurl -u 3ec834b08thisisecretf53: -H \"Content-Type: application/json\" -H \"X-Ionic-Application-Id: myappid\" https://push.ionic.io/api/v1/push -d '{\"tokens\":[\"devicetoken\"],\"notifi",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with IBM MobileFirst and Ionic - a follow up",
		"date":"Mon Mar 30 2015 05:48:05 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427694485,
		"url":"https://www.raymondcamden.com/2015/03/30/working-with-ibm-mobile-first-and-ionic-a-follow-up",
		"content":"Last week I blogged about using IBM's MobileFirst platform and Ionic. This is part of a series I'm doing discussing how to use both products together to build awesome mobile apps. (How awesome? Five Lion Robots that can transform into One Giant Robot awesome.) Currently there are a few steps you have to take to modify the default Ionic template to work with MobileFirst. My blog explains those steps and demonstrates it in a video. After discussing things with coworkers, we've come across a few small issues that slightly modify this process. In this post I'll explain the differences, but please be sure to read that first post so this makes sense.\n\nOk, first off, you can remove this line from the template:\n\nThis line does not exist in the Ionic template but does exist in the default MobileFirst hybrid project. You don't need it, so just skip that step.\nSecond, there is a CSS element for iOS that breaks Ionic - but not all the time. I only noticed it in the tabs layout, not the default which is what I had used for my first post. Check out the screen shot below:\n\nNotice how the tabs are missing the bottom portion. This is simple enough to correct. In your initialization routine, simply tell MobileFirst that you do not what to show the iOS7 status bar. As you can probably guess, this is only an issue on iOS. Here is the relevant code.\n\nI should point out that Carlos' templates up on GitHub had this fixed already - https://github.com/csantanapr/mfp-ionic-templates.\n\nAny way, I hope this helps, and later today I'll share another example of Ionic and MobileFirst!\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Tip: Viewing Network Requests with the Safari Debugger",
		"date":"Fri Mar 27 2015 05:18:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427433511,
		"url":"https://www.raymondcamden.com/2015/03/27/tip-viewing-network-requests-with-the-safari-debugger",
		"content":"I'm not a heavy Safari user, but I use the heck out of the web tools when testing PhoneGap/Cordova apps. Sometime recently (I believe), the UI changed a bit in terms of the Network request panel and I couldn't see my requests anymore. I finally figured out the issue and I thought I'd share. To be clear, when I say I figured it out, I mean I found the right post on StackOverflow and all credit goes to user enyo. I'm just writing this up and sharing pretty pictures.\n\nOk, so what exactly is the issue? I noticed recently that when I go to my debug tools, select Timelines, click Network Requests and record, nothing seemed to show up in the detail panel, specifically:\n\nI would click things in my app that I knew were firing XHR calls and nothing would show up in the detail. Turns out, the issue is due to timeline UI:\n\nSee that section I highlighted above? Look on the far right and see a darker gray &quot;thingy&quot; you can grab on top. Being that this is Apple they probably don't call it a thingy. What isn't obvious (well to me anyway) is that you can click and drag to select a portion of the time line. If you find the timeline moving too quick, just hit stop recording.\n\nWhat you will discover is that the Network panel will only show items within a selected timeframe! So that makes sense I suppose, but I wish that by default you could select nothing and have everything show up.\nOk - but once you know that you may run into another problem. For me, my timeline was zoomed in such that every inch or so of screen space was about one tenth of a second. Note the timestamps in the screen shot above. I wasn't sure how to zoom, but on the StackOverflow link I shared above, they mentioned that if you scroll up and down it will zoom. I confirmed that scrolling down let me &quot;zoom out&quot; rather high:\n\nI then selected from time zero to day 92000 or so, and frankly, if that isn't enough, then I don't know what is. ;)\nI asked Apple's Safari evangelist (Jonathan Davis) if there was some way to always show all items and he said not yet. The zoom level, however, will stick, so in theory you don't have to keep zooming out. It also appears as if the selected range also persists. That means the only thing you need to do is hit the Recording button.\np.s. As a quick note, the UI for recording versus non-recording may be a bit weird. When you are not recording, the UI shows a red flight, which typically means that something is recording:\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Using Grunt to Automate MobileFirst/Hybrid Builds",
		"date":"Thu Mar 26 2015 02:44:40 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427337880,
		"url":"https://www.raymondcamden.com/2015/03/26/using-grunt-to-automate-mobilefirsthybrid-builds",
		"content":"When working with MobileFirst and hybrid mobile applications, any changes to your web assets requires running both a build and deploy to see your changes. The command line makes this easy by letting you combine the calls into one (mfp bd), but while developing, it may be a pain to constantly run this. So for fun, I wrote the following Grunt script.\n\n\nBasically it just watches for changes to the common folder and then executes the mfp command to run bd. When done, it uses the Mac &quot;say&quot; program to speak which is totally necessary for the proper execution of this program! (Ok, it is totally not necessary and those of yo u on PC should remove that part.)\nSince build/deploy can take about 20 seconds, I use the interrupt option to kill an existing bd process. Let me know if this is helpful in the comments below!\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Reminder - Having a tool doesn't mean you always use that tool...",
		"date":"Wed Mar 25 2015 04:24:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427257459,
		"url":"https://www.raymondcamden.com/2015/03/25/reminder-having-a-tool-doesnt-mean-you-always-use-that-tool",
		"content":"Earlier this week Rebecca Murphy wrote an article about what she considers a &quot;baseline&quot; for JavaScript developers: A Baseline for Front-End [JS] Developers: 2015. It's a good article and I highly recommend reading it, but some of the comments raised an issue that I've seen in our community lately that I wanted to address.\n\nIt is an incredible time to be a web developer. That isn't to say we don't have a lot of problems, just that our field has grown in leaps and bounds over the past five years or so. JavaScript, and web standards in general, have created a rich playground for us to build not only web sites, but mobile and desktop apps as well. When I started, here were my tools:\n\nAt a high level, web developers have access to:\n\nMultiple different editors targeting multiple different types of web developers/designers. Everything from, yes, Notepad, to Sublime Text to Brackets to Dreamweaver and vi.\nBrowsers that are far more standards complaint than in the past and ship with incredible debugging tools baked in.\nCommand lines tools like Yeoman and Bower to help generate new applications and work with dependencies.\nTesting and linting frameworks like Jasmine and JSHint to help ensure the quality of your code.\nAutomation tools like Grunt and Gulp that relieve you from repeating manual steps.\nLibraries that cover every possible front-end need from date manipulation to web cam sharing to game development.\nMVC frameworks like AngularJS and Ember to help build larger and more complex applications.\n\nThe amount of options we have now is incredible. And - admittedly - overwhelming. No one can try to stay on top of this and no one should even try. That's not the point.\nHaving a tool does not mean you need to use it. A tool is there to help you when you have a problem, and you know what? Life provides plenty of problems without you needing to add more to them.\nAs a web developer, you cannot expect to know everything, or even a sizable percentage of everything. What your goal should be is to stay aware of our world in terms of what types of solutions exist. The goal is knowing what a tool is and what problems it solves - not being an expert in every tool.\nI know, for example, that there are some great tools for generating apps and scaffolding. But I don't need them now. When I start a new project, I'm not going to force myself to use them because they solve a problem I do not have! Keep that in mind.\nThere's a lot of opinions out there about how you should be building. I'm certainly someone who's been know to share his opinions as well. But at the end of the day - you need to focus on a process that makes you and your team successful.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Working with IBM MobileFirst and Ionic - Bootstrapping",
		"date":"Tue Mar 24 2015 10:01:04 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427191264,
		"url":"https://www.raymondcamden.com/2015/03/24/working-with-ibm-mobilefirst-and-ionic-bootstraping",
		"content":"Yesterday I described how to make use of the Ionic framework and IBM's MobileFirst platform. Today I'm going to build on that first post and talk about bootstrapping. Specifically - how can you coordinate the use of your application with the Cordova deviceReady and MobileFirst initialization routines.\n\nTo give some context, I first talked about this issue last August in a blog post, Ionic and Cordova’s DeviceReady – My Solution. In that post, I described how I wanted to coordinate the first view of my Ionic app with Cordova's deviceReady event. The solution I used there was to create a view that acted a bit like a splash screen. When the deviceReady event fired, I then pushed the user to the real initial page of the application and ensured they never went back to that initial temporary page.\nAt the time, that felt like a bit of a hack, but it also seemed sensible. However, other people suggested another approach. You can simply tell Angular to not bootstrap until deviceReady is fired. For whatever reason, that didn't seem as sensible as my approach back then, but now... yeah... now that makes sense. So how would we do this in a MobileFirst application?\nThe first thing to note is that within MobileFirst and hybrid applications, running WL.Client.init and using the success handler, wlCommonInit, gives you the same behavior as listening for deviceReady in a &quot;regular&quot; Cordova application. So basically - wlCommonInit is deviceReady. With that in mind, I began by removing the automatic bootstrap from my code. I changed:\n\nto\n\nThen I modified my wlCommonInit to do a manual bootstrap:\n\nThis works perfectly. But there's a twist. By default, an application doesn't connect to your MobileFirst server until you tell it to. This lets you delay or even skip actually connecting unless you need too. I definitely needed too, so I added that code in and moved my bootstrap code to there:\n\nThe WL.Client.connect method does exactly what you expect - connect to the MobileFirst server. Seems like a small mod, right? But when I did this, I noticed something bad.\n\nCan you see it? It is a FOUC (Flash of Unstyled Content). There's a simple fix for this though. We can simply use the splash screen to hide the site until things are ready. (And credit for suggesting this approach goes to Carlos again!) One of the initialization options you can provide is to not hide the splash screen. You can then use the JavaScript API to hide it. Here is that version.\n\nIn the code snippet above, you can see the new option added to wlInitOptions and then the call to WL.App.hideSplashScreen to hide the screen. This worked too - but don't forget that the mobile browser simulator does not have a splash screen! At first I thought this workaround wasn't working, but then I tested in the iPhone Simulator and it worked perfectly.\nSo - thoughts on this approach? Does it make sense? I've recorded a video of this process that you can watch below. I'm also attaching a copy of my common folder here: Download\n",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Having trouble with splash screens, Cordova, and Android?",
		"date":"Tue Mar 24 2015 02:38:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427164724,
		"url":"https://www.raymondcamden.com/2015/03/24/having-trouble-with-splash-screens-cordova-and-android",
		"content":"Yesterday I struggled to get - what I thought - was a simple thing working in Cordova - adding a splash screen to Android. According to the docs, at minimum, you should be able to do this:\n\n\nThis worked fine for me in iOS but in Android, just gave me a blank screen. (Not for the app, what I mean is, when the app loaded, no splash screen was used.)\nI did some Googling, and came across this bug: Splash screen does not display on Android. Within this bug, it was said that in order to use a splash screen on Android, you must specify the Android-only preference SplashScreen:\n\nNow - if you read the Android specific configuration docs, it has this to say about the setting:\n\nSplashScreen (string, defaults to splash): The name of the file minus its extension in the res/drawable directory. Various assets must share this common name in various subdirectories.\n\nThis - to me anyway - does not imply in any way that it enables the use of a splash screen. Rather, it says what the file name should be (minus extension) for a splash screen. But as soon as I tested by adding this value it worked right.\nSo we have two issues here. First - this tag is required to use a splash screen, which I don't think is documented well. Secondly - even though it says it defaults to splash, you still have to specify it anyway. (Err, ok, maybe that's still one issue. ;)\nIf you read that bug report, you can see my comments about this, and note that it looks like behavior will be changing (possibly) in the future, so please keep that in mind if you are reading this blog entry in the future. (And as always, I have been, and always will be, a firm supporter of our robotic overlords.)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Learning Cordova's config.xml? Check out this online tool",
		"date":"Mon Mar 23 2015 07:14:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427094894,
		"url":"https://www.raymondcamden.com/2015/03/23/learning-cordovas-config-xml-check-out-this-online-tool",
		"content":"If you are having trouble understanding how to use PhoneGap/Cordova's config.xml file, or perhaps just want to see examples of what various options do, please check out Holly Schinsky's excellent new tool: Interactive Guide to PhoneGap config.xml.\n\nHer tool lets you browse a config.xml file and see documentation on each and every major feature. Even better, she includes lots of screen shots and animations to give you a visual idea of what certain settings change.\n\np.s. Ok, this isn't new, but I don't see a lot of people talking about it so I figured it deserved some press. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with IBM MobileFirst and the Ionic Framework",
		"date":"Mon Mar 23 2015 03:42:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1427082178,
		"url":"https://www.raymondcamden.com/2015/03/23/working-with-ibm-mobilefirst-and-the-ionic-framework",
		"content":"Edit on March 30, 2015: Please see my follow up here for a few small tweaks: Working with IBM Mobile First and Ionic – a follow up\nA few weeks ago I blogged about using hybrid mobile applications and MobileFirst. Today I'm going to demonstrate how to work with Ionic and MobileFirst. What follows is a combination of the steps necessary for bringing any existing Cordova application into MobileFirst along with things necessary for Ionic specifically. As before, credit goes to my coworker Carlos Santana for figuring these steps out. I've also recorded a video of the process you can watch at the end of this blog post.\n\nCreate, and Start, your MF Server\nTo begin, I'm assuming you've got a MobileFirst server up and running already with a hybrid application. If you don't remember how to do that, please read my earlier blog post where I walk you through the commands you have to enter at the command line. \nI always take baby steps, so once you have your server running, confirm everything is kosher by opening up the console and running your app in the Mobile Browser Simulator.\n\nSet up the Bits\nOk, at the command line, or in Explorer/Finder, move the common folder elsewhere. Remember that the common folder is where your web assets live. You're going to want to copy some bits from this folder so for now don't delete it. \nNext, grab a copy of Ionic. I'm assuming you've already installed Ionic, but if not, follow the installation instructions. Ionic's CLI will normally create a new project as a Cordova project, but all we want are the web assets. The CLI has a flag for that, --no-cordova. You can create a new application without a full Cordova project like so:\n\nionic start --no-cordova ioniccode blank\n\nNotice I used the blank template and a folder called ioniccode. Open that folder up and you will see the following assets:\n\nCopy that www folder into the root of your app folder and rename it common. Your app folder should then look something like this:\n\nAgain, both my original common folder (common-orig in the screen shot above) and the Ionic folder (ioniccode) can be removed later. As I said, I'm keeping them in so I can copy some bits over.\nUpdate the Code\nOk, now you need to update the code a bit. Your common folder is a copy of the Ionic &quot;Blank&quot; starter, but what I describe here would apply to the other templates as well.\nFirst, we need to make some modifications to the index.html file:\n\nYou need to comment out (or just remove) cordova.js. This is necessary because MobileFirst will inject the script tag automatically.\n\nYou need to copy in MobileFirst's jQuery alias.\n\nYou need to add a new file to include the MobileFirst initialization stuff. Technically you don't need to add a new file, you could modify the existing app.js, but I think it makes sense to break this out into a new file. The default MobileFirst template uses three files, but we can combine them into one. I'm calling mine wlInit.js. MobileFirst used to be called Worklight, and a lot of the code uses \"WL\" as an acronym, so I used it in my file as well.\n\n\n\nHere is the completely updated index.html file.\n\nNext, open up style.css. This is blank (except for a comment) in the Ionic template, but we need to add the following style declaration:\n\nThis is required because the application ends up with a 0% height when run under MobileFirst. Odd - but just go with it.\nNow open app.js. In the run() portion there is code that works with the Ionic keyword plugin. For now, we're going to simply disable it. In a later blog post I'll talk about working with plugins and MobileFirst, but for now, you can safely comment it out.\n\nOk, so for the last part, we need to create wlInit.js. As I said, this is code that handles initializing MobileFirst stuff. In later blog posts, I'll demonstrate some of those cool features with Ionic, but for now, we're going to keep the file pretty simple. You can copy and paste the contents of initOptions.js and main.js directly into your new file. (And again, this is a personal preference. If you want to keep them in two separate files, that's fine too, just be sure to include them both back in index.html.)\nWe need to do one more small tweak. When using Ionic and a &quot;regular&quot; Cordova project, there is a hook that applies a style for Android and iOS. We don't have those hooks under MobileFirst, so we have to do it by hand, but that's easy enough. The MobileFirst JavaScript API has a getEnvironment function that returns information about the running environment. We can use that to sniff iPhone and iPad versus Android. (And again, credit for this goes to Carlos.) Here is the entire file with that modification within wlCommonInit.\n\nAnd that's it. Once you build/deploy (and don't forget you can do that with mfp bd), you should then see your MobileFirst application using the awesome power of Ionic!\n\nI've attached my copy of the common folder to this blog entry. Also, Carlos created a GitHub repo with modified versions of all the Ionic ",
		"tags":[
	        
            "ionic",
            
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "OS X Yosemite Tip - Maximizing Windows",
		"date":"Sun Mar 22 2015 02:17:10 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426990630,
		"url":"https://www.raymondcamden.com/2015/03/22/osx-yosemite-tip-maximizing-windows",
		"content":"For the most part, upgrading to OS X Yosemite didn't cause any issues for me, but the one change that bugged the heck out of me (and made me question what in the heck Apple was thinking), was switching the action of clicking the green icon in app windows. Previously, this would maximize the window on your screen. Now, it takes you full screen. (And this is where I question Apple. In all my time watching developers, I rarely see them use full screen. Heck, I used it for the first time last week for a video, but in my day to day work, never.)\n\nIf you Google for this, you'll see lots of people recommending holding alt-shift before clicking the green button. This works, but I've got the memory of a goldfish. Plus, in the same time I got my fingers set up to do this, I probably could have expanded the window manually.\nEarlier this week I was attending a user group, and the guy next to me (unfortunately I don't remember his name), suggested simply double clicking the title of the app. It works! And it is easy enough to remember, at least for me. As a test, this morning I googled with that particular phrase, and sure enough, you can see this documented here at Apple: OS X Yosemite: Take apps full screen.\nAnyway, I hope this helps others. As a quick note - Chrome seems to not widen in some of my tests (although I swear it did earlier). Holding shift makes it maximize that way too. That I think I can remember (maybe).\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Avoid Ratchet for PhoneGap/Cordova development",
		"date":"Sat Mar 21 2015 01:22:45 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426900965,
		"url":"https://www.raymondcamden.com/2015/03/21/avoid-ratchet-for-phonegapcordova-development",
		"content":"I'm writing this rather quickly (and a bit angrily, which is a recipe for disaster ;) but if you are building hybrid apps I'd suggest you avoid using Ratchet. I've used it for testing before but never for a real application. I thought it might be nice to use it for my last example in my Cordova book. I built up my sample app, tested in the browser, and everything was kosher.\n\nThen I tested in the simulator and discovered that push.js, the technology they use to convert links into XHR calls, does not work with file URIs, which is used by PhoneGap and Cordova. I did some Googling, and apparently it is a simple fix in the framework, but I tend to avoid modifying my frameworks to make things work. I worry that I'll update the framework six months down the line and either forget about my mod or my mod will no longer work.\nTo be clear, you can use the &quot;display&quot; portion of Ratchet just fine, if you combine it with something else to handle MVC, loading, etc.\nFor folks curious why I didn't just use Ionic, I had thought it was a bit much to add to my book since using Ionic requires knowledge about Angular as well. Plus, my publisher already has an Ionic book in development. I've now decided to get over my fears and introduce folks to Ionic anyway. As it stands, the more people who get introduced to the awesomeness of Ionic the better!\np.s. It may be that there is a super simple workaround for this that does not involve modifying the framework itself. If so, and I say here often, I'll be happy to be wrong! Please correct me.\np.s.s. @jcesarmobile on Twitter shared a link to the &quot;one line fix&quot;, https://github.com/ryanstewart/phonegap-ratchet-demo, and it really is a small fix, but, I stand by what I said earlier in this post that it isn't something I feel safe recommending.\np.s.s.s @jcesarmobile also pointed out that there is a PR already submitted to fix this issue! That's good. What's not so good is that the last release of Ratchet was nearly a year ago. My confidence that this will get released isn't necessarily high. I've reached out to one of the devs for Ratchet on Twitter to see if there is any kind of time frame for an update.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Creating simple comparative bars with JavaScript and CSS",
		"date":"Thu Mar 19 2015 03:27:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426735650,
		"url":"https://www.raymondcamden.com/2015/03/19/creating-simple-comparative-bars-with-javascript-and-css",
		"content":"Back a few months ago I reviewed the excellent SumAll service. One of the cooler parts of their service is a daily/weekly email summary of your stats. Here is a screen shot from my email this morning.\n\n\nWhat I like about this are the simple bars between each number. They give you a real quick way to see your relative growth/drop from one day to the next. Like any good web developer, I was curious as to how they built this, so I right clicked, selected Inspect Element, and took a look at the code.\n\nSo - first off - there's a lot of markup to make this work. That isn't because the SumAll developers suck, it's simply  a matter of life when dealing with HTML email. But the base mechanism isn't that difficult - a simple div with CSS. Obviously you could use one of the hundred or so different JS charting libraries out there, or Canvas, but why do all that when a bit of CSS is all you need.\nI thought it would be interesting to try to replicate the look for a web page outside of email where I could use JavaScript to make it more dynamic. I began by creating a simple HTML page to represent a particular metric - the number of page views from last week and this week.\n\nThere isn't anything particularly special about this layout, but note that I'm using a formatted number (490K) versus the real number (490121). I wanted it to be simpler to read for the end user. However, I know I'm going to need the real number, so I embed it in the HTML using a data property. (Off topic aside - but I freaking love data attributes. So simple, so practical!)\nYou can view this version of the page here: http://www.raymondcamden.com/demos/2015/mar/19/test1.html. Before we go any further - please actually view that link. It isn't pretty, but guess what? It works in every single browser known to humankind. Everything I do from now on will simply enhance the experience for people with JavaScript and more modern browsers. That's something we should all consider when adding interactivity/fancy UI/etc to our pages! (And to be fair, I'm guilty of not doing proper progressive enhancement as well.)\nOk, so let's build the next version. I began by modifying the dobar span to include a table to hold my bars. That may not be necessary, but I was mimicking what SumAll had built. I also included the CSS for each bar minus the portion that determined the height and the color. SumAll used black for the left side only, but I decided to use black for the 100% value and a different color for the other one. That just made more sense to me. This is the new HTML for the span:\n\nAnd now let's look at the JavaScript.\n\nSo really - it just comes down to math. Figure out the highest value, then the percentage difference for the other value. I used &quot;30&quot; to represent the highest bar so the other bar is a percentage of that. Then it is a simple matter of updating the CSS. Let me quickly thank Ian Devlin for his help finding a rookie mistake I made using jQuery.css. I had included a semicolon in the CSS value which totally broke the update. I'm sure I'll never make that mistake again.\nHere's a screen shot of the result:\n\nYou can see this version in all its glory here: http://www.raymondcamden.com/demos/2015/mar/19/test2.html\nSo not rocket science, but nice I think. For the hell of it, and because I'm easily amused, I made a third version. I added some range fields to the bottom of the page:\n\nI was kinda surprised by how well these are supported now (CanIUse data) but as this version is just for fun, I don't really care about what happens in older browsers. I then wrote a simple event listener for change on them and had them update the data when used.\n\nYou can then play around with the data and see the bars go up and down. Because... I don't know. It's fun.\n\nYou can test this version here: http://www.raymondcamden.com/demos/2015/mar/19/test3.html\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Ionic Serve's Lab option",
		"date":"Mon Mar 16 2015 03:41:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426477276,
		"url":"https://www.raymondcamden.com/2015/03/16/ionic-serves-lab-option",
		"content":"This isn't new, but as I prep for my FluentConf presentation on Ionic I'm remembering cool stuff that I've forgotten over time. If you already use Ionic then I bet you know this feature, but maybe you don't, and maybe you're waiting for just one more cool feature to be demonstrated before you make the jump.\n\nYou probably already know that Ionic has a live reload/view in browser feature activated via the command line like so:\nionic serve\nAmongst the ten plus options for this feature is the &quot;Lab&quot; feature, activated like so:\nionic serve -l\nThe CLI help describes it as &quot;Test your apps on multiple screen sizes and platform types&quot;. What this means in reality is this:\n\nAs you can see, it shows you both an iOS and Android version of the application. For the Tabs UI, this is especially helpful as you can see how iOS and Android display them differently. You can also click on the platform name to open a new tab with just that platform. What's cool is that live reload continues to work for the first tab and the second as well.\nThe one thing missing from this feature is sync - and by that I mean when I click on a tab in iOS I'd like the Android version to respond as well. Turns out that's already being considered.\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Quick Handlebars tip concerning precompilation",
		"date":"Sat Mar 14 2015 07:09:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426316994,
		"url":"https://www.raymondcamden.com/2015/03/14/quick-handlebars-tip-concerning-precompilation",
		"content":"So, I'm not sure I can say why I'm playing with Handlebars today (just asked if I could talk about it), but I ran into an interesting issue with it today that I thought I'd share.\n\nHandlebars supports precompilation of templates. This makes your application run a bit quicker by skipping the template compilation phase. It's done via a simple CLI program you can install via npm.\nHandlebars also supports partials. These are templates that are meant to be included in other templates. Oddly, the Handlebars site doesn't document this feature. (To be clear, it mentions the API for it, but doesn't describe it as a feature.) If you go to the Github repo for Handlebars though it is fully documented.\nOk, so the normal way to compile templates is like so:\nhandlebars file.handlebars file2.handlebars -f templates.js\nThis takes two files, file.handlebars and file2.handlebars, and compiles it into one file, templates.js. Once you've done that, and included the file, you can access the template function like so:\n\nThe file name, minus the extension, becomes the name of the template function. As an FYI, you should name your files with the .handlebars extension. If you don't, then the full file name becomes the name of the template function. If I had used file.html as my filename, then my template function would be file.html, and who knows what grief that dot would have caused.\nOk, so to handle partials, you have to pass a flag, -p:\nhandlebars mypartial.handlebars -p -f partials.js\nAs before, the name of the file dictates how you would use it. If I had used the file name from the code snippet above, I could include a partial like so: {{&gt; mypartial}}. As before, ensure you use the handlebars file extension. I had used .partial to help keep things organized and ran into the issue with the name being mypartial.partial instead of just mypartial.\nOk, so finally, the tip. As far as I know, there is no way to compile 'regular' templates and partials in one call. So to create my compiled templates and partials and store them in one file, I've ended up doing this:\n\nhandlebars file1.handlebars file2.handlebars file3.handlebars -f templates.js\nhandelbars file10.handlebars file11.handlers -p -f partials.js\ncat templates.js partials.js > templates_all.js\n\nThe handlebars CLI supports globs which means I could have used *.handlebars, but since I had my templates and partials in one folder, that wouldn't have worked. And obviously you could automate this with a simple Grunt script as well.\nIt is certainly possible that I'm wrong about the need to do this in multiple calls. I opened a ticket over on the Handlebars site to request this feature and if I'm missing the obvious, I'll update the blog entry. (And part of me hopes that I'm wrong and this entire blog entry is a waste. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Testing MetaCert's Security API Service",
		"date":"Fri Mar 13 2015 06:24:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426227862,
		"url":"https://www.raymondcamden.com/2015/03/13/testing-metacerts-security-api-service",
		"content":"Earlier this week I was contacted by folks at MetaCert. They provide an API that performs URL reputation checking. Basically, given a URL, it can report on possible issues with the content at that URL. Desktop browsers can help block URLs that lead to known malware (or other dangerous) sites, but mobile browsers don't typically have that protection. Heck, you can't even typically see the URL of a link when viewing web-based content in mobile apps. MetaCert's API lets you check a URL and then determine if it should be blocked.\n\nThe level of detail in the API is pretty impressive. As an example (and I'm stealing this right from the docs), given a URL &quot;http://imgur.com/r/sex/sexy.html&quot;, the API will report the entire URL as being porn, the folder (/r/sex/) as being porn, and that the domain (imgur) is an image sharing service. Given a completely different URL, like &quot;http://imgur.com/r/cats/kittens.html&quot; (this URL like the last one are made up), you may decide that even if the URL has nothing wrong with it, you don't feel comfortable with an image sharing domain at all. That level of specificity is really nice as it lets you decide how protective you want to be.\nCurrently the URLs are checked for malware and porn, but additional categories (alcohol, file sharing, and gambling are examples) will be added soon. While such a solution will never be 100% perfect, they currently have over ten billion URLs in their library which is pretty damn impressive.\nFrom a developer perspective, the API is about as simple as it can get. To test it, I built a quick Ionic app with a fake registration form:\n\nI then implemented a basic form submit handler in my controller.\n\nIgnoring the boilerplate form validation stuff, look at the $http portion. I set a header with my API key and craft a body containing the URL the user entered. The response from MetaCert will include a data key with three arrays: URLs, Folders, and Domains. At this point, what you do is dependent on your particular needs. To keep it simple, I check the URLs array if there is anything at all there, I flag it as an error. Going further when MetaCert has more categories, I would probably need to be more specific here and check to see exactly what type of category was returned.\nAs an example, here is a JSON response for the sexy URL used earlier:\n\nIf you want to try out this sample app, you'll need to sign up first so you can get an API key. Check their pricing info sheet for current prices, but at the time I wrote this, the cost is 10 bucks a month, which seems great. There is a free trial so you can try before you buy. If you want to try it with my demo, you can grab the source here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/metacert. Just modify the apiKey in the JavaScript file.\nAs always - if you try out this service (or have tried it already) I'd love to hear what you think in the comments below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Selecting multiple images in a PhoneGap/Cordova app",
		"date":"Thu Mar 12 2015 02:43:27 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426128207,
		"url":"https://www.raymondcamden.com/2015/03/12/selecting-multiple-images-in-a-phonegapcordova-app",
		"content":"A few days ago a reader asked me an interesting question: How can you select more than one image at a time in a PhoneGap/Cordova application? I did a bit of digging and came up with three different options for handling this. There are probably more, and if you would like to share how you've done it, please add a comment below.\n\nOption One - Camera API\nThe Camera API only lets you select one image at a time, so it may seem like it isn't a viable solution, but if your code simply keeps track of a list of selections, it may be an option. The user would need to click some button one time for each image they want to use, but depending on your use-case, that may be ok. For example, when I post a picture to Facebook, even though I can post multiple images, I can't honestly remember the last time I did. If Facebook only let me select one existing picture at a time, it wouldn't bother me at all, and that may work fine for your application too.\nAs an example of this, I created a simple Ionic application that lets you select an image. Every time you select an image, it gets added to a list that is rendered on screen. Here is the controller code I used to handle this:\n\nHere is an awesome animated gif of this in action:\n\nObviously this would be tied to a form process of some sort. You would use the array of images as values with the FileTransfer plugin to send them to your server.\nYou can view the full source code for this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiimageselect/multiimageselect1.\nOption Two - Media-Capture API\nAnother option to consider would be the media capture plugin. I don't use this terribly often and I tend to forget that it supports camera images as well. With this plugin, you can request multiple camera images. However, it does not support existing pictures - the user must take new pictures. Also, the user experience is pretty horrible. As I said, I don't use this terribly often and in my testing it wasn't obvious how you stopped the picture taking process. I had to click the device's back button (I only tested this one on Android) to get out of the picture taking cycle and on more than one occasion I accidentally closed the app.\nSo as I said, this is probably not what I'd use - but it is an option. Here's the controller code updated to make use of that plugin:\n\nYou can view this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiimageselect/multiimageselect3\nOption Three - cordova-imagePicker\nFor the final - and probably best - option there is the cordova-imagePicker plugin. This plugin does pretty much exactly what the reader wanted - letting you select multiple images. It has an incredibly simple API allowing for a maximum number of pictures and automatic resizing/quality changes too. Here's how the code works with the defaults:\n\nAnd here it is in action:\n\nHonestly, this seems like the best option. You can see the full source code for this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/multiimageselect/multiimageselect2\n(As a quick aside, yes, the numbers at the end of each folder don't match the order I did here. The media-capture example is multiimageselect3 and the nicer multi select plugin one is folder 2. Sorry if that's confusing - I just built them in a different order than how I wrote them up.)\nAs always - I'm curious about how people may have solved this problem in their own PhoneGap/Cordova projects. Please share your ideas below!\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Example of Ionic's Updating Feature",
		"date":"Wed Mar 11 2015 03:20:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1426044013,
		"url":"https://www.raymondcamden.com/2015/03/11/example-of-ionics-updating-feature",
		"content":"Ok, so this will be a pretty short post, and the main gist is, &quot;It works as advertised&quot;, but I had not had a chance to actually test this until recently and I was pretty darn impressed with how smoothly it worked so I thought I'd share.\n\nIf you use Ionic, you know that they update pretty frequently, and you may want to update your project to the latest framework bits. Turns out there is an incredibly easy way to do this. Just go into your Ionic project and run ionic lib:\n\nIt reports on the version of Ionic used in a project versus the latest release. Then to update, you do ionic lib update:\n\nConfirm you want to do the update, and then just stand back. The CLI will grab the bits and take care of everything. I'd kill for this within other projects. Just for completeness sake, this is what you get when the library you're testing is already up to date.\n\n",
		"tags":[
	        
            "ionic"
            
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Check out \"Ten Apps in Ten Weeks\"",
		"date":"Tue Mar 10 2015 10:04:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425981888,
		"url":"https://www.raymondcamden.com/2015/03/10/check-out-ten-apps-in-ten-weeks",
		"content":"A conference or so ago I met Mark Lassoff. I sat in on his PhoneGap presentation and at the end, I did that annoying &quot;So, I liked your preso, but here were a few things you did wrong&quot; type thing that presenters just love. In my defense, I did it after he was done and I had him one on one, but luckily he didn't hold it against me. A few weeks ago he released a new online course, Ten Apps in Ten Weeks, and he sent me a pass to take a look through the material.\n\nThe basic idea of the course is to give you ten different applications to build over ten weeks. I don't necessarily think you will need a week each, but it is a good schedule to aim for and give yourself some breathing room. If you read this blog regularly, then you know that I'm a huge fan of the &quot;cookbook&quot; style of instruction. To me, the best thing to do after a general &quot;101 Introduction&quot; to a language/pratform/etc is start building real apps. Mark's course material is a perfect &quot;next step&quot; for someone who has learned the basics of PhoneGap/Cordova (or who have perhaps read a new and upcoming book) and need to see some real world examples. While each application is small, they all represent real world apps you could see out in the app market. Examples include:\n\nMP3 Player\nWeather Forecast\nRestaurant Finder\nBat Hunt\n\nEach app contains a video introduction, a video tutorial, and a PDF as well. What's cool is that you can watch the videos and then use the PDF to build the app at your own speed. Obviously you can also download all the assets (code, images, etc) for each app. When you begin, the course also includes a video introduction to help you understand the basic mechanics of the course itself.\n\nI do have some problems with the course. He misused jQuery Mobile a bit, which doesn't really impact the apps themselves, and he has a few other technical issues that I've mentioned to him. I don't think any of these technical issues should stop you from buying the course, but an experienced front-end developer looking to learn more about hybrid development will notice them. Again, I think they are problems to correct, but I'd still recommend picking up the course.\nSo what's the cost? You get the course for $149. But - that price actually includes two courses. You get the course mentioned above plus Mobile App Development with HTML5. Seems like a great bargain to me. If you check the course out, please let me know what you thought below. (Also let Mark know. He is very open to feedback!)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Working with the Bluemix Personality Insights Service",
		"date":"Tue Mar 10 2015 02:50:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425955848,
		"url":"https://www.raymondcamden.com/2015/03/10/working-with-the-bluemix-personality-insights-service",
		"content":"As I continue to play around with IBM Bluemix, this week I spent some time playing with the Personality Insights service. This service uses IBM Watson to analyze textual input and try to determine personality aspects of the author. It focuses on three areas of analysis:\n\n\nDetermining what the needs of the author are. These needs are narrowed to twelve main areas and rated on a 0-100 percentile scale. The needs are: Excitement, Harmony, Curiosity, Ideal, Closeness, Self-expression, Liberty, Love, Practicality, Stability, Challenge, and Structure.\nDetermining what the values of the author are. These are things Watson believe will be important to the author. As with needs, values are focused on a set of core items: Self-transcendence / Helping others, Conservation / Tradition, Hedonism / Taking pleasure in life, Self-enhancement / Achieving success, and Open to change / Excitement.\nFinally, the PI service reports on the \"Big Five\" - this is a personality model that tries to describe how a person interacts with the world. \n\nAs you can imagine, this is pretty deep stuff, and frankly, my wife who is working on her sociology degree would probably have a better understanding of the results. You can check out the full docs as well as look at the API docs for more information.\nFor my demo, I decided to try something interesting. The PI service works best when it has at least 3500 words. A typical blog post may include five hundred or so words, and with a typical RSS feed being ten items, I decided to build an application that would analyze an RSS feed and try to determine the personality of the author. I called it the Blog Personality Scan. I'll link to the demo in a moment, but let's look at the code first.\nFirst, we'll look at the app.js file for the Node app. It is pretty trivial as there are only two views - the home page and the API to send an RSS url.\n\nNot terribly exciting and I wouldn't share it normally, but I specifically wanted to call out the bits that look at process.env.VCAP_SERVICES. This is how my app picks up the API credentials when running in the Bluemix environment.\nRSS reading is taken care of by the feedparser NPM package. This is the same one I used for ColdFusionBloggers.org. I'll skip that code as it isn't that exciting.\nThe real fun part comes in the code used to interact with the PI service:\n\nOk, perhaps &quot;exciting&quot; is a bit much. Honestly, it is just a HTTP hit and a JSON response. Simple - but that's kind of the point. A good service should be rather simple to use.\nThe rest was just presenting the results. The folks at Bluemix created a cool demo with charts and stuff, but I decided to keep it simple and just render the values - sorted. I used Handlebars to make it a bit nicer, which ended up being a bit confusing to me. It never occurred to me to consider what would happen when I used a Handlebars template for the client side in a view that was being run by a Node.js app using Handlebars on the client as well. As you can guess, it didn't work well at first. If you look back at that first code listing you'll see a helper called raw-helper. I needed to add this so I could use Handlebar's syntax in my view and have the server ignore it. This is how it looks in index.html:\n\nOnce I got this working, I was mostly OK, but then I did stupid crap like adding a helper in the Node.js app.js when I really needed it in the client-side app.js. I probably shouldn't have named those files the same. So what do the results look like? I'm going to link to the demo of course, but here are some examples. First, my own blog:\n\nNext, Gruber of Daring Fireball:\n\nAnd finally, Sarah Palin's blog:\n\nWant to try it yourself? Check out the demo here: http://bloginsights.mybluemix.net/. You can see the entire source code for the project here: https://github.com/cfjedimaster/bloginsights.\n",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Need to test a service that doesn't use CORS? There's a plugin for that.",
		"date":"Mon Mar 09 2015 06:35:07 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425882907,
		"url":"https://www.raymondcamden.com/2015/03/09/need-to-test-a-service-that-doesnt-use-cors-theres-a-plugin-for-that",
		"content":"Just a quick note - if you are trying to test a service that does not use CORS and want to hit via your desktop browser, there is a Chrome extension that let's you enable CORS for any particular URL you want to test. This is great for people using their desktop browser to prototype hybrid mobile applications. The plugin is called &quot;Allow-Control-Allow-Origin: *&quot; (rolls off the tongue, doesn't it?) and can be downloaded here.\n\nOnce installed, it adds a pretty little icon to your Chrome browser where you can toggle on/off the feature at will. Note that this feature is also available via a command-line switch, but I find a simple button a heck of a lot easier to use.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "My early access Cordova book is half off today!",
		"date":"Sun Mar 08 2015 23:40:01 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425858001,
		"url":"https://www.raymondcamden.com/2015/03/09/my-early-access-cordova-book-is-half-off-today",
		"content":" Want a copy of my Apache Cordova book while it is still in development? If you order it today, you can get it half off with the following code: dotd030915au. Six of the twelve chapters are available with more coming very soon. (I'm about to turn in the tenth chapter in a couple of days.) Remember that this early access purchase gives you the entire book, not just what's available now. My book will walk you through installing the Android SDK and Cordova, building apps, and best practices. At half price I think it is a steal today - but I may be just a bit biased. ;)\n\n\nWhile you're shopping, you should also pick up the early access addition of &quot;Ionic in Action&quot; - I did this morning. (And I'll review the book once complete.) You can also get this for fifty percent off using the same code. As much as I love Ionic, I don't cover it in my book as I figure there's enough going on without also asking the user to (potentially) learn Angular at the same time. Therefore, I think both books together would be a killer combination for someone hoping to get into hybrid development.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Warning about Ionic Live Reload and the PhoneGap Developer App",
		"date":"Sun Mar 08 2015 04:23:55 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425788635,
		"url":"https://www.raymondcamden.com/2015/03/08/warning-about-ionic-live-reload-and-the-phonegap-developer-app",
		"content":"This morning I ran into an odd issue with what should have been relatively simple code. I'm working on a set of demos using Ionic and Cordova that demonstrate a particular use case of the camera. While testing, I noticed that I couldn't see an image I had selected from the gallery.\n\nAt first, I thought it was the Angular issue (ok, they call it a feature, and I get the reasoning, but I call it a bug and I'm happy to be wrong) where the library will block you from injecting potentially dangerous stuff into the DOM. The fix for that is rather simple - just add a regex to imgSrcSanitizationWhitelist:\n\nHowever, that didn't work. I had been testing with iOS so I quickly switched to Android and tested there. I noticed I had the same issue.\nAt this point I hit a brick wall. I've used the Camera numerous times before with Cordova so I assumed it must have been an Angular issue. I then tried my basic camera demo from my Cordova examples repository - an app I had built a while ago and was as simple as possible - and that failed too!\nWhile testing, I was debugging of course, and I noticed this error in the console: Not allowed to load local resource. This is what had originally reminded me to use the sanitization setting in Angular, but I was seeing it in my non-Angular example as well.\nI then did one more test. I had been using Ionic's kick ass live reload feature while testing. On a whim, I stopped, and switched to just doing cordova emulate ios... and it worked. I then realized what the culprit was - live reload.\nWhen using live reload, you're actually running the assets off the computer and not the device. That means the URIs returned by the camera plugin were referencing URIs on the computer that did not exist.\nTo confirm this was an issue, I also tested with the PhoneGap Developer App and I had the exact same problem. This makes sense, but is definitely a bit of a bummer if you need to test anything involving the file system. In my particular use case, I could switch to using base64 images, but I'm going to avoid that as it isn't typically recommended.\nTo be clear, I'm not suggesting to avoid these features. Ionic's Live Reload is freaking helpful as hell, and the PhoneGap Developer App is the number one way to test PhoneGap/Cordova quickly (and will be what I use in presentations in the future), but you want to remember these issues when testing. I opened an ER for the Ionic CLI to warn users about this and I'd appreciate folks input (either for or against) if you are an Ionic user.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Warning a user before they leave a form",
		"date":"Fri Mar 06 2015 04:01:23 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425614483,
		"url":"https://www.raymondcamden.com/2015/03/06/warning-a-user-before-they-leave-a-form",
		"content":"A reader asked me this today and I thought it would be a good topic for discussion.\n\nI have a form and notice that not a lot of people complete it. Can you think of any way to catch those people that leave the page without filling out the form and direct them to a page that could get them to fill out something answering why they didn't fill out the form?\n\n\nSo, before getting into the technicalities, I think this touches on an incredibly important topic, one I actually raised way back in 2006, &quot;How ColdFusion can save your business!&quot; While that blog post was focused on ColdFusion, the point it raised was how you could use server-side technology to track when a user leaves the site. Or to be more precise, what the last URL was for a particular user. The example raised in the blog post was a multi-step form that may cause many users to abandon the site part way through. I asked the question - how would you determine that people were having an issue with one particular step?\nIn the blog post I used server-side tracking, but as folks brought up in the comments, Google Analytics (or any analytic program), could be used to help track this as well. At the end of the day, you as the owner of your site need to be proactive in looking for this type of information.\nNow - let's discuss the user's particular question. You can detect, with JavaScript, when a user leaves a page. This fires an event on the Window object called onbeforeunload. As far as I can tell, you can only allow or prevent the navigation. So the user's particular request to go to another page wouldn't be possible client-side. In theory you could use a cookie (or Session value) when the user loaded the form page and on his or her next hit, see if that variable is there and then redirect the user. But sticking to JavaScript, we can only warn the user, not redirect them. Here's a simple example.\n\nAn event handler is added to the Window object to listen for the user leaving and warn them beforehand. Then we can use the form submission handler to just remove the event.\n\nThis works, but is pretty annoying, so I'd say use sparingly. Then again, it isn't as evil as those darn modal windows asking you to &quot;Like&quot; the site - but that's a rant for later.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "My experience working with Jekyll",
		"date":"Thu Mar 05 2015 03:56:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425527771,
		"url":"https://www.raymondcamden.com/2015/03/05/my-experience-working-with-jekyll",
		"content":"Yesterday I blogged about the new static-site hosting service, Surge. In order to test it, I decided to rebuild JavaScript Cookbook as a static site. (Which, to be honest, was a silly decision. Surge takes about five minutes to use. My rewrite took about five hours. ;) I decided to give Jekyll a try and I thought I'd share my thoughts about the platform. Obviously I've just built one site with it so take what I say with a grain of salt, but if you're considering setting up a static site, maybe this post will be helpful.\n\n\nJekyll, like HarpJS, is run via a command line tool. Unlike Harp, Jekyll is a Ruby-based tool but you don't need to know Ruby in order to use it. I had kind of a crash course in Ruby while I worked with it, but that's only because of some of the requirements I had while building out my site. The full requirements are documented with the big red flag being that there is no Windows support. There's unofficial support, but I'd be wary of committing to Jekyll if you need to support developers on the Windows platform.\nOnce installed, you can fire up the Jekyll server from the command line and begin working. Jekyll will automatically refresh while you work so it is quick to get up and running. Speaking of testing, the command line includes an option to create a default site, simply do jekyll new directoryname.\nAt this point you can start typing away and testing the results in the browser. I'm assuming most of my readers are already familiar with why tools like this are cool, but in case you aren't, the point of a static site generator is to let you build sites in a similar fashion to dynamic server-side apps but with a flat, static file as the output. So as a practical matter that means I can build a template and simply use a token, like {{body}}, that will be replaced with a page's content. I can write a page and just include the relevant data for that page and when viewed in the browser it will automatically be wrapped in the template. This isn't necessarily that special - it's 101-level PHP/ColdFusion/Node stuff - but the generator tool will spit out flat HTML files that can then be hosted on things like S3, Google Cloud, or, of course, Surge.\nFor its templates, Jekyll allows for Markdown and Liquid. It does not support Jade, because Jade is evil and smelly and shouldn't be supported anywhere. I found Liquid to be very nice. You've got your basics (variable outputting, looping, conditionals) as well as some powerful filters too. For example, this will title case a string: {{ title | capitalize}}. This will do truncation: {{ content | truncate: 200, '...' }}. You can do this with EJS templates in HarpJS as well (but I didn't know that till today!).\nThe other big change in Jekyll is how it handles data for content. In Harp, this is separated into a file unique to a folder. In Jekyll, this is done via &quot;front matter&quot;, basically formatted content on top of a page. Initially I preferred Harp's way, but the more I played with Jekyll the more it seemed natural to include it with the content itself.\nYou can, if you want, also include random data files, which is cool. If you need something that isn't related to content you could abstract it out into a JSON or YAML file and make use of it in your site. Hell, you can even use CSV.\nAs a trivial example of a Liquid file, here's a super simple page I use for thanking people after they submit content. It doesn't have anything dynamic in it at all, but the content on top tells Jekyll what template to use and passes on a title value.\n\nHere is a slightly more complex example, the default layout for the site. Note the use of variables and conditions for determining which tab to highlight.\n\nOne of the cooler aspects of Liquid is the assign operator. Given that you have access to data about your site, a list of articles for example, you can quickly slice and dice it within your template. While Jekyll makes it easy to work with blog posts, my content was a bit different. I needed a quick way to get all my article content and sort it by the last date published. Here's how the &quot;Latest Articles&quot; gets generated.\n\nLike I said, that assign command just makes me happy all over.\nSo this is all well and good - but there is one killer feature of Jekyll that makes me think this may be the best tool for the job I've seen yet - plugins. Jekyll lets you create multiple additions to the server to do things like:\n\nCreate generators - code that will create new files for you\nAdd tags to the Liquid template system\nAdd filters that can be used in assign calls\n\nThese plugins must be written in Ruby, but even with my absolute lack of knowledge in the language I was able to create two plugins to complete my site. Let me be clear - without these plugins I would not have been able to complete the conversion. (Well, I would have had to do a lot more work.) Let me give you a concrete example of where this helps.\nOne of the issues you run into with static-site generators i",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "jamstack"
            
		]

	},

	{
		"title": "Hosting static sites with Surge",
		"date":"Wed Mar 04 2015 10:02:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425463332,
		"url":"https://www.raymondcamden.com/2015/03/04/hosting-static-sites-with-surge",
		"content":"My readers know that I've become somewhat of a proponent of static sites lately. As much as I've made a career out of building dynamic web apps, I love the simplicity of static files. I can push to a CDN and pretty much not worry about it again. Don't get me wrong, I know S3, Google Cloud, and their kind can - and probably will - go down. But I'm probably safe in saying that their engineers can get servers back up much quicker than I can. That's why I was excited to see a new option for hosting static sites launch - Surge.\n\n\nSurge is a command-line program that handles deploying static sites to a CDN. You install it via npm (npm install -g surge) and then simply go into a directory of static files and type surge. And yeah - that's it. Seriously.\nThe command line will prompt you for both a directory and domain, but you can accept a randomly selected domain generated by the service. This is a great way to show off a web site in development to a client.\nHere's an example. I've already registered and logged in so it skips that. I also had an existing random domain already so I typed that in. (And you can skip that by creating a file called CNAME with the host.)\n\n\nAbout 30 seconds or so later, the deployment is done. If you want, you can enter a &quot;real&quot; domain and simply update your DNS settings to point to the IP address returned in the surge output. (Read more about that here: Make something of your ridiculous domains).\nYou can even use Surge with Gulp/Grunt to fully automate the deployment.\nFor fun, I decided to &quot;static-ify&quot; (that's a word, honest) my Node.js site, JavaScriptCookbook.com. I decided to try Jekyll for this conversion and I'll blog about that process tomorrow. (Spoiler - it took a bit more than 30 seconds.) You can see the Surge-hosted version of the site here: http://aloof-zephyr.surge.sh/. Once I've clicked around a bit more to ensure I didn't screw anything up I'll shut down the Node.js version and make this one the production site.\nAnd by the way - the entire thing is free. Which kicks ass. You can see one of the creators demoing the feature in the video below. If you decide to make use of this service, let me know in the comments below. I'd love to see some examples.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Brackets Tip: Specifying one linter (the right way)",
		"date":"Tue Mar 03 2015 10:00:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425376846,
		"url":"https://www.raymondcamden.com/2015/03/03/brackets-tip-specifying-one-linter-the-right-way",
		"content":"A few days ago I noticed Brackets was no longer linting JSON files. I had recently updated so I assumed it was a bug with that particular extension. Like a good developer, I checked the console and when I didn't see anything, I filed a report and moved on. (For folks curious, the extension I was using is called &quot;JSONLint Extension for Brackets&quot; by Ingo Richter. Oh, and for what I used when it was't working - I used JSONLint.com.) Turns out the issue was a mistake I made in my preferences.\n\nBrackets has a pretty complex preferences system. While there isn't a UI for it, you can open your global preferences file by going to the Debug menu and selecting Open Preferences File. Brackets ships with a linter for JavaScript files called JSLint. I have another extension that wraps JSHint that I prefer. By default, if Brackets sees multiple linters for a file, it will display issues from both:\n\nThat's not really ideal so I checked out the Preferences docs on how I could correct this.\nI focused my attention on two settings: linting.prefer and lintingUsePrefferedOnly. The first lets you specify an order of preferred linters and the second specifies that you only want to use the preferred one. So I added this to my global preferences:\n\nSeems legit, right? I prefer JSHint and I only want to use that. However, this was the root of my issues. Because this preference was global, it applied to all file types. When reading the docs, I had focused in on the table of settings and missed this crucial detail:\n\nWithin either file, there are three levels of specificity at which you can set a preference:\n\ndefault - global (user-level file) or project-global (project-level file)\n\"path\" layer - overrides in effect for files that match the given path/filename wildcard\n\"language\" layer - overrides in effect for files that Brackets detects as the given programming language (this is also filename/extension based, but it's easier to work with since Brackets already understands many file extensions out of the box, and additional languages supported by Brackets extensions can automatically be used here too).\n\n\nBasically, you can specify settings per language, which is what I needed to do for JavaScript. Here is the corrected version:\n\nAnd that corrected it. In JavaScript I only see JSHint and my other linters work fine elsewhere. A big thank you to Randy Edmunds for pointing out my mistake.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Cordova CLI Updated",
		"date":"Tue Mar 03 2015 02:49:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425350984,
		"url":"https://www.raymondcamden.com/2015/03/03/cordova-cli-updated",
		"content":"Earlier this morning the Apache Cordova team released a cool update to the CLI (Tools Release: March 02, 2015). One of the most interesting aspects of this update is a new feature - the ability to save (and naturally restore) platforms and plugins.\n\nTo use this new feature, you simply add --save when adding a platform or plugin. As an example:\n\nNotice how it specifically mentions that it is being saved into config.xml. And here is an example of saving a plugin:\n\nIf you look at your config.xml, you can see this information is now added.\n\nSo when are these settings used? When cordova prepare is executed. If all you do is cordova emulate while working, then this will handle it as well.\nWhat about existing projects? Unfortunately there is no way to look at your current project and save everything as is. (I filed a bug report for that.) You would need to remove each platform and project and then re-add them with the flag. That won't take more than a minute though and is worth the effort if you're working on a team.\nBe sure to read the blog entry for a full list of updates, and OSX users doing iOS should pay special attention to the note to update ios-deploy. Doing so will finally get rid of the warnings you would receive in Terminal.\nAnother update is the ability to list devices and emulator images from the command line. (Covered in detail in this bug report.) I was a bit confused as to how to use this at the CLI so I figured I'd share some tips.\nFirst, if you want to get a list of emulator images, you would do this:\ncordova emulate --list\nThis will not fire up the emulator but just list the available emulator types for your installed plugins. I've only got iOS as a platform in this project, but here's an example of the output.\n\nTo use this, you can then pass it as a target value like so: cordova emulate --target iPad-2. In theory, all of this should work if you had multiple devices attached to your machine as well, but as I'm too lazy to go get my physical iOS devices, I'll just trust that it works. ;)\nAs a final tip, if you want to keep up to date with Cordova, you may want to subscribe to their RSS feed and use IFTTT to send you an email. Their RSS feed may be found at http://cordova.apache.org/rss.xml and IFTTT makes it easy to get emails from an RSS feed. This is what I do since I tend to fall behind on the public dev list.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Hosting Node.js apps on Bluemix",
		"date":"Mon Mar 02 2015 08:28:40 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425284920,
		"url":"https://www.raymondcamden.com/2015/03/02/hosting-node-js-apps-on-bluemix",
		"content":"One of the biggest issues I had when learning Node.js was how to take an application into production. Luckily theres multiple options to make this easier, including IBM Bluemix, a service I've been playing with over the past few weeks. In this post, I'm going to briefly describe what it takes to set up a new Node.js app on Bluemix as well as what it is like to migrate an existing site there.\n\nOk, so first off, you'll need to sign up for Bluemix. You won't need a credit card and you get a good long trial to play with things. There's a lot of cool stuff at Bluemix, far beyond what I'm talking about today, so the trial will give you time to play around with other features as well.\nThe very first time you sign in, you're asked to create a &quot;Space&quot;. You can think of this as a bucket for your various apps and services. You can name this whatever you want, and you'll just need to do this one time.\n\nAfter you've done that, you can then create an app.\n\nNext, select Web.\n\nAnd then select SDK for Node.js\n\nFor app name, select whatever makes sense.\n\nNote that the app has to be unique across all of Bluemix. So you may need to prefix the name of your app with something unique. So for example, if you were launching raymondcamden.com there, I'd pick a name that included that. (And yes, the name I used in the screen shot above was taken. I switched to RKCMyApp.\nOn the next screen, you're given some good information to help you get started.\n\nPay particular attention to the &quot;CF Command Line Interface&quot; download. This is the command line tool that you will use to help push updates from your machine to Bluemix. This is a one time install. Unfortunately you can't use npm to do the install, but hopefully in the future those tools will be published there.\nYou can, if you want, also download the starter app code. That's the code currently being used for your new application. If you are new to Node and want to learn, I'd recommend grabbing the code and playing with it. It uses Express (which in my opinion is one of the best Node libraries out there) but also uses Jade, which is the template framework of the devil. Luckily you can easily switch to a more sensible framework quickly enough.\nGo ahead and dismiss that welcome text to view your application console. If you ever want to get back to it, it is available by clicking &quot;Start Coding&quot; in the left hand nav.\n\nYou can see options for adding new instances, increasing memory, and adding new services. You can stop and restart the app as well as seeing a log of recent changes. Finally note the &quot;Routes&quot; section on top. Clicking on the URL there will take you to your application.\nUpdating and deploying your code is pretty easy. Assuming you've got Node already running locally, and assuming you grabbed the &quot;starter pack&quot; for this new app, open up the files in your editor and at your terminal, get it running by first installing dependancies (npm install) and then running it (node app). By the way, I strongly recommend nodemon while developing.\nAfter you've made your changes, how do you push it up to Bluemix?\nFirst, you have to tell the command line (cf) what API to use. This is a one time setting.\ncf api https://api.ng.bluemix.net\nThen login.\ncf login -u youremail -o yourorg -s yourspace\nThis login will persist (I'm not sure how long), so you won't have to constantly relogin as you do stuff.\nPushing updates to your app then is trivial:\ncf push appname\nThe update will take about a minute. I'm guessing if you add a bunch of new libraries to your package.json file it may take longer. But once done, you can hit your site and see it changed. You can see my app here, but note that I created this in a trial account that is ending in three days: http://rkcmyapp.mybluemix.net/. In case my trial account has expired, here is what I created in about 20 seconds:\n\nThe command line has a heck of a lot of power (basically everything the site has). You can run cf by itself to see docs at the command line. One option I found recently was cf logs MYAPP. This will begin tailing your logs, from your server, right in your terminal.\nMigrating to Bluemix\nSo all of the previous is related to working on a new Node.js app with Bluemix. What about migrating an existing app?\nFirst, you want to pay particular attention to the Node.js Bluemix documentation on deployment. Bluemix runs a customized version of Node.js. It isn't directly linked to from there, but you can find details about the modifications here: IBM SDK for Node.js Version 1.1.\nIn the docs linked to above, pay special attention to this portion:\n\nIf Procfile is not present, the IBM Node.js buildpack checks for a scripts.start entry in the package.json file. If a start script entry is present, a Procfile is generated automatically. Otherwise, IBM Node.js buildpack checks for a server.js file in the root directory of your application. If a server.js file is found, a Procfile is also generated automatically.\n\nAnd t",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Sunday OT: Rise of the Runelords",
		"date":"Sun Mar 01 2015 02:44:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425177876,
		"url":"https://www.raymondcamden.com/2015/03/01/sunday-ot-rise-of-the-runelords",
		"content":"\n  It has been a while since I've posted a game review, but it's also been a while since I've been able to play one. Yesterday one of my boys and I played a round of \"Rise of the Runelords\", a fairly complex card game by Paizo Publishing. Rise of the Runelords (RoR) is a deck building game which is easily my favorite type of game now. I got into Magic back in college, but couldn't afford, and simply didn't want to, spend a lot of money on deck packs. That's why I really dig games that have a similar experience without requiring a constant stream of purchases. Technically RoR also has purchases, but from what I see they act more like DLC in a video game - adding additional adventures. \n\nSetup is a fairly lengthy process. I'd say our first game took almost an hour to get prepared. That time will be significantly smaller in future games, but it was definitely a bit daunting. Gameplay was right on the edge of what I'd call too complex to be fun. As an example, when you get rid of a card during game play, there's something like four different things that can happen to it. Some easy to understand (discard), some vague (banish, recharge, and some other option too). Every particular encounter (combat, traps, getting an item, etc) is also pretty complex. I'd guess that sometimes there's something like 5-7 options for how to resolve an encounter. But once I got into it, I really dug that. It makes you think about how best to accomplish something and really plan your strategy.\nThe biggest thing missing from the game is a good reference sheet I think. My son and I both read the rules fairly closely, but sometimes an important detail (like combat!) is buried in with other text that's easy to miss. My plan is actually write up my own little reference sheet during play sessions.\nProbably the coolest aspect of the game is that it is persistent across play stations. One play session is a &quot;scenario&quot;, which is part of an &quot;adventure path&quot;, which is then part of one &quot;adventure.&quot; As you finish each scenario you get a bonus that can improve your character. You also get to improve your deck for the next scenario.\nGiven how busy life is, I'm a bit afraid about how long it will take to actually &quot;finish&quot; the game. I could see this taking a year, but, I'm OK with that. For the price of the game, that's an incredible amount of play time. To be clear, this isn't a one time thing, you can replay scenarios too and restart characters. And as I mentioned above, there are expansion sets that add new adventures.\nThe artwork in the game is beautiful. The company didn't have any promotional artwork on the site itself, so I'm copying a bit from the PDF ruleset.\n\nYou can see more, and the complete rule set, by downloading the rules here.\nI definitely recommend picking it up, just be prepared for a lengthy initial setup and somewhat of a commitment if you want to play through the entire story. Oh, and I died during my first game play but my son and I just pretended that didn't happen. There's no point in playing games where you can't bend the rules from time to time. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "games"
            
		]

	},

	{
		"title": "Quick Cordova tip - Preventing multiple sounds at once",
		"date":"Fri Feb 27 2015 02:51:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1425005508,
		"url":"https://www.raymondcamden.com/2015/02/27/quick-cordova-tip-preventing-multiple-sounds-at-once",
		"content":"I'm working on a simple application that acts - a bit - like a sound board. You select an item, click a button, and you hear an MP3. While letting one of my kids play with it, they quickly discovered that if they hit the button multiple times in a row, the sound would play multiple times, overlapping each other. While not the end of the world (this is the kind of bug that would only occur if the user was trying to do exactly that), I thought I should probably prevent this from happening.\n\nHere's the original method:\n\nIf you read the docs for the media plugin, you will discover that there are multiple ways to know when a Media object is done playing. In theory, I could use that to determine when it is safe for a new MP3 to play. But why go complex when you use simpler logic? How about simply seeing if the media variable is instantiated and call stop()? Calling stop is harmless if the sound is already done and it quickly handles the issue with one line of code (well, one line and one small mod).\nI moved my media variable outside the function and updated my function like so:\n\nAnd that's that. Now if you click the button like crazy, the previous sound stops before starting up again.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Reminder - You don't need AppCache for PhoneGap/Cordova",
		"date":"Wed Feb 25 2015 23:59:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424908799,
		"url":"https://www.raymondcamden.com/2015/02/26/reminder-you-dont-need-appcache-for-phonegapcordova",
		"content":"I've seen this come up a few times recently, and it was mentioned in a presentation I attended yesterday on PhoneGap, but just as a reminder, stop worrying about AppCache and PhoneGap/Cordova. It may not be entirely clear to people new to hybrid mobile development, but your application is on the device itself. AppCache makes no sense in this regard. It is a bit like having an HTML file on your desktop. Whether you are online or not does not matter!\nHowever...\n\nYou obviously do need to care about if the user is offline or not. First - if you are using a CDN for jQuery or some other library - stop. Just download the bits locally to your application and point to them there.\nSecondly, if you are using any remote APIs, then certainly you need to check the device's online/offline status. Make use of the Network Information plugin (*) and spend time to ensure your application responds correctly to different network conditions. I covered this way back in 2013 (Building &quot;Robust&quot; PhoneGap Applications) and I discuss it in my Cordova book.\n\nAs just a quick aside, I know some folks have had reliability issues with the Network Information plugin. I've seen some people actually write a simple XHR utility to hit a known URL and check the result. I've not had this issue myself, but I wanted to bring it up as just a warning that the plugin may not be perfect. Even if a user is reported as being online, I would hope you build your API calls with error handling. Heck, the API provider may be down themselves.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Migrating servers on Google Compute Engine",
		"date":"Tue Feb 24 2015 07:20:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424762413,
		"url":"https://www.raymondcamden.com/2015/02/24/migrating-servers-on-google-compute-engine",
		"content":"So, as folks know, I've been struggling a bit with my server here. Last night MySQL went down around 10PM, and while normally I'm up there, I had gone to bed early so I didn't restart it till 6AM this morning. That's a heck of a long time for it to be down. I decided to reach out again for help as well as start looking seriously at Jekyll. I prefer Harp but after watching Brian Rinaldi's demo of Jekyll I decided to try it out.\n\nOne of the suggestions that was made was to move from Apache to nginx. While I didn't think Apache was ever an issue, it was the process using most of the RAM on my machine and I figured - it couldn't hurt.\nIn order to give this a shot though I wanted to test it first, and that is what I'd like to cover here. How difficult is it to copy a Google Compute Engine instance? Turns out - it's really darn easy.\nIf you go into the instance detail page, you'll see a Clone button right away:\n\nBut do not use that first! When you clone, Google will ask you what disk to use. You can pick one of the main source disks (for different operating systems), a snapshot, or an existing disk. It wasn't clear if &quot;existing disk&quot; would copy or re-use the same disk. I assumed it would re-use the disk which isn't what I wanted obviously. (And I just confirmed with my friend at Google - it would re-use it.) So instead - make a snapshot of your disk. Then go back to the Clone operation and select that.\nWithin five minutes, heck maybe even two, I had a clone of my original instance with a copy of the drive. I could even confirm this by hitting my new IP and seeing my blog.\nI then worked on getting Nginx installed. Since this post is about the Google Compute area, I'll share some links about that portion at the end. Once I had everything up and running well, I decided it was time to trust that I hadn't screwed things up and point the static IP address I had for my first instance to the new instance.\nBack when I first launched on Google Compute, I went with the smallest instance possible. When I discovered I needed to move to a bigger instance, I ran into the issue of migrating the IP address. Back then I used the CLI to deallocate the IP from the server and re-allocate it to the new one. For the life of me I couldn't find that blog post detailing the process. On a whim, I went to the Network portion of my Google panel and discovered something cool. You could do the entire thing there!\nYou can see the two instances below.\n\nI began by clicking Change:\n\nI then simply selected my new instance - and that was that. Google started pointing my static external IP to the new box! Also notice how it restored an ephemeral IP address back to the original instance. That's pretty helpful.\nAll in all - a relatively painless process (well, the Nginx stuff was a bit involved). The only real hiccup is ensuring you use a new disk when you clone your server.\nNGinx and Wordpress\nOk, so this is off topic for the main post, but if you're curious about using NGinx and Wordpress, here are a few helpful links:\n\nHow To Install Linux, nginx, MySQL, PHP (LEMP) stack on Ubuntu 12.04 - I began here. I already had MySQL and PHP, but I installed PHP-FPM as it is supposed to be faster. \nHow To Install Wordpress with nginx on Ubuntu 12.04 - I then followed the configuration suggestions here.\nWP Super cache - This page detailed how to get Super cache working with Nginx.\nNginx - This was a more generic article on WordPress and NGinx, the main thing I copied from here were the rules blocking access to .htaccess and other files.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Brian Rinaldi on Static Sites",
		"date":"Tue Feb 24 2015 03:10:18 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424747418,
		"url":"https://www.raymondcamden.com/2015/02/24/brian-rinaldi-on-static-sites",
		"content":"As my readers know, I'm a huge fan of static sites. I wanted to share a good video on the topic by my buddy, Brian Rinaldi. As an FYI, I'll be covering the same topic at devObjective, although in a slightly different format.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "POC - Determining the \"Health\" of your GitHub Repos",
		"date":"Mon Feb 23 2015 07:57:34 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424678254,
		"url":"https://www.raymondcamden.com/2015/02/23/poc-determining-the-health-of-your-github-repos",
		"content":"I hinted about this in my blog post last night, but I've been working on a proof of concept application meant to judge the relative &quot;health&quot; of your GitHub repositories.\n\nMy idea was that there are various things we could check within a repository to judge how well it is being maintained. Things like the age of the last update, number of open bugs, etc. Obviously there is no way to fully automate this type of scan, but I wanted to give it a shot and see if I could learn anything interesting.\nI was initially going to build it in Node.js so I could OAuth against the GitHub API, but I decided I'd give hello.js a try. hello.js provided a client-side method of doing OAuth against a variety of services. In some cases it uses an app proxy on its own server and you have to do a bit of registration beforehand, but for the most part, it worked pretty well. As an example, this hits the GitHub API to read the current user's repositories.\n\nIn my application, I built a simple function that looped over your repos and added each one to a global array.\n\nThis is called after the authorization is complete which is also fairly easy in the hello.js library:\n\nYeah, that's it. You tie this with an event handler as well:\n\nSo... ok. That part didn't take too much time. The next part was actually figuring out how to determine the health of a repo. I decided to start simple - I'd check the age of the last update and the number of open issues - which luckily is part of the repository object information.\n\nAs you can see - I simply apply some arbitrary points based on how bad I think certain values are. I spent some time trying to figure out how many points certain things should get, but, then I said screw it. Let me ship this first version and let smarter folks then I figure it out.\nTo display everything, I just used Bootstrap. Why? Because.\n\nWant to see the full source code - and help make it better? It's up (of course) on GitHub: https://github.com/cfjedimaster/githubhealth.\nWant to run it yourself? You can see it in action here: https://static.raymondcamden.com/githubhealth.\nAs always, I'm curious to know what you think. Is this helpful? Should I keep hacking away at it?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Static site hosting on Google Cloud",
		"date":"Sun Feb 22 2015 15:16:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424618208,
		"url":"https://www.raymondcamden.com/2015/02/22/static-site-hosting-on-google-cloud",
		"content":"A few days ago I blogged about my experience using Google Compute for site hosting. Tonight I set up a new static site and I decided to use Google Cloud Storage and I thought I'd share a few notes about how it went.\n\nThe docs are pretty good so I won't bother repeating what is there. I will say the first big trip up I ran into was verification. I wanted to create a bucket for githubhealth.raymondcamden.com. If you follow me on Twitter (@raymondcamden) then you may have seen the messages and screenshots I've been sharing of a tool that will let you check the relative &quot;health&quot; of your GitHub projects. I'm going to blog about that tomorrow, but I had some free time tonight so I thought I'd go ahead and set up the hosting. When I tried to make a bucket with the same name as the site, Google's online tool would not let me. It said I needed to verify the site first.\nThat seemed like a bit of a Catch 22. How do I verify a site when I plan on hosting the site via the bucket? Luckily though you can also do verification via adding a TXT record. GoDaddy makes this pretty easy and Google was able to verify it within about ten minutes.\nAfter I had the bucket created, I had to get the files uploaded. Transmit (an OSX FTP client) has support for S3 sites built in, but I don't know if they support Google Cloud Storage. But my former boss, and current head of Google Cloud Evangelism at Google, wrote a blog post a few days ago talking about how to use the command line tool to work with buckets. I mentioned how cool the command line stuff was for Google Compute and it is just as cool for Cloud Storage. I used one command to copy my files up and another to set permissions.\nThe last step was to enable an index page for the bucket, which could have also been done via the CLI, but I used the web-based administrator instead.\nIf I remember right, Amazon made the security part of site hosting a real pain in the rear. You had to find some funky XML snippet and paste it in. Google has them beat here as far as I can tell. The only part I didn't really care for was verification, but I can get over that.\nAny way, I guess that's it. If you decide to use Google's Cloud Storage, let them know I sent you their way. For every five referrals I get a free copy of Chrome.\nIf you're curious about the UI, here's my bucket list (heh):\n\nAnd a detail view of my static site bucket:\n\nIf you want to hit the site (and again, I'll blog about it more tomorrow), you can do so here: http://githubhealth.raymondcamden.com.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using the MobileFirst Mobile Browser Simulator",
		"date":"Fri Feb 20 2015 09:49:47 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424425787,
		"url":"https://www.raymondcamden.com/2015/02/20/using-the-mobilefirst-mobile-browser-simulator",
		"content":"Last week I blogged about using hybrid mobile applications with IBM MobileFirst. One of the cooler tools you get with the MobileFirst platform is the Mobile Browser Simulator This is a desktop testing tool for your application, acting much like the old Ripple project. It is something better shown than written about so I created a quick video that demonstrates how it works. If you have any questions, just ask them in the comments below!\n\n",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "My experiences with Google Compute Engine",
		"date":"Thu Feb 19 2015 04:58:37 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424321917,
		"url":"https://www.raymondcamden.com/2015/02/19/my-experiences-with-google-compute-engine",
		"content":"A while ago I promised I'd write up my experiences working with Google Compute Engine. As my readers know, I've been suffering with some uptime issues here (which I do not blame on the platform, just my inexperience) and I was waiting until I got that 100% nailed down, but since I'm still working my way towards a final solution, I thought I'd take some time to write up what it was like to use Google Compute Engine (GCE).\n\nI decided to use GCE based on the recommendation of a good friend and former coworker. He works at Google and actually evangelizes GCE, so I figure if something went wrong, I could bug the heck out of him. (Spoiler - I did. ;) Going into it, my only experience with services like this was EC2, and even then, I didn't have a lot of experience. I've fired up two or three EC2 instances, and when I did in the past, I was using someone else's money so I didn't have to worry too much about sizing the instance and stuff like that. My biggest concern about GCE was cost. In the past, it had seemed that Amazon didn't make it easy to figure out what a full time instance would cost. (Again, this was my impression in the past, it may be different know.) Google does a decent enough job with their pricing calculator. Heck, it defaults to 24 hours a day/7 days a week which is nice. The only issue I had was with bandwidth estimates. I haven't really paid attention to my bandwidth numbers since I started using Google Analytics. It would be cool if they had some way of providing a rough estimate in their calculator, like, &quot;A blog getting 100K page views per month is roughly N gigs.&quot; I just checked my console, and for the life of me, I can't find a way to see what I &quot;spent&quot; last month in network traffic. It doesn't seem to be broken down in the bill, but I may be missing it.\n\nWhen it comes to servers, my primary experience is with Windows. While I love my Mac, I've always found Windows to be a heck of a lot easier for servers. The primary reason is that it makes it so much easier to see what services are being run. On my Mac, I can honestly say I have no flipping idea what's set to run on boot and how I can check/modify/etc those settings. The Windows Services control panel is - in my opinion - worth the cost of the license right there - but I'm a cheap guy so I went with Ubuntu for my instance. I also initially selected the Micro (0.6 gig) instance which was a big mistake. (One of a few I made.)\nOnce the instance is started, Google makes it very easy to work with it. You can run SSH in the browser, or, if you install their command line tools (which I recommend, more on why in a bit), they provide a SSH utility that makes it easy to connect. My knowledge of SSH is a bit rusty, but when I used Google's tool, it made it incredibly easy. It just worked, which was quite cool.\nAt that point I had to get stuff installed. Google has starter packs for various technologies, including WordPress, but at the time I thought it would make sense to do it myself. (And - maybe it was still in beta when I set up a few months ago. Honestly, I'm trying to remember why I didn't go the easy route, it seems like it would make sense to do so now.) Not having done a bunch of installations from the command line, it took a bit of searching, but I'd say within an hour I had Apache, MySQL, and WordPress set up on the instance. I made sure to copy down those command lines in case I needed to run them again.\nAt that point - it was pretty much done. I had assigned a temporary external IP address for testing. My mistake here was in forgetting this was temporary before I set up my DNS to point to it. At the time when I did that, there was no way to &quot;promote&quot; a temporary IP address to static. Again, from Googling, I discovered a simple workaround that used the command line tools. Looking at my web based console now it appears as if they added a way to do it there, but I know that in more than one case I found myself needing to use the CLI when the web based tool didn't provide a particular option. Consider this then another recommendation to grab those tools when you first set things up.\nThe other mistake I made was using a tiny CPU. Unfortunately, there is no way to upgrade an instance as is. I made a new instance, then detached my disk drive from it, reattached to the new one, and &quot;moved&quot; my static IP address. If I remember right, this was all done via the web based console and I did it while my blog was public, but I think I got it done within five or ten minutes so it wasn't a big deal.\n\nProbably the only other issue I ran into (minus the MySQL stuff sigh) was permissions. I had to do quite a bit of tweaking to get my Wordpress blog allowing for attachments and letting me tweak PHP files where necessary. I discovered many of these issues after I went live, which probably means I should have tested a bit better, but, it was for my personal blog so I only had myself to blame.\nThe one thing I decided to ",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Apache Cordova in Action MEAP Updated",
		"date":"Wed Feb 18 2015 08:40:01 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424248801,
		"url":"https://www.raymondcamden.com/2015/02/18/apache-cordova-meap",
		"content":"A few months ago I announced early access to my Apache Cordova book, &quot;Apache Cordova in Action.&quot; Today the MEAP (Manning Early Access Program) edition of the book was updated to include chapter 6. If you haven't yet checked it out, and don't mind getting in early (I won't call it &quot;rough&quot;, I'll call it &quot;frisky&quot;, yeah, that's it), today may be a great day to pick it up. The released chapters cover:\n\n\nWhat is Cordova?\nInstalling Cordova and the Android SDK\nCreating Cordova Projects\nUsing Plugins to Access Device Features\nMobile Design and User Experience\nConsiderations when Building Mobile Apps\n\nChapter 7 (testing and debugging) and chapter 8 (writing custom plugins) will be out soon. I'm currently working on the tenth chapter (had to jump a bit) but I'll be wrapping up principal writing by the end of next month.\nYou can buy the book now via the graphic below. If you don't mind, let me know in the comments what you think!\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Traffic on ColdFusion Bloggers",
		"date":"Wed Feb 18 2015 03:22:42 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424229762,
		"url":"https://www.raymondcamden.com/2015/02/18/traffic-on-coldfusion-bloggers",
		"content":"So a reader of mine pinged me today with this question:\n\nHi Ray,\nI hope that things are good with you!\nDo you have any stats on the number of articles posted on CFBloggers &amp; number of views over the years?\nI've been doing CF for 15 years, for places like Cornell Med Ctr in NYC, The Mayo Clinic in Rochester, Mn &amp; many other Fortune 500 companies.\nRight now the State of (Top Secret) is looking to move to Grails or Rails from CF.\nIs the CF gravy train coming to a halt and how quickly?\n\nI can share information about CFBloggers easily enough. To be fair, I don't think web traffic is a good metric for this site. Since it is an aggregator, I'd think most folks would use the RSS feed as a means to monitor the site. Also, everything is Tweeted so I'm thinking a lot of folks may skip hitting the page directly. Lastly, when I move the site from ColdFusion to Node I decided to stop proxying the hits. What I mean is - previously - when you clicked a link from CFB you would hit the site first, I'd log it, and then I'd forward you to the external blog entry. Now I just send you straight there.\nOk, so with that in mind, here is a chart showing page views, per month, since the site went live in 2007.\n\nAs you can see, there is a great jump in 2009, one I wish I could explain. I thought perhaps it was adding Twitter support, but that was done in July of 2008, a good year before the jump.\nAs you can see though it has slowly declined over time. I'm not sure that's necessarily indicative of ColdFusion but blogging in general. I don't have research to back this up, but it seems like more and more people are abandoning blogs versus starting new ones. My own traffic has gone up and down over the years, but is mostly up though. If you're curious, here is the history of  my blog in page views.\n\nSo yeah, let's hit that last statement, the one that isn't controversial at all.\n\nIs the CF gravy train coming to a halt and how quickly?\n\nLet me begin by saying that I am 100% completely the wrong person to try to answer this. I'm not wise about the industry. I know crap about business. I like to write code, write about code, and talk about code. What follows are my personal observations and nothing more.\nMore and more the server seems to be less important than it used to be. I've been learning about Node.js, and I think it is cool. (It has been a while since I used Groovy but it was cool too!) But I find myself doing much of the same stuff I did in ColdFusion. Listen for requests, hit a database, and output crap. ColdFusion was awesome because it made that incredibly easy. It still does! But the issue is that it isn't the only thing that makes it incredibly easy. Looking at the landscape of server technologies out there, I still think ColdFusion is the most approachable technology for non-computer professionals. (As an aside, some people may refer to these folks as &quot;Five Taggers&quot;, but I just look at them as programmers in training, or, people who need to get shit done. They don't overuse pound signs or skip using frameworks because they are stupid - they're just human and may not have the time to learn their craft like the rest of us. I wish some folks in our community would have a bit more patience and understanding.) However, outside of this demographic I don't know how much &quot;ease of use&quot; ColdFusion offers over other solutions. Groovy is a good example of that. Again, I'm years rusty with it, but it made Java coding a heck of lot easier from what I remember. By the same token, if you already know JavaScript, then learning Node means you've already got a huge leg up on beginning.\nKeeping in mind my focus for the past few years has been on client-side and mobile, I'm not seeing a lot of new people using ColdFusion. The ColdFusion questions I get tend to be from folks I already know, or from folks who are maintaining existing code. I don't have access to the number of developers using ColdFusion (Adobe would have that, and obviously it would only cover their customers, not necessarily people using Railo or Lucee), but I'd assume that number is growing well.\nGrowing the developer base has been discussed by a lot of people far smarter than I. I do not know what it will take to do it. I think Lucee is really exciting and could possibly attract folks, but again, this really isn't my area.\nI think ColdFusion will continue for a few years, I just don't think it will grow any more. I think we'll see a ColdFusion 12, but after that, I'm not sure. I'd expect Adobe to can it the minute it becomes unprofitable, which to be fair, is probably totally expected for a business. I do want to see ColdFusion 13 just because I think it would be a cool number to get too. ;)\nMyself, and others in the ColdFusion community, have been saying for nearly ten years now, that if you only know ColdFusion, you are making a huge mistake. That applies to any skill. As a web developer, you need to have a good set of skills in your wheelhouse. You c",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Reminder - Get this blog via email",
		"date":"Tue Feb 17 2015 05:09:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424149764,
		"url":"https://www.raymondcamden.com/2015/02/17/reminder-get-this-blog-via-email",
		"content":"Just a quick reminder that if you previously subscribed to my blog to get new posts by email, that feature went away when I changed to Wordpress. I'm using Feedburner for my RSS feed now and they allow you to subscribe. Just follow this link and you're good to go.\nAnd as another aside - if you like what you read here, consider visiting my Amazon Wishlist, and if you do, let me know! I just got &quot;Sleeping Dogs&quot; for the PS4 and Amazon included no information about who got it for me. If it is you, dear reader, thank you!\np.s. I played &quot;Sleeping Dogs&quot; already on the XBox, it was one of the free games you get as part of XBox Live Gold, which is a damn nice perk and easily worth the cost of Gold. The game is fun as heck - kind of an Asian Gran Theft Auto. I'm looking forward to playing it again.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Adding keyboard navigation to a client-side application",
		"date":"Mon Feb 16 2015 09:24:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424078649,
		"url":"https://www.raymondcamden.com/2015/02/16/adding-keyboard-navigation-to-a-client-side-application",
		"content":"Ok, so warning, this isn't exactly rocket science level JavaScript here. For a while now I've been wanting to build a simple demo of adding keyboard navigation to a client-side application and today I finally got off my lazy rear and wrote one up. As I said, this isn't exactly difficult to do, but I thought it might be helpful for others to see a simple example of how it can be used.\n\nTo begin, I create an incredibly simple slide show demo. I began by simply defining slides in div tags:\n\nI used a bit of CSS to hide the divs initially:\n\nAnd super simple JavaScript to handle responding to the Previous/Next clicks:\n\nJust to be absolutely clear, there are approximately 932 thousand jQuery plugins out there that handle slide shows better than this. I just wanted something simple to start off. (And I was spurred to finally write this post after looking at someone's slide deck embedded in a web page recently.) Since it is simple, I assume the code all makes sense so far. (If not, ask me in the comments below and I'll explain anything that isn't clear.)\nOk, so, how can we add keyboard navigation? The simplest way is to listen for a keyboard related events. There's keydown, keypress, and keyup. Keyup &quot;felt&quot; appropriate to me so I used that. Since we want to respond to the event at the page level, I added my listener to the document object. (Would window have been more appropriate? Let me know below.)\n\nThe next step is to respond intelligently to the event. What keys you care about depends on what you're building. For a slide show, it seemed to make sense to respond to the left and right arrows and move the current slide accordingly. (Maybe an up arrow could be used to return to slide 1 and down to the end.) All I had to do then was figure out what key code represented those values. According to the MDN docs for keyboard events, keyCode is deprecated and key should be used instead, but in my testing, key did not work in Chrome and keyCode worked in both Chrome and Firefox. Instead of Googling for a table of keyCode values, I quickly added a console.log and just typed left and right. This gave me a value of 37 for the left arrow and 39 for the right.\nTo make those work, I needed to decouple my &quot;go back&quot; and &quot;go forward&quot; code from the event handlers I had used before. Here is how I did that:\n\nAnd then I simply added calls to these new functions within my keyboard listening code.\n\nThis seems to work well, and is relatively simple, but I haven't tested with Internet Explorer yet. Want to take it for a spin? Hit up the demo here: https://static.raymondcamden.com/demos/2015/feb/16/test2.html.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Working with hybrid applications and IBM MobileFirst",
		"date":"Mon Feb 16 2015 02:13:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1424052836,
		"url":"https://www.raymondcamden.com/2015/02/16/working-with-hybrid-applications-and-ibm-mobilefirst",
		"content":"As with my last post, I'll warn folks that I'm still learning about MobileFirst and to please forgive any mistakes I make. I also want to give a shout out to my coworker Carlos Santana who has been incredibly helpful in getting me up to speed. As this is my first post on MobileFirst, let me begin with a bit of background.\n\n\nMobileFirst is a collection of products that offer features and support for mobile developers. This support covers the full range of mobile development, from native to hybrid (my main area of concern). These features/support include:\n\nLogging: You can using MobileFirst to enable/disable logging profiles for devices in the wild in real time. So for example, if users on Android begin complaining about a problem, you can quickly enable logging for that platform to try to determine what's going wrong.\nApplication version management: Control what versions of an application are allowed to be used and even send out notices to users to let them know about updates, service interruptions, etc.\nAPI management: You can proxy requests to remote APIs via MobileFirst giving you more control over API usage in your apps.\nPush notifications for all supported platforms.\nDeep analytics about app usage (device, version, etc).\nA QA and Security scanning service.\nFull web-based portal for all of the above to make it easy to use.\n\nAnd more, of course. If you know me, you know I try to get to the nitty gritty of products as opposed to talking about them at a high level. In order to get up to speed I've been working with MobileFirst specifically in terms of hybrid application.\nMobileFirst uses Cordova itself under the hood for hybrid apps. But you can't (for now) simply take an existing Cordova application and drop it into your MobileFirst project. Nor can you use Cordova tooling. While this may improve in the future, it isn't that difficult to take an existing application and bring it into MobileFirst. In this post I'm going to talk a bit about how to create hybrid apps, how to test them, and take a look at how you would take an existing Cordova application and bring it into MobileFirst.\nCreate the MobileFirst Server/Application\nA full look at installation, usage, etc. for MobileFirst is outside the scope of this document. You can see the Getting Started documentation for a start and peruse the hybrid-specific portions. The docs mainly focus on MobileFirst Platform Studio, an Eclipse plugin for managing MobileFirst projects via the IDE, but I much prefer using the CLI. You can find that download and installation instructions here.\nGiven you've got stuff downloaded and installed, you can fire up a new server and hybrid application quickly:\n\nIn the above screen shot, I create a new MobileFirst server, added a hybrid application, and then enabled support for iPhone and Android. Now let's take a look at the directory structure.\n\nThe folder we care about is the common folder. This is where your HTML, CSS, and JavaScript exists and represents what you'll spend most of your time editing. Right now we've got a basic application, essentially the MobileFirst version of the Cordova skeleton.\nEditing and Testing\nSo given that you now have a folder of web assets, how do we actually test this thing so we can see it in action. Also, what is the process like to edit and update the application?\nOne of the first things you want to do is start the server you created and then open up the console. That's done with mfp start and mfp console.\n\nThe console gives you a variety of options, but the one we care most about is the preview. Clicking the eye icon next to a platform opens up a preview tab.\n\nWhat you've got here is essentially a web browser view of the mobile app with tools to simulate various different events and plugins. Yep, this is much like the Ripple tool. I'm going to talk about this a bit deeper in another post, but for now, let's focus on the editing process. Whenever I edit my common resources, I need to tell MobileFirst about it. This is typically done with two commands: mfp build and mfp deploy, but you can combine these into one nice call: mfp bd. As you can imagine, you could further automate this with a Grunt file watcher to make it automatic.\nWhat about working with the native device, or simulator? Within the app folder, and next to the common folder, is one folder for each native platform you added via mfp add environment. For now, let's consider the iPhone folder:\n\nWithin it is an XCode project. If you double click to open it, you can then send the project to the simulator (or a device).\n\nSo in my testing, my process so far has been: Edit in Brackets, do mfp bd, then simply click the Build/Run button in XCode. You don't have to reopen or refresh the project at all, the command line takes care of that.\nNow - what about using an existing Cordova project? Perhaps an Ionic one? Let's start with a simple one - my Cordova skeleton folder from my Cordova Examples GitHub repo. This project is pretty minimal. It contain",
		"tags":[
	        
            "mobilefirst"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile",
            
                "uncategorized"
            
		]

	},

	{
		"title": "Interesting error reporting difference in Chrome and Firefox",
		"date":"Sat Feb 14 2015 07:03:37 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1423897417,
		"url":"https://www.raymondcamden.com/2015/02/14/interesting-error-reporting-difference-in-chrome-and-firefox",
		"content":"Ok, perhaps &quot;interesting&quot; is a bit of a stretch, but as I'm doing some work while waiting for new tires to be put on my car, my base level for &quot;interesting&quot; is a bit lower than normal. I'm testing some code that I wrote in a Powerpoint slide - code I was sure worked fine but I wanted to be really sure, and I found that I had a typo. Consider the snippet below - you will probably see it right away.\n\n\nWhen I ran this in Firefox, I got: SyntaxError: missing ; before statement test1.html:22.\nLine 22 is the line that adds the li with class short to the variable s. I looked at it and couldn't quite figure out what was wrong.\nI then switched to Chrome and got this error: Uncaught SyntaxError: Unexpected token )\nNow - that made sense! I immediately saw the extra ) I had at the end of my line. (I also had it two lines later.)\nBut what I found interesting is how different the errors were. Both were syntax errors, but the actual detail was quite different. Anyone have an idea as to why this is?\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "A few examples of \"tag as script\" in ColdFusion 11",
		"date":"Wed Feb 11 2015 23:34:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1423697690,
		"url":"https://www.raymondcamden.com/2015/02/12/a-few-examples-of-tag-as-script-in-coldfusion-11",
		"content":"Yesterday I posted a reminder that ColdFusion 11 developers can stop using the old &quot;CFCs for Tags&quot; feature that provided support for certain things in script. I mentioned that everything could be used in cfscript now and that the old CFCs were (sometimes, not always) a bit buggy.\n\nBrad Wood suggested I actually provide an example of this and I thought it was a good idea. I wrote up two quick demos that demonstrate the new syntax. This isn't super deep or anything, but hopefully it gives you a basic idea of how this new syntax looks. First, an example of a few different tags:\n\nMake note of how you can't use a result (ie, foo = goo()) in any of these calls and instead use an argument. That could be easily fixed and should be. Second, note how child tags are handled in the second cfhttp call. I don't have anything to add to it, but as I said, just note the syntax.\nFor the heck of it, I then wrote a second demo. This one takes an array of RSS URLs and uses cfthread to fetch them in parallel. It then merges the resultant queries and sorts them. A mini-aggregator if you will.\n\nI hope this helps!\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Reminder - stop using the old CFC-based tags in ColdFusion 11",
		"date":"Tue Feb 10 2015 23:57:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1423612676,
		"url":"https://www.raymondcamden.com/2015/02/11/reminder-stop-using-the-old-cfc-based-tags-in-coldfusion-11",
		"content":"Earlier last week I was helping someone diagnose a ColdFusion issue when I noticed he was using the old CFC-based tags in cfscript. Specifically:\n\n\nBack around ColdFusion 9 I believe Adobe made CFCs to support a few different tags in cfscript. They were: cfcollection, cfdbinfo, cffeed, cfftp, cfhttp, cfimap, cfindex, cfldap, cfmail, cfpdf, cfpop, cfquery, cfsearch, cfstoredproc, cfstoredprocresult.\nI used a few of these quite a bit, and in general they worked ok, but I ran into bugs from time to time. Surprisingly these CFCs are not encrypted. You can find them in an com.adobe.coldfusion folder under your server's CustomTags folder. Because they weren't encrypted, I actually wrote my own fixes from time to time.\nBut the point is - in ColdFusion 11, you should not be using any of these, and should be using the built-in support for tags in script. The documentation for this may be a bit hard to find. The feature as a whole may be found here: Script support for tags, but as far as I know, none of the wiki pages shows examples of this in regards to individual tags.\nAdam Cameron does a good write up about some of the mistakes in Adobe's implementation. I don't agree they are quite as bad as he points out, but I strongly agree that &quot;cf&quot; should have been removed from the calls and the need to pass in a result argument should have been fixed. Maybe ColdFusion 12 will correct that. (Or Luccee. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Possible Cordova FileSystem Resource",
		"date":"Tue Feb 10 2015 02:48:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1423536499,
		"url":"https://www.raymondcamden.com/2015/02/10/possible-cordova-filesystem-resource",
		"content":" I say &quot;possible&quot; because I haven't read it myself, but a reader pointed me to this book by Eric Bidelman, &quot;Using the HTML5 Filesystem API&quot;. It is nearly four years old, but the reader said it helped him, and I know folks are desperate for more help with the FileSystem so I thought I'd pass on the recommendation. There is a free chapter you can sample if you want to check before you buy it. If any of my readers get this, or have read it, I'd love to hear your thoughts below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using the new Bluemix Visual Recognition service in Cordova",
		"date":"Fri Feb 06 2015 05:11:41 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1423199501,
		"url":"https://www.raymondcamden.com/2015/02/06/using-the-new-bluemix-visual-recognition-service-in-cordova",
		"content":"Before I begin, a quick disclaimer. I've been at IBM for a grand total of five days. Considering three were taken up by travel and orientation, I'm very much the new kid on the block here. I've only begun to look into MobileFirst and Bluemix so you should take what I show here with the same confidence you would give anyone using a new technology for two days. In other words - proceed with caution! ;)\n\nBluemix is a cloud platform that offers both Platform as a Service (PaaS) and MBaaS (Mobile Backend as a Service). The PaaS offerings let you deploy web applications using a variety of engines, including Node. I'm currently use AppFog as my PaaS for a few sites so I'm already a bit familiar with the concept. The MBaaS side are services that integrate with your web or mobile apps. These include things like NoSQL datastorage and access to IBM Watson.\nYesterday we announced five new services that make use of the Watson. They include: Speech to Text and Text to Speech, Visual Recognition, Concept Insights, and Tradeoff Analytics.\nAs soon as I saw the Visual Recognition API (demo link) I thought - this would be cool in Cordova!\nOne thing I wasn't sure of though was how to use the service by itself, and not with a particular application. I checked the docs and came across this Enabling external applications and third-party tools to use Bluemix services:\n\nYou might have applications that were created and run outside of Bluemix, or you might use third-party tools. If Bluemix services provide endpoints that are accessible from the internet, you can use those services with your local apps or third-party tools.\nTo enable an external application or third-party tool to use a Bluemix service, complete the following steps:\n\nRequest an instance of the service for an existing Bluemix application. The credentials and connection parameters for the service are created. For more information about requesting a service instance, see Requesting a new service instance.\nRetrieve the credentials and the connection parameters of the service instance from the VCAP_SERVICES environment variable of the Bluemix application.\nSpecify the credentials and the connection parameters in your external application or third-party tool.\n\n\nMy understanding of that was that I needed to make a new Node application and simply create a view that would dump out VCAP_SERVICES. I wished it were easier, but I figured if I did this once, I could reuse the same Node code in the future when I needed to test out another service. Turns out I didn't need to do all those steps. This next section will focus on what you have to do on the Bluemix side to prepare the service. I'll then switch to the Cordova side.\nAfter signing up for Bluemix (you can get a free, 30 day trial), you want to first create an application. You won't actually be using the application, but it is necessary to get the proper credentials to use the service.\n\nOn the next screen I selected Web. This may be something I change next time I do this.\n\nAnd then I selected the Node option. Since I really wasn't using the app, I probably could have selected &quot;I Have Code Already.&quot;\n\nFor the application name, pick anything. If you pick &quot;RayIsTheBestNewIBMer&quot; you get double the time for your free trial. (Note - the preceding statement may not be exactly true.)\n\nOn the next screen, select Add a Service:\n\nAnd then - obviously - select the Visual Recognition service. Note the beta label. Results may vary. Yada, yada, yada.\n\nSince you added this service from an app, it will be automatically selected in the next screen. (You may not have a &quot;Space&quot; though. I made a few while testing and I don't remember if it is required or not.)\n\nYou'll get a warning about needing to restage the application, but since you don't have anything there anyway you can just go ahead and let it restart the app. Woot - almost done. Now we need the authentication and API info. Back on the app dashboard, note there is a link to show credentials for the service:\n\nClicking that will expose properties for the service including the ones we care about: url, username, and password:\n\nOk, we've got what we came for, let's talk about the Cordova side. I began by creating a simple application that would make use of the Camera API. I created a web page with two buttons - one to source from the device camera and one from the photo gallery. Once I had the image file, I would then make use of the File Transfer plugin to post to the API.\nDocumentation for the Visual Recognition API may be found here: http://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/apis/#!/visual-recognition/. I focused my attention on the POST call to /v1/tag/recognize. I saw there that I needed to send the image with a file name of img_file. Let's take a look at the code (this portion and the rest may be found in the Github URL I'll share towards the end):\n\nI assume the camera usage is not necessarily new for my readers. All I do &quot;fancy&quot;",
		"tags":[
	        
            "bluemix"
            
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Can't run ColdFusion Builder 3 on OS X? Read This.",
		"date":"Tue Feb 03 2015 09:08:50 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422954530,
		"url":"https://www.raymondcamden.com/2015/02/03/cant-run-coldfusion-builder-3-on-osx-read-this",
		"content":"I'm working on a new machine and as part of my install process discovered I couldn't run ColdFusion Builder 3. It had installed just fine, but running it gave me:\n\n\nI kinda remembered this from before, but when I clicked the link I was taken to an obviously abandoned, broken page on Apple.com:\n\nNothing on that page worked so I was pretty much stuck. I reached out to Adobe (still kinda weird to think of them as someone I don't work for) and got help from Krishna Reddy on the ColdFusion team. He acknowledged this appears to be an issue and shared the right URL for downloading the proper Java bits: Java for OS X 2014-001.\nHopefully this becomes a non-issue for CFB going forward.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Front-End Interview Questions – Part 4",
		"date":"Tue Feb 03 2015 00:46:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422924371,
		"url":"https://www.raymondcamden.com/2015/02/03/front-end-interview-questions-part-4",
		"content":"This post is part of a series I’m writing where I attempt to answer, to the best of my ability, a set of Front-End developer questions. I expect/hope my readers will disagree, augment, and generally hash out my answers in the comments below.\nCan you describe the difference between progressive enhancement and graceful degradation?\nProgressive enhancement refers to a feature of your web site that enhances the experience for browsers that support it, but has no impact if your browser does not. As an example - consider IndexedDB. It lets you store data in a client-side database. If my web site made use of Ajax to load a large amount of data, I could use IndexedDB to cache data. If your browser doesn't support IndexedDB, then we just do Ajax to load that data. Modern browsers get a benefit, older browsers don't get screwed.\nOn the flip side, graceful degradation takes that modern feature and acts as if it is the default, but will not completely break the older browser. So in this case, the use of IndexedDB would be considered &quot;normal&quot;, not the additional benefit for the modern browser.\nIn my opinion, it comes down to one side where you build for a certain base and provide additional, not-required features for better browsers (progressive enhancement) versus building for the more modern browser that assumes certain features, but assuring they &quot;break&quot; well.\nHow would you optimize a website's assets/resources?\nI can think of a few things here.\nFirst - I'd optimize my images. In the past, I've used a Grunt script for this which makes it brain dead easy. You can point to a set of images, specify an optimization level, run it, and check the output to ensure it still looks good.\nAnother option for images is to use CSS sprites for related icons and the such. That reduces the network requests.\nYou can use a CDN that supports knowing and responding to a user's location to better serve up the assets.\nFor text files, you can minify, both CSS and JavaScript. For CSS, you can remove unused CSS (https://github.com/addyosmani/grunt-uncss).\nI'm sure I missed a few more options here.\nHow many resources will a browser download from a given domain at a time?\nI'm going to answer this before I check Google to confirm: 8.\nLooks like I was wrong - kinda. I figured there was some variation per browser, but check this StackOverflow answer: http://stackoverflow.com/a/14768266/52160. For Chrome it is 6, Firefox 6, IE11 is 8 though. I'd assume 6 then as a good average. Here is another good table on this stat: http://www.browserscope.org/?category=network&amp;v=1\nWhat are the exceptions?\nSo the limits are per domain, so in theory, you could use a wildcard DNS where *.foo.com points to the same IP address. Seems like something I'd use with caution though. I think if you are at this point (of trying to get around the limit) then maybe you are doing a bit too much.\n",
		"tags":[
	        
            "front end interview questions"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Missing orders, Prime membership, Cloud music, etc with Amazon?",
		"date":"Mon Feb 02 2015 10:18:10 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422872290,
		"url":"https://www.raymondcamden.com/2015/02/02/missing-orders-prime-membership-cloud-music-etc-with-amazon",
		"content":"So, here's a scary incident. I logged into Amazon to access my Cloud Music library (big fan of the service) and noticed it was empty. I checked my profile and noticed that my past orders were also empty. Even more scary, I had somehow lost Prime membership. I saw this for the first time a few weeks back but never figured out how I fixed it. Not knowing what to do, I contacted Amazon via their email support, and, kudos to them, got a response in less than five minutes. But the response... oh my god:\n\n\nI'm sorry to hear about the trouble you had accessing your accounts. After some research, I've confirmed that you have four (4) Amazon.com accounts with the same e-mail address and different passwords. \nThis can happen if you've visited Amazon.com in the past and mistakenly indicated that you're a new customer when signing in. A new account with the same e-mail address and a different password would be created.\netc\n\nSo just to be clear: Amazon lets you register the same email with another password. For the life of me I cannot imagine in what universe allowing this makes sense. Perhaps they want to make it as easy as possible for users to buy stuff, but it seems like this would bite people in the ass later on - as it did me.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Lucee, new fork of Railo, has launched",
		"date":"Sun Feb 01 2015 02:43:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422758612,
		"url":"https://www.raymondcamden.com/2015/02/01/lucee-new-fork-of-railo-has-launched",
		"content":"This is definitely not new news, but I wanted to share it with folks who may have missed the myriad other posts as well. As the title says, Lucee was launched as a fork of the Railo project. I haven't used Railo much (typically only when a client requires it), so I don't follow that &quot;world&quot; much, but if you want to know more about the why of it I encourage you to read Adam's announcement post where he has a FAQ about the launch of the whole thing.\n\n\nYou can find out more, and download the bits, at the main site: http://lucee.org/.\nI played with it for a grand total of 10 minutes, just to see how well it works. You can currently download an &quot;Express&quot; edition that allows for unzip and run. If you are like me and a bit unfamiliar with how Railo did things, here are some tips:\nFirst, the port that Lucee will run on is 8888. I'm sure this is documented in some XML file some place but it took me a few minutes to find it. I already logged an ER with them to make the startup script actually say this when it runs.\nSecond, you can find docs at localhost:8888/lucee/doc/.\nIf you want to write CFMs and test them, go to the installation folder, then webapps/ROOT. This works just like Adobe ColdFusion - write a CFM, save it, open it in your browser.\nSo that all involves running it locally, how about finding out more about the project?\nAs I said above, the main site is:\nThe forum for Lucee may be found here: http://discourse.lucee.org/\nThe source (and bug tracker) for Lucee may be found here: https://bitbucket.org/lucee/lucee\nThe wiki for Lucee may be found here: https://bitbucket.org/lucee/lucee/wiki/Home\nAll in all, an interesting development. Any of my readers playing around with this yet?\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Thank you, Jack Wilber",
		"date":"Fri Jan 30 2015 08:30:59 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422606659,
		"url":"https://www.raymondcamden.com/2015/01/30/thank-you-jack-wilber",
		"content":"As I prepare to leave Adobe, I want to acknowledge the enormous amount of help I had from Jack Wilber. One of the Adobe benefits I had was the use of an editor, and Jack has been helping improve my blog posts over the past few years. He never once complained about my constant misuse of its and it's or than and then. I'm double checking this post as well to ensure I didn't miss anything, but most likely, I did.\nThanks Jack, you're awesome.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Latest Firefox adds Storage to Developer Tools",
		"date":"Thu Jan 29 2015 23:59:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422575993,
		"url":"https://www.raymondcamden.com/2015/01/30/latest-firefox-adds-storage-to-developer-tools",
		"content":"I try to keep track of what all the browsers add to their developer tools, but somehow I missed this. In a recent Firefox update, they finally added a &quot;Storage&quot; tab to the developer tools. For some reason, this tab was deselected in my Firefox. I only knew it existed when I opened up the options looking for something else.\n\n\nOnce added, and selected, you will then get the ability to view cookies, IndexedDB databases, and Local and Session Storage. (No WebSQL since Firefox doesn't support it.)\n\nExpanding the IDB section, you can browse the structure of local databases:\n\nAnd when you click an object store, you will see a list of data. If you click one row, you get details:\n\nThis will be very handy for folks working with local data. (And reminds me I need to give my presentation on this topic again!)\np.s. The title of this post says it was the &quot;latest&quot; Firefox that added this but honestly I may have missed it from an earlier update.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Suggestions for learning JSON?",
		"date":"Thu Jan 29 2015 10:34:54 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422527694,
		"url":"https://www.raymondcamden.com/2015/01/29/suggestions-for-learning-json",
		"content":"Yesterday a reader asked me a question about one of my blog posts. In the blog post, I described a Cordova application that would retrieve a list of URLs from a server. It would then fetch each URL and download the resource to the device. I casually described the data as &quot;an array of URLs in JSON&quot; and assumed everyone would know what that meant. Of course, not everyone does know what JSON means.\n\nIt got me thinking about how someone would learn more about JSON and become more familiar with it. You can start over at json.org for a formal definition, but I found the Wikipedia page, specifically the syntax example here, to be a bit simpler to digest.\nAt it's heart, JSON is a string format that can represent data. Just like XML, it can take abstract data structures and represent them in a string format that can be sent over the wire. That makes it specifically well suited for APIs. JavaScript has native support for working with it as do loads of other languages (including ColdFusion). Compared to XML, JSON is much slimmer and just as easy to read.\nJSON represents simple values, like strings and numbers, just as they are, so &quot;ray&quot; in JSON is... &quot;ray&quot;, and 6 is 6.\nArrays are represented by using brackets and a comma between each item: [&quot;ray&quot;, 6, &quot;i am not a number&quot;, &quot;etc&quot;]\nObjects (ColdFusion folks - think structures) are represented by curly brackets. Each name/value pair is represented by name, colon, value. So: { name:&quot;Ray&quot;, age:41, gender:&quot;awesome&quot;}.\nWorking with JSON in JavaScript is relatively trivial. If you use jQuery to make an XHR request then you can simply tell jQuery that a URL returns JSON and you've get a native JavaScript object to work with. Modern browsers also ship with support to create JSON (stringify) and read it (parse). You can read more about that at the excellent MDN docs: JSON.\nKnowing the function that creates JSON, a nice way to play with it is to open your browser console and just try a few things out.\n\nAny other suggestions? On a whim, I asked on Twitter how folks learned or were introduced to JSON. I got some fun responses (and hopefully this works, the preview doesn't show replies, but they should be showing up):\n@raymondcamden Coming from JavaScript, when I first saw JSON, I was like, “Oh, it’s JavaScript” :) Which, it’s not. But, made if familiar.&mdash; Ben Nadel (@BenNadel) January 29, 2015\n\n@raymondcamden first heard about JSON at CF meetup in Phx @nathanstrutz (I think). Took some time for comfort, just over time with use.&mdash; Brandon Moser (@brandonmoser) January 29, 2015\n\n@raymondcamden same as @bennadel. Came from a Javascript bg, so it was easy to understand the structure. JSON looks close to js ojects.&mdash; Sean Thompson (@ImSeanThompson) January 29, 2015\n\n@raymondcamden When it was added to the Adobe runtimes, of course ;)&mdash; Joseph Labrecque (@JosephLabrecque) January 29, 2015\n\n@raymondcamden I adopted JSON shortly after learning of it. It just made sense to xfer data in similar syntax to the objects I was coding.&mdash; Eric Leonard (@TheHeretical) January 29, 2015\n\n@raymondcamden Learned about JSON from REST interfaces. Loved it right away for its terseness and ease of use.&mdash; Robert Munn (@robertdmunn) January 29, 2015\n\n@raymondcamden RE: JSON, I was working with a new API and it was like love at first sight. So much easier and faster than XML&mdash; Jon Clausen (@jclausen) January 29, 2015\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Front-End Interview Questions – Part 3",
		"date":"Wed Jan 28 2015 11:01:02 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422442862,
		"url":"https://www.raymondcamden.com/2015/01/28/front-end-interview-questions-part-3",
		"content":"This post is part of a series I’m writing where I attempt to answer, to the best of my ability, a set of Front-End developer questions. I expect/hope my readers will disagree, augment, and generally hash out my answers in the comments below.\nTalk about your preferred development environment. (OS, Editor or IDE, Browsers, Tools, etc.)\nI covered this pretty deeply in a blog post from late last year (My Cordova/PhoneGap Developer Setup (Fall 2014)). While that post was focused on Cordova development, it pretty much covers my development setup in general. I don’t think I have anything to add to that list so I’ll carry on.\nWhich version control systems are you familiar with?\nGit, although I suck at branches and merging. I’ve made a note in Evernote that lists different types of Git commands based on what I’m trying to achieve. I’d like to be a bit better at Git, but my current level of skill hasn't really hurt me so far. When I have some free time, I plan on taking the Code School Git course as I absolutely love Code School.\nI can use SVN as well, but since I use it so rarely I keep a note around to help me remember the basics.\nI have some experience with Perforce. I’ll use it if you put a gun to my head.\nAnd yes - you will all laugh at me - but I used to love Visual SourceSafe. It was simple, easy to use, and I never had data corruption with it.\nCan you describe your workflow when you create a web page?\nThat’s a bit open-ended. In my editor, I use snippets so I can quickly lay out a simple HTML page, quickly add jQuery if I need it, etc. Most of my demos are JavaScript-based so I’ll normally:\n\nMake a new folder for my test.\nMake an index.html file and drop in the snippet.\nMake an app.js and start coding.\n\nThat’s it. I don’t use Yeoman as I find it super heavy for quick one-offs. Ditto for Bower. My issue with these tools is that they tend to add a huge amount of files. If I were building a proper &quot;project&quot;, i.e. something I’d be working on for a month, it may make sense, but for my blog posts, presentations, etc, they are way overkill. I actually built my own tool for Brackets to quickly download JavaScript frameworks with a simple right click. It only downloads the core files needed to use them because - that’s all I want to do - use them. (Maybe I’m crazy that way. ;)\nIf you have 5 different stylesheets, how would you best integrate them into the site?\nOh, this is a good one. Keeping in mind I try to avoid CSS and just use a library (like Bootstrap), it seems to me I’d do the following:\nIf the 5 style sheets were all application specific, I'd combine them into one file. If they were a combination of application specific style sheets and libraries (like Bootstrap), I'd only combine the application specific ones. I’d want Bootstrap (or whatever) separate to ensure it is easy to update in the future.\nI never really thought much about &quot;organization&quot; of my CSS because I rarely write much of it. I’d run CSSLint against it, but I don’t think CSSLint deals with &quot;organization&quot; of a large sheet. I’d also run a tool to find unused css, like uncss.\n",
		"tags":[
	        
            "front end interview questions"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "An example of application specific scheduled tasks in ColdFusion",
		"date":"Tue Jan 27 2015 10:11:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422353479,
		"url":"https://www.raymondcamden.com/2015/01/27/an-example-of-application-specific-scheduled-tasks-in-coldfusion",
		"content":"Yesterday I blogged about my migration of ColdFusion Bloggers from ColdFusion to Node. During that conversion, I was impressed by how easy it was to set up a scheduled task. (Once I got past the vagaries of the CRON syntax.) I remembered that ColdFusion 10 had added some dramatic improvements to scheduled tasks. You can read a high level review of that here: Using Scheduler. While I knew about these improvements, I never actually got around to playing with them. Last night I did - and here's what I found.\n\nI decided that I would play with two features that were new to scheduled tasks in ColdFusion 10:\n\nApplication-specific tasks\nCFC based tasks (instead of URLs)\n\nI built the following Application.cfc as a demo:\n\nI used onApplicationStart as my event registration place. Technically it doesn't need to be there, but that made the most sense. I just had to add a quick url &quot;hack&quot; in onRequestStart to give me a quick way to rerun it properly.\nSetting the task as application specific was as simple as mode=&quot;application&quot;. When you do this, the task still shows up in the ColdFusion Administrator, but if another application uses the LIST feature of cfschedule, it won't see your task. And obviously - two different applications can have the same task name. Here is a screen shot showing how the CF Admin differentiates between the different types of tasks.\n\nThat part was rather simple, but then I found myself fighting the somewhat poorly documented rules for creating tasks. So I wanted a daily task. I forgot, though, that ColdFusion requires a start date for such a task. In my mind it should just start tomorrow if not specified. What's weird is that if you leave off startdate, you don't get an error. Instead, the task was registered as a chained task. I highly recommend keeping the CF Admin open as you test creating scheduled tasks from code. If it looks funky in the Admin then you've done something wrong.\nI then tried using a CFC for my scheduled task. At the time I played with it, the docs were very unclear about how to use it. They simply said that the CFC must implement ITaskEventHandler. I did some digging and discovered that this is actually in the CFIDE/scheduler folder. I edited the official docs wiki to include the full path. Here is a bare bones CFC that uses the interface:\n\nThat argument, context, is not documented for the execute method, but is documented for the others. It contains the group (another new feature in ColdFusion 10), the task name, and the mode. As you can guess, this means you could do some code sharing between similar tasks. So you could have tasks like so: petCat, petDog, petDragon, etc, all using the same CFC and simply inspect the name value to determine what to do. That's similar I suppose to using one CFM for N tasks and passing different URL arguments.\nAll in all - outside of the weirdness of arguments for defining tasks - I really like this improvement. Anyone using them?\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Front-End Interview Questions - Part 2",
		"date":"Tue Jan 27 2015 02:55:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422327331,
		"url":"https://www.raymondcamden.com/2015/01/27/front-end-interview-questions-part-2",
		"content":"This post is part of a series I'm writing where I attempt to answer, to the best of my ability, a set of Front-End developer questions. I expect/hope my readers will disagree, augment, and generally hash out my answers in the comments below.\nWhat did you learn yesterday/this week?\nNot that it is terribly important per se, but I learned that in some browsers, you can drag and drop a file onto an input/file field. I thought that was cool. It also got me thinking about what other browser features I may be missing out on. I tend to focus on what the browser supports in terms of HTML/JS/CSS, but in regards to UX like this (that’s how I’d classify it), I wonder how much stuff I’m missing (and how much “casual” users are aware of).\nWhat excites or interests you about coding?\nMainly figuring things out - although a lot of times it is simply seeing my input converted into actions on the computer. That sounds lame, or, well, what all programming does, but it is honest as well. I remember my first experience with coding and the joy I felt when I actually got the computer (Apple 2e FTW!) to do what I wanted. That joy was like a drug I’ve not been able to shake off since then.\nI’d say I get more excitement though from sharing with others. That’s why I write on this blog and do presentations.\nWhat is a recent technical challenge you experienced and how did you solve it?\nI wish it were more exciting, but, I’m dealing with moving a large Mongo database from my local machine to a remote server. I had to learn how to make backups and how to restore. I also had to deal with timeout issues. How did I solve it? I Googled. But in order to be sure I could get to the answers easier next time, I stored the command line calls in Evernote. I tend to have pretty crappy memory, especially with CLIs, so I use Evernote as a reference for things like this.\nWhat UI, Security, Performance, SEO, Maintainability or Technology considerations do you make while building a web application or site?\nAh, a big one. :) Let me try to break this down a bit.\nIn terms of UI, as I’m mostly doing small POCs (proof of concepts), I try to keep things as simple as possible. If I need things to look nice quickly, I’ll use Bootstrap. If I’m building something in Cordova and it will be a medium to large demo, I’ll use Ionic.\nIn terms of security, it really depends. Almost all of my work is client-side now, and I know that you can’t trust anything from the client when speaking to a server. So I know, for example, that if I’ve built a mobile app that stores data on the server, I have to employ authentication/authorization rules on the client and on the server, and I damn well better get it right on the server. One of the things I do when playing with a cool front end web app is open up the dev tools and look at the network requests being made. I’ll then - sometimes - open up those requests in other tabs and see how well things are locked down. I'll combine this with curl in the terminal if I really feel like testing out an API.\nIn terms of performance, guess what - it depends. :) I’ll be honest and say that performance of client-side code is not something I’ve explored deeply yet. I’m certainly aware of the tools browser vendors provide in this area and I know where to begin if I'm seeing &quot;jank&quot; in my front-end code. Maybe I’m biased, but this is exactly the kind of answer I’d hope from someone about this. I.e., &quot;I don't know, but I know the tools exist, where to find it, and how to start Googling to dig deeper.&quot;\nSEO - nope - I don’t care. :) Ok, I guess I do care, but, it isn’t something I think about. (Maybe I need to more. There is a SEO plugin for WordPress that a friend recommended to me.)\nMaintainability - I suppose this falls into basic things I’ve been aware of since I started my career. Proper documentation, clean code, source control to help keep track of what’s changed, using a bug tracker to help manage requests versus email, etc. I’m not sure how well this is taught now. I don’t remember it being taught back when I was in comp sci, but that was decades ago. I think I just found something to blog deeper about later on.\nTechnology - This part of the question is a bit open ended. At the end of the day, whether I use ColdFusion or Node or - heck - Perl CGI - I want to ensure my technology stack performs well, can be secured, and is generally stable. I’d think most of the common stacks out there match those criteria so it comes down to what is best for the team developing the project. On the client-side, the biggest thing that comes to mind here is progressive enhancement. Given technology X that may only be supported on 10% of browsers, you can add it to your project in such a way that the other 90% are not impacted by the lack of said technology.\n",
		"tags":[
	        
            "front end interview questions"
            
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "ColdFusion Builder 3 Updated (the right way)",
		"date":"Mon Jan 26 2015 10:34:17 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422268457,
		"url":"https://www.raymondcamden.com/2015/01/26/coldfusion-builder-3-updated-the-right-way",
		"content":"About two weeks ago I blogged about how Adobe had updated ColdFusion Builder but had - oddly - not used the regular Eclipse update mechnism to distribute the update. Instead the expectation was that users should redownload the entire product again.\nWhile I was happy as heck to see installers updated (that makes your post-installation experience a bit nicer imo), I couldn't understand why they didn't release an updater.\nWell I'm happy to say Adobe released the update the right way. You can now update your Builder directly in the editor itself. To be honest, none of the bug fixes were things that impacted me, but you should check the blog post to see if they impact you.\nOn the off chance you may not know how to check for updates in Eclipse (come on, Eclipse is simple, right?), just go to the Help menu and select Check for Updates.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "ColdFusion Bloggers migrated to Node.js",
		"date":"Mon Jan 26 2015 05:02:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1422248573,
		"url":"https://www.raymondcamden.com/2015/01/26/coldfusion-bloggers-migrated-to-node-js",
		"content":"Yes, I did it again. If Adobe ever kills ColdFusion you can blame me. ;) This is just an FYI to let folks know I've rewritten ColdFusion Bloggers as a Node.js site running on the AppFog platform. To be clear, no, I'm not trying to kill ColdFusion! I'm migrated off my old ColdFusion server and setting up my old sites in a simpler form because - well - I want my life to be simpler. My only real &quot;server&quot; will be this blog, and as I'm still adjusting the settings a bit and tuning WordPress, I want every other thing I run to be as simple and low-maintenance as possible. Plus - I also kinda want to get better at Node.js!\n\nAs before, if folks are curious about the code, I've put it up on GitHub for you to look at and laugh at: https://github.com/cfjedimaster/nodecfbloggers. To be clear, this is not meant to be an example of good Node.js programming. It is just meant to be... well.. an example. (And let me publicly thank Derick Bailey. After I posted about the CFLib migration, he shared some online training he created with me. I haven't had a chance to check it out yet, but I definitely appreciate him sharing his knowledge with me.)\nFor the most part the conversion was simple. As with CFLib, I wrote a script in ColdFusion that used CFMongoDB to insert the data into Mongo. For folks curious as to how that looked, here is the script.\n\nI then went about rebuilding the functionality with Node. I removed quite a bit - including all user management. I didn't have many users and the main functionality (alerts for keywords) can easily be done with IFTTT. I had an alert for my name that I've already moved over there. If folks need help with it, let me know. I also ripped out the jQuery UI, removed the Ajax page loading, and just simplified as much as possible.\nTotal side note: The better I get at front end stuff and server stuff - the more I want to make things as simple as possible. Am I alone in that?\nDuring the rewrite there were two really interesting parts I enjoyed writing. First, here is how I handled running the aggregation every hour:\n\nEven though I find the cron format confusing as hell, I love how simple that is. For folks not aware, don't forget ColdFusion 11 also lets you define scheduled tasks for an application. (I'll blog an example of that later this week. I haven't tried it yet and I want to set up a good demo.) You can read more about this Node module here: https://github.com/ncb000gt/node-cron\nThe next part I enjoyed was parsing RSS feeds. I used Node-Feedparser, which worked incredibly well. I had assumed that part of the rewrite was going to take me a few hours, but I finished in less than one. To be fair, it isn't exactly like the ColdFusion version. I'm not doing the conditional HTTP get, but with far fewer blogs to parse nowadays I'm not as concerned about it. On the flip side, cffeed doesn't let you parse RSS from pure data so I had to use the file system in the ColdFusion version. That's not something I had to worry about here.\nOh, and once again, I used FormKeep to handle my form. It works. It's simple. And it's free.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "javascript"
            
		]

	},

	{
		"title": "Front-End Interview Questions",
		"date":"Thu Jan 22 2015 05:29:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421904571,
		"url":"https://www.raymondcamden.com/2015/01/22/front-end-interview-questions",
		"content":"A few days ago, either someone tweeted or someone shared with me an interesting document: Front-end Developer Interview Questions. This document (well, Git repo really) is a large set of questions that could be appropriate for interviewing someone for the role of a front-end developer. While this was new to me, apparently this document started way back in 2009 (did we even have browsers then?) and has had contributions from quite a few people.\n\nI've recently gone through a few interviews myself. I've discovered that I'm quite bad at it. Like probably some of you, the second someone asks me a question during a job interview I tend to freeze up. If I'm giving a presentation then it typically doesn't really bother me. If I don't know the answer, it is an excellent opportunity to show people what I do when I don't know the answer. (Which, frankly, is more important than rote knowledge imho.) While I'm not interviewing anymore (hello IBM!), it bothered me that I performed badly. It also bothered me that there were quite a few things on this list that I felt I didn't have a firm grasp on. I'm not ashamed to reveal my ignorance to my blog readers. (Not too ashamed.) Here are a few things that I would have struggled with in a job interview. To be clear, for some of these I think I know the answer, but my confidence in sounding intelligent when answering them would have been low.\n\nWhat are the limitations when serving XHTML pages?\nAre there any problems with serving pages as application/xhtml+xml?\nHow do you serve a page with content in multiple languages?\nWhat are the different ways to visually hide content (and make it available only for screen readers)?\nAny familiarity with styling SVG?\nWhat are some of the \"gotchas\" for writing efficient CSS?\nWhat's the difference between inline and inline-block?\nExplain how this works in JavaScript (Yes, I admit this. I think I know it, but I don't think I have a 100% lock down on the answer here and I want a 100% lock down.)\nAMD vs. CommonJS?\nWhat's the difference between host objects and native objects?\n\nSo here's an idea. A challenge - for myself really - but open to my readers. Would it be useful to go through all of the questions, a few at a time, and write up my blog entries on what my answer would be? I don't think this is something I could do quickly, I'm thinking this could be a year long project, and heck, maybe I'll give up, but there are topics on that list that I want a firmer understanding of and I think my readers could help flesh out my own misunderstandings.\nPhoto credit: Question mark by ed_needs_a_bicycle\n",
		"tags":[
	        
            "front end interview questions"
            
		],
		"categories":[
            
                "design",
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Drag and Drop on File Inputs in HTML",
		"date":"Wed Jan 21 2015 06:50:36 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421823036,
		"url":"https://www.raymondcamden.com/2015/01/21/drag-and-drop-on-file-inputs-in-html",
		"content":"A big thank you to Sara Soueidan for sharing this on Twitter. I've worked with a few web apps that allow for drag and drop file uploads and when it works well, it is really handy. In fact, being able to drag and drop an image onto my Wordpress editor is one of the things I'm most happy about since my migration. But did you know that you can drag and drop a file onto a regular HTML input file field?\n\nAs Sara discovered, and I verified, you absolutely can. It works in Chrome, Firefox, and Safari. I confirmed it myself in Chrome and Firefox. I tested IE11 and - unfortunately - it doesn't work. Both Chrome and Firefox will also support dragging multiple files if you include the multiple attribute in the input field.\nAs I said, I didn't test Safari, but Chrome also provides UI feedback during the drag that makes this feature a bit more obvious:\n\nNotice how the button becomes highlighted. So - quick show of hands - how many of you knew your browser supported this?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "PhoneGap/Cordova Tip: Working with files under www and Android",
		"date":"Wed Jan 21 2015 04:05:24 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421813124,
		"url":"https://www.raymondcamden.com/2015/01/21/phonegapcordova-tip-working-with-files-under-www-and-android",
		"content":"This is a topic that has come up a few times in comments recently but I wanted to post something a bit more explicit. First and foremost, you cannot use the File system APIs to work with files under the www folder. The docs for the File plugin incorrectly states that you have Read access to the application directory (which would contain www) but that is incorrect.\nYou can use XHR to read in files from under www. For text files this is rather trivial. For binary data you want to be careful before reading in large amounts of data. Remember that you can work with binary data via Ajax using XHR2 (spec and support levels).\nFinally - one problem you may run into is supporting a dynamic list of files. Since you can't read the directory, if you want to support a random set of assets under www then you would need to ship a file that contains a list of those resources. You would then do an XHR to that file, get the list, and process as you see it.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Alt-History on TV - The Man in the High Castle",
		"date":"Sun Jan 18 2015 04:14:13 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421554453,
		"url":"https://www.raymondcamden.com/2015/01/18/alt-history-on-tv-the-man-in-the-high-castle",
		"content":"As readers know, I am a huge fan of alt-history. It has been a while since we've had any on TV (earlier seasons of Fringe), but I was stoked to discover this week that Amazon Prime has created a new adaptation of Philip K Dick's famous story, &quot;The Man in the High Castle.&quot; While one of the more common alt-history types (Nazi Germany wins WWII), the story is quite fascinating and well worth a read. If you have Amazon Prime, you can watch the premier now.\n\nFrom the beginning, I loved it. The title sequence (and sorry, this was the best quality I could find) is both eerie and cool at the same time.\n\nThe story takes place in a America that is split between Japan and Germany with a neutral zone between them. The world-building is done very well.\n\nI found myself pausing multiple times just so I could see all the peculiarities of Nazi/Japanese America.\n\nSome of the best parts though were more subtle. As one of the main characters crosses the country, you get snippets of this America that are chilling.\nRight now it is just a pilot and I'm not sure if Amazon will pick it up for a series but I sure hope they do. (And hey, maybe we can do a Kickstarter for a new Sliders while we're at it?)\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Lame, client-side 404 handling with Amazon S3",
		"date":"Sat Jan 17 2015 03:18:30 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421464710,
		"url":"https://www.raymondcamden.com/2015/01/17/lame-client-side-404-handling-with-amazon-s3",
		"content":"Yes, I'm calling this lame, consider that a warning. About a year or so ago I migrated ColdFusion Cookbook to a static site using HarpJS. While doing this conversion, I changed my URL for entries from:\n\n/entry/XX/NNNN (where XX is a database primary key and NNNN is a URL friendly text version of the title)\nto\n/entries/NNNN (where NNNN is the URL friendly text version of the title)\nUnfortunately, I could not create a redirect for this because Amazon S3 only supports static redirects and maxes out at 50. You can't use a regex at all with their system. I kinda figured Google would take care of (and from what I can see, searches definitely turn up the right URLs) but this doesn't handle existing links. A reader emailed me about just that issue yesterday. I told him what I said here - I couldn't really do anything about it* but I wondered if I could write some simple JavaScript code to attempt to help out with this.\nI added the following code snippet to my 404 page:\n\nTaking it piece by piece, I begin by checking the current URL location, specifically the pathname. If I see /entry in the request, I then split it and count the pieces. My old URL syntax should return four pieces, and if so, I assume it is of the old format. I could be more anal here and check the second part to see if it is a number, but that felt like overkill.\nI then simply generate a new URL and append it to the 404 page as a suggestion. I specifically decided against auto-redirecting as I thought the 'flash' of the 404 page would be annoying and seem like a bug. My code could also check to see if document.querySelector actually exists, or even simpler, use document.getElementById.\nYou can see an example of this here: http://www.coldfusioncookbook.com/entry/53/How-do-I-determine-if-a-number-is-even-or-odd.\nAs I said, this is lame, but I figure it is better than nothing. I wish S3 would support regex URLs, but for the price I pay (I'm averaging about 4 cents a month for my static sites there) I really can't complain.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "jamstack"
            
		]

	},

	{
		"title": "New Year, New Job",
		"date":"Fri Jan 16 2015 08:16:47 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421396207,
		"url":"https://www.raymondcamden.com/2015/01/16/new-year-new-job",
		"content":"I've been leaving hints the last few weeks that this was coming, but today I can finally share with all of you. When January ends I will be leaving Adobe. I joined Adobe a few years back as an evangelist, something that had been a dream of mine for nearly a decade. I joined a team of really smart and talented people. I had no problem considering myself the worst person there. That wasn't a slight on me at all but merely how damn good the team was. I joined Adobe at an interesting time. Not long after they begun a serious push into web standards. I saw some amazing tools get created (Brackets, Edge Animate, Inspect) and something I never thought I'd see - folks mentioning Adobe at web conferences. Sure it may have been, &quot;Can you believe Adobe is doing this?&quot; but that was perfectly fine with me.\n\nUnfortunately, things change, and developer evangelism ceased to exist. I've got my opinions about that but, at the end of the day, it is what it is. I hopped around to a few different groups, each time being surprised - again - by how friendly and smart the people at Adobe were - but my heart wasn't in it.\nI like to teach. I like to share. As shy as I am in most situations, I love to be in front of a group of developers showing them cool crap. To this day I remember the joy of my very first program running (after an hour or so of it failing) and when I present I feel like I get to share some of that joy with others.\n\nI'm happy to announce that - starting in February - I'll be joining IBM as a MobileFirst Developer Evangelist, the same team my buddy, and former coworker, Andy Trice is on. The platform makes use of Apache Cordova so you can expect me to still blog quite heavily on it. Once I google how to tie a tie, I expect to be on the road as I was before with Adobe so hopefully I can meet more of my readers this year.\nI'm excited, scared, and anxious to start this new adventure. Wish me luck!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Know WordPress? Need some advice.",
		"date":"Tue Jan 13 2015 23:50:53 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421193053,
		"url":"https://www.raymondcamden.com/2015/01/14/know-wordpress-need-some-advice",
		"content":"Ok, time to throw in the towel and ask for help. ;) After upgrading the virtual server this blog runs on to a better level of hardware (1.7 gigs of RAM versus 0.6), my uptime improved quite a bit. But I still get - a few times a week - the infamous &quot;Error Establishing Database Connection&quot; issue. I've got a monitor set up for it now so I can reboot quickly, but last night it happened about an hour and a half after I went to bed so it was down for hours.\nI've Googled quite a bit but most of what I've found focuses on the issue happening immediately and focus on your authentication values for MySQL. Obviously that isn't the problem. Other items I found focus on using caching plugins to help with performance. I'm using WP Super Cache so I've already done that.\nSo - any ideas? All I can think of is to try to find out if MySQL isn't using as much RAM as it can. Maybe there is a setting where I can tweak that higher.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "PhoneGap Online/Offline Tip (2)",
		"date":"Tue Jan 13 2015 03:32:16 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421119936,
		"url":"https://www.raymondcamden.com/2015/01/13/phonegap-onlineoffline-tip-2",
		"content":"A while ago I posted an article discussing how to handle offline and online events in Cordova/PhoneGap (PhoneGap Offline/Online Tip). While working on my book I came across some differences to this behavior that I wanted to document.\n\nAs a reminder, the idea is simple. Your PhoneGap/Cordova app should be network aware if it makes any use of remote resources. While the Network Information plugin isn't perfect (and I'll be showing an example of that in a second), there really is no excuse to not add some type of support for this in your application. Having an application that just silently fails is unprofessional - especially considering how easy it is to fix.\nRead the earlier entry first - I'll wait.\nNow that you've seen an example, there are a few changes you should be aware of.\nAt the time I wrote the entry, I recommended putting the event listeners in the document ready block if you were using jQuery. Now the recommendation (and this is documented) is to place them in the deviceready listener.\nThe good news is that the handlers still works as my other blog entry says. They will fire automatically on application startup. The bad news though is that your code needs to be a bit more intelligent about any warnings it may use. What do I mean? My sample code alerted the user when they went offline and online. It makes sense to warn the user immediately if the application starts up offline. It does not make sense to do the same if the application is online. So you need a work around for that. The good news is that the workaround also meshes nicely with a bug.\nCordova issues 7787 describes a bug where in Android, the event listeners may fire multiple times. What that means is that when you test putting your device offline, the event may fire twice or more. If you are using an alert, then that's a problem.\nTo get around this I used a simple hack. It is harmless on iOS and actually addresses the &quot;starting up online&quot; issue as well. Consider this pseudo-code:\n\nBasically, I set a global variable to see if I've switched from one state to another. That handles the Android issue and like I said is harmless on iOS. Notice the enableForm method called in online. This will fire if the application starts online, but we add a check (lastStatus != '') that handles the good startup case and suppresses the alert. (The line removing disabled is harmless too.)\nAnyway, I hope this helps!\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "jquery",
            
                "mobile"
            
		]

	},

	{
		"title": "ColdFusion Builder 3 Updated (and I bet you didn't know...)",
		"date":"Mon Jan 12 2015 04:00:12 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1421035212,
		"url":"https://www.raymondcamden.com/2015/01/12/coldfusion-builder-3-updated-and-i-bet-you-didnt-know",
		"content":"Did you know ColdFusion Builder 3 was updated? There was a tweet and blog post about it: Remote server settings lost after restart, now fixed in ColdFusion Builder 3. Unfortunately, the normal way of doing updates, the method that was actually used already for CFB3, was not applied here. You can't use the &quot;Check for Updates&quot; method to get this update. Instead, you must download the installer and reinstall CFB3 from scratch. Now - I'm happy that the installer was updated - that's something I've wanted to see for CF server for some time. (Even with the new updater, it would be nice if the bits you get were 100% updated.) But I don't understand why the usual mechanism for Eclipse-based projects wasn't used. I'm trying to find out if this will happen in the future for folks who may not want to do a complete update.\nIn case you need reminding, the link to download CFB is http://www.adobe.com/go/trycoldfusionbuilder/.\nQuick show of hands - of my readers using CFB3 - how many of you knew about the update?\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Dropdown to Ajax call to ColdFusion example",
		"date":"Fri Jan 09 2015 04:22:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1420777364,
		"url":"https://www.raymondcamden.com/2015/01/09/dropdown-to-ajax-call-to-coldfusion-example",
		"content":"A reader pinged me last night with a relatively simple request:\n\nI want a dropdown and when the user changes the value, I want to use Ajax to hit a CFC and do something with the result.\n\n\nThis is incredibly trivial, but I thought it might be kind of fun to share the code as I write it. Normally I make a demo and share the final bits, but perhaps it would useful to folks to see how I build things like this.\nAs I said, this is super trivial, but even when I'm doing simple stuff, I'll build up the bits in steps and check in the browser to ensure I'm not making any stupid mistakes. With that in mind, let's get started.\nFirst, I built an HTML template.\n\nThe important bits are:\n\nI chose jQuery for my Ajax, DOM manipulation library and put in an empty ready block. As I said, I take things in steps, so there isn't any JavaScript code written yet.\nI added a dropdown. I know I'm going to be listening for changes to the dropdown so I gave it an ID. There are 3 values, but the first one simply reflects a null state.\nThe reader wants to display the result so I've added a blank div. Again though, I'm sure to include an ID value so I can access it from jQuery.\n\nNext - I'm going to add code to listen for the dropdown change. This code block, and the rest, are within the document ready block and I won't be showing the entire file. At the very end I'll share the template.\n\nNothing scary here - just listen for the change, get the selected value, and log it to the console. This is all jQuery 101 stuff, but yes, I do stuff like this and run it in the browser just to be sure. I code fast, and I can alt-tab/alt-r quickly, so this doesn't take me long, but as I tend to screw things up quite a bit I like to build out in steps.\nOk, so the next part involves taking the value and sending it to ColdFusion to do something. The actual server-side logic isn't important here. For now the logic will be to take the value passed and return it preprended with, &quot;I was sent: &quot;.\nHere is the component for that logic. I wrote this in script form but you could use tags. But don't. Seriously.\n&lt;pre\ncomponent {\nurl.returnformat=&quot;json&quot;;\nremote function doStuff(required string input) {\nreturn &quot;I was sent: #arguments.input#&quot;;\t\n}\n}\n\nI'm assuming this is self-explanatory, except for perhaps the url.returnformat line. That lets me skip passing JSON as a returnformat to the query string when using the API. What we really need (and I'll go file a bug report) is a way to specify a default returnformat at the application level. WDDX was cool ten years ago but there is no reason it should be the default now - backwards compat or not. You can do a default returnformat per method, but that gets messy. To confirm it worked, I opened it up in my browser directly: http://localhost:8501/testingzone/ddajax/api.cfc?method=dostuff&amp;input=foo\nOk, so now back to the JavaScript. All we need to do is run an XHR against the CFC and put the response in the div:\n\nThe first change is to cache the result div selector. Since I'll be updating it multiple times (well, if the user makes multiple changes) it makes sense to grab it once. The next change is to use $.get to fetch the data. We could have use $.getJSON too. Finally, we take the result and place it in the div. (Note - we are using JSON to wrap a string which is overkill. We could have used the 'plain' returnformat value as well. I expect that for most folks they will be returning 'data' not just strings so I kept the code as you see to better reflect a real use case.)\nAnd that's it. Not exactly rocket science, but there you go. Here is the complete HTML file.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Article: A Case Study in JavaScript Code Improvement",
		"date":"Thu Jan 08 2015 03:42:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1420688576,
		"url":"https://www.raymondcamden.com/2015/01/08/article-a-case-study-in-javascript-code-improvement",
		"content":"My latest article, A Case Study in JavaScript Code Improvement, has just been released on the Telerik Developer Network. Over the past year or so I've spent a lot of time thinking about what I call &quot;201 Level&quot; JavaScript/web development. The stuff above intro level and below Rockstar/Ninja Level. It is an area I think developers need more help with (myself included) and I've been trying to focus my writing/presenting on this general theme.\nMy article takes a simple project (my Star Wars API wrapper) and discusses how I migrate it from a quick and dirty one-off project to something that is a bit more professional.\nLet me know what you think!\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Check out Happy Metrix (again)",
		"date":"Tue Jan 06 2015 05:54:34 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1420523674,
		"url":"https://www.raymondcamden.com/2015/01/06/check-out-happy-metrix-again",
		"content":"Way back in the old days of 2013, I reviewed an application called Happy Metrix (Check out Happy Metrix). Back then, Happy Metrix was an Adobe AIR desktop application that let users create dashboards of stats for various metrics. I'm kind of a stat junkie (see my recent look at SumAll) so apps like these really interest me. The creators of Happy Metrix reached out to me a few weeks ago to let me know they had a pretty big update and asked me to take a look. Here's what I found.\n\nFirst and foremost, this is an entirely new application. (And as a warning, if you have the older version installed, this one will not overwrite it. You probably want to delete it manually.) Along with a new desktop application, Happy Metrix now runs on iOS and the web. It doesn't look very different from the older version, just a bit more polished, but since it looked darn good before that's totally fine.\n\nWhat's improved though is the &quot;onboarding&quot; experience for the first time user. Last time I used Happy Metrix I spent a lot of time setting up my dashboards. Now the application helps you set up a default dashboard tied to a particular website's analytics. The screen shot above is an example of the dashboard setup out of the box. It's a great set of metrics for a web site and you can add this for other sites when you add new dashboards:\n\nI like the idea of dashboard templates and I hope they add more soon. Another cool aspect is live updating of the widget while you edit it. You can tweak various settings and see it update live on the dashboard. Not sure how well it will come out in the screenshot, but take note of how the other widgets are grayed out and the focus is on the widget being edited.\n\nAs before you can export an entire dashboard (PDF, JPG, PNG) but you can now also export a widget by itself. That could be useful for including in emails or perhaps in a corporate presentation. Here's a sample:\n\nI don't know about you, but I think that's beautiful.\nCompared to SumAll, the number of &quot;sources&quot; for widgets is pretty limited. YouTube, for example, was supported before but isn't now. I questioned them about it and they mentioned it had low usage which is why it was pulled. Currently you can &quot;speak&quot; to Google Analytics, Twitter, Facebook, Instagram, LinkedIn, and MailChimp.  Of course, they still support custom widgets, but unlike before you aren't limited to static data. You also have a butt load of ways to display them:\n\nOnce you select a chart type, you can then just supply a URL:\n\nAs a test, I wrote up a quick ColdFusion template to output two random values and put it up at this url, http://www.coldfusionbloggers.org/temp/test2.cfm. I set it up as a pie chart and here is how Happy Metrix rendered it:\n\nThe app isn't terribly clear about how often it will refresh. From what I can see it refreshes every time I view the dashboard. Along with custom data URLs, you can also add static pictures and text. I guess that could be useful for simple messages and the like. You can also create chart widgets for static data. I'd imagine that would be useful for data you want to display that doesn't need to change often.\nProbably one of the coolest new features though is the ability to share. You can share any dashboard:\n\nShared dashboards will have a unique URL, an expiration, and you can also secure them with a basic pin:\n\nNote that the &quot;optional message&quot; thing is currently bugged. If you leave it blank, it is still displayed to users (as a blank window). You can see my shared dashboard here: http://cloud.happymetrix.com/1whfd2kxet5llyty.\nUnfortunately, I don't see a way to manage shares. I've tested this a few times and there seems to be no way to delete/remove shared links. Hopefully that's something they add in the future.\nAnd of course, support for the web and iOS is great too. Personally I like using it as a desktop app, but having options is great. I was going to share a screenshot of it running in iOS, but it looks identical, minus the ability to edit/share.\nSo - how much does the app cost? Pricing is currently at 15 Euros per month (with a year long subscription). That's not exactly cheap, but overall the current version of the app has come quite a bit farther recently. In general I can see this price being more than reasonable for folks who have to monitor these types of stats, and especially if they have to generate reports. Being able to export/share the data in such a beautiful format is is certainly something I can see paying for. You can try out the application for free, but note that they have a &quot;nag&quot; screen that is very annoying. I've already spoken to them about that and strongly suggested they tone down the amount of nagging it does.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "CFLib moves to Node.js",
		"date":"Mon Jan 05 2015 10:04:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1420452262,
		"url":"https://www.raymondcamden.com/2015/01/05/cflib-moves-to-node-js",
		"content":"This isn't necessarily new per se, but as I just completed some small tweaks I figured I'd share that I've migrated CFLib to Node.js. For the most part the conversion was fairly simple, but I thought I'd share some of the highlights (and issues) of the new code base.\n\nFirst, let's talk about the old stack. The code base was very old. The last revision was written in 2008. It used ModelGlue and Transfer. MySQL was used as the database. For the most part, the site ran fine. I ran into issues with Transfer if I accidentally saved a UDF with the same name as another but as that was pretty rare I never got around to investigating it or even fixing it. I started on a FW/1 rewrite over a year ago and actually got everything but the admin done, but I hate writing admins so much I never got around to finishing it. (Yes, that is lame. And guess what part I just finished in the Node version?)\nContent submissions have slowed down quite a bit, but honestly, I put the blame on me for that. Adam Cameron helped out a bit for a while, as have others, but at the end of the day, I was the sticking point for things getting released, no one else. I apologize for that. The site still gets &quot;ok&quot; traffic, nearly 100K page views last year, so I'm hoping that having a new home for it will give me less of an excuse to ignore it. Y'all can nag me if I don't do a better job this year.\nFor the new site, my stack couldn't be more different:\n\nNode.js, of course.\nExpress 4.X\nMongoDB\nMongoose (a wrapper for Mongo)\n\nAs I rebuilt the site, I decided to kill off some features, the biggest being ratings (there were maybe 200 ratings) and author pages. I can return author pages if people feel strongly enough about it. I also switched the code renderer to PrismJS, my favorite library for code coloring.\nYou can see all the code up on GitHub: https://github.com/cfjedimaster/cflibwww. Before you look at the code, please be aware that I'm a Node noob! I've played around a lot with Node and have released multiple sites, but I still feel like a first year freshman with it. I'd say my code is slightly better than my first year ColdFusion code, but only slightly.\nThis is - I think - the largest site I've built in Node.js so far. I feel like the biggest issue is my core application file, index.js. It isn't too big (323 lines currently), but it feels messy. When I began working on an Admin controller, I moved code for those routes (think code that handles a particular request) into a unique file, and I'd like to do the same with the main views as well. Ditto for all the helpers I wrote in Handlebars.\nBasically - I can look at my code and at least &quot;smell&quot; the things that seem wrong. I think that's good.\nMongo is - of course - a pleasure to work with. I won't pretend NoSQL DBs are a silver bullet, but my God, if I never write another line of SQL again I'll be happy. Working asynchronously can be a bit of a pain at times. So for example, I needed a way to get my libraries, and for each library, get a count of the number of UDFs in it. I used a library named async which allowed me to take those N async calls and listen for them to complete:\n\nThe thing that bugged me the most, and I never really realized it before, is how limited Handlebars can be at times -- specifically in terms of doing anything in logic. Handlebars wants to minimize the amount of logic you include in your views, and I can get that, but it really bit me in the rear at times. As an example, when editing a UDF, I need to display a list of libraries the UDF can belong to, and then set the current library as the selected item. Handlebars supports IF clauses, but the expression must be simpler. So you can't do: {{if something is something}}. Nope. You have to actually write a helper function for it:\n\nHere's how that looks in the Handlebars template:\n\nNot horrible, but not ideal either. Also, ColdFusion spoiled me. Being able to do this: dateFormat(something, mask) is nice compared to how I did it in the new site. First I got the MomentJS module. I then used it in a Handlebars helper. Finally I called it from my template like so: {{fullDate udf.lastUpdated}} That's readable at least, but not necessarily simple.\nAll in all though, I'm happy with this new version, and I've got three UDFs waiting to be released. I'm going to release those later in the week. For those of you who are Node/Express experts, I will gladly take your feedback. For those who don't know Node and have specific questions about the code base, ask away!\nIt's come a long way from this...\n\nBy the way - that wasn't the first version. Unfortunately I couldn't find it via the Wayback Machine.\np.s. This new version will have broken the API used by the ColdFusion Builder extension. If anyone actually made use of that, speak up, and I'm sure I can get it working again.\np.s.s. The next conversion will be ColdFusion Bloggers.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "javascript"
            
		]

	},

	{
		"title": "Using Grunt and Jasmine and having issues with XHR? Read this.",
		"date":"Fri Jan 02 2015 03:58:09 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1420171089,
		"url":"https://www.raymondcamden.com/2015/01/02/using-grunt-and-jasmine-and-having-issues-with-xhr-read-this",
		"content":"I recently did some work with Grunt and Jasmine. I had a Jasmine suite of tests that were entirely Ajax based. (You can see the test for yourself here if you want.) These tests ran just fine when using the Jasmine spec runner. This was my first time doing async-based testing with Jasmine and I was pretty impressed by how easy it was.\n\nEverything was kosher until I started trying to run the unit tests via Grunt. When I ran the suite, the same one that always passed, via Grunt, I consistently got an error in some, but not all, of my tests. The error was:\n\nError: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL. (1)\n\nThis seems to imply that those particular calls were taking too long to respond, but even if I modified the DEFAULT_TIMEOUT_INTERVAL value to a high value, it never passed.\nThe code I'm testing made use of XHR directly (no jQuery) so I added an onerror to see if it reported anything. I noticed in all the failing calls, onerror ran immediately with a status code of 0.\nAfter a bit of Googling I found two interesting possibilities. The first was this bug report: Async loading files stalls Grunt task. It mentioned that by default, Jasmine is running the tests via PhantomJS and the file system. The bug report suggested passing '--local-to-remote-url-access' to allow the tests to hit remote URLs. That makes some sense, but my Jasmine suite had passing tests that were hitting the remote URL just fine. (I tried it anyway, it didn't help.)\nI then discovered this StackOverflow post: Using phantomjs to make ajax calls using extjs proxy on local file. The answer there suggested this setting: --web-security=no. Using this is what eventually worked. You can see my Grunt script here: https://github.com/cfjedimaster/SWAPI-Wrapper/blob/master/Gruntfile.js\nHonestly, I'm still confused by this. When I tried telling Grunt/Jasmine/PhantomJS to use a web server, it didn't help. Also, my code was still able to do XHRs to the remote server just fine. It was just some of the calls failing. They all used the exact same utility function to do XHR. If for some reason the remote API didn't enable CORS for those calls my web-based testing would have failed as well.\nSo... there ya go. Who knows. :)\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "My 2014",
		"date":"Tue Dec 30 2014 03:07:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419908863,
		"url":"https://www.raymondcamden.com/2014/12/30/my-2014",
		"content":"As the year wraps up, I thought I'd write up some thoughts about my 2014. This is pretty personal, so I don't actually expect a lot of people to read this, but if you do, thanks for listening. It was an interesting year overall, and I end it with health, a roof over my head, and people who love me, so no matter what else, I'm blessed.\n\nThe Blog\nLet's talk about the blog first. As folks know, I finally bit the bullet and transitioned from my own ColdFusion blogware to a WordPress blog. It was rocky (and I realize now I still haven't talked about the process) but I feel pretty happy about the transition overall. WordPress is an amazingly powerful blog engine. I know I still have kinks to work out (right now I'm seeing it crash about once a week), but I think I made the right choice. In terms of content, my top entries for 2014 are:\n\nBarcode Scanner sample, and new repo for Cordova examples\nBrowser as a platform for your PhoneGap/Cordova apps\nMy perspective of working with the Ionic Framework\nCordova File System – Important Update\nExamples of the Marvel API\nIndexedDB on iOS 8 – Broken Bad\nCordova Sample: Check for a file and download if it isn’t there\nApache Cordova 3.3 and Remote Debugging for Android\nCordova Sample: Reading a text file\nIonic and Cordova’s DeviceReady – My Solution\n\nMissing from the list is my favorite blog entry - one that got no comments (outside of my own) - my demo of the New York Times API. I thought that was cool - but apparently no one else did (grin). But if there's one thing I've learned in ten plus years of blogging, the entries I expect to get comments don't and the ones I do not expect to get comments will get a huge number of them.\nOverall I'm happy with the content I created this year. In terms of my writing outside of the blog, I also wrote the following articles for other publications:\n\nEasy and Shareable Local Web Servers with Fenix for Flippin’ Awesome\nUsing the HTML5 Gamepad API to Add Controller Support to Browser Games for Game Tuts\nWorking with Intl for Nettuts+\nExpose Yourself with ngrok for Flippin’ Awesome\nHTML out of the Browser for Mozilla Hacks\nDebugging with Firefox DevTools for Nettuts+\nCreating Brackets Extensions for Nettuts+\n\nConferences, Presentations, Etc\nIt was a slow year for me in terms of conferences, and I still don't do a great job of recording when and where I speak in a way that is easy to search. Due to my job changes (see below) and the adoption late in the year, I had to refrain from submitting as much as I'd like. I even had to cancel two conferences in October because of my travel to China. (Which I don't regret of course. :) One of my resolutions this year was to make better use of Google Hangouts. I did create a few presentations on the platform and I'd like to do more next year. It isn't as good as being in person of course, but it is significantly easier. (To be clear, it still takes time to prepare and presenting itself is work!) In fact, if you, dear reader, are still here, and don't see me announce something in terms of an online presentation for January, remind me.\nCareer\nWell... yeah. I've been pretty honest about how my role has changed at Adobe. For about a year and a half now I've not been an evangelist. In fact, the entire evangelism team at Adobe is pretty much down to a few people, just concerned with Creative Cloud. I'd love to talk to you (the generic you) about what Adobe does product-wise. I'd love to help people learn more about our efforts in terms of web standards and are web-related products. But yeah. Not going to happen. I guess I'm lucky. I spent some months doing basic HTML work for the Learn content. That work included things like copying and pasting CSS for someone who didn't know how to use source control. (Seriously.) Early in 2014 I transitioned to the Creative SDK team. I got training in ObjectiveC which was absolutely fascinating, but my role now is essentially that of a technical writer. Certainly I like to write, but I can't say that this is a role that I'm best suited for.\nI love to teach. I'm not that smart - but I think I'm damn good at explaining things in a way that makes it easier for others to learn. If I want to be a developer evangelist than it will not be at Adobe. That's all I'll say about that. (For now.)\nMy Favorite Song\nI played this about a bazillion times this year and it never gets old.\n\nMy Favorite Movie\nEasy - the new Transformers movie.\n(Sorry - just threw up a little.)\nThere were some really enjoyable films this year, but the best is - well - the two best - would be Guardians of the Galaxy and Captain America 2. I want to give a slight edge to Captain America 2 for being probably one of the best sequels ever. But then I want to give a slight edge to GoG for being so damn cool even though I had known nothing at all about the characters. And it has a racoon with a gun. So yeah, I'll pick both. I keep thinking this winning streak Marvel has is going to end soon, but while it lasts, it is an a",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Looking to learn Node and Express?",
		"date":"Mon Dec 29 2014 05:23:42 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419830622,
		"url":"https://www.raymondcamden.com/2014/12/29/looking-to-learn-node-and-express",
		"content":"\n\nThis isn't a formal book review per se, more of an FYI, but I wanted to share a quick note about one of the best books I've read yet on Node. If you are interested in learning Node, and specifically want to learn a framework at the same time, I highly recommend &quot;Web Development with Node and Express&quot; by Ethan Brown. The book does an amazing job of introducing both Node and Express at the same time. I've mentioned it before, but it was Express that really made Node seem useful to me.\nI had sat through more than a couple Node presentations that showed building a simple web server, and while it seemed cool, it also seemed highly impractical. Seeing Express in action though made a huge difference for me. Express has gone through some pretty big changes recently and this book uses the most recent edition. I've written about Node and Express before, but it was a good year or so ago so this book was a great help for me in getting up to speed with how it has changed.\nThis book covers everything related to building a web site. You've got basics (adding views), you've got form handling, you've got form handling with file uploads, etc. It covers sessions and database persistence. It uses the best (well, one of the best) templating languages, Handlebars. It also includes unit testing from the very beginning. This book is so darn good that I plan on picking up a physical copy so I can have it nearby. (I got an ebook version for free.) I really can't recommend it enough and if one of your New Year's Resolutions is to learn a new language, you really couldn't do any better than this book.\nIf you want to check it out before you buy, you can download a sample chapter (PDF) first. If you end up picking up the book, let me know what you think in the comments below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "My 2014 Book List",
		"date":"Sat Dec 27 2014 03:44:32 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419651872,
		"url":"https://www.raymondcamden.com/2014/12/27/my-2014-book-list",
		"content":"Presented just for the heck of it, here is the list of books I finished in 2014. Like last year, I'm a bit disappointed, but considering I doubled the amount of kids in my house, I think I have a valid excuse. My favorite of the year? Probably &quot;Spin.&quot; But I enjoyed the hell out of the Merchant Princes series. (My favorite genre is alt-history and I especially enjoy alt-history books that mix in our world as well.) My least favorite was &quot;The Uncertain Places.&quot; I nearly gave up about halfway through.\n\n\n.gr_custom_container_1419694785 {\n/* customize your Goodreads widget container here*/\nborder: 1px solid gray;\nborder-radius:10px;\npadding: 10px 5px 10px 5px;\nbackground-color: #FFFFFF;\ncolor: #000000;\n}\n.gr_custom_header_1419694785 {\n/* customize your Goodreads header here*/\nborder-bottom: 1px solid gray;\nwidth: 100%;\nmargin-bottom: 5px;\ntext-align: center;\nfont-size: 150%\n}\n.gr_custom_each_container_1419694785 {\n/* customize each individual book container here /\nwidth: 100%;\nclear: both;\nmargin-bottom: 10px;\noverflow: auto;\npadding-bottom: 4px;\nborder-bottom: 1px solid #aaa;\n}\n.gr_custom_book_container_1419694785 {\n/ customize your book covers here /\noverflow: hidden;\nheight: 160px;\nfloat: left;\nmargin-right: 4px;\nwidth: 98px;\n}\n.gr_custom_author_1419694785 {\n/ customize your author names here /\nfont-size: 10px;\n}\n.gr_custom_tags_1419694785 {\n/ customize your tags here /\nfont-size: 10px;\ncolor: gray;\n}\n.gr_custom_rating_1419694785 {\n/ customize your rating stars here */\nfloat: right;\n}\n\n\n\n\n2014 Books\n\n\n\n\n\n\n\n\n\nThe Family Trade\n\n\nby Charles Stross\n\n\nReally happy with it!\n\n\ntagged:\n2013 and 2014\n\n\n\n\n\n\n\n\n\n\nThe Hidden Family\n\n\nby Charles Stross\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Clan Corporate\n\n\nby Charles Stross\n\n\nSo far so good.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Merchants' War\n\n\nby Charles Stross\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Revolution Business\n\n\nby Charles Stross\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Trade Of Queens\n\n\nby Charles Stross\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nFeed\n\n\nby M.T. Anderson\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nAngularJS\n\n\nby Brad Green\n\n\nDecent enough book.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Gate Thief\n\n\nby Orson Scott Card\n\n\nI'd love to give this book a higher rating. It has a great premise, and cool moments, but so much of the book was mind numbingly boring I wanted to give up. It seemed like every other page was:\nAnd then Dan learned that his gate magic X...\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThree Princes\n\n\nby Ramona Wheeler\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nHis Majesty's Dragon\n\n\nby Naomi Novik\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nSpin\n\n\nby Robert Charles Wilson\n\n\nAwesome book!\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nOptions: The Secret Life of Steve Jobs - A Parody\n\n\nby Fake Steve Jobs\n\n\nI feel bad giving this one star as it wasn't a poorly written book, but I absolutely hated the main character so much I just despised the entire read. Sure, it was funny, but a funny story about an asshole is still a story about an assho...\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nTransition\n\n\nby Iain M. Banks\n\n\nInteresting. Bit confusing at times... but interesting.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Lies of Locke Lamora\n\n\nby Scott Lynch\n\n\nExceptional! It just got better and better as it went on. Essentially one long &quot;heist&quot; novel set in a fantasy world. Definitely worth your time!\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Uncertain Places\n\n\nby Lisa Goldstein\n\n\nDisappointing.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nZoo City\n\n\nby Lauren Beukes\n\n\nDamn good.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nThe Drowned World\n\n\nby J.G. Ballard\n\n\nBoring as hell.\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nAssassin's Apprentice\n\n\nby Robin Hobb\n\n\n\n\ntagged:\n2014\n\n\n\n\n\n\n\n\n\n\nStar Trek : Ships of the Line\n\n\nby Doug Drexler\n\n\ntagged:\n2014\n\n\n\n  \n    \n  \n  \n    Share book reviews and ratings with Raymond, and even join a book club on Goodreads.\n  \n  \n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "books"
            
		]

	},

	{
		"title": "Avoid the minified AngularJS library in development",
		"date":"Fri Dec 26 2014 04:52:40 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419569560,
		"url":"https://www.raymondcamden.com/2014/12/26/avoid-the-minified-angularjs-library-in-development",
		"content":"I'm still learning AngularJS, which means I can get stuff done but I'm dangerous. A few days ago I ran into a problem that drove me crazy. I was trying to use ngCordova in an existing AngularJS application and kept running into a problem injecting the library into my controller. The error message was less than helpful. Heck, it was useless. Let's look at an example.\n\nI grabbed a copy of the AngularJS seed project (https://github.com/angular/angular-seed) to create a quick simple AngularjS application. (Before this I tried yeoman but after thirty minutes of fighting it and it hanging I decided to give up.) I then set the application to use the minified version of the AngularJS library. That's the key. Then I opened up one of the JavaScript files and modified the dependencies to include something bad (literally):\n\nThe error reported by AngularJS was this:\n\nError: [$injector:modulerr] http://errors.angularjs.org/1.2.28/$injector/modulerr?p0=myApp&p1=%5B%24injector%3Amodulerr%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.2.28%2F%24injector%2Fmodulerr%3Fp0%3DmyApp.view1%26p1%3D%255B%2524injector%253Amodulerr%255D%2520http%253A%252F%252Ferrors.angularjs.org%252F1.2.28%252F%2524injector%252Fmodulerr%253Fp0%253Dsomethingbad%2526p1%253D%25255B%252524injector%25253Anomod%25255D%252520http%25253A%25252F%25252Ferrors.angularjs.org%25252F1.2.28%25252F%252524injector%25252Fnomod%25253Fp0%25253Dsomethingbad%25250Az%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A6%25253A450%25250AYc%25252Fb.module%25253C%25252F%25253C%25252Fb%25255Be%25255D%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A20%25253A1%25250AYc%25252Fb.module%25253C%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A20%25253A1%25250Ae%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A267%25250Ar%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A7%25253A288%25250Ae%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A207%25250Ae%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A284%25250Ar%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A7%25253A288%25250Ae%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A207%25250Ae%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A284%25250Ar%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A7%25253A288%25250Ae%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A33%25253A207%25250Aec%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A36%25253A309%25250Adc%25252Fc%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A18%25253A170%25250Adc%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A18%25253A387%25250AWc%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A17%25253A415%25250A%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A216%25253A78%25250Aa%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A146%25253A93%25250Ane%25252Fc%25252F%25253C%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A31%25253A223%25250Ar%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A7%25253A288%25250Ane%25252Fc%252540http%25253A%25252F%25252Flocalhost%25253A8000%25252Fapp%25252Fbower_components%25252Fangular%25252Fangular.min.js%25253A31%25253A207%25250A%250Az%252F%253C%2540http%253A%252F%252Flocalhost%253A8000%252Fapp%252Fbower_components%252Fangular%252Fangular.min.js%253A6%253A450%250Ae%252F%253C%2540http%253A%252F%252Flocalhost%253A8000%252Fapp%252Fbower_components%252Fangular%252Fangular.min.js%253A34%253A97%250Ar%2540http%253A%252F%252Flocalh",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Creating Spreadsheets with ColdFusion without headers",
		"date":"Wed Dec 24 2014 00:27:22 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419380842,
		"url":"https://www.raymondcamden.com/2014/12/24/creating-spreadsheets-with-coldfusion-without-headers",
		"content":"This question came in yesterday and I thought I'd share it. Paul asked a simple question - he had a query and wanted to write it to a spreadsheet without having the query columns being used as headers. There is an excludeHeaderRow attribute in cfspreadsheet, but it applies to reading only.\n\nTo properly handle this, you have to skip using cfspreadsheet completely. Instead, simply use the various spreadsheet functions to write out the query row by row. Here is an example minus the fake query created earlier.\n\nIn case you're wondering why I create an array and then turn it back into a list, that was done to ensure the empty cells are preserved in the spreadsheet row.\nSo as I wrote this, I decided to look at the docs a bit more. It bugged me that all the functions seemed to require either a query or a list. Specifically, it bugged me that I couldn't pass an array. Using a list in this example is inherently dangerous because a query cell value could include a comma. I then noticed that spreadsheetAddRows does support using an array. Unfortunately the function is broken. Like, seriously - try the sample code at the link I just shared. First fix the typo of course (if I have time I'll edit the wiki page). You get this error trying to add rows: Invalid row number (-1) outside allowable range (0..65535). I'll file a bug report and add the link as a comment.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Cordova Demo - Viewing all Contacts",
		"date":"Tue Dec 23 2014 01:51:44 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1419299504,
		"url":"https://www.raymondcamden.com/2014/12/23/cordova-demo-viewing-all-contacts",
		"content":"A quick demo of something I've wanted to see myself for a while now - a user on Stackoverflow asked if it was possible to view all the contacts on a device using PhoneGap/Cordova. It is rather trivial to do so. Simply perform a search and skip providing an actual filter. Here is an example.\n\n\nAll I've done here is run the find method of the Contacts plugin. You must provide the first argument,  which specifies which fields you will search against, but you do not have to actually provide a search value. Running this code as is will return the entire contact object, but obviously you could, and should, ask for a subset of the contacts if you only care about particular values.\nI tested it on my Android phone and it worked really darn fast, despite having 400+ contacts. Here is the result being viewed via GapDebug:\n\nNot really rocket science, but maybe useful. If you want to copy this code into your own project, you can find it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/stealcontacts.\nOk, I was about to post this as is, but then I thought, let me add something fun. I modified the code slightly to see if a contact photo exists - and if so - append it to the DOM:\n\nA simple little tweak, but with a fun result: \nEnjoy!\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Introduction to jQuery",
		"date":"Fri Dec 19 2014 06:06:21 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418969181,
		"url":"https://www.raymondcamden.com/2014/12/19/introduction-to-jquery",
		"content":"I'm happy to announce that today I'm releasing a new video series, Introduction to jQuery. It may seem a bit odd to be introducing something that most people already know, but in my day to day conversations (ok, emails) with folks and my experience helping people on Stack Overflow, it seems like it is a topic that folks still need help on. I used to present on this topic quite a bit a few years ago, but I started thinking a few months ago about how I'd present it now.\nI began working on a video series back in September. It was originally going to be released with a major publisher, but things fell through with them (not anyone's fault though) so I decided to simply put it up on YouTube.\nThe series is almost exactly 2.5 hours long and covers DOM modification, AJAX, effects, and even deferreds (which still feel like black magic to me). If you already know jQuery, I don't think you'll discover anything new in this series, but if you are looking to learn jQuery, or perhaps get a more formal introduction, I hope this series will help.\nAll of the code for the series as well as the slide decks and an errata document may be found here: https://github.com/cfjedimaster/introduction-to-jquery.\nFinally, this series is presented 100% free of charge. If you find it worthwhile, please consider visiting my Amazon Wishlist and making a donation.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Get jQuery Mobile Web Development Essentials for Five Dollars!",
		"date":"Thu Dec 18 2014 23:52:52 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418946772,
		"url":"https://www.raymondcamden.com/2014/12/19/get-jquery-mobile-web-development-essentials-for-five-dollars",
		"content":"\nFrom now until January 6th, you can pick up my jQuery Mobile book, &quot;jQuery Mobile Web Development Essentials&quot; for the grand total of five dollars. That's less than the price of a good beer at a hotel bar. Heck, that's less than the price of a bad beer at a hotel bar. There are a bunch of other books on sale as well. This sale only applies to the electronic book, but it is still a great deal.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery",
            
                "mobile"
            
		]

	},

	{
		"title": "Viewport and Cordova Tip",
		"date":"Thu Dec 18 2014 11:18:11 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418901491,
		"url":"https://www.raymondcamden.com/2014/12/18/viewport-and-cordova-tip",
		"content":"Yesterday I ran into an interesting thing with Cordova and I thought I'd share. I assume most folks are aware of the benefits of adding a meta tag specifying viewport when building mobile-friendly websites. If you aren't, here are a few examples demonstrating the idea. I created a quick Cordova application yesterday specifically to demonstrate this for the book I'm writing. Using the same base HTML, I made two applications and in one of them I used the meta tag.\n\nWhen I viewed both applications in my mobile simulator, I noticed something odd. They both looked the exact same. I saw the same behavior in both iOS and Android. To make things even more confusing, I then used Mobile Safari to open up the www folders from the applications. The one without the meta tag demonstrated the problem that the tag was meant to solve. So what's going on?\nIf you look at the iOS configuration guide at the Cordova docs, you will notice this preference: EnableViewportScale. This preference allows you to specify a view port scale with the meta tag. The default is false. That explains it right there - and serves as a reminder to read, and read often the configuration guides. I tend to focus primarily on the JavaScript of my Cordova apps, but there is quite a bit you can do with the configuration values as well.\np.s. Someone needs to take the MediaPlaybackRequiresUserAction attribute and apply it to the web as a whole. :\\\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "AppFog and MongoDB 2 Configuration",
		"date":"Wed Dec 17 2014 23:46:46 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418860006,
		"url":"https://www.raymondcamden.com/2014/12/18/appfog-and-mongodb2-configuration",
		"content":"Just a quick tip. I'm using AppFog for my Node.js version of CFLib.org and ran into an issue with their MongoDB2 support. When you use AppFog services, they document how your code can dynamically read configuration information from their server. This is required since things like MongoDB support are configured uniquely per deployed application.\n\nThe documentation demonstrates how to do this in various languages. Here is the code sample for Node.js:\n\nSimple, right? Except that AppFog offers both MongoDB 1.8 and 2.4.8. If you use 2.4.8, you must change the line reading credentials too:\n\nThe error is pretty obvious when deploying from the CLI, but I wasn't sure of the exact change until I console.logged the JSON variable and viewed my log.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Online presentation tonight on ColdFusion 11",
		"date":"Tue Dec 16 2014 23:19:48 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418771988,
		"url":"https://www.raymondcamden.com/2014/12/17/online-presentation-tonight-on-coldfusion-11",
		"content":"I'll be giving an online presentation tonight to a user group on ColdFusion 11. As I normally do when I present like this, I asked the user group manager if he minded me opening it up to anyone and he agreed it was ok. I'll give priority to the user group for questions, but if you are free tonight and want to watch me present on ColdFusion, this is your opportunity. The presentation starts at 6PM Central and the link (requires Flash) is: http://experts.adobeconnect.com/raycffavorites/.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "A quick message for the ColdFusion community",
		"date":"Mon Dec 15 2014 09:12:57 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418634777,
		"url":"https://www.raymondcamden.com/2014/12/15/a-quick-message-for-the-coldfusion-community",
		"content":"You've been warned - this video has no educational value.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "cfwddx doesn't work in script - FYI",
		"date":"Mon Dec 15 2014 05:16:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418620603,
		"url":"https://www.raymondcamden.com/2014/12/15/cfwddx-doesnt-work-in-script-fyi",
		"content":"This will probably impact about three people, but as I ran into it, I thought I'd share. In ColdFusion 11, you were supposed to be able to call pretty much any tag from cfscript. Unfortunately, cfwddx doesn't work. And yeah, I actually need it. (I used it to store some complex data for CFLib and I'm working on a migration script to convert it to MongoDB.) Here is the bug report in case anyone wants to vote for it: https://bugbase.adobe.com/index.cfm?event=bug&amp;id=3909707.\n\nAs a quick aside, I wrote this to get around it. The syntax is actually shorter than what I would have used if the feature worked.\n\nOops - it DOES work! This is what I get for coding too fast. My issue was trying to do x=cfwddx(...) as opposed to just cfwddx by itself. In my opinion, what I wrote should have worked with x simply being undefined, but, whatever. I'm keeping my UDF as it is quicker to use anyway, but please forgive this misfire on a bug report!\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "ColdFusion, CFHTTP, and java.io.ByteArrayOutputStream",
		"date":"Fri Dec 12 2014 07:23:21 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418369001,
		"url":"https://www.raymondcamden.com/2014/12/12/coldfusion-cfhttp-and-java-io-bytearrayoutputstream",
		"content":"I believe I may have blogged this before, but a reader ran into this yesterday so I thought it might be worth sharing. If you are using CFHTTP to hit a remote and seeing java.io.ByteArrayOutputStream in the result, there is a simple solution around that.\n\nHere is a sample cfdump of just such a response. You are expecting a string of some sort in fileContent, but instead end up with a Java object:\n\nTo work with this result as a string, you simply need to run toString() on the value. So for example, if you know the response is JSON:\ndeserializeJSON(result.filecontent.toString())\nI'll be honest and say I don't know why some servers return that, or why cfhttp can't simply toString it itself. I'm assuming there is a good reason that probably involves all kind of crap I really don't care about. ;)\np.s. It looks like this might be just ColdFusion 9 and earlier - see this bug report. I can say it has been a while since I've seen it. I've run ColdFusion 10 since it came out pretty much.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Cordova Example - Sending SMS Messages",
		"date":"Thu Dec 11 2014 04:47:08 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418273228,
		"url":"https://www.raymondcamden.com/2014/12/11/cordova-example-sending-sms-messages",
		"content":"\nSince the time I wrote this article, the plugin I used does not exist anymore. It is somewhat related to the plugin cordova-plugin-sms, but that plugin is Android only. You probably want to use cordova-sms-plugin. Note the different. This plugin has a slightly different API. You can read more about it here: https://github.com/cordova-sms/cordova-sms-plugin. I have updated my demo code in the GitHub repo linked at the bottom and it works for me, but the main text of this blog post has not been updated to reflect the new plugin and code. \n\nYesterday a reader contacted me asking for help sending SMS messages from a PhoneGap/Cordova application. I've made use of a nice plugin for this before so I thought I'd whip up a quick example of it for him, and my readers.\n\nThe plugin I used is Sms Custom Cordova Plugin - not the most imaginative name but really darn simple to use. It has one method, sendMessage, that - wait for it - will send a SMS message. On Android this is done completely behind the scenes. In other words, it will send a SMS message without letting the user know. Obviously you shouldn't do that, but keep that in mind. On iOS and Windows it will actually open the SMS application so the user has to actually finish the process themselves. Let's look at a simple example. As with my other Cordova examples, this is all up on GitHub for you to play with.\nFirst, the HTML. This application does one thing - prompt for a telephone number and message. To make it a tiny bit prettier I added Ratchet.\n\nWhich renders this lovely UI:\n\nNow let's consider the JavaScript:\n\nSo as I mentioned above, the plugin has one method. It takes an object containing the phone number and message. The second argument is the success handler and the third is for failures. If you try to run this on the simulator, you will get an error: &quot;SMS feature is not supported on this device.&quot; This is useful in case your application is being run on a wifi-only tablet.\nAs I mentioned, on iOS and Windows Phones, the user will see the 'real' SMS application pop up. It will be pre-populated with the data sent from the plugin. Here is an example:\n\nThe user will need to hit Send to finish the process, but they won't have to actually type anything.\nThat's it. If you want a copy of this code, you can grab it here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/smscomposer\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Really useful Chrome Dev Tools tip",
		"date":"Wed Dec 10 2014 03:18:58 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418181538,
		"url":"https://www.raymondcamden.com/2014/12/10/really-useful-chrome-dev-tools-tip",
		"content":"This is not necessarily a new feature, but one that really helped me yesterday so I thought I'd share it. I do not believe this exists in Firefox now (I'm using Firefox as my primary browser now), and I did not check IE, so it may be a Chrome-only feature at the moment. Yesterday I was trying to help a friend debug an incredibly weird problem with an incredibly simple bit of jQuery.\n\nHis page had a form with a submit handler. On submit he serialized the code, sent it to a service, and put the response in a div. That is about as simple as you can get. But oddly it was doing different things in different browsers. When it worked right, it was fine, but sometimes it would display the current page inside the div that was meant to display the service result.\nTo make this even more crazy, when I commented out that part of the code, it still did that. I was going crazy trying to figure it out. I then decided to try something. I went to the div in question, right clicked, and used a Chrome Dev Tools feature. Within the Elements panel, you can right click on any DOM element and tell the browser to pause when something messes with it.\nHere is an example:\n\nI reloaded his page, submitted the form, and as expected, the browser paused. Here is a sample of what that looks like:\n\nI've added a few callouts to the screen shot to make it a bit more obvious. The lowest callout is just a nice message from the Dev Tools saying why it fired. It may be obvious, but I dug that. The second call out is the crucial one. It is telling me exactly what script/code modified my DOM. In his code (the screen shots above are just from a local sample), it was actually another piece of code loaded by the template. It was a generic forms handler plugin that had also listened in for the form submit and was modifying the DOM. I had no idea this was even being used on the site until I tried this particular type of debugging.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Oops, RSS issue",
		"date":"Wed Dec 10 2014 00:32:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418171539,
		"url":"https://www.raymondcamden.com/2014/12/10/oops-rss-issue",
		"content":"So while I (believe) I've tackled the uptime issues here, looks like I screwed up my RSS feed a bit. I just fixed it, so ColdFusion Bloggers will pick it up and anyone who subscribed to the feed will also get the last set of entries.\nAs a reminder, the feed is here: http://feeds.feedburner.com/raymondcamdensblog. If you want to subscribe to the feed to get email notifications simply use the form on the right hand side of the blog.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "ColdFusion updated, and some notes about query caching",
		"date":"Tue Dec 09 2014 08:45:31 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418114731,
		"url":"https://www.raymondcamden.com/2014/12/09/coldfusion-updated-and-some-notes-about-query-caching",
		"content":"Earlier today the ColdFusion team released a big update for ColdFusion 11 and 10. You can read the juicy details here: ColdFusion 11 Update 3 and ColdFusion 10 Update 15 are available now. While looking over the release notes for ColdFusion 11, I saw this odd little gem:\n\n\nBelow caching functions now accept object instead of String parameter for CacheId attribute: \n1) cacheGet\n2) cacheRemove\n3) cacheGetMetadata \n4) cacheIdExists\n\nThat seemed.... weird. So I went over to the ColdFusion docs for cacheGet and was disappointed to find that no one had updated this. When you make a change to the language, you need to update the docs as well. Release notes in an update are not enough. I'll raise this internally so hopefully it won't happen again. There is also the corresponding need of an update for ColdFusion Builder. I'd have to assume (and I can definitely be wrong!) that this language tweak would require a minor update to CFB, so that should have been released as well. Again, I'll raise this internally.\nSo what is this change about? I looked at the &quot;Issues Fixed&quot; document (PDF link) and discovered bug 3741588. Oh, if you click that link and end up back the bug tracker home page... yeah that's a bug. Click it again. (sigh)\nFrom what I can read in the bug report by itisdesign, at some point there was a change for queries that were cached using cachedWithin/cachedAfter. If you do not specify a specific cache ID for the query, then a Java object is used for the ID instead. Here is an example.\n\nIn the code above, I'm caching two queries, but I only specify a specific ID in the second one. Notice the result when I dump the IDs:\n\nYep, a complex object. So the change is to allow this object to be passed as an ID value to various caching functions. Makes sense I suppose but wasn't obvious to me. While investigating this I came across this document (Enhanced query caching using Ehcache) that talks about how queries may now be stored in fancy Ehcache versus ugly old school cache. This happened back in ColdFusion 10 and I knew it, but while reading it I discovered two new Application.cfc variables I had not heard of: cache.useInternalQueryCache and cache.querysize. These are not listed on the wiki so I fixed that: Application variables\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "My Cordova/PhoneGap Developer Setup (Fall 2014)",
		"date":"Mon Dec 08 2014 04:56:19 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1418014579,
		"url":"https://www.raymondcamden.com/2014/12/08/my-cordovaphonegap-developer-setup-fall-2014",
		"content":"Over the years of developing with Cordova and PhoneGap I've used many different tools to help build my applications. I thought it would be nice to share my standard &quot;checklist&quot; of the tools I use for hybrid development. I fully expect this list to change over time, so please make note of the date this entry is being written. If you are reading this in the future, please note that I have always supported our AI Overlords and see my most recent blog posts on what I may be using now. I also want to stress that what I consider to be my setup is not necessarily what I expect will work well for everyone. There are things on this list that I do not mention when presenting or writing. That's because the best tool to help you learn to do hybrid mobile development may not be the best first tool for you to use. Ok, let's get started.\n\nDevelopment Machine - Apple\nThis is part personal preference and part simple fact of life. You can't develop iOS apps on a Windows machine. You can work around it. PhoneGap Build makes it very easy to do so. My issue with a remote service though is that it takes time. Not a lot of time - not at all! But when I develop, I tend to make a lot of small changes very quickly. When I'm trying to build something complex, or debug an issue, I tend to make very small atomic changes. Because of this, even the quick(ish) turnaround time of PhoneGap Build is too slow for me. If that isn't the case for you, then it really comes down to personal preference. MacBook Pros seem to be the most popular developer machine these days, but honestly I think Windows is perfectly fine as well. The only thing I can recommend is to ensure you go SSD for your hard disk. That makes a huge difference in performance.\nIf you go Windows, I'd strongly suggest getting a cheap Mac (refurbished, Mac Mini, etc) and using Synergy as a virtual KVM between them. When I got my first Mac I used this and it performed incredibly well. This was like five years or so ago so I imagine (hope) it has only gotten better.\nSDKs - Android and iOS\nFor SDKs, I typically use iOS and Android only. I'm not opposed to the others, but for the most part, I tend to only test my projects and demos in those two. I still think Windows Phone has the most innovation lately in terms of the UI, but I simply don't get questions/requests/etc from folks asking me to test stuff there. It is very simple to set up a Windows VM and use the SDK if you are on OS X.\nCLI - Cordova\nI've got both the PhoneGap and Cordova CLIs installed, but I generally use the Cordova CLI more often. However, with the recent update to the PhoneGap CLI, I may actually switch back. Right now I've just got more muscle memory for Cordova than I do PhoneGap.\nI'm going to talk a bit more about frameworks in a bit, but the Ionic CLI is actually my favorite. I didn't set it as &quot;the&quot; recommendation simply because I tend to do a lot of one off demo/tests more than actual project work. While you can use the Ionic CLI without using Ionic, if I know I'm doing something fast and small, I'll just use the Cordova CLI. Again, this is muscle memory.\nThe main reason I think the Ionic CLI is the best is the live reload and Terminal logging features (which I reviewed here). I'd love to see both of them rolled back into Cordova or PhoneGap. (Woot woot for open source!)\nEditor - Brackets\nFor &quot;real&quot; projects, I use Brackets. I'm a little afraid of the future of Brackets as it seems to be turning its focus to designers. To be clear, I'm not worried Brackets will go away. I'm afraid it will morph into a tool that simply doesn't work for me as a developer.\nOutside of that, I will also use Sublime as well. The biggest thing Sublime does better than Brackets is simply open from the command line.\nFramework - Ionic\nAh, the million dollar question. ;) For quick testing I'll not use anything but jQuery. I've got a Cordova &quot;skeleton&quot; app that I use for my one-offs that is slim and simple. But if I am doing anything serious, or for a client, I'm going to use Ionic. I am not ashamed to say it - I am an Ionic fan boy. I love everything they are doing for hybrid development. They have a killer UI, UX, CLI, and even more services. While not alone, I think they are doing the most right now to make hybrid development awesome. If I didn't have six kids and a fear of the &quot;start up&quot; life I'd join them in a heartbeat.\nOf course, using Ionic also means using Angular. I like Angular, but it is also a big step for a new developer. That's why when I teach hybrid development, I'll typically use something simpler like jQuery Mobile. I was a huge fan of jQuery Mobile (and wrote a few books on it), and while I may prefer Angular now, I think jQuery Mobile is much more friendly framework for people who may be new to JavaScript development in general. If I'm giving a course on Cordova, I know I can teach people the basics in 10-20 minutes and move on. I don't feel confident I can do that with",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Quick review of SumAll.com",
		"date":"Sun Dec 07 2014 04:20:29 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417926029,
		"url":"https://www.raymondcamden.com/2014/12/07/quick-review-of-sumall-com",
		"content":"For a few months now I've been using SumAll as a &quot;stat aggregator&quot; and I thought I'd share my experiences with it. I'm not necessarily super-involved with my &quot;brand&quot; (yes, I just threw up a little saying that), but I do care about how my web site performs and I like to have a basic idea of how much reach I have to the audience I care about. I've built an aggregator for Google Analytics before (Proof of Concept – Dashboard for Google Analytics) but that only handles web traffic.\n\nSumAll, a free service (with paid upgrades), works by connecting you to a variety of services. Everything from the expected stuff for social media and web (Google Analytics, Facebook, Twitter, etc) to the more personal (FitBit, RunKeeper, even banks).\n\nI'm not sure how often they add new sources, but I can say that I was just surprised by how many I saw. It definitely seems to be a lot more compared to when I first signed up. Each source can be added to one or more groups so you could organize your data as you see fit.\nAfter you've added a source (or multiple sources), SumAll will begin importing your data. How long it takes to prepare that data varies. This morning I added Google Analytics (why did I just recently do that - I'll explain below) and it took about twenty minutes before it was ready. On the flip side, I just added RunKeeper and it reported the data pretty much immediately.\nSumAll has a beautiful design. It summarizes source data in a very slim, very focused manner. It provides a quick way to switch date filters (today, this week, this month, or custom) and is just a pleasure to use. Here are a few random examples from my own stats.\nFirst - my Google Analytics:\n\nThen - my Twitter stats:\n\nAnd here is Google Plus and YouTube. YouTube was - in particular - a revelation for me. I tried to make more use of YouTube (and Google Hangouts) this year and while I didn't achieve as much as I wanted, I was surprised by how many views my YouTube videos were getting. I'm no Gangnam Style, but what I saw was enough to convince me to ensure I keep up the effort in 2015. To be clear, this is not anything I couldn't have found out myself, but having it aggregated directly in SumAll was incredibly helpful.\n\nEach stat (well I assume most stats) also has a detail view and a chart display as well. Here is the detail from my blog's Page View stats (and it was a rough week for me as you can tell by the downturn):\n\nAnd here is the chart view. Note that there seems to be a rendering issue with the chart being a bit off screen in Firefox. I just tested in Chrome and it was perfect, so I'll be filing a bug report on this.\n\nOn top of simply adding sources to your SumAll dashboard, there are a number of &quot;Power Ups&quot; as well.\n\nThese power ups (again, some free, some paid) provide additional functionality on top of just stat aggregation. For a while now (and you know this if you follow me) I've used the &quot;Performance Tweet&quot; that summarizes how I've done on Twitter over the past week with an auto tweet. Another example (included at the paid level), is &quot;Always Aware&quot;, which provides alerts when certain thresholds aren't made. For example, if you want to ensure you always tweet a certain amount every day, the power up can warn you if you haven't hit that threshold. I'm using the &quot;Email Digests&quot; power up to get an email report every week. (Sometimes I forget to go directly to the SumAll site.)\nYou can also download mobile apps for SumAll for both Android and iOS. I had difficulty with the iOS version. To log on to the mobile app you have to use a &quot;real&quot; password for SumAll. I had originally logged in via my Twitter account but that isn't sufficient for mobile access. That is not made clear and I just assumed the iOS app was broken. I randomly discovered a note about this on the SumAll Account page so I got lucky. I've already reached out to their support (and again, more on that in a bit) about making this more clear. I tried again with the Android app (after adding a real password on SumAll.com) and it worked great. I really like the mobile app. You can't add any sources there, which is fine, but it is a great &quot;viewer&quot; for what you've set up. Here are two screen shots I stole from Google Play:\n\n\nOverall, for a free service, SumAll kicks butt. I love it. But it isn't perfect. I've been trying to add Google Analytics support since I first signed up, but it has been broken until I tested this morning. I'm not sure why it took so long to get corrected, and as I've paid them nothing, I can't really complain too much (grin), but I'm happy it finally got corrected. I still run into odd bugs as I use the service and sometimes the site is down (of course, so is this site), so it has the feel of a new service still getting the kinks out.\nAbout the &quot;Premium&quot; level, I had some difficulty in finding out what the actual cost is. I went to a power up that was marked premium only, cli",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Update on migration and Disqus Thread Migration tool",
		"date":"Thu Dec 04 2014 02:43:43 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417661023,
		"url":"https://www.raymondcamden.com/2014/12/04/update-on-migration-and-disqus-thread-migration-tool",
		"content":"Just a quick note on the migration process. I'm still planning on talking about the migration in detail, but I want to wait till I feel confident that most of the bugs are gone and the server is stable. Yesterday my blog crashed pretty quickly after launch with a database connection error. I added the SuperCache plugin (which multiple people had recommended) and it seemed to work well, but sometime around 3AM this morning it went down again. Right now I'm thinking that maybe a micro instance (0.6 gig ram) is a bit too small and I should maybe jump up to the next higher level. I'm researching that now.\nAlso - I made a small tweak to the script I wrote (and blogged about here) that helps with Disqus migration issues. My previous script would return one row for every row in the input CSV. Now the code only writes to the new CSV when an actual change has happened. This makes the new CSV quite a bit smaller. I've pasted the code below, but don't forget the map UDF needs to be customized for your needs. Enjoy.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Welcome to the 2015 RaymondCamden.com",
		"date":"Wed Dec 03 2014 00:40:35 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417567235,
		"url":"https://www.raymondcamden.com/2014/12/03/welcome-to-the-2015-raymondcamden-com",
		"content":"Ok, so it technically isn't 2015 yet, but welcome to the new blog. Regular readers know I've been planning this for a few weeks now and this morning I decided to go forward with the update. I am 100% sure some things will break, and I ask for your patience while I work out a few kinks. I plan another post talking about the migration and what I did, but for now, welcome.\nAt the end of the day, I figure my readers care about what I say, not how I say it, so hopefully this will change won't impact that.\nHopefully I'll have some other big changes to announce soon as well. ;)\nOh - as an FYI, the RSS feed has been updated. Please change to the new URL.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "HTML5 (or HTML in general) book recommendations?",
		"date":"Mon Dec 01 2014 08:12:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417421520,
		"url":"https://www.raymondcamden.com/2014/12/01/HTML5-or-HTML-in-general-book-recommendations",
		"content":"\nA reader asked what I'd recommend in terms of a good book for learning HTML5 and after realizing I had no idea, I thought I'd post it here and let my smart readers (yes I'm buttering you up) have a say. To be clear, the reader is looking for books. In general I'd easily recommend the Mozilla Developer Network for this information online, but obviously you can't (shouldn't) print that out.\n\n\nSo, what do recommend?\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5"
            
		]

	},

	{
		"title": "Selecting a random record from an IndexedDB Object Store",
		"date":"Sun Nov 30 2014 04:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417320660,
		"url":"https://www.raymondcamden.com/2014/11/30/Selecting-a-random-record-from-an-IndexedDB-Object-Store",
		"content":"\nA few days ago I ran across an interesting post on Stack Overflow, Is there any way to retrieve random row from indexeddb? The posted answer used the following process:\n\n\n\nOpen an index.\nIterate over every row.\nOn the first iteration, use the key value as an upper bound, and select a random number from 1 to that number.\nIterate until you hit that number.\n\n\nWhile this worked, it seemed like a bad idea to iterate over - possibly - a huge number of rows to get to the random row you wanted. I double checked the spec and discovered what seems to be a simpler solution. When you have opened a cursor (and for those who don't know IDB very well, think of it as simply a way to iterate over a table), you can either continue to the next row, continue to a specific key, or use the advance method to go forward a specific number of rows.\n\n\nI figured advance would be a good way to handle this. I built a simple demo that demonstrates this. I won't bother showing the HTML as it is just two buttons (one to add some seed data and one to select the random value), but you can view source on the linked demo below if you want. Here is the JavaScript. Note - this code can definitely be rewritten to be a bit tighter.\n\n\n\nI assume we can skip the IDB setup and seed functions. I built the bare minimum so I could test the random aspect. The random selection code works by first doing a count on the object store. This returns - yes - the count. Once we have that, we open a cursor that would normally let us iterate over the entire store. On the first iteration it has the first row. We then ask for a random number. Our range starts at 0 because we want to support the first row being acceptable as well. Based on that number we advance X number of rows and return that result to the done function. If for some reason 0 was selected, we run done right away.\n\n\nNot rocket science, but it seems to work well. At most you run two iterations, not N, so it seems like it should be much more performant than the original answer on Stack Overflow. As I said, this was my first version so the code could definitely be organized a bit better. You can view the demo here: http://www.raymondcamden.com/demos/2014/nov/30/test1.html.",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Star Wars Teaser",
		"date":"Fri Nov 28 2014 04:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1417147860,
		"url":"https://www.raymondcamden.com/2014/11/28/Star-Wars-Teaser",
		"content":"\nI'm assuming most people who care have already watched this online, but I'm sharing it anyway. Today the teaser for the next Star Wars film launched and it is incredible. I know a lot of folks hated the prequels and will remind me that the first teaser for them was pretty exciting as well. I don't care. The prequels were not as cool as I had hoped, but frankly, I didn't mind. I enjoyed them. Not as much as the original series, which by the way, also had its groan-worthy moments, but I had a heck of a lot of fun just watching them. I'm thrilled by what I see here. The Stormtroopers, in particular, look to be bad ass. One of the best parts of the Prequels was seeing the Clone Troopers as an effective fighting force. I'd like to see them kicking butt again in this new series, especially if we get to see the Empire in the Rebellion's role from the classic series. Much smaller, much leaner, etc. Any way, enjoy.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "movies"
            
		]

	},

	{
		"title": "Disqus update (and BlogCFC export script)",
		"date":"Wed Nov 26 2014 08:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416989460,
		"url":"https://www.raymondcamden.com/2014/11/26/disqus-update-and-blogcfc-export-script",
		"content":"\nAs folks know, I've been working on transitioning to Disqus over the past week. I ran into multiple problems, and I made multiple mistakes, but today the process completed and I'm ready to share details about my BlogCFC export script as well as some tips for others who may be considering making the jump.\n\n\n\nI had two main issues when I did my import. The first was that some comments on my earliest blog entry didn't show up. This issue went away. I'm not sure why it did - but it was a minor issue compared to the second issue so I'm not concerned about it.\n\n\nThe second issue was the big one. When I did my import I discovered that my comments were not in the right order. This was because I screwed up my call to ColdFusion's timeFormat function. Yes - timeFormat. I've been using ColdFusion for about fifteen years and I made a rookie mistake there. (Actually I screwed it up twice which is even worse - but let's just pretend that I didn't.) This is where I ran into the problem with Disqus. When I reran my import, the changes were not reflected. I even went through the step of deleting all 6000+ of my previously imported comments, 25 at a time, to remove them and do my import. That didn't help either.\n\n\nTurns out there is no way to do replacements in Disqus. Period. They recommend you test your imports on a dev forum, but even that wouldn't be helpful if you screwed up. You would need to make multiple testing forums which is probably not desirable. Luckily the fix was easy enough. Disqus looks at the &lt;wp:comment_id&gt; tag value in your imported XML to determine the uniqueness of a comment. I was using the UUID from my database table. To get around the issue, I literally just prefixed \"m1_\" in front of the ID. (Why m1? I assumed I was going to screw up again.) I should note that some folks on Twitter also suggested this but I held off trying it until I got confirmation from Disqus that this would work.\n\n\nSo... for the most part, that was the end of it. I ran my script about 4 times - generating \"pages\" of data over my BlogCFC entry list. Disqus recommends creating XML files less than 50 megs big. From what I could see I would have been a bit over that if I had done them all at once, but for folks who want to use my code you can probably generate a complete export if your comment count is less than mine. Another thing to watch out for is errors. I had about 20 comments in my database that were blank in regards to the actual text. I don't know why. Disqus considered these errors (rightly so), and reported the import as an error... but only while it was processing. Here is a screen shot of what I'm talking about:\n\n\n\n\n\nDo you see how it says it only imported 900 or so? And see the error? This worried me but then I realized that it was still processing. The status seemed to imply a finished state but it was actually still digging through stuff. As I reloaded the number went higher and higher. (For folks curious, it took maybe 5 minutes to import 20K+ comments.)\n\n\nIf that UI in the screen shot doesn't match what you see in the Disqus import screen, that's because there is apparently two different places you can check imports. I was shown this url: http://import.disqus.com/group/FORUMNAME. This site seemed to provide slightly clearer reports so you may want to check it if you do a big import.\n\n\nI want to give huge thanks to Matt Robenolt of Disqus. As I said, I had trouble with the \"main\" Disqus support. They were somewhat slow. I found Matt via contacts on Twitter and he dug deep into the issue. He agreed that there probably needs to be a way to force a reimport so hopefully that will come in the future. \n\n\nFor those of you on ColdFusion and running BlogCFC, I've attached my script. It was written for ColdFusion 11 but you can backport it easily enough to earlier versions. If you use it and it works for you, please let me know in the comments below.\nDownload attached file.",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Early Access to My Next Book - Apache Cordova in Action",
		"date":"Tue Nov 25 2014 02:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416881460,
		"url":"https://www.raymondcamden.com/2014/11/25/Early-Access-to-My-Next-Book-Apache-Cordova-in-Action",
		"content":"\nHey - guess what? I'm working on a book. This time the publisher is Manning, and one of the cool things they do is allow early access to a book. You basically pay for it while it is being developed and you can provide feedback to help improve the text. Obviously you get corrected/new chapters as they come out as well. If this is appealing to you, you can head over to the book page now.\n\n\n\n\"Apache Cordova in Action\" will help readers learn how to use Apache Cordova. As you know, I'm an avid user of Cordova and PhoneGap. Sometimes the documentation can be a bit difficult to grasp. I think the docs are good, but there are a lot of different things developers need to synthesize in order to really get it. This book - I hope - will help tremendously in that area.\n\n\nCurrently three chapters are available, and you can read the first chapter (PDF) for free. The fourth should be released very soon with other chapters coming every two weeks or so.\n\n\nFor your perusal, here is the table of contents. This may change as the book is developed.\n\n\nPart 1: GETTING READY\n1 What is Cordova?\n2 Installing Cordova and the Android SDK\n\nPart 2: CORE CONCEPTS\n3 Creating Cordova Projects\n4 Working with Cordova Plugins\n5 Mobile Design and User Experience\n6 Considerations when Building Mobile Apps\n7 Creating Custom Plugins\n8 Debugging Mobile Apps\n9 Packaging Options\n10 Using PhoneGap Build\n\nPart 3: WHAT'S NEXT?\n11 Submitting to the App Stores\n",
		"tags":[
	        
		],
		"categories":[
            
                "books",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Status of Disqus updates, and a tool for URL migration",
		"date":"Sun Nov 23 2014 04:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416715860,
		"url":"https://www.raymondcamden.com/2014/11/23/Status-of-Disqus-updates-and-a-tool-for-URL-migration",
		"content":"\nWell, I'm disappointed to say the least. I've had good luck with Disqus pretty much everywhere I've used it, but my attempts to migrate my 50K+ comments over have been met with a great amount of difficulty. I'm not giving up, but hopefully my notes here will help others.\n\n\n\nI created a tool that read my database and exported comments in the XML format described by Disqus. I built it so it could do a \"slice\" of blog entries. My first test covered the first three blog entries ever posted and the second test covered the fourth entry plus a thousand more.\n\n\nWhen I had these imported, and Disqus got them in pretty darn quickly (they warn you it can take 24 hours, but for me it was no more than 5 minutes), I ran into two issues.\n\n\nThe first thing I noticed was that comments on my very first blog post did not show up. I looked in the Disqus admin and they were there. Clicking to go to that thread from the admin worked. But they didn't actually load on the page.\n\n\nThe second thing I noticed was that my order was wrong on a particular thread. I discovered that my export script had screwed up the time output. Totally my fault. I fixed it and resubmitted the XML. I noticed though that it wasn't updating.\n\n\nSo Thursday night I submitted both issues as support problems via the Disqus site. I got a reply around noon on Friday. For the \"missing comments\" issue I sent them the URL. For the \"replacing\" issue, their email seemed to imply that you couldn't replace comments. I asked for clarification.\n\n\nIn both cases, I replied within minutes of getting their email. I've heard nothing back since then. \n\n\nSo I thought - screw it. I went into the admin and began deleting comments. You can sort by oldest so I knew I was safe to just mass delete the old ones. You can delete 25 at a time. I did this for 6000+ comments. \n\n\nI resubmitted and... nothing. I seem to be unable to get these comments back in - even though they don't exist. I submitted another help request, but I guess they don't work the weekends. \n\n\nOk - so how about another issue? I found that some of my discussion threads had the wrong URL in them. This is because by default, Disqus uses window.location.href for the identifier. Their docs say you should not allow this default to happen, but if you don't know that, don't read carefully, etc, you miss that. I guess that's my fault too, but I really think Disqus should call this out. Heck, you only see this if you go into the JavaScript configuration variables page which is not the default. Disqus should really do a better job of pushing this on end users.\n\n\nSo luckily they have a URL migration tool. You can submit a CSV file of old URLs to new URLs so they can be migrated. Their docs suggest using their export tool to get the old URLs. That's fine and all but I've got almost 2000 old URLs in the system and guess what? The export can't be used for the import because it is only an old URL. You would have to copy and paste every URL so you have a valid CSV file. \n\n\nFine. So - I wrote a ColdFusion script to let me do this. You point it to an input file, tell it what to export, and modify a UDF that maps old URLs to new.\n\n\n\nI've done this - and submitted the CSV - but while comment imports worked pretty quickly, this seems to be taking longer. Also, Disqus doesn't provide a status for this feature. (They do for importing.)\n\n\nI know I'm a free user, but I'm really disappointed by the support from Disqus. At this point I'd pay to get technical support. I know once the import is done - and now that I've fixed the URL issue - I'll be good - but this delay is very frustrating. \n\n\nFinally, and I don't think this is really impacting anyone, but I am sorry for regular readers of this blog who may be having issues now!\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Check out FormKeep, another option for static sites",
		"date":"Fri Nov 21 2014 08:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416557460,
		"url":"https://www.raymondcamden.com/2014/11/21/Check-out-FormKeep-another-option-for-static-sites",
		"content":"\n\nOver the past year or so I've been playing around with static site generators. After nearly fifteen years spent building dynamic web applications on the server-side, a simple solution involving a static site generator can be pretty darn appealing. Unfortunately, moving to static means you need to find alternatives for things that simply can't be static. Forms, forums, calendars, etc. I discussed options to handle this problem in my article for modernweb, Moving to Static and Keeping Your Toys. Today I'm going to introduce you to another option I discovered yesterday, FormKeep\n\n\n\nFormKeep is a service that provides an end point for form submissions. They basically give you a place to point your forms, handle the processing, and then send your users to another page. It is incredibly simple but very useful. Let's look at a simple example. After signing up, you can quickly create your first form:\n\n\n\n\n\n\n\n\nAs you can see, you are given a form tag and one hidden form field. That's it. There are some suggestions for other fields, but at minimum, you just need those two. Now - to be clear - there is not a \"form builder\" here. After you copy and paste those two fields you will then need to build the rest of the form yourself. There is also no form checking built into the service. You would handle it client-side. If the end user disables that (and we all know end users can do that, right?) then FormKeep won't be able to prevent invalid submissions. \n\n\nBy default, form submissions will display a FormKeep page as a response:\n\n\n\n\n\nBut you can also easily input a return URL. Since their server simply outputs the URL to the browser to handle, you can use localhost as a test or a domain that resolves to 127.0.0.1 while you test. As an example, I used this: http://localhost/testingzone/trash/test2.html?done=1. That's the URL for my local server and the file I was using to test. Now let's look at the code I used.\n\n\n\nMy example just mimics a basic contact form. But since FormKeep is returning with something in the query string, I used a bit of JavaScript to handle displaying a thank you message when it exists. FormKeep's FAQ also links to this cool example that does something similar but with CSS alone. As we know though CSS is the work of the devil so I had to use JavaScript instead.\n\n\nBy default form submissions are sent to you via email. This works as you imagine:\n\n\n\n\n\nYou also get a decent little online viewer as well:\n\n\n\n\n\nTo be clear - just sending an email is actually just the simplest thing you can do. You can use a webhook URL to send form data to - well - anything really. That includes things like Salesforce, Campaign Monitor, etc. Email is just the default. If you can find a service that lets you ping data to it via a URL, you can set up FormKeep to hit it with your form data.\n\n\nFinally, there are two export options as well. You can dump everything out to CSV or to JSON via their \"API.\" I put API in quotes there because right now the API is just \"dump the whole thing to JSON\", but I'm assuming we'll see more in the future. At minimum it will need to accept date filters.\n\n\nPayment is kind of interesting. You basically pay what you want and get unlimited usage. Free usage will limit your dashboard view of data to the last ten entries, but that's certainly enough for testing. \"Near Free\" for personal users sounds like a pretty good deal to me.\n\n\nSo, check it out and let me know what you think in the comments below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "ColdFusion Startup Issue with Hostname",
		"date":"Fri Nov 21 2014 03:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416539460,
		"url":"https://www.raymondcamden.com/2014/11/21/ColdFusion-Startup-Issue-with-Hostname",
		"content":"\nSo - this is kind of crazy. Yesterday I fired up ColdFusion 11 to test something and discovered that every request for a CFM returned an error. The error was a Null Pointer Exception so not terribly helpful. (Or so I thought.) I checked the logs and saw this:\n\n\n&lt;pre&gt;\nCould not determine local hostname.\njava.lang.NullPointerException\n    at coldfusion.runtime.RuntimeServiceImpl.getQueueLimit(RuntimeServiceImpl.java:2145)\n    at coldfusion.runtime.RuntimeServiceImpl.load(RuntimeServiceImpl.java:487)\n&lt;/pre&gt;\n\nSo I googled some more and came across this post on the forums: ColdFusion 10 install on RHEL 6.1. I looked closely at the exception reported by the user there and noticed it matched what I was seeing on my server, specifically this part: org.apache.catalina.authenticator.AuthenticatorBase.invoke.\n\n\nIf you read that post, you will see that Rupesh (part of the ColdFusion team) says this:\n\nThe license service makes use of InetAddress.getLocalHost() API and therefore your /etc/hosts file should have an entry for both localhost as well as for your host name.\n\nWell, my hosts file definitely had an entry for localhost, but on a whim, I added my hostname. In terminal I typed hostname (total guess, I had no idea that was a valid function) and it reported Raymonds-MBP-8. I added that to my hosts file, restarted ColdFusion, and everything was gravy.\n\n\nI have absolutely no idea why this happened yesterday, but hopefully it will help others if they run into the same problem. I am having issues with my router where DNS lookups and other things will fail from time to time. So maybe something on my machine got hosed network-wise enough to confuse ColdFusion. Of course, I don't even have a license for my local ColdFusion 11 server so why in the heck did it need to check a license server?\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Blog Migration Update",
		"date":"Thu Nov 20 2014 12:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416485460,
		"url":"https://www.raymondcamden.com/2014/11/20/Blog-Migration-Update",
		"content":"\nJust a quick note. I've mentioned that I'm in the process of migrating the blog to a new platform (Wordpress). As part of that migration, I've went ahead and switched to Disqus. I'm still working on my export script (I'll share that when done) and since it will take up to 24 hours for the data to migrate (when I actually get the migration script even done!) I thought I'd take the plunge and enable it now.\n\n\nHowever - that leaves me with having to display both the \"old\" comments (all 50,000+ of them) along with Disqus. I've done that now. Basically I just added the embed and removed the \"Add Comment\" form. I also put a little note so it was (hopefully) a bit obvious as to what in the heck was happening.\n\n\nI know some folks aren't a fan of Disqus, but, going forward, this helps me make the blog a bit more migratable. (That's not a word and my editor is going to fuss at me. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Determining installed plugins at runtime for Cordova and PhoneGap applications",
		"date":"Wed Nov 19 2014 08:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416384660,
		"url":"https://www.raymondcamden.com/2014/11/19/Determing-installed-plugins-at-runtime-for-Cordova-and-PhoneGap-applications",
		"content":"\nEarlier today on Twitter a user asked an interesting question: How can I tell - via JavaScript - if a particular plugin is installed in a Cordova/PhoneGap application. I responded by asking how you wouldn't know since it is your own app, but then he mentioned that his code base was stand alone and would be used within other projects. (So basically - just JavaScript code that other Cordova/PhoneGap applications would use.)\n\n\n\nThere are a couple of different ways to handle this. The first, and simplest, is to look for the \"hook\" the plugin adds. For example, the barcode plugin adds methods to a cordova.plugins.barcodeScanner object. It would be trivial to see if that exists. Cool, problem solved, right?\n\n\nWell in his case he was using InAppBrowser. This plugin simply modifies window.open so it isn't something you can really (as far as I can see) introspect. I did some more digging and found something interesting.\n\n\nIf you look at the platform build version of your www code, you will notice it includes a cordova_plugins.js file:\n\n\n\n\n\nIf you open it up, you will see a list of any plugins you have installed. I opened up cordova.js (honestly, I don't look at it often, but I should) and saw that cordova_plugins.js was being loaded in dynamically and parsed. My assumption is that this has to happen before deviceready fires so you can safely use any plugins you've got installed. On a whim then I tried the following code inside my deviceready:\n\n\n\nThe metadata part came from what I saw in cordova_plugins.js. While the rest of the file has random stuff based on the plugins installed, metadata appears to be just a list of your plugins. I confirmed that this worked well:\n\n\n\n\n\nSo - that's it. I should note that I spoke with Shazron and he mentioned that if you used browserify, it might mess with how the JS file is generated. I'd say use with caution and let me know (via the comments) how it works for you.\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Using the autodivider feature in jQuery Mobile (take two)",
		"date":"Tue Nov 18 2014 10:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416305460,
		"url":"https://www.raymondcamden.com/2014/11/18/Using-the-autodivider-feature-in-jQuery-Mobile-take-two",
		"content":"\nAlmost a year ago I blogged about using the autodivider feature in jQuery Mobile. This is a simple feature that enhances list views with dividers. It makes content a bit easier to parse when working with a large list. \n\n\nOne of my readers, Fher (who has a cool name - I kind of imagine her/him as a fire-breathing wolf), asked if there was a way to add a bubble count to the dividers. You can see an example of this on the docs for listview, but this is what the feature looks like:\n\n\n\n\n\nSo, the short answer is no, you can't do this with autodividers. Why? While the feature allows you to build a function to create dynamic dividers, it only lets you specify the text for the divider, not random HTML. However, if you are willing to give up having the \"pretty bubble\" effect, you can simply use it as part of the label. To make that work, I modified my code a bit from the previous demo (and again, you can read that here, I'd suggest checking it out just so you can see the context). Here is the complete JavaScript code. (The HTML didn't change.)\n\n\n\nThe first change was to abstract out the code used to generate the divider - basically turning the date value into a label. Once I have that, I iterate over my data to figure out how many unique date labels I have. This is done with a simple object and a counter. Finally, my autodividersSelector function is modified to make use of this count. Here is the result.\n\n\n\n\n\nThere you go. Not exactly rocket science, but hopefully helpful. It is possible to create dividers with list bubbles, just not quite as simply as this entry demonstrates. I'll show that tomorrow.\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "jquery",
            
                "mobile"
            
		]

	},

	{
		"title": "Looking for Suggestions: Best Conferences for Mobile/Web Development",
		"date":"Tue Nov 18 2014 05:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1416287460,
		"url":"https://www.raymondcamden.com/2014/11/18/Looking-for-Suggestions-Best-Conferences-for-MobileWeb-Development",
		"content":"\nEarlier today my buddy Andy Trice posted a question about conferences:\n\n\nWhat are the best conferences are for mobile and/or web development? native, hybrid, etc... all OK-need help finding best ones (pls RT!)&mdash; Andrew Trice (@andytrice) November 18, 2014\n\n\nI RTed it to help spread the word, but I thought I'd blog it here as well and see if folks could share their thoughts in the comments. This will hopefully reach a greater audience and let people have a bit more room to comment. If you manage a conference, I don't mind you sharing, just be sure to mention that in the comment.\n\n\nAs it stands, I was avoiding a lot of conferences over the past six months because of my adoptions, so I'm hoping to attend/present at more next year. You would be helping me as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Cordova's copy-from tip",
		"date":"Fri Nov 14 2014 04:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415938260,
		"url":"https://www.raymondcamden.com/2014/11/14/Cordovas-copyfrom-tip",
		"content":"\nYesterday I was proof-reading a blog post about an update to the PhoneGap CLI (which you should read - PhoneGap CLI 3.6.3) and I discovered something interesting. For a while now the Cordova CLI has had the ability to create a new project based on another. This is great because the default Cordova/PhoneGap application annoys the heck out of me. \n\n\n\nYou can see this feature by typing cordova help create. Here is how the feature is documented:\n\n\n--copy-from|src= ... use custom www assets instead of the stock Cordova hello-world.\n\n\nOk, nice and simple, right? So I built my own skeleton application with a minimal amount of code and just carried on my happy way. While reading that article about PhoneGap's update though I discovered this feature actually does two things.\n\n\nIf you point --copy-from at a folder that is not a Cordova application, it will copy the assets into the www folder of your new project.\n\n\nIf you point --copy-from at a folder that is a Cordova application, it will copy the www folder, the hooks folder, and the config.xml file. I had no idea that this was supported, and while I probably won't use the feature that often, it is good to know it exists. (And I'm going to file a bug report right now for the Cordova CLI to update the help text.)\n\n\np.s. As a reminder, definitely read that article about the PhoneGap CLI update. I primarily use the Cordova CLI but the PhoneGap one now has some features that Cordova does not. One of them I really like - if you try to run a platform that doesn't exist, it will simply add it for you. Nice. \n",
		"tags":[
	        
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Test with Extract",
		"date":"Thu Nov 13 2014 10:05:56 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415873156,
		"url":"https://www.raymondcamden.com/2014/11/13/test-with-extract",
		"content":"This is just a test of my stuff.\nI'm hitting enter now.\nHow do I do an extract?\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "Any WordPress users familiar with BlogCFC?",
		"date":"Thu Nov 13 2014 09:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415869860,
		"url":"https://www.raymondcamden.com/2014/11/13/Any-WordPress-users-familiar-with-BlogCFC",
		"content":"\nA long time ago a reader shared a BlogCFC to WordPress script. I'm assuming it is too far out of date to be useful. Has anyone converted a modern BlogCFC database to WordPress 4.0? I'm assuming I can figure it out myself, but if someone has a script handy, I'd appreciate it.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Hello world!",
		"date":"Thu Nov 13 2014 07:39:01 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415864341,
		"url":"https://www.raymondcamden.com/2014/11/13/hello-world",
		"content":"Hi, this is just a test.\n",
		"tags":[
	        
		],
		"categories":[
            
                "uncategorized"
            
		]

	},

	{
		"title": "ColdFusion Example: Using jQuery UI Accordion with a ColdFusion query",
		"date":"Wed Nov 12 2014 03:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415761860,
		"url":"https://www.raymondcamden.com/2014/11/12/ColdFusion-Example-Using-jQuery-UI-Accordion-with-a-ColdFusion-query",
		"content":"\nA reader pinged me yesterday with a simple problem that I thought would be good to share on the blog. He had a query of events that he wanted to use with jQuery UI's Accordion control. The Accordion control simply takes content and splits into various \"panes\" with one visible at a time. For his data, he wanted to split his content into panes designated by a unique month and year. Here is a quick demo of that in action.\n\n\n\nI began by creating a query to store my data. I created a query with a date and title property and then add up to three \"events\" over the next twelve months. I specifically wanted to support 0 to ensure my demo handled noticing months without any data.\n\n\n\nTo handle creating the accordion, I had to follow the rules jQuery UI set up for the control. Basically - wrap the entire set of data in a div, and separate each \"pane\" with an h3 and inner div. To handle this, I have to know when a new unique month/year \"block\" starts. I store this in a variable, lastDateStr, and just check it in every iteration over the query. I also need to ensure that on the last row I close the div.\n\n\n\nAnd the end result:\n\n\n\n\n\nSo, not rocket science, but hopefully helpful to someone. Here is the entire template if you want to try it yourself.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "Good post on Cordova, PhoneGap, and versioning",
		"date":"Mon Nov 10 2014 12:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415621460,
		"url":"https://www.raymondcamden.com/2014/11/10/Good-post-on-Cordova-PhoneGap-and-versioning",
		"content":"\nThis is from last week but it is still pretty important. Holly Schinsky wrote an excellent article about versioning and Cordova/PhoneGap. Things have gotten slightly more complex in this area and I strongly recommend reading her blog post. She really clears things up.\n\n\n\nCordova/PhoneGap Version Confusion\n",
		"tags":[
	        
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "Random code I'm sharing for no good reason",
		"date":"Sun Nov 09 2014 16:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415549460,
		"url":"https://www.raymondcamden.com/2014/11/09/Random-code-Im-sharing-for-no-good-reason",
		"content":"\nOk, the title should be your warning. I'm only posting this because it is Sunday night and I'm bored. I'm working on a demo for my jQuery video (did I mention I'm working on a jQuery video?) that mimics a typical car dealership inventory search. As we just upgraded our car I'm pretty familiar with these. The demo will focus on building the UI to create a search engine that can filter cars by model, trim, color, price, and features. In order to actually have something to search against, I wrote a script that creates an inventory of cars. Here is that script. Enjoy.\n\n\n\n\nIn case you're curious, I'm going to write a simple module that wraps calls to this data so that I don't have to use an application server to serve it up.\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Cordova and Asset Downloads",
		"date":"Fri Nov 07 2014 10:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415355060,
		"url":"https://www.raymondcamden.com/2014/11/07/Cordova-and-Asset-Downloads",
		"content":"\nA few weeks ago (before I thought it would be a good idea to fly to China for a few weeks and dramatically increase the size of my family), I blogged about how a Cordova application could handle downloading binary assets after release. (You can find the discussion here: Cordova and Large Asset Downloads - An Abstract.) I finally got around to completing the demo.\n\n\n\nBefore I go any further, keep in mind that this demo was built to illustrate an example of the concept. It isn't necessarily meant to be something you can download and use as is. Consider it a sample to get you inspired. Here is how the application works.\n\n\nFirst off, we don't do anything \"special\" until we need to, so the home page is just regular HTML. I'm using jQuery Mobile for this example (don't tell Ionic I strayed). \n\n\n\n\n\nClicking the Assets button is what begins the thing we want to demonstrate. When this page loads, we need to do a few things. First, we check the file system to see if we have any downloaded assets. If we do, we display them in a list. If not, we tell the user. \n\n\n\n\n\nAt the same time, we do a \"once-per-app\" hit to a server where new assets may exist. In my case I just added a JSON file to my local Apache server. This JSON file returned an array of URLs that represent new assets. For each we compare against the list of files we have (and on our first run we will have none), and if we don't have it, we use the FileTransfer plugin to download it. \n\n\nThe next time the user views that particular page, they will see a list of assets. In my demo they are listed by file name which may not be the best UX. You could return metadata about your assets from the server to include things like a nicer name.\n\n\n\n\n\nTo wrap up my demo, I used jQuery Mobile's popup widget to provide a way to view the assets. (Note: I've got an odd bug where the first click returns an oddly placed popup. The next clicks work just fine. Not sure if that is a jQuery Mobile bug or something else. Since it isn't really relevant to the topic at hand, I'm going to drink a beer and just not give a you know what.)\n\n\n\n\n\nOk, so let's take a look at the code. \n\n\n\nThere is a lot going on here so I'll try to break it into somewhat manageable chunks.\n\n\nThe core code is in the pageshow event for the download page. (The page you see when you click the assets button. I had called it downloads at first.) This event is run every time the page is shown. I used this instead of pagebeforecreate since we can possibly get new assets after the page is first shown.\n\n\nAs mentioned above we do two things - check the file system and once per app run, hit the server. The file reader code is abstracted into a method called getAssets. You can see I use a promise there to handle the async nature of the file system. This also handles returning a cached version of the listing so we can skip doing file i/o on every display. \n\n\nThe portion that handles hitting the server begins inside the condition that checks globals.checkedServer. (And yeah, using a variable like globals made me feel dirty. I'm not a JavaScript ninja and Google will never hire me. Doh!) For the most part this is simple - get the array and compare it to the list we got from the file system. When one is not found, we call a function, fetch, to handle downloading it.\n\n\nThe fetch method simply uses the FileTransfer plugin. It grabs the resource, stores it, and appends it to the list of assets. This is what is used for the page display. One issue with this setup is that we will not update the view automatically. You have to leave the page and come back. We could update the list, just remember that it is possible that the user left the page and went do other things in your app. I figured this was an implementation detail not terribly relevant so I kept it simple.\n\n\nSo that's it. Thoughts? You can find the complete source code for this here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/asyncdownload\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "List auto-dividers in Ionic",
		"date":"Thu Nov 06 2014 11:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415272260,
		"url":"https://www.raymondcamden.com/2014/11/06/Autolist-dividers-in-Ionic",
		"content":"\nThis question came up on StackOverflow recently and I took a stab at answering it. The user had a list of data that included dates. They were looking to see if there was an easy way to add dividers to the list automatically. jQuery Mobile actually supports this out of the box - and supports it well. By default it will use the first letter of your list data as a separator (so \"Andy\" and \"Al\" will be prefixed with an \"A\" divider) but you can also use a function to specify your own logic.\n\n\n\nWhile there is no similar feature in Ionic (I filed this issue for it) it isn't too difficult to build it manually. The CodePen I built handles it like so.\n\n\nFirst, the controller code modifies the array to include an item for each necessary divider.\n\n\n\nThat feels lame - I mean modifying the list - but I'm doing it in my controller and not the back end service so I can still sleep at night. The next change is to the front end. (And the Ionic team deserves credit for this as I modified this from one of their examples.)\n\n\n\nBasically - since a list divider is simply a class change, we can iterate over our array and include the class when the letter property exists in the data. I don't like the fact that I use person.name when I'm doing a letter, but, it works. I really think smarter people than me can do this nicer, so please feel free to fork the CodePen and make it nicer!\n\n\nCode Pen: List Test - Autodividers\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Cordova Example: Writing to a file",
		"date":"Wed Nov 05 2014 03:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415157060,
		"url":"https://www.raymondcamden.com/2014/11/05/Cordova-Example-Writing-to-a-file",
		"content":"\nAs you know, lately I've been publishing simple Cordova examples that involve the file system. I'm working on a demo that involves background asset downloads (see the blog entry) but I thought I'd take a break from that and write up something super simple, but hopefully helpful, that demonstrates file writing.\n\n\n\nWith that in mind I built a demo that writes to a log file. The idea being that your app may want to record what it is doing. Normally you would do that via XHR to a server, but logging to a file ensures it will work offline as well. And perhaps you don't really need the data on your server but just want a log you can check later if things go wrong. Let's take a look at the code bit by bit.\n\n\nThe first thing I need to do is get a handle to the file. I'm going to use a file inside cordova.file.dataDirectory, which is an alias to an application-specific folder with read/write access.\n\n\n\nresolveLocalFileSystemURL converts the alias path into a directory entry object. That has a variety of methods but the one we care about is getFile. Note the create:true flag. This ensures that the file will be created the first time it is run. I also copy the file object into a variable logOb that is global to my application. Finally I call writeLog. That's my utility function. Let's look at that next.\n\n\n\nSo first off, if logOb wasn't created, we simply return. My thinking here is that log file writing is not required for the application. I want to silently ignore if we couldn't write to the file system for whatever reason. I modify the input a bit (adding a timestamp and a newline) and then begin the write operation. This particular code was taken right from the HTML5Rocks article on the FileSystem API. It uses seek to append as opposed to overwrite the file. \n\n\nWith this in place I could then add calls to the log utility from my application. Since my \"application\" is just a demo, I added two buttons that do nothing but log.\n\n\n\nThe final thing I did was add a special function that would read the file out and send it to console. I did this just for testing.\n\n\n\nTo see this in action, I used GapDebug and my iOS simulator. That let me run justForTesting right from my browser. In the screen shot below, note that the beginning of the file isn't formatted right. That's because I forgot the newline initially.\n\n\n\n\n\nI hope this helps. You can find the complete source in my GitHub repo with the rest of my demos: https://github.com/cfjedimaster/Cordova-Examples/tree/master/writelog\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Syncing Brackets extensions across multiple machines",
		"date":"Tue Nov 04 2014 05:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1415077860,
		"url":"https://www.raymondcamden.com/2014/11/04/Syncing-Brackets-extensions-across-multiple-machines",
		"content":"\nThis really isn't a new tip, but as someone just asked on Twitter for a quick explanation, I thought I'd write it up. If you want to sync Brackets extensions across multiple machines, the easiest way to do it is with Dropbox, or a Dropbox-like service. As long as it creates a physical folder on your machine, you can simply store your extensions there (for me it is /Users/ray/Dropbox/BracketsExtensions) and then create a symlink between that folder and the folder Brackets uses for extensions. What folder is that?\n\n\n\nIn the Bracket's Help menu, simply click \"Show Extensions Folder.\"\n\n\n\n\n\nMake a note of that path, drop into Terminal, or CMD (and yes, Windows can do symlinks), and make the connection.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development"
            
		]

	},

	{
		"title": "Using PhoneGap Build? Check out the new support forum",
		"date":"Mon Nov 03 2014 04:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1414987860,
		"url":"https://www.raymondcamden.com/2014/11/03/Using-PhoneGap-Build-Check-out-the-new-support-forum",
		"content":"\nEdit: I'm sorry folks - it looks like this forum is not released yet. I'll edit the post when it is.\n\n\nThe title pretty much says it all. :) If you use PhoneGap Build, there is a new support site located here: https://forums.adobe.com/community/phonegap\n\n\nAnd because I feel weird posting a blog entry that is so short, here is a random image from placekitten.com:\n\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "mobile"
            
		]

	},

	{
		"title": "self.getFamily().addOne().addOne()",
		"date":"Sat Nov 01 2014 14:11:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1414851060,
		"url":"https://www.raymondcamden.com/2014/11/01/selfgetFamilyaddOneaddOne",
		"content":"\nAs I've mentioned almost every year, November is \"National Adoption Month.\" My wife and I have grown our family via adoption. It is an incredibly wonderful experience and one I'd recommend to anyone. It isn't easy, but, nothing about being a parent is, right? If you've ever considered adoption then this would be a great time to look deeper into the process. You can start at Adoption.com or check with your adoption agency. There are multiple different ways to adopt, or foster, and plenty of opportunities to help a child in need. My wife and I chose international adoption (South Korea and China), but you can adopt domestically as well of course. (And yes, people adopt American kids from outside the USA.) \n\n\n\nYou can probably guess what I'm going to say next. A few weeks ago I mentioned I'd be too busy to post for a while and then I dropped a few obvious hints. Yes - we adopted again. We flew to Guangzhou, China on October 17th. On the 20th we met our two new daughters. Yes, two. Because we're crazy. Just a bit. We have one new three year old and a twelve year old. Adopting an older child has its own special issues but it is something I've felt we needed to do since we began adopting.\n\n\nWe landed back in America yesterday morning and while we have a lot of work ahead of us, I don't think our family could be any better. \n\n\n\n\n\nI apologize again for having to drop out of the ColdFusion conference and PhoneGap Day, but I think I had a pretty good excuse. ;)\n",
		"tags":[
	        
		],
		"categories":[
            
                "adoption"
            
		]

	},

	{
		"title": "My Modern Web Conference presentation is now available online",
		"date":"Wed Oct 29 2014 12:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1414584600,
		"url":"https://www.raymondcamden.com/2014/10/29/My-Modern-Web-Conference-presenation-is-now-available-online",
		"content":"\nAs the title says, my presentation from the Modern Web Conference is now available to watch online. I've embedded it below. You can find the complete list of videos here.\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Beautiful",
		"date":"Fri Oct 24 2014 23:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1414192200,
		"url":"https://www.raymondcamden.com/2014/10/25/Beautiful",
		"content":"\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Yesterday",
		"date":"Thu Oct 23 2014 12:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1414066200,
		"url":"https://www.raymondcamden.com/2014/10/23/Yesterday",
		"content":"\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "OOO for a bit so why not say hi to each other?",
		"date":"Thu Oct 16 2014 11:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1413457800,
		"url":"https://www.raymondcamden.com/2014/10/16/OOO-for-a-bit-so-why-not-say-hi-to-each-other",
		"content":"\nAs I despise email auto responders I'll use this as my official OOO (Out Of Office) notification for the next two weeks. I do not expect to be blogging at all. I probably won't be on Twitter. Emails will probably pile up as well. Everything will be back to normal(ish) around November 1st. In the meantime...\n\n\n\n\n\n\nEvery year or so I do a survey of my readers to find out what they are interested in, how well I'm meeting those needs, etc. I thought it might be kind of cool if my regular readers would like to introduce themselves to each other. For those who feel comfortable doing so - introduce yourself, say what you do, and have a nice little chat going as OT as you would like. \n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Thoughts on Integrating Ionic into an Existing Application",
		"date":"Thu Oct 16 2014 05:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1413436200,
		"url":"https://www.raymondcamden.com/2014/10/16/Thoughts-on-Integrating-Ionic-into-an-Existing-Application",
		"content":"\nEarlier this week a user asked me a question about integrating Ionic into an existing application.\n\n\n@raymondcamden - Do you have a resource which documents how to integrate ionic into a current Cordova/PG project ?&mdash; Brian Hamana (@MobileWebApp) October 15, 2014\n\n\nI had some thoughts on this and spent some time talking to Holly Schinsky as well about the topic. Here are some things to consider in no particular order.\n\n\nFirst, remember that Ionic is, at the simplest level, an Angular application with CSS and directives. These directives both simplify the use of the CSS and provide various UX features like \"Pull to Refresh.\" The Ionic \"family\" (not really a term they use) contains more than that, stuff like the CLI improvements and their visual creator, etc., but we're focusing this discussion on just the end result - the application.\n\n\nOne option would be to just use the CSS. That would allow you to keep your current application as is and just update styling where appropriate - changing classes to ULs and DIVs etc to match the Ionic way of doing things. That won't give you any of the directives or UX stuff, but it is an option.\n\n\nAnother option would be to migrate your code completely. This could be a huge undertaking. If your current application isn't using an MVC framework of any sort then you're going to have to do a lot of breaking stuff up. I think that's an improvement in general so it is time well spent, but you should be prepared to spend that time. \n\n\nAnd obviously if you don't know Angular going into it, you must spend some time getting familiar with it. I am very much an Angular Newbie. I can build... things... but I have lot to learn. With that being said, I feel like I know enough to do cool stuff with Ionic. But I would not recommend trying to use Ionic with no existing Angular skills. I think one day spent doing Angular's tutorial and perusing the docs will give you at least enough context to look at Ionic, but you will want to plan time to get up to speed with Angular in general.\n\n\nSo what if you are using an existing MVC framework, like Backbone? I'm a bit rusty with Backbone but I had thought that this could perhaps make things a bit easier. You have code split into controllers and services anyway, right? But this is where Holly set me straight. She reminded me that Backbone is very different from Angular. I'm going to quote her here:\n\n\n\nangular is DOM extension\n\n\nbackbone is less rigid\n\n\nangular, you have to follow certain patterns and ways of doing things, backbone you can use loosely\n\n\n\nSo it may not be easier at all if you are switching from Backbone. It may be worthwhile to google \"Backbone to Angular\" or Ember, etc. That particular part of the process will apply to Ionic.\n\n\nDo folks have any opinions on this? Please share below.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript"
            
		]

	},

	{
		"title": "Nunjucks templating by Mozilla",
		"date":"Wed Oct 15 2014 05:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1413349800,
		"url":"https://www.raymondcamden.com/2014/10/15/Nunjucks-templating-by-Mozilla",
		"content":"\nThis is mainly just a FYI type post, but earlier this week I discovered Nunjucks, a client-side templating language by Mozilla. I've been pretty much sold on Handlebars as my template language, but Nunjucks has a lot going for it too. Out of the box it seems to support a lot more than Handlebars (inheritance and asynchronous support for example) and the template synax is as friendly as Handlebars'. \n\n\n\nOf course, my only real requirement for template syntax is to not suck as bad as Jade but that's just me.\n\n\nIt supports client-side (obviously) and server-side (for Node) so it's ready to go pretty much anywhere. I'd love to see this supported in Harp in the future.\n\n\nIf you want to take it for a test drive, I built an online Nunjucks tester here: http://www.raymondcamden.com/demos/2014/oct/15/test.html. You can't test everything there of course (inheritance for example), but you can quickly test out some of the syntax and see how it feels.\n\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "Delaying an Edge Animate asset until visible - Part 6",
		"date":"Tue Oct 14 2014 11:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1413285000,
		"url":"https://www.raymondcamden.com/2014/10/14/Delaying-an-Edge-Animate-asset-until-visible-Part-6",
		"content":"\nWelcome back to the thread that won't die. I've blogged (see related links below) about this topic six times now. It started off as something simple - making an Edge Animate animation wait to run until visible - but it has turned into a pretty complex set of entries discussing not only how to do it but alternatives and other modifications. Today's entry is rather simple though as it just covers updates for the October 2014 release of Edge Animate.\n\n\n\nReader @jdesi posted a comment this morning about an issue he was having with my code in the latest release of Edge Animate. (You can read details about that update here: Edge Animate reduces runtime size by 55%, \"Save to Custom Folders\" feature, new Preloader options, and more!) I did some digging and discovered a few different issues with my code.\n\n\nBefore I go any further, please note that I worked on a modified form of the first demo I built for this feature. My later entries in this thread made the behavior a bit more complex. I'm assuming people can apply the updates I describe below to those versions as well.\n\n\nThe first thing I discovered is that jQuery is no longer included by default in the HTML template. This is discussed in the blog entry I linked to above and while I could have certainly worked around needing jQuery, it was simpler to just add it back in. I did so in the index.html file and included it before the Edge JavaScript include.\n\n\nThe next thing I noticed was that sym.element wasn't available. I checked the (updated) JavaScript API and saw that a new API existed: sym.getSymbolElement\n\n\nThe next change was a bit more subtle (but still documented!) - the element will now be wrapped in jQuery, if you have included it. From the docs:\n\n\n\"Note: If you have added jQuery as an external dependency in the Edge Composition, then sym.getSymbolElement() will return a jQuery wrapper, as AdobeEdge.$ gets redefined to jQuery in such cases. You can use any of the jQuery APIs on the result in this case.\"\n\n\nSo with that being the case, the method I wrote to check if the element was in view was able to remove the $ wrappers. Here is the updated version of the code.\n\n\n\nYou can test this version here: http://www.raymondcamden.com/demos/2014/oct/14/test.html. As a reminder, this one won't pause if you scroll out and won't restart if you scroll back in. That's covered by later versions of my demo and can be used if you simply apply the fixes described here to them. Enjoy!\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "jquery"
            
		]

	},

	{
		"title": "My review of Ionic's Visual Application Builder",
		"date":"Fri Oct 10 2014 09:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412932200,
		"url":"https://www.raymondcamden.com/2014/10/10/My-review-of-Ionics-Visual-Application-Builder",
		"content":"\nLet me begin by saying that - like most developers I think - I have a pretty strong distrust for visual builders for applications. I've been burned by too many tools that create something pretty on screen but generate a horrible mess of code behind the scenes. I think there are definitely tools that do a good job of it now, but once you've been scarred by seeing div tags nested twenty layers deep, you get a bit sensitive. I've recently become a bit more open minded about it. XCode, in general, has a very powerful UI building metaphor to it and I kinda dig how the Android tools handle it in Eclipse as well. Now that you understand how I approach these tools, let me talk to you a bit about the upcoming Ionic Creator tool from the folks behind Ionic.\n\n\n\nIonic Creator is currently in private beta. For a while now folks have been able to sign up for testing and the Ionic team is inviting new batches of people at regular intervals. Earlier this week I was invited to the beta and gave it a spin. When you fire up the tool for the first time, you're prompted to create a new project. You can specify a starter page based on a few preset templates. This list is a bit bigger than what the CLI provides but for the most part I think you can figure out what each does.\n\n\n\n\n\nOnce your project is created you're provided with a blank slate for your first page. \n\n\n\n\n\nAt the time of this review, there was a pretty serious bug where the project would load the page in an invalid state. If you try to do anything at all now, for the most part, it won't work. The clue is the lack of a header on the page. If you see this, hit reload, and notice how it changes:\n\n\n\n\n\nIt is a small thing, but I ran into this whenever I loaded, or changed, projects. Once past this then it is a simple matter to begin dragging and dropping components onto the page. Components include buttons, cards, images, lists, and form elements. You can also drop in an HTML or Markdown component for free form typing. Currently the application will not let you drop a form element onto the page unless you put it inside a form component. That's good, but there isn't any feedback as to why you can't drop the component. I've already filed an issue suggesting they provide some feedback. Here is my attempt to add some basic controls to the page.\n\n\n\n\n\nNotice that the upper left hand corner is used as a simple tree view. You can select items there to edit properties or select them on the page itself. Each component has different things you can modify. As an example, it is pretty easy to modify the list:\n\n\n\n\n\nYou can also select items on the page and move them up. Creator intelligently handles \"collections\" so moving the list will also move the list items. Adding new pages is also a simple matter:\n\n\n\n\n\nEach page has a name and a \"routing url\". You can then specify that a button, for example, will link to another page. I created a new page called Other and kept the default routing url of /page2. I went back to my first page, selected one of the buttons, and set it to load up the new page:\n\n\n\n\n\nSo for the most part, that's it for the 'drag/drop' aspect. You can setup the components as you see fit and create as many pages as you would like. Once you're done playing around with the widgets, you can easily test it directly in the web page itself:\n\n\n\n\n\nI haven't tested the preview mode very hard, but basic links do work just fine:\n\n\n\n\n\nOk, so, that's not bad. But what about the code? Creator provides three different ways to get to the bits. Clicking Export brings up your choices:\n\n\n\n\n\nThe first one simply provides you with a unique ID to use at the command line. This is probably the one most folks will use. As far as I know this is a one way street. You can't push back changes you made locally to Creator, but honestly there is probably no good way to handle what people would send back in. Obviously the first line, npm install doesn't make sense if you already have the CLI installed. There should probably be a note there warning folks they don't need to reinstall Cordova and the Ionic CLI. Then again, the audience for this is probably more on the newbie side and may include people who haven't ever used the CLI.\n\n\nThe Zip File option is just that, a zip of your code. Finally, the Raw HTML is a version of your application. I say version because they combine the HTML and JavaScript into one file to make it easier to cut and paste. I see the logic of that but it still bugs me a bit. ;) I just noticed that the zip version is also one file. Again, I don't like that, but that's just me.\n\n\n\n\n\nIf you source a new project via the code given in the export prompt you get a 'proper' Ionic project that you can immediately begin using. Oddly, some of the application logic is in index.html as opposed to app.js, but that may be a personal preference type thing. Ignoring that though you can go from the visual builder to your simulator in seconds - yep - seconds:\n\n\n\n\n\nSo, final verdict? I'm not sure this",
		"tags":[
	        
		],
		"categories":[
            
                "design",
            
                "development",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Cordova, the Camera plugin, AngularJS, and Ninja Cats.",
		"date":"Fri Oct 10 2014 04:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412914200,
		"url":"https://www.raymondcamden.com/2014/10/10/Cordova-the-Camera-plugin-AngularJS-and-Ninja-Cats",
		"content":"\nJust a random tip for folks who may run into this in the future. I'm working on a mobile app for a client and I'm using both Cordova and AngularJS. The application allows people to select a photo from their gallery or take a new picture. It then renders a thumbnail to the web page. It supports any number of selections so my view simply loops over an array.\n\n\n\n\nPretty simple, right? In my testing I always used the simulator as it doesn't have a real camera, and I typically tested on iOS only since they were testing Android. Also, the camera is pretty simple to use so it just plain works most of the time.\n\n\nBut then the client reported something odd. Whenever he selected a picture from the gallery, the image thumbnail would show up broken. I quickly ran it (and since I said quickly you know I used Genymotion and not the Android emulator) and confirmed it failed. Like any other good hybrid developer I fired up my dev tools and checked the console. This is what I saw.\n\n\n\nGET unsafe:content://com.android.providers.media.documents/document/image%3A21  \n\n\n\nSome Googling turned up this Stackoverflow post. Long story short - it is an Angular security measure. I've run into Angular security stuff before (trying to inject HTML into the DOM) so this isn't the first time I've had an issue like this, but it threw me for a loop since it worked in iOS. If you look at the file URI returned by Camera/Gallery selections though it makes a bit more sense. Here is an example of a file URI returned from iOS (spaces added so it will wrap):\n\nfile:///Users/ray/Library/Developer/CoreSimulator/Devices/ C8202B3B-300B-4819-8CD3-4C4AA690CE7C/ data/Applications/D82BF64E-6DD1-4645-B637-BCF65001FD29/tmp/cdv_photo_003.jpg\n\nThe exact same code on Android returns a file URI like so:\n\ncontent://com.android.providers.media.documents/document/image%3A21\n\nAngular sees this as something weird and says, \"No Way Man!\". Luckily the fix is simple. In my application's configure block, I added this:\n\n\n\nSo, keep it in mind if you are using the camera in a Cordova/AngularJS application.\n\n\nNinja cat provided for no good reason.\n\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Another bug with queryExecute - Threads",
		"date":"Thu Oct 09 2014 06:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412835000,
		"url":"https://www.raymondcamden.com/2014/10/09/Another-bug-with-queryExecute-Threads",
		"content":"\nWow, not a good morning for one of my favorite new features of ColdFusion 11. This morning I reported on a bug found with queryExecute by a user on StackOverflow. I did some more digging and found that if you use queryExecute inside a thread, it returns an undefined value. Here is a simple test case:\n\n\n\n\nIn the code above I'm running two threads that use queryExecute. In the first thread the value of result is undefined. In the second thread it throws an error because result is not defined.\n\n\nSo, you can't use queryExecute inside a thread call. I've reported this here: https://bugbase.adobe.com/index.cfm?event=bug&id=3836820.\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Bug with queryExecute - use with caution",
		"date":"Thu Oct 09 2014 02:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412820600,
		"url":"https://www.raymondcamden.com/2014/10/09/Bug-with-queryExecute-use-with-caution",
		"content":"\nOne of the nicer features in ColdFusion 11 was the addition of queryExecute. It lets you run a query from cfscript easier than using the component based system that was added in the past. However, a StackOverflow user discovered an interesting bug with it.\n\n\n\nScott J. posted a question to StackOverflow about something odd he saw in ColdFusion's debugging output when he used queryExecute. Imagine the following simple call:\n\ndata = queryExecute(&quot;select * from tblblogentries limit 0,1&quot;, {}, {datasource:&quot;myblog&quot;});\n\nIf you turn on ColdFusion Debugging, you would expect to see \"data\" as a query. Instead he saw this:\n\n\n\n\n\nIt looks like ColdFusion is assigning a temporary variable to the query before returning it to the variable you specify. If you run multiple queryExecute calls they use the same variable. If you use a tag-based query it works correctly. If you remember, the debug templates are simply ColdFusion files. I checked in there to ensure there wasn't a bug, but as far as I can see, it is working right.\n\n\nAlso, for fun, I did: _queryname_var0 = 9; and yep - it gets overwritten. Oddly it won't exist in the Variables scope, but if you use it as a variable and then run queryExecute, it gets removed completely. I wouldn't go so far as to say queryExecute is unsafe to use, but, you may want to ensure you are not using a variable with the same name. It also makes debugging a bit more difficult as you will have multiple items in the report with the same name. \n\n\nI've filed a bug report: https://bugbase.adobe.com/index.cfm?event=bug&id=3836702\n",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Timing out users by role in a ColdFusion Application",
		"date":"Wed Oct 08 2014 06:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412748600,
		"url":"https://www.raymondcamden.com/2014/10/08/Timing-out-users-by-role-in-a-ColdFusion-Application",
		"content":"\nLate last month a reader asked me if it was possible to override the session timeout so that he could provide different time outs based on a user role. As far as I know there is no direct way of doing this. There may be a way if you get to the underlying Java Session stuff, but I recommended something simpler - if you keep a variable for when the user last hit your site and do a quick time check, you can easily log them out early. To be clear, this is not the same as ending the session, but honestly, thats not what he really needed. He simply needed to toggle a flag (loggedin) from true to false if that time limit had expired. I thought I'd whip up a quick set of example code to demonstrate this.\n\n\nTwo quick notes before I continue. I wrote this using ColdFusion 11. What I'm demonstrating here could easily be done in ColdFusion MX and higher. I'm not going to rewrite it in tags. Ditto for the member functions I used. It should be trivial to port that to ColdFusion 10 as well. Secondly, I didn't use a framework for the two apps I built. I wanted to keep it super simple.\n\nOk, let's start off with an incredibly simple application that enforces login. First, the Application.cfc.\n\n\n\nI'm assuming nothing here is new to folks. This is the same authentication logic you have probably used in a hundred or so applications. My userservice.cfc is literally a method that checks if username and password are \"admin\". I won't bother sharing that (but you can see it in the attachment). My index.cfm simply says \"Hello World\" and the login.cfm file is a form, nothing more. Again, this is just the bare minimum. Now let's look at the updated version.\n\n\n\nLet's cover the important changes, one by one. \n\n\nFirst, I've specified a timeout for the Application. Technically this isn't required, but it makes it a bit easier to test. The biggest change is in onRequestStart. Whereas before we simply had two checks (one for logging in, one to see if authenticated), we've added a new check to see if the user is logged in, has a role of user, and has been idle for more than 60 seconds. I kinda feel bad about this logic being here, it seems like perhaps it should be in the userService, but, I think you get the point. If we determine that \"too much\" time has passed (and the value is arbitrary), then we mark the user as logged out.\n\n\nI do want to share the userService now as it is a tiny bit more complex. It now returns a structure that includes a status and user information as well.\n\n\n\nAnyway, I hope this is useful. I've included both versions as an attachment to this blog entry.\nDownload attached file.",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Cordova and Large Asset Downloads - An Abstract",
		"date":"Tue Oct 07 2014 11:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412680200,
		"url":"https://www.raymondcamden.com/2014/10/07/Cordova-and-Large-Asset-Downloads-An-Abstract",
		"content":"\nDoing something a bit different today. A PhoneGap user contacted me yesterday with an interesting problem. He and I discussed it over a quick Google Hangout, and I thought I'd write up some thoughts about our discussion. Ultimately I want to build a proof of concept around this idea, but I thought I'd start first with an explanation, sans code, to see what people thought.\n\n\nThe problem, at a high level, involves downloading data files to a PhoneGap/Cordova application after the user has installed it. These would not be required downloads. Think more of things like DLC, or additional songs for a game, optional items.  His question was how an application could be architected to support such a system. Here is what I told him. (And as always, I welcome other opinions in the comments below.)\n\n\n\nThe first thing I mentioned is that he would need some form of application server in order to handle hosting the resources. This application server needs to be able to respond to a request asking what resources are available. How the server responds isn't really important. A JSON-encoded array of URLs, with perhaps some meta information about each resource, would be sufficient. \"Application Server\" need not be anything complex. In fact, you could build something with Parse.com to handle the entire process. But at the end of the day you need that \"responder\" that can list what resources are available for download.\n\n\nOn the client side, you then need to be able to ask that server for a list. Obviously this is nothing more than an XHR request. You could add a layer of complexity to the process by supporting a filter. For example, if the server has 10 resources available and the client has downloaded 5 of them, maybe there is some way for the client to send that information to the server so that server only responds with what is new. Again, how complex you build this is up to you. I'd imagine that a simple list, even of 100-200 items, would transfer so fast (even on mobile) that you wouldn't need to worry about filtering. \n\n\nSo - you've got two parts now, server side and client side. Fetching the resources isn't terribly difficult using the File Transfer plugin. Handling a bunch of these so you don't intefere with normal application usage is another matter. In a SPA, you could simply fire off a method to do these while the user carries on their merry way. In theory then these resources just become available when they get downloaded. If you grab one at a time you can even gracefully handle the application being killed off half way through. (To be clear, I need to test that. If I download a file called foo.jpg and it doesn't finish because the application is killed, does that file exist on the file system but in a corrupt state? I'll find out!)\n\n\nThat's the issue at a high level. The user who spoke with me asked me to take a look at this with jQuery Mobile, and I know I'm all about AngularJS and Ionic now, but I'm going to give it a shot there first to see if I can get a proof of concept working. What about you? Have you built something like this? Do you have any advice?\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "CreativeSDK (really) Launches",
		"date":"Mon Oct 06 2014 12:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412597400,
		"url":"https://www.raymondcamden.com/2014/10/06/CreativeSDK-really-Launches",
		"content":"\nSo a while ago I mentioned I was working on the CreativeSDK project. My role is to help out with documentation, including tweaks to the API guides and writing articles. As someone still pretty new to ObjectiveC, my articles are pretty simple, but hopefully that's exactly the kind of content that can help people use it. The SDK is now public and you can peruse some of the terribly exciting articles I wrote that demonstrate things like CC file access and PSD extraction.\n\n\nThe best place to start though is the Getting Started article, which walks you through project set up (XCode is pretty darn impressive by the way) and how to handle basic authentication with Creative Cloud.\n\n\n\n\n\nCheck it out at http://creativesdk.adobe.com. \n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "ColdFusion Jedi is no more...",
		"date":"Mon Oct 06 2014 08:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412583000,
		"url":"https://www.raymondcamden.com/2014/10/06/ColdFusion-Jedi-is-no-more",
		"content":"\nI've already had multiple people ping me about this so I thought it was a good idea to make sure folks knew I was aware. I have not used the coldfusionjedi.com domain in a long time, but each year I was renewing. After some advice from a reader, I put it up for auction and sold it. So - yep - I know that requests for coldfusionjedi.com go someplace else. I also have some blog entries that may link to that domain. I'll correct them as I run across them.\n",
		"tags":[
	        
		],
		"categories":[
            
                "misc"
            
		]

	},

	{
		"title": "Figuring out what version of Cordova created a project",
		"date":"Fri Oct 03 2014 11:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412334600,
		"url":"https://www.raymondcamden.com/2014/10/03/Figuring-out-what-version-of-Cordova-created-a-project",
		"content":"\nEarlier today a user on Twitter asked how one could determine the version of Cordova used to create a project. As far as I knew there wasn't really a way to determine this, especially since there are multiple versions in play now. But I double checked on the main developer list just to be sure.\n\n\n\nOnce again, Cordova contributor Michal Mocny chimed in to provide some context. What follows is a summarized version of what he said. It meshes with what I knew as well so that makes it doubly true. ;)\n\n\nWhile the \"main\" release of Cordova is (at the time I write this) 3.6.3, there are actually version numbers for the command line interface, each platform, and each individual plugin. In theory you should be more concerned with the platform and plugin versions. If the CLI updates, that may impact what you can do with the CLI (like some new feature), but it isn't changing what your application can do. \n\n\nYou can determine what version was used for your platforms by typing cordova platforms, as seen here:\n\n\n\n\n\nAnd of course the same can be done for plugins:\n\n\n\n\n\nSo long story short - your main concern is with the versions of each platform and plugin. \n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "mobile"
            
		]

	},

	{
		"title": "Ionic has a CDN (use with caution)",
		"date":"Thu Oct 02 2014 11:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412248200,
		"url":"https://www.raymondcamden.com/2014/10/02/Ionic-has-a-CDN-use-with-caution",
		"content":"\nI kinda stumbled upon this by accident, but Ionic has a CDN you can find here:\n\n\n\nhttp://code.ionicframework.com/\n\n\nAs you can imagine, it contains links for JavaScript and CSS resources and lets you grab earlier versions.\n\n\n\n\n\nTo be clear, this is not recommended for normal usage, and is especially not recommended for a PhoneGap/Cordova app. But if you are creating an online demo or using something like CodePen, this can be really helpful. I used it for my \"tab button calls actionsheet demo\" for a StackOverflow answer from earlier in the week.\n",
		"tags":[
	        
		],
		"categories":[
            
                "development",
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "UX question, handling notifications",
		"date":"Thu Oct 02 2014 05:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412226600,
		"url":"https://www.raymondcamden.com/2014/10/02/UX-question-handling-notifications",
		"content":"\nOk, just a random note. I was going to post this on Google+ but figured I'd get a broader reach here on my blog. Lately I've run into two sites that handle notifications in a way that kinda bug me. I wanted to see if it bugs you too. I'm also curious if folks think my ideas for improvement makes sense, or if perhaps something else would be better.\n\n\n\nThe first example I want to share is Stack Overflow. When you have a notification, it modifies an \"inbox\" icon at the top of the page giving you a numeric indicator of the number of messages. I didn't take a screen shot before I cleared them (and that's the important point I'm getting too) but this is what I'm talking about:\n\n\n\n\n\nClicking on it opens a list of your most recent messages. Again, I didn't take a screen shot of this, but unread messages will have a darker background. This is nice as it makes it obvious which messages are new.\n\n\n\n\n\nSo far so good, but the problem I run into is that when you have two or more unread messages, as soon as you click one, the entire list is marked as read. I know this will happen, so it actually prevents me from clicking sometimes as I don't want to respond unless I have time to respond to all of them.\n\n\nAgreed? Shouldn't it keep the messages as unread until you actually view each page? I can see the flip side to this as well. If you have a butt load and don't want to read them all it would be a pain to click them all, but perhaps a small link in the dropdown could do a \"Mark All Read\" action.\n\n\nThe next thing that bugs me is Google Plus and their notifications. When you click, you see a nice list:\n\n\n\n\n\nNotice how you get an abbreviated version of the post/comment in question. If you click, it switches to a detail view, in context, which is slick, but when you click back, it deletes the notification. What ends up hitting me is I simply want to read the comment now but may not want to respond till later. Having it automatically hide is a nuisance if I plan on responding later. \n\n\nThis used to be more of a pain as it was difficult to find the previously read link, but now it is at the bottom of the list. It's still behavior that bugs me though.\n\n\nSo to be clear, I do not think I can do better UX than Stack Overflow or Google (grin), but I was thinking about these behaviors and just wanted to see what others thought as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "design",
            
                "development"
            
		]

	},

	{
		"title": "Cordova, Plugins, and Determining What Supports What",
		"date":"Wed Oct 01 2014 05:10:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1412140200,
		"url":"https://www.raymondcamden.com/2014/10/01/Cordova-Plugins-and-Determining-what-supports-what",
		"content":"\nEarlier today a user on the Cordova development list asked if plugins are tested against only the current release of the SDK. This brought up an interesting discussion that I'm summarizing here. \n\n\n\nFirst, there is still an idea of \"core\" plugins versus \"third party\" plugins. Core plugins include the things that have traditionally been part of the core feature set, like Camera and Geolocation. While there is no firm list of what is considered core, I'd say anything under the org.apache.cordova namespace is core. You can see a list of them here: http://plugins.cordova.io/#/search?search=org.apache.cordova\n\n\nThe question was - in general - when a new version of a plugin is released, is it tested against the most recent version of Cordova.\n\n\nThe answer is yes. Nice and simple. Michal Mocny (Apache Cordova committer) had this to say:\n\n\n\"When we do a platform release, we test with the latest\nplugins to make sure the platform isn't breaking things.  When we do a\nplugins release, we test with the latest platforms to make sure the plugins\nare not breaking things.\"\n\n\nAgain, nice and simple. Michal also mentioned something I hadn't noticed before. If you go to plugins.cordova.io and view an individual plugin, you may see this:\n\n\n\n\n\nThis comes from the plugin's plugin.xml file containing the engine tag. As an example:\n\n\n\nUnfortunately, not all plugins use this, and obviously you have to trust the developer when they said they've tested something.\n\n\nIt is also worth nothing that the engine referenced above refers to the CLI, not an individual platform release. It should still be relatively safe, but keep that in mind as well.\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "mobile"
            
		]

	},

	{
		"title": "The Future of cfObjective.",
		"date":"Mon Sep 29 2014 12:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1411992540,
		"url":"https://www.raymondcamden.com/2014/09/29/The-Future-of-cfObjective",
		"content":"\nSo, I don't do this very often, but, I was asked to share a message, and it is one I agree with, so I decided to pass it along. This comes from a member of our community - so please give it a few moments of your time.\n\n\n\nHas cf.Objective() Outgrown Its Name?\n \nHave you considered how cf.Objective() has changed over the years? When cf.Objective() started, it seemed targeted toward ColdFusion developers who were looking to improve their ColdFusion development skills.  This was awesome, and it was exactly what the community needed at the time. cf.Objective() has always had content that reflected what the community needed at the time. \n \nThere was once a time when being a web developer meant that you could do your job with a very simple set of tools. For example, you could do virtually everything you needed to do with just HTML and ColdFusion, or HTML and PHP. But as with everything related to development, things became more complicated, and cf.Objective() has been there to help us through those changes with the content we needed at the time. \n\nFor almost 10 years, cf.Objective() has been adapting and expanding to meet the needs of the intermediate and advanced-level developers it attracts. When server-side frameworks were the new \"big thing\" in application development, cf.Objective() was there to provide us with the very best content related to those frameworks, many times from the framework authors themselves. When TDD was what people wanted to learn about, cf.Objective() retained the best in the business to deliver that content. Looking at the content history of cf.Objective(), it's clear that cf.Objective() has continually and reliably grown and expanded to bring web developers what they wanted and needed. \n\nThen, in 2011, the reemergence of JavaScript began. And in 2012, cf.Objective() came back with an entire three-day track dedicated to JavaScript. It was cleverly named js.Objective(). The result is that cf.Objective() changed. Not in a bad way, but in a big way. JavaScript's return changed how we develop web applications, and cf.Objective() responded to that change. \n \nSince 2012, JavaScript has changed the face of web development. Additionally, other tools have become essential parts of the developer toolbox. What was once a toolbox with two tools has now become a toolbox with dozens of tools. As we look back on the beginnings of our careers we can probably see that we accomplished most of our work with just ColdFusion and HTML, and a smattering of SQL. In contrast, if we look at the work we are doing now and the tools we are using now, we will see it has changed a lot. Consider this list:\n\n\nServer-side Frameworks\nDependency Injection\nAutomation and Continuous Integration Tools (Jenkins, Cruise Control)\nTask runners (ANT, Gradle, Grunt)\nTDD/BDD tools (MXUnit, TestBox)\nOther testing tools (Selenium, WebDriver, jMeter)\nJavaScript Libraries (jQuery, Scriptaculous, MooTools)\nJavaScript Frameworks (AngularJS, EmberJS, Backbone.js)\nExtract-Transform-Load (ETL) tools\nSecurity frameworks and best practices\nContent Management Systems\nServer-side integration with other platforms\nWeb Services (SOAP and REST)\nCSS and Front-End Frameworks\nVersion Control\nVirtual Machines\nCloud Computing\nMobile Development tools\nORM\nServer Clustering and load balancing\nNoSQL Databases\nCommand line tools\nPlatform Package managers\nIDEs\n \n\nLook at that list and ask yourself, how many of those tools and technologies do you now use? Has your toolbox grown greatly since you started development? How many of those tools did you learn about at cf.Objective()? How many do you only know about because of cf.Objective(). The number of tools that web developers need has changed drastically over the last 10 years. During that time, cf.Objective() has been there, every step of the way, delivering content relevant to what is going on in the web development world. \n\nWith these changes and cf.Objective()'s growth and expansion, have you noticed a trend in the ColdFusion content at cf.Objective()?  Looking at the schedule from previous years, you see that there is still a lot of ColdFusion content, and it is still fantastic, unique ColdFusion content. But it is being displaced a bit by all of these other tools and technologies we want to learn about. That's OK, ColdFusion is probably not 100% of your job, so why should it be 100% of your conference?  We do other things besides ColdFusion.\n \nDid you notice that in 2013 less than half of cf.Objective()'s content was directly related to ColdFusion? This is not to say that all of it couldn't have been related to the work we do, but less than half of it was ColdFusion-specific.  That means that more than half of the content from cf.Objective() 2013 could relate to any kind of web development. In 2012 and 2014, the numbers were similar.\n\nSo for the last three years the trend is that cf.Objective() has adapted to the way the web is changing, and that adaptation has meant, while ColdFusion is still a huge par",
		"tags":[
	        
		],
		"categories":[
            
                "coldfusion"
            
		]

	},

	{
		"title": "Syncing Edge Animate with Window Scroll",
		"date":"Mon Sep 29 2014 02:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1411956540,
		"url":"https://www.raymondcamden.com/2014/09/29/syncing-edge-animate-with-window-scroll",
		"content":"\nRecently I've come across a few sites that will tie window scrolling with animation. When used in a subtle, small fashion, this is kind of cool. When used to change large portions of the view or really screw with scrolling, I detect it. Like most things, it all comes down to how you use it I suppose. But I was thinking recently - how can we do this with Edge Animate? Turns out it is rather simple.\n\n\n\nI began by creating a simple animation of a box moving from left to right. That is - unfortunately - the best I can design. Don't blame Edge Animate. Blame me. Next, I disabled autoplay for the animation. If you can't find this, be sure the Stage is selected and uncheck the box.\n\n\n\n\n\nWith autoplay turned off, I then figured out what I needed to do to tie scrolling to animation.\n\n\nFirst, I need to ensure the animation stays visible.\nSecond, I need to detect a scroll event.\nThird, I then need to figure out how much the person has scrolled against the total amount they can scroll. Basically, what percentage?\nFinally, I need to set the animation's current position to that percentage.\n\n\nLet's break this down. I began by working in the animation's creationComplete event. I added an onscroll event first.\n\n\n\nSo, the first thing I do is get the scrolled percentage. That comes from this function:\n\n\n\nThankfully Sam Deering had figured this out for me already and posted it here.  Next, I figured out where I was, percentage wise, in my animation's timeline. The value animSize is simply the size (timewise) of my animation:\n\n\n\nFinally, that last line is what moves the animation to a specific point. I was stuck on this for a while as the JavaScript API for Edge Animate does not specifically call out how to do this. My coworker Elaine Finnell pointed out that the stop() method lets you move to a particular position. This is documented but I had not even considered looking at stop() as an option!\n\n\nThis worked great, but I wanted to add one more thing. If I had scrolled down a bit on my test page and reloaded, I wanted the application to recognize this on load and animate accordingly. I added some code to run immediately on load:\n\n\n\nBasically this is a repeat of my other code, which is kinda bad, but notice the setTimeout. Edge Animate has a bug - or quirk - with working with the DOM in creationComplete. I've run into this before and setTimeout, while lame, works around it. Here is my creationComplete code then as a whole.\n\n\nAnd it works awesome! Well, to me anyway. Check it out here: https://static.raymondcamden.com/demos/2014/sep/29/stickandscroll/Untitled-2.html.\n\n\nOf course, that's my ugly version. Imagine if someone with some decent design skills tried it. Elaine did so - and with help from other Adobians (Max Vujovic and\n Bem Jones-Bey) came up with this much cooler version: https://static.raymondcamden.com/demos/2014/sep/29/elainesample/scroll.html. Check it out!\n",
		"tags":[
	        
		],
		"categories":[
            
                "design",
            
                "development",
            
                "javascript"
            
		]

	},

	{
		"title": "Another IndexedDB Bug - Possibly",
		"date":"Fri Sep 26 2014 05:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1411708140,
		"url":"https://www.raymondcamden.com/2014/09/26/Another-IndexedDB-Bug-Possibly",
		"content":"\nWhile looking at another IndexedDB bug with Safari (https://bugs.webkit.org/show_bug.cgi?id=136155), I encountered a bug with Chrome. It was a bit awkward to describe so I decided to make a quick video. I'd greatly appreciate it if folks could try and recreate this. If so, I'll file a bug with the Chrome team.\n\n\n",
		"tags":[
	        
		],
		"categories":[
            
                "javascript"
            
		]

	},

	{
		"title": "IndexedDB on iOS 8 - Broken Bad",
		"date":"Thu Sep 25 2014 04:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1411618140,
		"url":"https://www.raymondcamden.com/2014/09/25/IndexedDB-on-iOS-8-Broken-Bad",
		"content":"\nLet me begin by saying that credit for this find goes to @jonnyknowsbest on Twitter and his SO post here: Primary Key issue on iOS8 implementation of IndexedDb. I did my research into this issue early this morning and I hope that I, and jonny, are both wrong. I'd love to be wrong about this. Unfortunately, I don't think that is the case.\n\n\nSo, as you know, iOS 8 finally brought IndexedDB to Mobile Safari. I may be biased, but I find features like this far more useful than CSS updates. Not to say that I don't appreciate them, but to me, deep data storage on the client is something that is more practical and useful to more people. Of course, I work for a company that is all about designers and not developers, so what do I know? ;)\n\n\n\nUnfortunately, it seems as if Apple may have screwed up their implementation of IndexedDB - and screwed it up bad. Like real bad. If you read the SO post I linked to above, you will see that he was using assigned IDs and discovered that if you assigned the same ID to data in two datastores, then the data inserted in the first objectstore is removed. Let me restate that just to be obvious.\n\n\nImagine you have two object stores, people and beer. You want to add an object to both, and in both cases, you use a hard coded primary key of 1. When you do, no error is thrown, but the person object is deleted. Only beer remains. (Not the worst result...) Here is a full example showing this bug in action.\n\n\n\nThis demo uses a simple form to ask you for a PK. When you click the button, it then adds a static person and note object using the value you gave for a PK. When you run this, no error is thrown. The success handler for both operations is run. But the data you created for the person is gone. This is horrible. \n\n\nBut wait! Who uses defined primary keys? Only nerds! I like auto incrementing keys, so why not just switch to that? Simple enough, right? I made a new demo, with a new database, and modified my objectstores:\n\n\n\nAnd the same damn error occurs. I kid you not. Ok, fine iOS. So I then tried something else. According to the spec, you can create a transaction with multiple objectstores. I thought, maybe if I did that, iOS would handle the inserts better. So let's try this:\n\n\n\nBut this threw an error:  DOM IDBDatabase Exception 8: An operation failed because the requested database object could not be found.\n\n\nOk, so next I thought - what if we used autoIncrement and different key names. Maybe the key name being the same was confusing things:\n\n\n\nNope, same error. So... finally I gave up. I specified an ID number and prefixed it with a string. \n\n\n\nThis worked. Of course, you still have the suck part of creating your own keys. You can, however, ask the objectstore for the size and simply increment yourself. I wrote up a new version that does this. This seems to work well and for now is what I'd recommend. It works fine in Chrome too so it isn't \"harmful\" to use this workaround. \n\n\n\nI hope this helps folks. As I said, maybe I'm being stupid and missing something obvious. I hope so. But considering that iOS 8 also broke file uploads (both \"regular\" and via XHR2), it isn't too surprising that this could be broken as well. I'm going to file a bug report now. If their reporting system supports sharing the URL, I'll do so in a comment.\n",
		"tags":[
	        
		],
		"categories":[
            
                "html5",
            
                "javascript",
            
                "mobile"
            
		]

	},

	{
		"title": "Browser as a platform for your PhoneGap/Cordova apps",
		"date":"Wed Sep 24 2014 12:09:00 GMT+0000 (Coordinated Universal Time)",
		"date_timestamp": 1411560540,
		"url":"https://www.raymondcamden.com/2014/09/24/browser-as-a-platform-for-your-phonegapcordova-apps",
		"content":"Over a year later and this blog post is still highly popular. If you like this content, be sure to subscribe to the blog to get the latest updates. You may also want to check out my Apache Cordova book and the JavaScript videos I have available.\n\nThis is a pretty exciting change. If you've recently updated to the latest version of Cordova, you will notice that a new platform exists: browser. What exactly does this mean? It means the browser is now (well, becoming) a viable way to test your PhoneGap/Cordova applications. For a long time now I've done a lot of my development in the browser. Most of the time I'm not concerned about some random Cordova feature, instead I'm more concerned about something else. So I'll skip, or mock, a Cordova feature and focus on the important stuff. But eventually I hit that point where I need to do something via a core plugin and then I leave the desktop. Now we have an alternative.\n\n\n\nPlugins currently ship with code for each of their supported platforms. This means that for a plugin to support Cowbell (random example) it ships code for Android, iOS, and whatever other platform it wants to support. They ship a standard JavaScript interface so your use of the code \"just works\" across platforms. (To be clear, there are quirks sometimes, and most plugins do a good job of documenting this.)\n\n\nWith the addition of \"Browser\" as a platform, a plugin can write code to support running under a desktop environment. How it does this is completely up to the plugin. As an example, the Barcode scanner plugin could simply return a hard coded barcode value. \n\n\nAs an additional change, when you run the browser platform, the deviceready event will automatically fire. Normally when I'm running on the desktop I add a line of code to fake it until I switch to a device, but now I can skip it. So how does it work?\n\n\nFirst and foremo