Jekyll2018-07-04T15:47:18-04:00https://scrawlon.com/ScrawlonProgramming blog and professional portfolio of Baltimore-area web developer, Scott McGrathSwiper JS Fullscreen Image Slider2017-07-29T00:00:00-04:002017-07-29T00:00:00-04:00https://scrawlon.com/2017/07/29/swiper-js-fullscreen-image-slider<p>Image slideshows are ubiquitous in modern web design. The <a href="http://idangero.us/swiper/">“Swiper”</a> JavaScript library is a simple lightweight solution. Here, I’m going to create an image slider with a fullscreen viewing option.</p>
<p>The set up is pretty straightforward, include the CSS and JS libraries (we’re including jQuery as well for this tuturial). Then add your Swiper html and an init script that tells the Swiper library how to build your slideshow. You can see many examples at the <a href="http://idangero.us/swiper/demos/">Swiper demos page.</a></p>
<p>Let’s take a look at a basic slideshow example I made on CodePen. It’s really just a few divs initialized with SwiperJS code. You can add as many “swiper-slide” divs as you want with text and images and html. See the <a href="http://idangero.us/swiper/api/"> API documentation</a> for more info of the settings.</p>
<p>(use the ‘edit on CodePen’ button to view the code in another browser tab)</p>
<p data-height="482" data-theme-id="0" data-slug-hash="WErovE" data-default-tab="html,result" data-user="smcgrath" data-embed-version="2" data-pen-title="WErovE" class="codepen">
See the Pen <a href="https://codepen.io/smcgrath/pen/WErovE/">WErovE</a> by Scott McGrath (<a href="https://codepen.io/smcgrath">@smcgrath</a>) on <a href="https://codepen.io">CodePen</a>.
</p>
<script async="" src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<script async="" src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>Compare that with the code below. As you can see, it doesn’t take much to build a working image slider with a fullscreen view.</p>
<p data-height="524" data-theme-id="0" data-slug-hash="aydBNx" data-default-tab="html,result" data-user="smcgrath" data-embed-version="2" data-pen-title="aydBNx" class="codepen">
See the Pen <a href="https://codepen.io/smcgrath/pen/aydBNx/">aydBNx</a> by Scott McGrath (<a href="https://codepen.io/smcgrath">@smcgrath</a>) on <a href="https://codepen.io">CodePen</a>.
</p>
<script async="" src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>You’ll notice that I’ve moved the CSS and JavaScript out of the html markup, and into the separate windows in the CodePen editor. This is just to make it easier to view. You could place this code directly in the html markup, or create .css and .js files that you can include.</p>
<p>Let’s examine the html first. There’s a “.swiper-container” div with three slides. Each slide has a numbered ‘id’. Inside each slide is an image and a caption. Below the .’swiper-container’ are two empty divs, “#fullscreen-swiper” and “#fullscreen-swiper-backdrop”.</p>
<p>Now, let’s look at the JavaScript. First we define and initialize the main swiper, “var swiper = new Swiper…”. This is the swiper that appears when we load the page. The init script includes pagination, next/prevButton, and it calls for our slideshow to have 2 ‘slidesPerView’.</p>
<p>Next I have a jQuery ‘onClick’ function attached to the ‘.swiper-container .swiper-slide’ element. When a slide is clicked, this code grabs the slide’s id attribute and passes it to the ‘openFullscreenSlider’ function.</p>
<p>The first thing the ‘openFullscreenSlider’ function does is copy the html markup in the ‘.swiper-container’ div and cache it in the “mainSwiperMarkup” variable. We use jQuery to append this markup (plus the “X” div) to our ‘#fullscreen-swiper’ div. Then we call a Swiper init script on the ‘#fullscreen-swiper’ div, “var fullscreenSwiper = new Swiper…” Notice, the ‘#fullscreen-swiper’ is slightly different than the main swiper. It has only 1 ‘slidesPerView’ and has an ‘initialSlide’ definition. The ‘initialSlide’ tells Swiper which slide to open the slider to. Since Swiper slides are zero-indexed, we subtract 1 from the id passed to the function (slide 1 = index 0, slide 2 = index 1, etc.).</p>
<p>The remainder of the code manages the fullscreen lightbox experience. Fade in the black ‘#fullscreen-swiper-backdrop’. Add a ‘no-scroll’ class to ‘html and body’ elements. Add a click event to the ‘X’ close button that removes the ‘#fullscreen-swiper’, fades out the backdrop and removes the ‘no-scroll’ class.</p>Scott McGrathImage slideshows are ubiquitous in modern web design. The “Swiper” JavaScript library is a simple lightweight solution. Here, I’m going to create an image slider with a fullscreen viewing option.Dysfunctional WordPress Droplets on DigitalOcean2016-07-02T00:00:00-04:002016-07-02T00:00:00-04:00https://scrawlon.com/2016/07/02/dysfunctional-wordpress-droplets-on-digitalocean<p>I’ve been a DigitalOcean user for a few years now, so when I decided to start this WordPress blog I chose the DigitalOcean WordPress One-Click Install. Unfortunately, the WordPress One-Click Install wasn’t very stable out-of-the-box.</p>
<h2 id="the-symptom">The Symptom</h2>
<p>Not long after creating my new WordPress droplet (a basic blog with very low expected traffic), I noticed my site was offline. This sort of thing can happen, so I just restarted the server, and didn’t give it another thought.</p>
<h2 id="chronic">Chronic</h2>
<p>Some time after, maybe a few weeks or a month later, the site went down again. I restarted again. It was at this point that the issue got worse. It became a weekly occurrence, and eventually the site wouldn’t stay up for more than a few hours at a time.</p>
<p>Sometimes the page would just time out and other times I’d get the error <a href="https://www.google.com/search?num=100&amp;espv=2&amp;q=digitalocean%20error%20establishing%20a%20database%20connection&amp;oq=digitalocean%20error%20establishing%20a%20database%20connection">“Error establishing a database connection”</a>.</p>
<h2 id="some-of-the-solutions">(SOME of) the Solutions</h2>
<p>Once it became clear that the problem wasn’t going away, I start looking for answers. I found a few tutorials that proved helpful.</p>
<ul>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04">Add a swapfile</a></li>
<li><a href="https://www.digitalocean.com/community/questions/mysql-server-stops-very-frequently">Optimize MySQL</a></li>
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-optimize-apache-web-server-performance">Optimize Apache Web Server</a></li>
<li><a href="https://www.digitalocean.com/community/questions/error-establishing-a-database-connection-wordpress?answer=25981">Block XML-RPC attacks</a></li>
</ul>
<h2 id="fixed">Fixed?</h2>
<p>I followed much of the advice from the above articles. I’d implement a change, restart my droplet and wait for the next crash. The results were from a few hours of uptime to a full day. None of the above changes fixed the problem, so continued searching the web for solutions to MySql and Apache running out of memory.</p>
<p>Tips I found on these two pages finally did the trick:</p>
<ul>
<li><a href="https://maanasroyy.wordpress.com/2012/05/05/apache-performance-tuning-keepalive-to-remove-latency/">Apache Performance Tuning: KeepAlive to remove latency</a></li>
<li><a href="http://www.narga.net/optimizing-apachephpmysql-low-memory-server/">Configuring Apache/PHP/MySQL for Low Memory (RAM) VPS</a></li>
</ul>
<p>My site has been stable for a few months now, so it appears that something I did fixed it. Unfortunately, as these things often go, I can’t say for sure what combination of the above fixes actually did the trick.</p>Scott McGrathI’ve been a DigitalOcean user for a few years now, so when I decided to start this WordPress blog I chose the DigitalOcean WordPress One-Click Install. Unfortunately, the WordPress One-Click Install wasn’t very stable out-of-the-box.Add More Social Media Options to WordPress Divi Theme2015-09-21T00:00:00-04:002015-09-21T00:00:00-04:00https://scrawlon.com/2015/09/21/add-more-social-media-options-to-wordpress-divi-theme<p>Code from this guide is available on <a href="https://gist.github.com/scrawlon/62a87592c38bb2adbcc4">GitHub</a>.</p>
<blockquote>
<p><strong>Update 3/26/16:</strong> It looks like Divi version 2.6+ moved one of the files used in this tutorial. The file ‘/wp-content/themes/Divi/epanel/options_divi.php’ is now located at ‘/wp-content/themes/Divi/options_divi.php’. That’s the only change needed to make this code work in the latest Divi.</p>
</blockquote>
<hr />
<blockquote>
<p><strong>Update 6/30/16:</strong> The tutorial and code have been updated to work with version 2.7 of Divi. Comment below if you run into any issues.</p>
</blockquote>
<hr />
<h2 id="the-divi-builder-really-good-but-not-perfect">The Divi Builder (really good, but not perfect)</h2>
<p>My employer began using the WordPress <a href="http://www.elegantthemes.com/gallery/divi/">Divi theme</a> about a year ago. The Divi Builder is real game changer for web designers. It provides a selection of the most commonly used page elements that can be easily added to our designs. That said, Divi can’t be everything for all people, and sometimes client’s ask for the less-popular options that Divi doesn’t natively support. Fortunately, the Divi codebase includes <a href="https://codex.wordpress.org/Pluggable_Functions">pluggable functions</a>, that permit us to customize it as needed.</p>
<p><strong>This tutorial assumes you have knowledge of WordPress and PHP, you have ftp access to your site’s files, you have the <a href="http://www.elegantthemes.com/gallery/divi/">Divi theme</a> installed and you’ve created a Divi child theme (<a href="http://www.eleganttweaks.com/learn/creating-a-child-theme/">learn more about child theme’s here</a>).</strong></p>
<h2 id="find-the-divi-epanel-options-files">Find the Divi epanel options files</h2>
<p>The first step in extending the Divi social media options, is to find and override the function that loads those options. If you look in the file ‘/wp-content/themes/Divi/epanel/custom_functions.php’, the function we’re looking for is <em>et_load_core_options</em>. The purpose of this function is to load the file ‘/wp-content/themes/Divi/options_divi.php’. Inside that file is the global $options array. That’s the code that builds the General Options admin screen at ‘Divi &gt; Theme Options &gt; General’ in your WordPress admin menu.</p>
<p><em>While it is possible to edit that file directly, it is not recommended. Any theme file that you edit will be overwritten when you update that theme, causing your work to be lost and your site to stop working. For that reason, we’ll make our changes in the child theme.</em></p>
<h2 id="create-the-custom-epanel-options-file">Create the custom epanel options file</h2>
<p>Our next step, is to create a custom options array and combine it with the original Divi options array. In your child theme folder create a new folder named ‘epanel’ and add a new file named ‘/epanel/custom_options_divi.php’. Add the following code to the new file:</p>
<script src="https://gist.github.com/scrawlon/62a87592c38bb2adbcc4.js?file=custom_options_divi.php"></script>
<p>This is our custom options array. We’re adding two new options, one for GitHub and one for LinkedIn. You could add others, but for the purpose of this tutorial, let’s just focus on those. If you’re curious where this code comes from, you can take a look at the original file ‘/wp-content/themes/Divi/options_divi.php’.</p>
<p>The variables $epanel_key and $epanel_value define where our custom options will appear on the admin screen. If you take a look at the original file ‘/wp-content/themes/Divi/options_divi.php’, you’ll see an array that includes the ‘name’ =&gt; ‘Show RSS Icon’ key value pair. Our options will appear after RSS feed button in the admin screen. You could move the new custom options by changing the values of $epanel_key and $epanel_value.</p>
<h2 id="make-the-new-options-available-to-divi">Make the new options available to Divi</h2>
<p>With the custom_options_divi.php file created, we can create the new <em>et_load_core_options</em> function that puts it all together. Add the following code at the bottom of your child theme’s functions.php:</p>
<script src="https://gist.github.com/scrawlon/62a87592c38bb2adbcc4.js?file=functions.php"></script>
<p>If you log into your WordPress admin, and open ‘Divi &gt; Theme Options &gt; General’, you should see the GitHub and LinkedIn buttons at the bottom. You can enable them and add URLs for them, but the icons won’t appear on your site until we add the new icons to the template.</p>
<h2 id="load-font-awesome-icon-fonts-and-create-a-new-social-media-template">Load Font Awesome icon fonts and create a new Social Media template</h2>
<p>We’re going to use the <a href="https://fortawesome.github.io/Font-Awesome/">Font Awesome icon font</a> to get all of the current social media logos. The easiest way to add this to WordPress is the <a href="https://wordpress.org/plugins/better-font-awesome/">Better Font Awesome plugin</a>. Install that before continuing.</p>
<p>The last thing we need to do is override the Divi social icons template file. In your child theme folder, create a new folder called ‘includes’ and add a file called ‘/includes/social_icons.php’. Paste in the following code:</p>
<script src="https://gist.github.com/scrawlon/62a87592c38bb2adbcc4.js?file=social_icons.php"></script>
<p>Here we have an array of social networks and their associated Font Awesome icons. The code loops through the array, adding each social network that has been activated in the Divi epanel.</p>
<p>That’s it. If you activate GitHub or LinkedIn now, their icons will should now appear in you WordPress site’s header or footer (depending on your settings). In order to add other social networks, you can add them to the code in ‘/epanel/custom_options_divi.php’ and ‘/includes/social_icons.php’.</p>Scott McGrathCode from this guide is available on GitHub.Control Your Webcam with HTML and JavaScript2015-07-26T00:00:00-04:002015-07-26T00:00:00-04:00https://scrawlon.com/2015/07/26/control-your-webcam-with-html-and-javascript<p>Code from this post is available on <a href="https://gist.github.com/scrawlon/ff7afd87f81edb0ce004">GitHub</a>.</p>
<p>The more I learn about programming, the more I want to build things that are just for me. If I have access to a capable device, I want to take control of it and create the user interface that works best for me personally. Now that I have a small stack of old smartphones, tablets and laptops, it’s time to experiment. Today, I’m going to look at what can I do with a webcam and a web browser.</p>
<h2 id="html-is-still-a-tangled-mess-yay-">HTML is still a tangled mess (YAY !?)</h2>
<p>So, HTML5 provides us with <em><a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/getUserMedia">Navigator.getUserMedia()</a></em>, so all we need to do is call that function and we’re gold. Right? Hell no! Just like the pre-HTML5 days, we still have browser prefixes, API differences and Internet Explorer incompatibilities.</p>
<p>Let’s quickly cover the top 3 browsers, so we can get to the code ASAP:</p>
<ol>
<li>
<p><strong>Firefox</strong>: Browser prefix is <em>navigator.mozGetUserMedia</em></p>
</li>
<li>
<p><strong>Google Chrome</strong>: Browser prefix is <em>navigator.webkitGetUserMedia</em></p>
</li>
<li>
<p><strong>Internet Explorer</strong>: API is unsupported on all versions at the time of this writing, as Microsoft is focusing on an entirely separate API. This should surprise no one.</p>
</li>
</ol>
<p>OK. That doesn’t seem so bad, just use the correct browser prefix and avoid Internet Explorer. Simple. Not quite. There are further differences in the APIs between Firefox and Chrome, and if you’re building a web app, you’ll want it to run in all the browsers. There are shims (<a href="https://github.com/addyosmani/getUserMedia.js/">for example</a>) to handle these incompatibilities, but this shouldn’t be necessary.</p>
<p>For this basic tutorial, I’m only interested in handling the basics and making an app that works locally. Unfortunately, Google Chrome requires a running server to use the getUserMedia API. If you’re using <strong>Chrome</strong>, continue to the <em>“Install a Local Server”</em> section. If you’re using <strong>Firefox</strong> you can skip to <em>“Access the Webcam”</em>. If you’re using Internet Explorer, you clearly haven’t been reading very carefully, and I don’t care what you do. ;)</p>
<h2 id="install-a-local-server">Install A Local Server</h2>
<p>It would be nice if the makers of Google Chrome would let us access our local webcams from local html files directly, but that’s not how it works. Fortunately, there are several free/simple to run local web servers. I recommend <a href="https://www.apachefriends.org/index.html">XAMPP (Apache + MySQL + PHP + Perl)</a>. The rest of this post assumes Google Chrome users are running XAMPP, installed in c:\xampp</p>
<h2 id="access-the-webcam">Access The Webcam</h2>
<p>There are plenty of tutorials and intros to the <a href="http://www.html5rocks.com/en/tutorials/getusermedia/intro/">HTML5 getUserMedia() API</a>. Most of the code I’ll be sharing here comes directly from the API, so I recommend taking a look at that info.</p>
<p>First, we need an html page with some basic elements. Create a new file called <em>mirror.html</em>. If you’re using Chrome, you should have XAMPP installed in c:\xampp and it should be running. Chrome users should add these new files in the c:\xampp\htdocs directory. Firefox users can create them in any directory you want (as it should be).</p>
<p>Keeping this as simple as possible. Add the code below to the new <em>mirror.html</em> file.</p>
<script src="https://gist.github.com/scrawlon/ff7afd87f81edb0ce004.js?file=mirror.html"></script>
<p>It’s just an empty page with a <strong>video</strong> element and a <strong>JavaScript</strong> file (which we will create next). If you tried to load this page now, you wouldn’t see anything. That’s because most of the getUserMedia() magic happens in the JavaScript. We’re going to insert our webcam video into the <em>video</em> element with JavaScript.</p>
<p>Create a new file called <em>mirror.js</em> in the same directory as <em>mirror.html</em>:</p>
<script src="https://gist.github.com/scrawlon/ff7afd87f81edb0ce004.js?file=mirror.js"></script>
<p>Let’s walk down through the code.</p>
<ol>
<li>
<p>First we capture the <em>video</em> element from the html file and define our webcam video resolution (1280x720 for HD video).</p>
</li>
<li>
<p>In the main <em>activateWebcam()</em> function:</p>
</li>
</ol>
<ul>
<li>
<p>We have to deal with those browser prefixes and API differences. I’ve added two small functions to separate these details: <em>hasUserMedia()</em> and <em>getBrowserVideoSettings()</em>. The first returns the current browser’s correct getUserMedia() prefix or NULL. The second returns the current browser’s video resolution definition object. <strong>These necessary alternative APIs are so similar, it’s difficult to fathom that they couldn’t agree to use one or the other…</strong></p>
</li>
<li>
<p>If <em>hasUserMedia()</em> isn’t null, then we can finally call <em>getUserMedia()</em> with the appropriate video resolution object and apply the webcam video stream to our html <em>video</em> element’s src.</p>
</li>
</ul>
<p>That’s it. Now, you just need to load the file in your browser.</p>
<ul>
<li>
<p>In Firefox, press CTRL+O to load the <em>mirror.html</em> file.</p>
</li>
<li>
<p>In Chrome, make sure XAMPP is running and browse to http://localhost/webcam/mirror.html</p>
</li>
</ul>
<p>You should be prompted with a webcam access prompt. Click ‘Allow’ or ‘Share Selected Device’ and your browser screen should be filled with webcam video.</p>
<h2 id="finale---make-it-a-mirror">Finale - Make it a Mirror</h2>
<p>Those are the basics to accessing your webcam with a web browser. As a finale, I’ll share a few lines of CSS I found <a href="http://christianheilmann.com/2013/07/19/flipping-the-image-when-accessing-the-laptop-camera-with-getusermedia/">here</a> that will flip the video horizontally. Put this in the head of the html file, and your video will appear like a mirror.</p>
<script src="https://gist.github.com/scrawlon/ff7afd87f81edb0ce004.js?file=mirror.html.extra"></script>
<p>If all of that worked for you, then ‘cool’. You can take these basics and build on them. What else would this be useful for? You could do time lapse photography, create a security camera or a photobooth app. I hope to follow up this tutorial with those very projects soon.</p>
<p>If you had issues getting this to work, or want just want to discuss the code, leave a message in the comments or <a href="https://twitter.com/scrawlon">hit me up on Twitter</a>.</p>Scott McGrathCode from this post is available on GitHub.Writing On Glass2015-07-06T00:00:00-04:002015-07-06T00:00:00-04:00https://scrawlon.com/2015/07/06/writing-on-glass<h2 id="infinite-notebook">Infinite Notebook</h2>
<p>Before the age of touchscreen computers, smartphones and tablets, I dreamed of a simple computing device that could do just one thing, mimic pen and paper - an infinite virtual notebook contained within a single page. The modern Windows 8.1 tablet w/ Wacom digitizer is the closest we’ve come to that ideal, plus they are fully functioning PCs as well.</p>
<p>Once I knew such a thing existed, I wanted one. The questions was, as always, how much was I willing to spend?</p>
<h2 id="tablet-one">Tablet One</h2>
<h3 id="samsung-series-7-slate"><a href="http://www.samsung.com/us/support/owners/product/XE700T1A-A04US">Samsung Series 7 Slate</a></h3>
<p><strong>Specs:</strong> i5 2467M, 4gb ram, 64gb SSD, Windows 7, 11.6” screen, weight: 2 pounds, price $1099, released in 2011</p>
<p>Is $1099 too much for a device used primarily as a digital notepad? YES! So I waited a couple years until I could find one on eBay for $300 in ok condition. I also bought a copy of Windows 8 and installed it immediately. I used the heck out of this tablet for taking notes. Since it’s a full Windows 8.1 PC, I also used it for games, web surfing, photo editing and writing code as well.</p>
<p><strong>Pros:</strong></p>
<ul>
<li>Screen - It’s bright. The size is a little smaller than a legal pad. It doesn’t flex as I write or rest my hand on it. Great pen accuracy.</li>
<li>Pen - It’s the size and shape of a normal pen, and the eraser side works like an eraser.</li>
<li>CPU/RAM - Aside from the handwriting, it’s a decent pc. Add a keyboard. Attach a monitor. It’s as capable as a laptop.</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Battery life - It’s good for about 2-3 hours of usage. It’s a shame, because I could overlook all it’s other shortcomings, if the battery life were better.</li>
<li>Heat/Fan noise - This tablet could double as a hot plate. Because of that, the fan runs a lot.</li>
<li>No pen slot - There’s no pen holder, so you have to carry the delicate stylus around separately.</li>
</ul>
<h2 id="tablet-two">Tablet Two</h2>
<h3 id="lenovo-thinkpad-s1-yoga-ultrabooktablet-hybrid"><a href="http://shop.lenovo.com/ca/en/laptops/thinkpad/yoga-series/yoga/">Lenovo Thinkpad S1 Yoga</a> (ultrabook/tablet hybrid)</h3>
<p><strong>Specs:</strong> i7 4600U, 8gb ram, 256gb SSD, Windows 8.1, 12.5” screen, 3.6 pounds, price $1499, released in 2013</p>
<p>I was actually looking for an ultrabook when I bought this. I loved having a pen tablet, so the hybrid concept drew me in. I thought I could replace two devices with one. That plus a 7 hour battery life, are what sold me on this machine. I grabbed one on eBay for $750.</p>
<p><strong>Pros:</strong></p>
<ul>
<li>Battery life - 7+ hours!</li>
<li>Pen slot - The pen is always tucked away in it’s slot and ready to go when you need it.</li>
<li>Hybrid - It’s a full ultrabook as well, so it’s as useful as any other computer. Since this post is about tablets, I won’t cover the specific pros/cons of the non-tablet features</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>The size/weight: Hybrid is an interesting concept, but until the size/weight can be brought down even further, it’s not practical. I only ever use the screen for writing if my dedicated tablet is unavailable.</li>
<li>Pen accuracy: For whatever reason, the screen doesn’t capture pen strokes as accurately as the Samsung Slate. The larger screen size can make it difficult to avoid inadvertent scribble marks caused by my hand, wrist or arm touching the screen before the palm rejection kicks in as the pen gets close enough to the screen. There’s also a bit of screen flex when the pen is pressed down while writing. The first time I noticed it, I was afraid I was pressing too hard, but any pressure at all will cause it.</li>
<li>The Pen: The pen is short and thin. It’s uncomfortable to hold. It’s easy to accidentally press the stylus button. The eraser end does nothing.</li>
</ul>
<h2 id="tablet-three">Tablet Three</h2>
<h3 id="thinkpad-tablet-2"><a href="http://shop.lenovo.com/us/en/tablets/thinkpad/thinkpad-tablet-2/">Thinkpad Tablet 2</a></h3>
<p><strong>Specs:</strong> Atom Z2760, 2gb ram, 64gb SSD, Windows 8.1, 1.3 pounds, price $799, released in 2012</p>
<p>This is my current note taking tablet. I found one on eBay for $150. It’s light. It’s small. It’s got great battery life. It’s almost exactly what I want.</p>
<p><strong>Pros:</strong></p>
<ul>
<li>Battery life: 10 hours!!</li>
<li>Size/Weight: It feels like I’m carrying a normal notepad.</li>
<li>Charger: Standard micro USB. I never have to worry about forgetting my proprietary charger.</li>
<li>Quiet: No fan. No noise.</li>
<li>Pen slot: Like the Thinkpad S1 Yoga, the pen fits in a small slot and is always available to use.</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Pen accuracy: The screen suffers the same issues as the other Thinkpad. It doesn’t pick up the pen strokes as well as the Samsung Slate.</li>
<li>The pen: This has the exact same pen as the Thinkpad S1 Yoga. It’s small and uncomfortable to hold.</li>
</ul>
<h2 id="now-the-easy-part">Now the Easy Part…</h2>
<p>Once I had a tablet, I was set. I could start taking notes then. Right? Almost… The <a href="http://windows.microsoft.com/en-us/windows7/create-and-personalize-handwritten-notes-using-windows-journal">Windows Journal</a> app comes pre-installed in Windows 8, but it’s not widely known or particularly good. Recently, Microsoft released a free version of <a href="https://www.onenote.com/">OneNote</a>, a popular note taking app with many features. Unfortunately, like of so many other MS products (ahem… Word), it’s bloated and overly complicated to use. All I want is a digital notepad. How hard is that?</p>
<p>My app of choice is <a href="http://bamboopaper.wacom.com/">Bamboo Paper</a> for that very reason. It’s simple to use, has all the right features and it’s free (plus some Pro in-app purchases). In under a minute, I can wake my tablet, start Bamboo Paper, choose a notebook and start writing. I use it daily. Every note I’ve taken is just a few clicks away. It is essential to my workflow.</p>Scott McGrathInfinite NotebookChrUbuntu + Acer C7202014-07-11T00:00:00-04:002014-07-11T00:00:00-04:00https://scrawlon.com/2014/07/11/chrubuntu-tips<p>I’ve been running ChrUbuntu on a C720 for a couple months now.
It’s a great cheap laptop option for developers, but it does require a few
OS tweaks. This is a list of the ones I’ve been using.</p>
<p>I found most of these in the
ChrUbuntu communities on <a href="http://www.reddit.com/r/Chrubuntu">Reddit</a> and <a href="https://plus.google.com/communities/108883927831773328803">Google+</a>.
If you know any other tips, leave a comment and I’ll add them to the list.</p>
<h2 id="restore-the-trackpad">Restore the trackpad:</h2>
<ol>
<li>Ctrl+Alt+T
<ul>
<li>wget http://goo.gl/kz917j</li>
<li>sudo bash kz917j
<ul>
<li>This can take a long time.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>SOURCE: <a href="https://plus.google.com/communities/108883927831773328803">https://plus.google.com/communities/108883927831773328803</a></p>
<h2 id="enable-suspend">Enable suspend:</h2>
<ol>
<li>Ctrl+Alt+T
<ul>
<li>edit: /etc/default/grub</li>
<li>comment out: GRUB_CMDLINE_LINUX_DEFAULT</li>
<li>add: GRUB_CMDLINE_LINUX_DEFAULT=”quiet splash add_efi_memmap boot=local noresume noswap i915.modeset=1 tpm_tis.force=1 tpm_tis.interrupts=0 nmi_watchdog=panic,lapic”</li>
<li>close file</li>
<li>sudo update-grub</li>
<li>sudo update-grub2</li>
<li>reboot</li>
</ul>
</li>
</ol>
<p>SOURCE: <a href="http://realityequation.net/installing-elementary-os-on-an-hp-chromebook-14">http://realityequation.net/installing-elementary-os-on-an-hp-chromebook-14</a></p>
<h2 id="change-the-default-screen-brightness">Change the default screen brightness</h2>
<ol>
<li>Ctrl+Alt+T
<ul>
<li>edit: /etc/rc.local</li>
<li>add: echo 300 &gt; /sys/class/backlight/intel_backlight/brightness
<ul>
<li>substitute any number up to 900 for “300” in the line above</li>
<li><strong>To test different brightness levels</strong></li>
</ul>
</li>
</ul>
<ol>
<li>echo 300 | sudo tee /sys/class/backlight/intel_backlight/brightness &gt; /dev/null
<ul>
<li>repeat above command substituting numbers up to 900</li>
</ul>
</li>
</ol>
</li>
</ol>
<p>SOURCE: <a href="https://plus.google.com/communities/108883927831773328803">https://plus.google.com/communities/108883927831773328803</a></p>
<h2 id="remap-the-search-key-to-shift-key">Remap the search key to shift key</h2>
<ol>
<li>Ctrl+Alt+T
<ul>
<li>xmodmap -e “keycode 133 = Shift_L”
<ul>
<li>Unfortunately, this is a temporary fix. You’ll need to rerun this command at every reboot.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>SOURCE: <a href="http://darknet.co.za/blog/?page=posts.2011&amp;post=2011-02-HappyHackTipremapyourkeyboardbuttons.md">http://darknet.co.za/blog/?page=posts.2011&amp;post=2011-02-HappyHackTipremapyourkeyboardbuttons.md</a></p>
<h2 id="disable-trackpad-while-typing">Disable trackpad while typing</h2>
<ol>
<li>Ctrl+Alt+T
<ul>
<li>syndaemon -i 1 -K d
<ul>
<li>This will continue running in your Terminal window. Press Ctrl-C to stop it.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>SOURCE: <a href="http://askubuntu.com/questions/299868/disable-touchpad-while-typing-does-not-work">http://askubuntu.com/questions/299868/disable-touchpad-while-typing-does-not-work</a></p>
<h2 id="mute-the-login-sound">Mute the login sound</h2>
<ol>
<li>On the login screen, before you enter your User Name and Password,
go to the speaker icon in upper right-hand corner of your screen and select mute.
<ul>
<li>From now on the audio will be muted on the login screen. Audio will return to normal once you’re logged in.</li>
</ul>
</li>
</ol>
<p>SOURCE: <a href="http://askubuntu.com/questions/24946/how-do-i-disable-the-drum-beat-sound-on-the-login-screen">http://askubuntu.com/questions/24946/how-do-i-disable-the-drum-beat-sound-on-the-login-screen</a></p>Scott McGrathI’ve been running ChrUbuntu on a C720 for a couple months now. It’s a great cheap laptop option for developers, but it does require a few OS tweaks. This is a list of the ones I’ve been using.FatSecret OmniAuth: A Quick Intro - Part 22013-08-27T00:00:00-04:002013-08-27T00:00:00-04:00https://scrawlon.com/2013/08/27/quick-intro-to-fatsecret-omniauth-ruby-gem-part-2<p>In <a href="/quick-intro-to-fatsecret-omniauth-ruby-gem/" title="part one">part one</a> we installed and configured the FatSecret gem. In part two,
we’ll explore ways to call the FatSecret API.</p>
<p><em>Also, you’ll need to make sure you’re using the latest version of the
<a href="https://github.com/scrawlon/fatsecret-omniauth" title="fatsecret-omniauth gem">fatsecret-omniauth gem</a>. The current version at the time of this blog post is 0.0.2.</em></p>
<h2 id="the-apis-controller">The Apis Controller</h2>
<p>We created the ApiTokens controller in Part One for obtaining and saving
FatSecret auth tokens. Now, we need a controller to handle api
requests. Open your app folder in a console window and type the following:</p>
<p><code class="highlighter-rouge">rails g controller Apis fatsecret</code></p>
<p>Edit the new <code class="highlighter-rouge">app/controllers/apis_controller.rb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ApisController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">fatsecret</span>
<span class="n">tokens</span> <span class="o">=</span> <span class="p">{}</span>
<span class="k">unless</span> <span class="n">params</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">].</span><span class="nf">nil?</span>
<span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:user_id</span><span class="p">])</span>
<span class="n">tokens</span> <span class="o">=</span> <span class="n">user</span><span class="p">.</span><span class="nf">api_tokens</span><span class="p">.</span><span class="nf">find_by_provider</span><span class="p">(</span><span class="s1">'fatsecret'</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">request</span> <span class="o">=</span> <span class="no">Fatsecret</span><span class="o">::</span><span class="no">Api</span><span class="p">.</span><span class="nf">new</span><span class="p">({}).</span><span class="nf">api_call</span><span class="p">(</span>
<span class="s1">'FATSECRET_KEY'</span><span class="p">,</span>
<span class="s1">'FATSECRET_SECRET'</span><span class="p">,</span>
<span class="n">params</span><span class="p">,</span>
<span class="n">tokens</span><span class="p">[</span><span class="s1">'auth_token'</span><span class="p">]</span> <span class="o">||=</span> <span class="s2">""</span><span class="p">,</span>
<span class="n">tokens</span><span class="p">[</span><span class="s1">'auth_secret'</span><span class="p">]</span> <span class="o">||=</span> <span class="s2">""</span>
<span class="p">)</span>
<span class="vi">@response</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">body</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Create a new route for the apis#fatsecret method above all other routes in <code class="highlighter-rouge">config/routes.rb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">post</span> <span class="s2">"/fatsecret"</span><span class="p">,</span> <span class="ss">to: </span><span class="s2">"apis#fatsecret"</span>
</code></pre></div></div>
<p>The apis#fatscret method provides a way to make any FatSecret API request.
You only need to include your FatSecret query in a params hash,
and send this to api#fatsecret. See the <a href="http://platform.fatsecret.com/api/Default.aspx?screen=rapiref" title="FatSecret API method docs">FatSecret API method docs</a>
for details on what parameters are required for each FatSecret API method.</p>
<p>To test this, let’s create a form to use the <strong>foods.search</strong> method without user authentication:</p>
<p>Edit <code class="highlighter-rouge">app/views/home/index.html.erb</code> to include a search form:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">h3</span><span class="o">&gt;</span><span class="no">Home</span><span class="o">&lt;</span><span class="sr">/h3&gt;
&lt;h1&gt;Try FatSecret Food Search&lt;/</span><span class="n">h1</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="sx">%= form_tag fatsecret_path %&gt;
&lt;%=</span> <span class="n">label_tag</span><span class="p">(</span><span class="ss">:search_expression</span><span class="p">,</span> <span class="s2">"Search for food:"</span><span class="p">)</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= text_field_tag(:search_expression) %&gt;
&lt;%=</span> <span class="n">hidden_field_tag</span> <span class="s1">'method'</span><span class="p">,</span> <span class="s1">'foods.search'</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= submit_tag("Search") %&gt;
&lt;% end %&gt;
&lt;% @users.each do |user| %&gt;
&lt;p&gt;User: &lt;%=</span> <span class="n">link_to</span> <span class="n">user</span><span class="p">.</span><span class="nf">name</span><span class="p">,</span> <span class="n">user</span> <span class="sx">%&gt;&lt;/p&gt;</span>
<span class="o">&lt;</span><span class="sx">% end </span><span class="o">%&gt;</span>
</code></pre></div></div>
<p>Edit <code class="highlighter-rouge">app/views/apis/fatsecret.html.erb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">h1</span><span class="o">&gt;</span><span class="no">Apis</span><span class="c1">#fatsecret&lt;/h1&gt;</span>
<span class="o">&lt;</span><span class="n">pre</span><span class="o">&gt;&lt;</span><span class="sx">%= @response %&gt;&lt;/pre&gt;
</span></code></pre></div></div>
<ul>
<li>Now start the Rails server and open the home page in your browser.</li>
</ul>
<p><img src="/images/fatsecret-omniauth-foods-search-form.jpg" alt="FatSecret foods.search form" title="foods.search form" /></p>
<ul>
<li>Enter a food and press the ‘Search’ button.</li>
</ul>
<p><img src="/images/fatsecret-omniauth-foods-search-results.jpg" alt="FatSecret foods.search results" title="foods.search results" /></p>
<p>If everything worked as expected you should see something like my search for ‘banana’ above.</p>
<p>Awesome. Now, let’s tie everything together and make an authenticated API call using the
using the auth_token and auth_secret we obtained in Part 1 of this blog post.</p>
<p>First, add a new route for authenticated API calls below <code class="highlighter-rouge">post "/fatsecret", to: "apis#fatsecret"</code>
in <code class="highlighter-rouge">config/routes.rb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">post</span> <span class="s2">"/users/:user_id/fatsecret"</span><span class="p">,</span> <span class="ss">to: </span><span class="s2">"apis#fatsecret"</span><span class="p">,</span> <span class="ss">as: </span><span class="s2">"fatsecret_auth"</span>
</code></pre></div></div>
<p>Edit <code class="highlighter-rouge">app/views/users/show.html.erb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">h3</span><span class="o">&gt;</span><span class="no">User</span><span class="o">&lt;</span><span class="sr">/h3&gt;
&lt;p&gt;User: &lt;%= @user.name %&gt;&lt;/</span><span class="nb">p</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="nb">p</span><span class="o">&gt;</span><span class="no">Email</span><span class="p">:</span> <span class="o">&lt;</span><span class="sx">%= @user.email if @user.email %&gt;&lt;/p&gt;
&lt;h4&gt;Your APIs&lt;/h4&gt;
&lt;ul&gt;
&lt;% user_apis =</span> <span class="p">[]</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">% @user.api_tokens.each </span><span class="k">do</span> <span class="o">|</span><span class="n">api</span><span class="o">|</span> <span class="sx">%&gt;
&lt;li&gt;</span><span class="o">&lt;</span><span class="n">b</span><span class="o">&gt;&lt;</span><span class="sx">%= api.provider.camelize %&gt;:&lt;/b&gt;&lt;/li&gt;
&lt;% user_apis &lt;&lt; api.provider %&gt;
&lt;% end %&gt;
&lt;% if user_apis.include?('fatsecret') %&gt;
&lt;%=</span> <span class="n">link_to</span> <span class="s2">"Get FatSecret Profile"</span><span class="p">,</span> <span class="n">fatsecret_auth_path</span><span class="p">(</span><span class="vi">@user</span><span class="p">,</span> <span class="ss">:method</span> <span class="o">=&gt;</span> <span class="s1">'profile.get'</span><span class="p">),</span> <span class="ss">:method</span> <span class="o">=&gt;</span> <span class="s1">'post'</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">% else </span><span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= link_to 'Add FatSecret', new_user_api_token_path(@user) %&gt;
&lt;% end %&gt;
&lt;/ul&gt;
</span></code></pre></div></div>
<ul>
<li>Restart the Rails server and open the home page in your browser.</li>
<li>Signup and login as a new user.</li>
<li>Click on your username in the User list at the bottom of the page.</li>
<li>On your user screen click on ‘Add FatSecret’and authenticate with your FatSecret account
(as described in <a href="/quick-intro-to-fatsecret-omniauth-ruby-gem/" title="Part 1 of this post">Part 1 of this post</a>).</li>
<li>If successful, your FatSecret user auth_token and auth_secret will be saved in you app
user’s api_tokens database, and you should see the ‘Get FatSecret Profile’ link as pictured below.</li>
</ul>
<p><img src="/images/fatsecret-omniauth-profile-get.jpg" alt="FatSecret profile.get link" title="FatSecret profile.get link" /></p>
<ul>
<li>When you click the ‘Get FatSecret Profile’ link, an authenticated API call to FatSecret will return
your FatSecret user profile.</li>
</ul>
<p><img src="/images/fatsecret-omniauth-profile-get-response.jpg" alt="FatSecret profile.get response" title="FatSecret profile.get response" /></p>
<p>That’s it. We made authenticated and unauthenticated calls to the FatSecret API. We used a form for one and a link for the other.
Going forward, all you need to do is read the FatSecret API docs and make sure to include all required
FatSecret method parameters in the params hash you send to the apis#fatsecret method.</p>
<h2 id="bonus-points">Bonus Points</h2>
<p>Here’s an example of posting data to a user’s FatSecret account via an authenticated api call.
This follows the same pattern as before. Read the API docs and include the required parameters in the params hash.</p>
<p>Add this form below the ‘Get FatSecret Profile’ link in <code class="highlighter-rouge">app/views/users/show.html.erb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nb">p</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="n">h3</span><span class="o">&gt;</span><span class="no">Update</span> <span class="n">your</span> <span class="n">weight</span> <span class="n">on</span> <span class="no">FatSecret</span><span class="ss">:&lt;</span><span class="o">/</span><span class="n">h3</span><span class="o">&gt;</span>
<span class="o">&lt;</span><span class="sx">%= form_tag fatsecret_auth_path(@user) do %&gt;
&lt;%=</span> <span class="n">label_tag</span><span class="p">(</span><span class="ss">:goal_weight_kg</span><span class="p">,</span> <span class="s2">"Enter your goal weight in kg:"</span><span class="p">)</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= text_field_tag(:goal_weight_kg) %&gt;&lt;br /&gt;
&lt;%=</span> <span class="n">label_tag</span><span class="p">(</span><span class="ss">:current_height_cm</span><span class="p">,</span> <span class="s2">"Enter your current height in cm:"</span><span class="p">)</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= text_field_tag(:current_height_cm) %&gt;&lt;br /&gt;
&lt;%=</span> <span class="n">label_tag</span><span class="p">(</span><span class="ss">:current_weight_kg</span><span class="p">,</span> <span class="s2">"Enter your current weight in kg:"</span><span class="p">)</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= text_field_tag(:current_weight_kg) %&gt;&lt;br /&gt;
&lt;%=</span> <span class="n">hidden_field_tag</span> <span class="s1">'method'</span><span class="p">,</span> <span class="s1">'weight.update'</span> <span class="o">%&gt;</span>
<span class="o">&lt;</span><span class="sx">%= submit_tag("Update Weight") %&gt;
&lt;% end %&gt;
&lt;/p&gt;
</span></code></pre></div></div>Scott McGrathIn part one we installed and configured the FatSecret gem. In part two, we’ll explore ways to call the FatSecret API.FatSecret OmniAuth: A Quick Intro - Part 12013-08-09T00:00:00-04:002013-08-09T00:00:00-04:00https://scrawlon.com/2013/08/09/quick-intro-to-fatsecret-omniauth-ruby-gem<p>The following is a quick guide to the <a href="https://github.com/scrawlon/fatsecret-omniauth" title="FatSecret OmniAuth gem">FatSecret OmniAuth gem</a>.
With this gem, you can obtain authorization to access a user’s FatSecret account.
You’ll use your <a href="http://platform.fatsecret.com/api/Default.aspx?screen=si" title="FatSecret API">FatSecret API</a> consumer_key and consumer_secret
to work with the example app. If you’re not familiar with OmniAuth,
you might want to browse the <a href="https://github.com/intridea/omniauth/blob/master/README.md" title="browse the README on Github">README</a>.</p>
<h2 id="install-a-dummy-user-app">Install a dummy User app</h2>
<p>Before we do anything, we need a Rails app with a User model. We’ll take advantage of the
<a href="https://github.com/RailsApps/rails-composer" title="Rails Composer project">Rails Composer project</a> to get us started.</p>
<ol>
<li>
<p>First let’s clone the bare dummy app:<br />
<code class="highlighter-rouge">git clone https://github.com/RailsApps/rails3-devise-rspec-cucumber.git</code></p>
</li>
<li>
<p>Now let’s initialize and start the app to make sure everything is working:<br />
<code class="highlighter-rouge">cd rails3-devise-rspec-cucumber</code>
<code class="highlighter-rouge">bundle install</code><br />
<code class="highlighter-rouge">rake db:migrate</code><br />
<code class="highlighter-rouge">rails s</code></p>
</li>
<li>
<p>Open ‘http://localhost:3000’ in your internet browser. You should see a
simple web page with ‘login’ and ‘Sign up’ links at the top.</p>
</li>
</ol>
<h2 id="install-fatsecret-omniauth">Install FatSecret OmniAuth</h2>
<p>If you followed the instructions to set up the example Rails app, return to your command prompt and stop the
Rails server with <code class="highlighter-rouge">ctrl-c</code>.</p>
<ol>
<li>
<p>Add FatSecret OmniAuth gem to your <code class="highlighter-rouge">Gemfile</code>:
<code class="highlighter-rouge">gem 'fatsecret-omniauth'</code></p>
</li>
<li>
<p>Install the gem:
<code class="highlighter-rouge">bundle install</code></p>
</li>
<li>
<p>Create <code class="highlighter-rouge">omniauth.rb</code> in the <code class="highlighter-rouge">config/initializers</code> directory, and add the following code with your real FatSecret key and secret:</p>
</li>
</ol>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">middleware</span><span class="p">.</span><span class="nf">use</span> <span class="no">OmniAuth</span><span class="o">::</span><span class="no">Builder</span> <span class="k">do</span>
<span class="n">provider</span> <span class="ss">:fatsecret</span><span class="p">,</span> <span class="s1">'consumer_key'</span><span class="p">,</span> <span class="s1">'consumer_secret'</span>
<span class="k">end</span>
</code></pre></div></div>
<h2 id="create-an-api_token-modelcontroller">Create an api_token model/controller</h2>
<ol>
<li>
<p>Generate the model and migrate the database. This gives us a way to save
FatSecret tokens and associate them with individual users:</p>
<p>$<code class="highlighter-rouge">rails g model ApiToken provider:string auth_token:string auth_secret:string user_id:integer</code>
$<code class="highlighter-rouge">rake db:migrate</code></p>
</li>
<li>
<p>Create a has_many/belongs_to association between the User and ApiToken models.</p>
<p>Below <strong>class User &lt; ActiveRecord::Base</strong> in <code class="highlighter-rouge">app/models/user.rb</code> add:<br />
<code class="highlighter-rouge">has_many :api_tokens</code></p>
<p>Below <strong>class ApiToken &lt; ActiveRecord::Base</strong> in <code class="highlighter-rouge">app/models/api_token.rb</code> add:<br />
<code class="highlighter-rouge">belongs_to :user</code></p>
</li>
<li>
<p>Create the ApiTokens controller:</p>
<p>$<code class="highlighter-rouge">rails g controller ApiTokens create</code></p>
<p>Edit <code class="highlighter-rouge">app/controllers/api_tokens_controller.rb</code>:</p>
</li>
</ol>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">ApiTokensController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
<span class="n">before_filter</span> <span class="ss">:authenticate_user!</span>
<span class="k">def</span> <span class="nf">create</span>
<span class="n">auth</span> <span class="o">=</span> <span class="n">omniauth</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="nf">env</span><span class="p">[</span><span class="s1">'omniauth.auth'</span><span class="p">])</span>
<span class="n">user_id</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">env</span><span class="p">[</span><span class="s1">'omniauth.params'</span><span class="p">][</span><span class="s1">'user_id'</span><span class="p">]</span>
<span class="n">origin</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="nf">env</span><span class="p">[</span><span class="s1">'omniauth.origin'</span><span class="p">]</span>
<span class="vi">@user</span> <span class="o">=</span> <span class="no">User</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">user_id</span><span class="p">)</span>
<span class="vi">@new_api</span> <span class="o">=</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">api_tokens</span><span class="p">.</span><span class="nf">build</span><span class="p">(</span><span class="n">auth</span><span class="p">)</span>
<span class="k">if</span> <span class="vi">@new_api</span><span class="p">.</span><span class="nf">save</span>
<span class="n">redirect_to</span> <span class="n">origin</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">omniauth</span> <span class="n">auth</span>
<span class="n">params</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"provider"</span> <span class="o">=&gt;</span> <span class="n">auth</span><span class="p">[</span><span class="s1">'provider'</span><span class="p">],</span>
<span class="s2">"auth_token"</span> <span class="o">=&gt;</span> <span class="n">auth</span><span class="p">[</span><span class="s1">'credentials'</span><span class="p">][</span><span class="s1">'token'</span><span class="p">],</span>
<span class="s2">"auth_secret"</span> <span class="o">=&gt;</span> <span class="n">auth</span><span class="p">[</span><span class="s1">'credentials'</span><span class="p">][</span><span class="s1">'secret'</span><span class="p">]</span>
<span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>There’s a lot going on here. Let me explain. When our users sign into FatSecret, the FatSecret OmniAuth gem
returns a <strong>request.env</strong> hash to the <strong>api_tokens_controller</strong> including the <em>provider name</em>,
the <em>auth tokens</em>, the <em>user_id</em>, the <em>origin</em> (the route back to where the user started) and
a lot more data we’re not using. Check out the <a href="https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema" title="Auth Hash Schema doc">Auth Hash Schema doc</a> for more info.</p>
<h2 id="create-the-routes-and-views">Create the routes and views:</h2>
<p>Edit <code class="highlighter-rouge">config/routes.rb</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="no">Rails3DeviseRspecCucumber</span><span class="o">::</span><span class="no">Application</span><span class="p">.</span><span class="nf">routes</span><span class="p">.</span><span class="nf">draw</span> <span class="k">do</span>
<span class="n">match</span> <span class="s1">'/users/:user_id/api_tokens/new'</span> <span class="o">=&gt;</span> <span class="n">redirect</span><span class="p">(</span><span class="s1">'/auth/fatsecret?user_id=%{user_id}'</span><span class="p">)</span>
<span class="n">get</span> <span class="s1">'/auth/fatsecret/callback'</span><span class="p">,</span> <span class="ss">to: </span><span class="s1">'api_tokens#create'</span>
<span class="n">authenticated</span> <span class="ss">:user</span> <span class="k">do</span>
<span class="n">root</span> <span class="ss">:to</span> <span class="o">=&gt;</span> <span class="s1">'home#index'</span>
<span class="k">end</span>
<span class="n">root</span> <span class="ss">:to</span> <span class="o">=&gt;</span> <span class="s2">"home#index"</span>
<span class="n">devise_for</span> <span class="ss">:users</span>
<span class="n">resources</span> <span class="ss">:users</span> <span class="k">do</span>
<span class="n">resources</span> <span class="ss">:api_tokens</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Edit <code class="highlighter-rouge">app/views/users/show.html.erb</code>:</p>
<div class="language-erb highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h3&gt;</span>User<span class="nt">&lt;/h3&gt;</span>
<span class="nt">&lt;p&gt;</span>User: <span class="cp">&lt;%=</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">name</span> <span class="cp">%&gt;</span><span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;p&gt;</span>Email: <span class="cp">&lt;%=</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">email</span> <span class="k">if</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">email</span> <span class="cp">%&gt;</span><span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;h4&gt;</span>Your APIs<span class="nt">&lt;/h4&gt;</span>
<span class="nt">&lt;ul&gt;</span>
<span class="cp">&lt;%</span> <span class="n">user_apis</span> <span class="o">=</span> <span class="p">[]</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%</span> <span class="vi">@user</span><span class="p">.</span><span class="nf">api_tokens</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">api</span><span class="o">|</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;li&gt;&lt;b&gt;</span><span class="cp">&lt;%=</span> <span class="n">api</span><span class="p">.</span><span class="nf">provider</span><span class="p">.</span><span class="nf">camelize</span> <span class="cp">%&gt;</span><span class="nt">&lt;/b&gt;</span>( token: <span class="cp">&lt;%=</span> <span class="n">api</span><span class="p">.</span><span class="nf">auth_token</span> <span class="cp">%&gt;</span>, secret: <span class="cp">&lt;%=</span> <span class="n">api</span><span class="p">.</span><span class="nf">auth_secret</span> <span class="cp">%&gt;</span> <span class="nt">&lt;/li&gt;</span>
<span class="cp">&lt;%</span> <span class="n">user_apis</span> <span class="o">&lt;&lt;</span> <span class="n">api</span><span class="p">.</span><span class="nf">provider</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%</span> <span class="k">unless</span> <span class="n">user_apis</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="s1">'fatsecret'</span><span class="p">)</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%=</span> <span class="n">link_to</span> <span class="s1">'Add FatSecret'</span><span class="p">,</span> <span class="n">new_user_api_token_path</span><span class="p">(</span><span class="vi">@user</span><span class="p">)</span> <span class="cp">%&gt;</span>
<span class="cp">&lt;%</span> <span class="k">end</span> <span class="cp">%&gt;</span>
<span class="nt">&lt;/ul&gt;</span>
</code></pre></div></div>
<p>In the routes, we’ve made :api_tokens a nested resource of :users. This performs some magic for us:</p>
<ul>
<li>It creates a few named routes, including the <strong>new_user_api_token_path</strong> we’re using for our ‘Add FatSecret’ link.</li>
<li>It includes the user_id in the route, so we can send that to our api_tokens_controller along with our Fatsecret data.</li>
</ul>
<h2 id="done-get-connected-to-fatsecret">Done. Get connected to FatSecret!</h2>
<p>Start the app $<code class="highlighter-rouge">rails s</code>
Open <code class="highlighter-rouge">http://localhost:3000</code> in your browser.<br />
Sign up and login as a new user.<br />
Click on your user name.<br />
Click ‘Add Fatsecret’.</p>
<p>You should be redirected to the FatSecret website.<br />
Sign In (if you’re already signed in click ‘Allow’)<br />
You’ll be redirected back to the app.
Your FatSecret tokens are saved in the database.</p>
<h2 id="conclusion">Conclusion</h2>
<p>If you were able to follow along, you should have an app that can retrieve FatSecret auth data.
If you get stuck or have any questions, leave a comment here or Tweet me @scrawlon.</p>
<p>The next step is using the auth data to connect to the FatSecret REST API and do searches on a user’s
behalf. That will be the topic of my next post.</p>
<p><strong>UPDATE</strong> <a href="http://scrawlon.com/quick-intro-to-fatsecret-omniauth-ruby-gem-part-2/" title=" Added Part 2 of this tutorial explaining how to make FatSecret API calls.">Added Part 2 of this tutorial explaining how to make FatSecret API calls.</a></p>Scott McGrathThe following is a quick guide to the FatSecret OmniAuth gem. With this gem, you can obtain authorization to access a user’s FatSecret account. You’ll use your FatSecret API consumer_key and consumer_secret to work with the example app. If you’re not familiar with OmniAuth, you might want to browse the README.Installing Vagrant on Windows 82013-07-10T00:00:00-04:002013-07-10T00:00:00-04:00https://scrawlon.com/2013/07/10/installing-vagrant-on-windows-8<p><em>** <strong>Disclaimer</strong> ** I make no guarantees that the following instructions will work for you.
Proceed at your own risk.</em></p>
<p>I just bought a used Windows 8 tablet, and I need to get a working development environment on it.
I usually work with Macs and Linux, so my aim is to run Linux from Windows. VirtualBox makes this possible.
Just like running any other program, the new OS opens in its own window.</p>
<p>Vagrant uses VirtualBox to create custom command-line driven virtual machines for projects.
The real magic in Vagrant, is that it
syncs folders between your real OS and your virtual machines.
You can edit a file in your real OS, start your app on localhost in the
virtual machine and then load it in the browser on your real OS. It may sound confusing, but it’s very simple
once you start using it.</p>
<h2 id="installation">Installation</h2>
<ul>
<li>
<p>Dependencies (You’ll need these installed in order to run Vagrant): <br />
<a href="https://www.virtualbox.org/wiki/Downloads">VirtualBox</a> -&gt; “4.2.16 for Windows hosts”<br />
<a href="http://railsinstaller.org">Ruby and Git</a> -&gt; “Rails Installer 1.9 will install Ruby, Rails, Git and more”<br />
<a href="http://downloads.vagrantup.com/">Vagrant</a> -&gt; 1.2.3 .msi for Windows</p>
</li>
<li>
<p>Run this command after running Rails Installer to make ssh work: <br />
<code class="highlighter-rouge">setx PATH "%PATH%;C:\RailsInstaller\Git\bin"</code> <br />
<em>You have to exit and reopen your command prompt to make windows see the new path.</em></p>
</li>
</ul>
<p><em>Versions listed were the latest at the time of this post</em></p>
<h2 id="create-your-first-vagrant-box">Create your first Vagrant box</h2>
<p>Most of these instructions are taken directly from the <a href="http://docs.vagrantup.com/v2/getting-started/index.html">“Getting Started”</a>
guide at <a href="http://vagrantup.com">Vagrantup.com</a></p>
<ol>
<li>
<p>If you installed Ruby from the link above you should have a <em>‘Command Prompt with Ruby on Rails’</em> shortcut
on your desktop. Start by opening your command prompt.</p>
</li>
<li>
<p>Create a new directory for your first Vagrant virtual machine. For example:
<code class="highlighter-rouge">C:\vagrant_test</code> and change to that directory.</p>
</li>
<li>
<p>Next, we’ll initialize this directory and download a Vagrant box.
<code class="highlighter-rouge">vagrant init precise32 http://files.vagrantup.com/precise32.box</code><br />
This step could take an hour or more, depending on you connection speed. Luckily, you only need to download the ‘precise32’ image once.</p>
</li>
<li>
<p>Now we can start Vagrant and connect to our new virtual machine.
<code class="highlighter-rouge">vagrant up</code><br />
<code class="highlighter-rouge">vagrant ssh</code></p>
</li>
</ol>
<p>If everything worked, you should see a prompt like this:
<code class="highlighter-rouge">vagrant@precise32: $</code> If so, congrats! You’ve got a real Ubuntu environment running in Windows.
Your shared folder is in the “/vagrant” directory of your virtual machine.</p>
<p>To get back to your normal prompt, just type “exit”. To stop the Vagrant virtual machine,
type “vagrant halt” from your normal prompt. To delete a Vagrant virtual machine, type “vagrant destroy”.</p>Scott McGrath** Disclaimer ** I make no guarantees that the following instructions will work for you. Proceed at your own risk.How to Resize a VirtualBox VM2013-06-29T00:00:00-04:002013-06-29T00:00:00-04:00https://scrawlon.com/2013/06/29/how-to-resize-a-virtualbox-vm<p><em>**Last edited 03 Jul 13</em></p>
<p>So, I recently found out what happens when a virtual machine
runs out of space. Just resize the hard drive, right? Intuitively, one might
think there’d be a button for that… There isn’t.</p>
<p>A little background: I’m on OS X 10.7.5 and VirtualBox 4.2.12</p>
<p><em>** <strong>Disclaimer:</strong> ** This could potentially destroy all data on your virtual
machine. Back up all your data, and proceed at your own risk.</em></p>
<h2 id="heres-how-to-do-it">Here’s how to do it:</h2>
<ol>
<li>
<p>Shut down the virtual machine you’re trying to resize.</p>
</li>
<li>
<p>Open an OS X terminal window and navigate to the directory where your virtual
machine is. Mine is in “VirtualBox VMs/Ubuntu”.</p>
</li>
<li>
<p>Clone the virtual hard drive, so you’ll have a backup:<br />
<code class="highlighter-rouge">VBoxManage clonehd Your_virtual_machine.vdi clone.vdi</code><br />
<strong>If your vm is in .vmdk format, you need to convert to .vdi:</strong>
<code class="highlighter-rouge">VBoxManage clonehd Your_virtual_machine.vmdk clone.vdi -format VDI</code> <br />
<em>You should see a progress bar. This will take at least a few minutes.</em></p>
</li>
<li>
<p>Resize the hard drive:<br />
<code class="highlighter-rouge">VBoxManage modifyhd clone.vdi --resize 20000</code> <br />
(Where the number is the new size in mb.)</p>
</li>
<li>Create a new virtual machine:
<ul>
<li>Go back to VirtualBox and click ‘New’. Choose a new name and select the same OS and memory settings as the original.
<img src="/images/2013-06-29-how-to-resize-a-virtualbox-vm/screenshot1.jpg" alt="screenshot 1" /></li>
<li>At the ‘Hard drive’ screen, select ‘use an existing virtual hard drive file’
and choose the file you created in step 4. Click ‘Create’.
<img src="/images/2013-06-29-how-to-resize-a-virtualbox-vm/screenshot2.jpg" alt="screenshot 2" /> <br />
<em>The new machine will have all the installed programs and data as the original.</em></li>
</ul>
</li>
<li>Repartition the hard drive:
<ul>
<li>Fortunately, someone has
already written an excellent guide:
<a href="http://www.howtogeek.com/124622/how-to-enlarge-a-virtual-machines-disk-in-virtualbox-or-vmware">How To Enlarge a Virtual Machine’s Disk in VirtualBox or VMware</a>
<strong>- Skip about halfway down to the paragraph that begins “You can use a GParted live
CD…“</strong><br />
<strong>TIP:</strong> If you’re unable to resize your partition because there’s a swap file in
the way, follow this guide:
<a href="http://blog.mwpreston.net/2012/06/22/expanding-a-linux-disk-with-gparted-and-getting-swap-out-of-the-way/">Expanding a Linux disk with gparted (and getting swap out of the way)</a></li>
</ul>
</li>
</ol>
<p>Virtualization is an amazing thing.
The ability to create customized, clonable, disposable virtual computing
environments is endlessly useful. If that interests you, stay tuned for my upcoming
post about <a href="http://http://www.vagrantup.com/">Vagrant</a>.</p>Scott McGrath**Last edited 03 Jul 13