From cfdc2a8f1b8d073b26bcfb407ebfcd8573ed51dd Mon Sep 17 00:00:00 2001 From: James Doyle Date: Sun, 1 Dec 2024 12:32:26 -0800 Subject: [PATCH] Changed: run build --- docs/android/jellybean-nexuss/index.html | 2 +- .../light-weight/privacy-policy/index.html | 1 + docs/atom.xml | 8523 ----------------- docs/demo/flexbox-demo/index.html | 2 +- docs/demo/generated-content-title/index.html | 4 +- docs/demo/godaddy-digital-ocean/index.html | 2 +- docs/demo/index.html | 2 +- .../demo/letterpress-loader-in-css/index.html | 2 +- docs/demo/mozilla-dev-derby/index.html | 2 +- docs/demo/no-javascript-accordion/index.html | 2 +- docs/demo/socketio-works-howto/index.html | 2 +- docs/fiddle/css-date-card/index.html | 2 +- docs/fiddle/css3-pagebend/index.html | 2 +- .../generated-content-in-css/index.html | 4 +- docs/fiddle/index.html | 2 +- docs/fiddle/no-js-image-preview/index.html | 2 +- docs/index.html | 2 +- .../april-2013-redesign/index.html | 2 +- .../assemble-starter/index.html | 2 +- .../atom-monokai-dark/index.html | 4 +- .../canadian-provinces-field/index.html | 2 +- .../chrome-reverse-geocode/index.html | 2 +- .../clean-css-in-office-hours/index.html | 2 +- .../clean-css-updated/index.html | 2 +- .../docracy-logo-svg/index.html | 2 +- .../git-website-workflow/index.html | 2 +- .../github-wiki-to-html/index.html | 4 +- .../grunt-highlight/index.html | 4 +- .../personal-project/grunt-sundown/index.html | 6 +- docs/personal-project/index.html | 2 +- .../jquery-doodal-js/index.html | 4 +- .../koding-interview/index.html | 2 +- .../kube-in-stylrework/index.html | 2 +- .../kube-node-express/index.html | 2 +- .../lico-luvit-cms/index.html | 2 +- .../minimal-raspberry-pi-os/index.html | 6 +- docs/personal-project/npm-logo-svg/index.html | 2 +- docs/personal-project/nudeproject/index.html | 2 +- .../phalcon-micro-starter/index.html | 2 +- docs/personal-project/phile-cms/index.html | 2 +- .../phile-intro-video/index.html | 2 +- .../php-websocket-chat/index.html | 2 +- .../pico-download-plugin/index.html | 2 +- .../pico-get-by-filename-plugin/index.html | 2 +- .../pico-slider-plugin/index.html | 2 +- .../pico-useragent/index.html | 4 +- .../pyro-blurb-field/index.html | 2 +- .../pyro-github-markdown/index.html | 6 +- .../pyro-image-select-field/index.html | 2 +- .../pyro-image-widget/index.html | 2 +- .../pyro-list-field/index.html | 2 +- .../pyro-module-generator-2/index.html | 2 +- .../pyro-module-generator/index.html | 2 +- .../pyro-swipe-js-module/index.html | 2 +- .../pyro-twitter-widget/index.html | 2 +- .../pyrocms-pagewidgets-field-type/index.html | 2 +- .../pyrocms-ua-sniffer-plugin/index.html | 2 +- docs/personal-project/rework-math/index.html | 2 +- docs/personal-project/rework-shade/index.html | 2 +- .../personal-project/simple-binder/index.html | 4 +- .../startup-canada-svg/index.html | 2 +- .../sublime-node-snippets/index.html | 6 +- .../index.html | 4 +- docs/personal-project/vim-svg/index.html | 2 +- .../zepto-drag-swap/index.html | 2 +- docs/portfolio/grey-nimbus-website/index.html | 2 +- docs/portfolio/index.html | 2 +- docs/portfolio/my-old-website/index.html | 2 +- docs/portfolio/new-business-cards/index.html | 2 +- docs/sitemap.xml | 4 + .../animation-events-in-javascript/index.html | 2 +- docs/snippets/apax-in-htdocs/index.html | 2 +- .../autocomplete-tailwind-classes/index.html | 4 +- .../backup-mysql-and-email-it/index.html | 2 +- .../index.html | 2 +- docs/snippets/bash-select-example/index.html | 2 +- .../index.html | 4 +- .../copy-file-path-clipboard-osx/index.html | 4 +- .../index.html | 2 +- .../easy-ffmpeg-video-posters/index.html | 2 +- .../grunt-terminal-notifier-setup/index.html | 2 +- .../snippets/hostmonster-phpmailer/index.html | 2 +- docs/snippets/index.html | 2 +- .../jquery-plugin-snippets/index.html | 2 +- .../list-file-permission-numbers/index.html | 2 +- .../lodash-memo-with-timeout/index.html | 2 +- .../lodash-translation-function/index.html | 2 +- .../modernizr-svg-fallback-to-png/index.html | 2 +- docs/snippets/nodelist-each/index.html | 2 +- .../index.html | 4 +- .../phalconphp-crop-to-fit/index.html | 2 +- .../purge-file-from-github/index.html | 4 +- .../raspberry-pi-php-and-lighttpd/index.html | 6 +- docs/snippets/render-php-with-data/index.html | 2 +- docs/snippets/running-go-in-docker/index.html | 2 +- .../salt-js-mirco-selector-library/index.html | 4 +- .../simple-php-json-response/index.html | 4 +- .../tailwind-screens-in-js/index.html | 4 +- .../target-mozilla-only-in-css/index.html | 2 +- .../index.html | 2 +- .../using-node-in-applescript/index.html | 4 +- .../validate-email-with-lua/index.html | 2 +- .../index.html | 2 +- .../vue-stateful-form-component/index.html | 2 +- docs/snippets/vuex-crosstab/index.html | 4 +- docs/snippets/vuex-stateful-url/index.html | 4 +- .../wysiwyg-in-pyrocms-widgets/index.html | 2 +- .../snippets/zsh-new-open-function/index.html | 2 +- .../index.html | 2 +- .../disallowed-characters-in-uri/index.html | 2 +- .../index.html | 6 +- docs/tricks/hammerspoon-hyper-key/index.html | 2 +- .../index.html | 2 +- docs/tricks/index.html | 2 +- .../index.html | 2 +- .../openssl-passwd-without-prompt/index.html | 2 +- .../index.html | 4 +- .../index.html | 2 +- .../add-getstylesheet-to-jquery/index.html | 2 +- docs/web/angular-through-iframe/index.html | 2 +- .../index.html | 2 +- .../index.html | 2 +- docs/web/checkboxes-options/index.html | 2 +- docs/web/cms-watch-list/index.html | 2 +- .../index.html | 6 +- docs/web/css3-badge-logo-in-svg/index.html | 2 +- docs/web/custom-google-forms/index.html | 2 +- .../index.html | 6 +- docs/web/flexible-svg-placeholders/index.html | 2 +- docs/web/font-awesome-svg-icons/index.html | 2 +- .../index.html | 2 +- docs/web/index.html | 2 +- docs/web/kijiji-vector-logo/index.html | 2 +- .../web/laravel-scout-sonic-driver/index.html | 2 +- docs/web/laravel-sqlite-cache/index.html | 2 +- docs/web/nuxt-firebase-starter/index.html | 4 +- docs/web/phalconphp-completions/index.html | 2 +- docs/web/radio-checkboxes/index.html | 4 +- .../index.html | 2 +- docs/web/simple-slash-commands/index.html | 2 +- docs/web/simple-spam-stopper/index.html | 2 +- docs/web/slack-url-meta-data/index.html | 6 +- docs/web/source-code-pro-sublime/index.html | 2 +- docs/web/sql-as-an-api/index.html | 6 +- docs/web/styling-input-redux/index.html | 2 +- docs/web/the-100-dollar-website/index.html | 2 +- docs/web/tips-for-using-svgs/index.html | 2 +- docs/web/typeform-vector-logo/index.html | 2 +- .../index.html | 2 +- .../index.html | 2 +- docs/web/use-nginx-for-a-b-testing/index.html | 2 +- docs/web/using-digitalocean-spaces/index.html | 2 +- docs/web/using-slots-in-vue-js/index.html | 4 +- docs/web/varnish-for-static-sites/index.html | 4 +- .../index.html | 2 +- docs/web/vue-omnibar-component/index.html | 4 +- docs/web/vue-toggle-component/index.html | 4 +- .../wordpress-browser-body-class/index.html | 2 +- docs/web/wordpress-plugin-swipe-js/index.html | 2 +- .../index.html | 4 +- .../zapier-webhooks-for-html-forms/index.html | 4 +- 161 files changed, 213 insertions(+), 8729 deletions(-) create mode 100644 docs/apps/light-weight/privacy-policy/index.html delete mode 100644 docs/atom.xml diff --git a/docs/android/jellybean-nexuss/index.html b/docs/android/jellybean-nexuss/index.html index 1a1fa63..02d347e 100644 --- a/docs/android/jellybean-nexuss/index.html +++ b/docs/android/jellybean-nexuss/index.html @@ -1 +1 @@ -Installing Android 4.1.1 Jelly Bean for Nexus S | OhDoyleRules

Go home

So I have been flashing ROMs for a while now. I recently installed Jelly Bean 4.1. I used the OTA(Over The Air) version from here. Just make sure you also flash the Simple-Root.zip file. I did not. So I had to root my phone again and then install it. Which wasn't too bad but still annoying.

As with flashing any kind of ROM there is always the thought and reality that you could break something. I had a custom ROM on before I switched to a Jelly Bean based ROM. It had the nice notification power controls which was awesome. But Jelly Bean (or at least any of the ROMs so far) do not. So with a bit of searching I found Power Controls.

It works well and has a lot of customizable features. It is a recommend.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Installing Android 4.1.1 Jelly Bean for Nexus S | OhDoyleRules

Go home

So I have been flashing ROMs for a while now. I recently installed Jelly Bean 4.1. I used the OTA(Over The Air) version from here. Just make sure you also flash the Simple-Root.zip file. I did not. So I had to root my phone again and then install it. Which wasn't too bad but still annoying.

As with flashing any kind of ROM there is always the thought and reality that you could break something. I had a custom ROM on before I switched to a Jelly Bean based ROM. It had the nice notification power controls which was awesome. But Jelly Bean (or at least any of the ROMs so far) do not. So with a bit of searching I found Power Controls.

It works well and has a lot of customizable features. It is a recommend.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/apps/light-weight/privacy-policy/index.html b/docs/apps/light-weight/privacy-policy/index.html new file mode 100644 index 0000000..635e360 --- /dev/null +++ b/docs/apps/light-weight/privacy-policy/index.html @@ -0,0 +1 @@ +Light Weight Privacy Policy | OhDoyleRules

Privacy Policy for Light Weight

Last Updated: 2024-12-01

1. Introduction

Welcome to Light Weight ("App"). This Privacy Policy explains our commitment to protecting your privacy. Our app is designed to function entirely offline and does not collect, store, or transmit any personal data.

2. Data Collection

Our app does NOT collect:

  • Personal information
  • Usage statistics
  • Device data
  • Location information
  • Any form of user tracking

3. Offline Functionality

Light Weight is designed to work completely offline:

  • No internet connection required
  • No data sent to external servers
  • All functionality occurs locally on your device

4. Permissions

The app may request minimal device permissions necessary for core functionality. These permissions do not involve data collection or transmission.

5. User Privacy Commitment

We are committed to:

  • Protecting your privacy
  • Ensuring complete data confidentiality
  • Providing a secure, offline experience

6. Data Storage

Any data created within the app is:

  • Stored locally on your device
  • Completely under your control
  • Not accessible by any third parties

7. Updates to Privacy Policy

While our privacy practices remain consistent, we may update this policy periodically.

8. Contact Information

For any privacy-related questions, contact:

By using Light Weight, you acknowledge our commitment to your privacy and offline functionality.


Note: This privacy policy confirms our commitment to user privacy and offline operation.

\ No newline at end of file diff --git a/docs/atom.xml b/docs/atom.xml deleted file mode 100644 index 56511c7..0000000 --- a/docs/atom.xml +++ /dev/null @@ -1,8523 +0,0 @@ - - - James Doyle - The personal blog of James Doyle (james2doyle) Web Developer in Canada. - - - Zola - 2024-04-21T00:00:00+00:00 - https://ohdoylerules.com/atom.xml - - Hammerspoon hyper key - 2024-04-21T00:00:00+00:00 - 2024-04-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/hammerspoon-hyper-key/ - - <p>If you aren't using <a href="https://www.hammerspoon.org/">Hammerspoon</a> on OSX, you are missing out!</p> -<p>It has some great features that you can use to control your desktop, automate tasks, build small UIs and toolbar apps. It can also trigger key presses and modifiers like shift, alt, etc.</p> -<p>Here is the script I use to create &quot;hyper&quot; key shortcuts. If you aren't aware, the hyper key is a modifier key that combines the Shift, Control, Alt, and Command/Windows keys simultaneously. Then you can press a regular key and use that for shortcuts.</p> -<p>I wrote an article a while back to <a href="/tricks/hammerspoon-number-pad-shortcuts/">&quot;Use Your Numberpad To Control Google Hangouts/Meet&quot;</a> which is pretty handy. It uses a similar trick to play out a combination of keys and trigger actions in Chrome.</p> -<p>The script below watches the keyboard for the hyper key + another key. In the script below, I just bind the hyper key + some letter keys to trigger apps to focus.</p> -<pre data-lang="lua" style="background-color:#2b303b;color:#c0c5ce;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#65737e;">-- verbose logging -</span><span style="color:#65737e;">-- hs.logger.setGlobalLogLevel(5) -</span><span style="color:#b48ead;">local </span><span style="color:#bf616a;">hyperKeyLogger </span><span>= </span><span style="color:#bf616a;">hs</span><span>.logger.</span><span style="color:#bf616a;">new</span><span>(&#39;</span><span style="color:#a3be8c;">hyperkey</span><span>&#39;,&#39;</span><span style="color:#a3be8c;">debug</span><span>&#39;) -</span><span> -</span><span style="color:#65737e;">-- a table of mapping single keys to the apps they open/switch to -</span><span style="color:#b48ead;">local </span><span style="color:#bf616a;">hyper_key_shortcuts </span><span>= {} -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">C</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">Google Chrome</span><span>&quot; -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">F</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">Finder</span><span>&quot; -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">S</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">Sublime Text</span><span>&quot; -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">T</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">WezTerm</span><span>&quot; -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">W</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">WhatsApp</span><span>&quot; -</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>[&quot;</span><span style="color:#a3be8c;">Z</span><span>&quot;] = &quot;</span><span style="color:#a3be8c;">Zed</span><span>&quot; -</span><span style="color:#65737e;">-- you can add some more apps here -</span><span> -</span><span style="color:#65737e;">-- sort through all the items in the table and add the listeners for them -</span><span style="color:#b48ead;">for </span><span style="color:#bf616a;">key</span><span>,</span><span style="color:#bf616a;">title </span><span style="color:#b48ead;">in </span><span style="color:#bf616a;">hs</span><span>.fnutils.</span><span style="color:#bf616a;">sortByKeys</span><span>(</span><span style="color:#bf616a;">hyper_key_shortcuts</span><span>) </span><span style="color:#b48ead;">do -</span><span> </span><span style="color:#bf616a;">hs</span><span>.hotkey.</span><span style="color:#bf616a;">bind</span><span>({&quot;</span><span style="color:#a3be8c;">cmd</span><span>&quot;,&quot;</span><span style="color:#a3be8c;">ctrl</span><span>&quot;,&quot;</span><span style="color:#a3be8c;">option</span><span>&quot;,&quot;</span><span style="color:#a3be8c;">shift</span><span>&quot;}, </span><span style="color:#bf616a;">key</span><span>, </span><span style="color:#bf616a;">title</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">hyperKeyLogger</span><span>:</span><span style="color:#bf616a;">v</span><span>(&quot;</span><span style="color:#a3be8c;">[hyper </span><span>&quot;..</span><span style="color:#bf616a;">key</span><span>..&quot;</span><span style="color:#a3be8c;">] pressed</span><span>&quot;) -</span><span> </span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">hyperKeyLogger</span><span>:</span><span style="color:#bf616a;">v</span><span>(&quot;</span><span style="color:#a3be8c;">[hyper </span><span>&quot;..</span><span style="color:#bf616a;">key</span><span>..&quot;</span><span style="color:#a3be8c;">] released</span><span>&quot;) -</span><span> </span><span style="color:#65737e;">-- @see https://www.hammerspoon.org/docs/hs.application.html#open -</span><span> </span><span style="color:#65737e;">-- this will open the app if it is closed, or focus it if it is open -</span><span> </span><span style="color:#bf616a;">hs</span><span>.application.</span><span style="color:#bf616a;">open</span><span>(</span><span style="color:#bf616a;">title</span><span>) -</span><span> </span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">hyperKeyLogger</span><span>:</span><span style="color:#bf616a;">v</span><span>(&quot;</span><span style="color:#a3be8c;">[hyper </span><span>&quot;..</span><span style="color:#bf616a;">key</span><span>..&quot;</span><span style="color:#a3be8c;">] repeated</span><span>&quot;) -</span><span> </span><span style="color:#b48ead;">end</span><span>) -</span><span style="color:#b48ead;">end -</span></code></pre> - - - - - Use Your Numberpad To Control Google Hangouts/Meet - 2023-03-10T00:00:00+00:00 - 2023-03-10T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/hammerspoon-number-pad-shortcuts/ - - <h3 id="the-number-pad">The number pad</h3> -<p>Number pads can be pretty handy. Not just for accountants or spreadsheet junkies. Did you know that the keys on a number pad register in their own way? If your number pad is setup right, pressing a <code>1</code> on your keyboard number row and pressing <code>1</code> on your number pad, will be different keys. I have a bunch of my number keys on my number pad set to control the window positions on my desktop.</p> -<p>I also use the <code>0</code> key and the <code>.</code> key to control Google Hangouts/Meet. Read more to find out how.</p> -<h3 id="hammerspoon">Hammerspoon</h3> -<p>If you aren't using <a href="https://www.hammerspoon.org/">Hammerspoon</a> on OSX, you are missing out! It has some great features! I will write some more articles on using Hammerspoon to control your Apple trackpad and also tricks with &quot;hyper&quot; keys to control your desktop.</p> -<p>Back on track. These are all the keys on the number pad that Hammerspoon will treat as &quot;unique&quot;:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>pad., pad*, pad+, pad/, pad-, pad=, -</span><span>pad0, pad1, pad2, -</span><span>pad3, pad4, pad5, -</span><span>pad6, pad7, pad8, pad9, -</span><span>padclear, padenter -</span></code></pre> -<p>This means you can bind functionality to those keys without them also applying to the number row. Cool, right?</p> -<h3 id="switch-to-google-hangouts-meet">Switch to Google Hangouts/Meet</h3> -<p>The following code is used to focus Chrome and then switch to the hangouts tab when <code>pad0</code> is pressed:</p> -<pre data-lang="lua" style="background-color:#2b303b;color:#c0c5ce;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#b48ead;">local </span><span style="color:#bf616a;">shortcutsLogger </span><span>= </span><span style="color:#bf616a;">hs</span><span>.logger.</span><span style="color:#bf616a;">new</span><span>(&#39;</span><span style="color:#a3be8c;">shortcuts/shortcuts</span><span>&#39;,&#39;</span><span style="color:#a3be8c;">debug</span><span>&#39;) -</span><span> -</span><span style="color:#65737e;">-- &quot;pad0&quot; -- focus the Chrome, switch to the hangouts tab -</span><span style="color:#bf616a;">hs</span><span>.hotkey.</span><span style="color:#bf616a;">bind</span><span>({}, &quot;</span><span style="color:#a3be8c;">pad0</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">Focus Hangouts</span><span>&quot;, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad0] pressed</span><span>&quot;) -</span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad0] released</span><span>&quot;) -</span><span> </span><span style="color:#b48ead;">local </span><span style="color:#bf616a;">win </span><span>= </span><span style="color:#bf616a;">hs</span><span>.appfinder.</span><span style="color:#bf616a;">appFromName</span><span>(&quot;</span><span style="color:#a3be8c;">Google Chrome</span><span>&quot;) -</span><span> </span><span style="color:#bf616a;">win</span><span>:</span><span style="color:#bf616a;">activate</span><span>() -</span><span> </span><span style="color:#65737e;">-- @see https://www.hammerspoon.org/docs/hs.eventtap.html#keyStroke -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStroke</span><span>({&quot;</span><span style="color:#a3be8c;">cmd</span><span>&quot;,&quot;</span><span style="color:#a3be8c;">shift</span><span>&quot;}, &quot;</span><span style="color:#a3be8c;">A</span><span>&quot;, </span><span style="color:#d08770;">300</span><span>, </span><span style="color:#bf616a;">win</span><span>) -</span><span> </span><span style="color:#65737e;">-- needed in order to use the tab search -</span><span> </span><span style="color:#bf616a;">hs</span><span>.timer.</span><span style="color:#bf616a;">doAfter</span><span>(</span><span style="color:#d08770;">0.3</span><span>, </span><span style="color:#b48ead;">function</span><span>() -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStrokes</span><span>(&quot;</span><span style="color:#a3be8c;">meet</span><span>&quot;, </span><span style="color:#bf616a;">win</span><span>) -</span><span> </span><span style="color:#bf616a;">hs</span><span>.timer.</span><span style="color:#bf616a;">doAfter</span><span>(</span><span style="color:#d08770;">0.2</span><span>, </span><span style="color:#b48ead;">function</span><span>() -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStroke</span><span>({}, &quot;</span><span style="color:#a3be8c;">Return</span><span>&quot;, </span><span style="color:#d08770;">100</span><span>, </span><span style="color:#bf616a;">win</span><span>) -</span><span> </span><span style="color:#b48ead;">end</span><span>) -</span><span> </span><span style="color:#b48ead;">end</span><span>) -</span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad0] repeated</span><span>&quot;) -</span><span style="color:#b48ead;">end</span><span>) -</span></code></pre> -<p>You can see that we do need to &quot;wait&quot; a bit between key presses in order to make sure we are triggering things properly.</p> -<p>One of the great things about using <code>cmd+shift+A</code> is that it will find <strong>tabs across windows</strong>. This means you can have multiple windows open or even multiple desktops and it should still work!</p> -<h3 id="switch-to-google-hangouts-meet-1">Switch to Google Hangouts/Meet</h3> -<p>I have another shortcut setup on <code>pad.</code> that will switch to the hangouts tab and toggle &quot;mute&quot;:</p> -<pre data-lang="lua" style="background-color:#2b303b;color:#c0c5ce;" class="language-lua "><code class="language-lua" data-lang="lua"><span style="color:#65737e;">-- &quot;pad.&quot; -- focus the Chrome, switch to the hangouts tab, toggle mute -</span><span style="color:#bf616a;">hs</span><span>.hotkey.</span><span style="color:#bf616a;">bind</span><span>({}, &quot;</span><span style="color:#a3be8c;">pad.</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">Toggle Hangouts Mute</span><span>&quot;, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad.] pressed</span><span>&quot;) -</span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad.] released</span><span>&quot;) -</span><span> </span><span style="color:#b48ead;">local </span><span style="color:#bf616a;">win </span><span>= </span><span style="color:#bf616a;">hs</span><span>.appfinder.</span><span style="color:#bf616a;">appFromName</span><span>(&quot;</span><span style="color:#a3be8c;">Google Chrome</span><span>&quot;) -</span><span> </span><span style="color:#bf616a;">win</span><span>:</span><span style="color:#bf616a;">activate</span><span>() -</span><span> </span><span style="color:#65737e;">-- @see https://www.hammerspoon.org/docs/hs.eventtap.html#keyStroke -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStroke</span><span>({&quot;</span><span style="color:#a3be8c;">cmd</span><span>&quot;,&quot;</span><span style="color:#a3be8c;">shift</span><span>&quot;}, &quot;</span><span style="color:#a3be8c;">A</span><span>&quot;, </span><span style="color:#d08770;">300</span><span>, </span><span style="color:#bf616a;">win</span><span>) -</span><span> </span><span style="color:#65737e;">-- needed in order to use the tab search -</span><span> </span><span style="color:#bf616a;">hs</span><span>.timer.</span><span style="color:#bf616a;">doAfter</span><span>(</span><span style="color:#d08770;">0.3</span><span>, </span><span style="color:#b48ead;">function</span><span>() -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStrokes</span><span>(&quot;</span><span style="color:#a3be8c;">meet</span><span>&quot;, </span><span style="color:#bf616a;">win</span><span>) -</span><span> </span><span style="color:#bf616a;">hs</span><span>.timer.</span><span style="color:#bf616a;">doAfter</span><span>(</span><span style="color:#d08770;">0.2</span><span>, </span><span style="color:#b48ead;">function</span><span>() -</span><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStroke</span><span>({}, &quot;</span><span style="color:#a3be8c;">Return</span><span>&quot;, </span><span style="color:#d08770;">100</span><span>, </span><span style="color:#bf616a;">win</span><span>) -</span><mark style="background-color:#65737e30;"><span> </span><span style="color:#65737e;">-- presses this combo after the tab is in focus -</span></mark><mark style="background-color:#65737e30;"><span> </span><span style="color:#bf616a;">hs</span><span>.timer.</span><span style="color:#bf616a;">doAfter</span><span>(</span><span style="color:#d08770;">0.3</span><span>, </span><span style="color:#b48ead;">function</span><span>() -</span></mark><mark style="background-color:#65737e30;"><span> </span><span style="color:#bf616a;">hs</span><span>.eventtap.</span><span style="color:#bf616a;">keyStroke</span><span>({&quot;</span><span style="color:#a3be8c;">cmd</span><span>&quot;}, &quot;</span><span style="color:#a3be8c;">D</span><span>&quot;, </span><span style="color:#d08770;">100</span><span>, </span><span style="color:#bf616a;">win</span><span>) -</span></mark><mark style="background-color:#65737e30;"><span> </span><span style="color:#b48ead;">end</span><span>) -</span></mark><span> </span><span style="color:#b48ead;">end</span><span>) -</span><span> </span><span style="color:#b48ead;">end</span><span>) -</span><span style="color:#b48ead;">end</span><span>, </span><span style="color:#b48ead;">function </span><span>() -</span><span> </span><span style="color:#bf616a;">shortcutsLogger</span><span>:</span><span style="color:#bf616a;">d</span><span>(&quot;</span><span style="color:#a3be8c;">[pad.] repeated</span><span>&quot;) -</span><span style="color:#b48ead;">end</span><span>) -</span></code></pre> -<p>Those added lines trigger the keyboard shortcut that will toggle mute in hangouts.</p> -<h3 id="different-browsers">Different browsers?</h3> -<p>If you want to modify the code above to work in other browsers, just change the line for <code>hs.appfinder.appFromName(&quot;Google Chrome&quot;)</code> to your browser. You will also need to figure out what the key combo is for finding tabs in that browser.</p> -<p>In Firefox, for example, you can search the open tabs using <code>%</code> in the search bar. So you would need to do something like the following:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>1. cmd+L (activate the search bar) -</span><span>2. % (start the tab search) -</span><span>3. &quot;meet&quot; (type meet into the bar) -</span><span>4. Return (press enter to go to the tab) -</span></code></pre> -<p>This is just a list of possible steps. Try it yourself and report back to me!</p> - - - - - Using Browser Devtools To Improve Your Bug Reports - 2023-03-09T00:00:00+00:00 - 2023-03-09T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/using-browser-devtools-to-improve-your-bug-reports/ - - <h3 id="good-bug-reports">Good bug reports</h3> -<p>Reporting bugs can be very difficult when you are not a developer. How do you make sure the bug can be recreated? How do you avoid the &quot;it works on my machine&quot; rebuttal?</p> -<p>Well, we can greatly improve bug reports on web apps by using some simple tools built into the browsers we use every day.</p> -<h3 id="easy-screenshots">Easy Screenshots</h3> -<p>Did you know you can <a href="https://developer.chrome.com/blog/new-in-devtools-62/#node-screenshots">take screenshots of DOM nodes</a> right from the browser?</p> -<div class="center"> - <a href="/images/1-devtools.png" target="_blank" title="take a node screenshot in devtools"> - <img src="/images/1-devtools.png" alt="take a node screenshot in devtools" /> - </a> -</div> -<p>Handy right? Use this to easy capture a smaller area of the screen and attach that to your bug report. Easy!</p> -<h3 id="saving-console-output">Saving &quot;console output&quot;</h3> -<p>Have you ever seen a blast of red in the console that states an error? You can try to explain what happened and where the error occurs. Or, you can export your console output to a log file and send it along to a developer!</p> -<div class="center"> - <a href="/images/2-devtools.png" target="_blank" title="save console output to a file"> - <img src="/images/2-devtools.png" alt="save console output to a file" /> - </a> -</div> -<p>The output will look something like the following:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span>(</span><span style="color:#bf616a;">index</span><span>):233 Uncaught Error: Oh no! Something happened -</span><span> </span><span style="color:#bf616a;">at</span><span> (index)</span><span style="color:#bf616a;">:233:9 -</span><span>(</span><span style="color:#bf616a;">anonymous</span><span>) @ (index)</span><span style="color:#bf616a;">:233 -</span><span>(</span><span style="color:#bf616a;">index</span><span>):265 on localhost - not loading service worker. Query: http://localhost:1313/sw.js 2023-03-09 -</span></code></pre> -<p>This will make it much easier for the developer to track down the area where the error occurred.</p> -<h3 id="save-har-content">Save &quot;HAR&quot; content</h3> -<p>The ultimate way to report an error with a network call is to use a HAR file to capture the state of the network when the file is created.</p> -<p>What is a HAR file?</p> -<blockquote> -<p>HAR (HTTP Archive) is a file format used by several HTTP session tools to export the captured data. The format is basically a JSON object with a particular set of fields. Note that not all the fields in the HAR format are mandatory, and in many cases, some information won't be saved to the file.</p> -</blockquote> -<p>You can capture a HAR file from the network tab in your devtools and then save that file to be attached to your bug report:</p> -<div class="center"> - <a href="/images/3-devtools.png" target="_blank" title="save network requests to a HAR file"> - <img src="/images/3-devtools.png" alt="save network requests to a HAR file" /> - </a> -</div> -<p>A HAR file is just JSON that follows the &quot;HTTP Archive&quot; specification that allows it to be read in various tools. Here is an example of the error above:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{ -</span><span> &quot;</span><span style="color:#a3be8c;">log</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">version</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">1.2</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">creator</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">WebInspector</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">version</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">537.36</span><span>&quot; -</span><span> }, -</span><span> &quot;</span><span style="color:#a3be8c;">pages</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">startedDateTime</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">2023-03-10T04:23:47.227Z</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">id</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">page_10</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">title</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://localhost:1313/tricks/using-browser-devtools-to-improve-your-bug-reports/</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">pageTimings</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">onContentLoad</span><span>&quot;: </span><span style="color:#d08770;">282.40099999675294</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">onLoad</span><span>&quot;: </span><span style="color:#d08770;">511.1339999930351 -</span><span> } -</span><span> } -</span><span> ], -</span><span> &quot;</span><span style="color:#a3be8c;">entries</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">_initiator</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">type</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">script</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">stack</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">callFrames</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">functionName</span><span>&quot;: &quot;&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">scriptId</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">700</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">url</span><span>&quot;: &quot;&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">lineNumber</span><span>&quot;: </span><span style="color:#d08770;">0</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">columnNumber</span><span>&quot;: </span><span style="color:#d08770;">0 -</span><span> } -</span><span> ] -</span><span> } -</span><span> }, -</span><span> &quot;</span><span style="color:#a3be8c;">_priority</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">High</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">_resourceType</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">fetch</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">cache</span><span>&quot;: {}, -</span><span> &quot;</span><span style="color:#a3be8c;">connection</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">121426</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">pageref</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">page_10</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">request</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">method</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">GET</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">url</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://localhost:1313/missing-file.json</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">httpVersion</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">HTTP/1.1</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">headers</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Accept</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">*/*</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Accept-Encoding</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">gzip, deflate, br</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Accept-Language</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">en-CA,en;q=0.9,en-GB;q=0.8,en-US;q=0.7,nb;q=0.6,ar;q=0.5,fr;q=0.4,la;q=0.3</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Cache-Control</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">no-cache</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Connection</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">keep-alive</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Host</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">localhost:1313</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Pragma</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">no-cache</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Referer</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">http://localhost:1313/tricks/using-browser-devtools-to-improve-your-bug-reports/</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Sec-Fetch-Dest</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">empty</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Sec-Fetch-Mode</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">cors</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Sec-Fetch-Site</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">same-origin</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">User-Agent</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">sec-ch-ua</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Chromium</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">;v=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">110</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">, </span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Not A(Brand</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">;v=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">24</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">, </span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Google Chrome</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">;v=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">110</span><span style="color:#96b5b4;">\&quot;</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">sec-ch-ua-mobile</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">?0</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">sec-ch-ua-platform</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">macOS</span><span style="color:#96b5b4;">\&quot;</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">sec-gpc</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">1</span><span>&quot; -</span><span> } -</span><span> ], -</span><span> &quot;</span><span style="color:#a3be8c;">queryString</span><span>&quot;: [], -</span><span> &quot;</span><span style="color:#a3be8c;">cookies</span><span>&quot;: [], -</span><span> &quot;</span><span style="color:#a3be8c;">headersSize</span><span>&quot;: </span><span style="color:#d08770;">701</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">bodySize</span><span>&quot;: </span><span style="color:#d08770;">0 -</span><span> }, -</span><span> &quot;</span><span style="color:#a3be8c;">response</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">status</span><span>&quot;: </span><span style="color:#d08770;">404</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">statusText</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Not Found</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">httpVersion</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">HTTP/1.1</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">headers</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Content-Type</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text/html; charset=utf-8</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Date</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Fri, 10 Mar 2023 04:23:55 GMT</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Transfer-Encoding</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">value</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">chunked</span><span>&quot; -</span><span> } -</span><span> ], -</span><span> &quot;</span><span style="color:#a3be8c;">cookies</span><span>&quot;: [], -</span><span> &quot;</span><span style="color:#a3be8c;">content</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">size</span><span>&quot;: </span><span style="color:#d08770;">8321</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">mimeType</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">text/html</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">compression</span><span>&quot;: </span><span style="color:#d08770;">-20</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">text</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">&lt;!DOCTYPE html&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;html lang=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">en</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">no-js</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;head&gt;&lt;script src=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">/livereload.js?mindelay=10&amp;amp;v=2&amp;amp;port=1313&amp;amp;path=livereload</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> data-no-instant defer&gt;&lt;/script&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta charset=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">utf-8</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta http-equiv=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Cache-control</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">public</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta http-equiv=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">X-UA-Compatible</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">IE=edge</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">viewport</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">width=device-width,initial-scale=1,maximum-scale=5</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> &lt;title&gt;James Doyle | OhDoyleRules&lt;/title&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:title</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle | OhDoyleRules</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:title</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle | OhDoyleRules</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">google-site-verification</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">KM-5h_iJ7JJsGeUp4ncEoYCBKft1ko1A4gBpjIzT0p4</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">https://plus.google.com/109231487156400680487</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">author publisher</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:type</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">website</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:title</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> &lt;meta itemprop=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">name</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:site_name</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">OhDoyleRules</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta itemprop=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">url</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/404.html</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta itemprop=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">email</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">james2doyle@gmail.com</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:url</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/404.html</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta itemprop=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">image logo</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/logo.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta property=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">og:image</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/logo.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">57x57</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-57x57.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">60x60</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-60x60.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">72x72</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-72x72.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">76x76</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-76x76.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">114x114</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-114x114.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">120x120</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-120x120.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">144x144</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-144x144.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">152x152</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-152x152.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">apple-touch-icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">180x180</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/apple-icon-180x180.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">image/png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">192x192</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/android-icon-192x192.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">image/png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">32x32</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/favicon-32x32.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">image/png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">96x96</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/favicon-96x96.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">image/png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> sizes=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">16x16</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/favicon-16x16.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">manifest</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/manifest.json</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">msapplication-TileColor</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">#ffffff</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">msapplication-TileImage</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/ms-icon-144x144.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">theme-color</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">#333333</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:card</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">summary</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:site</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">@james2doyle</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:creator</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">@james2doyle</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:image</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/logo.png</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">twitter:domain</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/icons/logo.svg</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n \n \n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">dns-prefetch</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">https://www.google-analytics.com</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">shortcut icon</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/favicon.ico</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">canonical</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/404.html</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">sitemap</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">application/xml</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> title=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Sitemap</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/sitemap.xml</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;meta id=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">themes</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> name=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">themes</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> content=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/css/new.light.css,http://localhost:1313/css/new.dark.css</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;link id=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">stylesheet</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> rel=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">stylesheet</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/css/new.light.css</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;style type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">text/css</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> body {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> font-feature-settings: &#39;lnum&#39; 1;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> font-variant-numeric: slashed-zero;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> text-rendering: geometricPrecision;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -webkit-font-smoothing: antialiased;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -moz-osx-font-smoothing: grayscale;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -webkit-tap-highlight-color: rgba(0, 0, 0, 0);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -webkit-text-size-adjust: 100%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -ms-text-size-adjust: 100%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> opacity: 0;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> transition: opacity 0.2s ease;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> will-change: opacity;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> body.light, body.dark {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> opacity: 1;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .show-on-light,</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .show-on-dark {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> display: none;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .light .show-on-light,</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .dark .show-on-dark {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> display: block;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .post-info {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> margin-bottom: 1rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> a.none {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> color: inherit;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border: none;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> opacity: 1;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> a.none:hover {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background: none;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> opacity: 0.6;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .center {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> text-align: center;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .center img {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> max-width: 100%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> height: auto;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> pre {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> color: #f8f8f2;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background-color: #282a36;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -moz-tab-size: 4;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> -o-tab-size: 4;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> tab-size: 4;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> pre code.language-diff span:nth-child(1n + 7) {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> color: #50fa7b;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch-wrapper {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> display: flex;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> align-items: center;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> position: absolute;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> top: 1rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> right: 2rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> font-size: 80%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> position: relative;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> margin: 0 0.4rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch input {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> position: absolute;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> width: 100%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> height: 100%;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> z-index: 1;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> opacity: 0;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> cursor: pointer;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch label {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> display: flex;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border-radius: 9999px;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> height: 0.8rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> width: 1.8rem;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background-color: rgba(0, 0, 0, .1);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border: 1px solid rgba(0, 0, 0, .3);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch input:checked + label {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background-color: #357edd;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border: 1px solid #357edd;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> justify-content: flex-end;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> .switch div {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> width: calc(0.8rem - 2px);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> height: calc(0.8rem - 2px);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border-radius: 9999px;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border: 1px solid rgba(0, 0, 0, .3);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background-color: #FFF;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> body.dark table.highlight tr:nth-child(even) {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> background-color: rgb(246, 248, 250);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> body.dark table.highlight td,</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> body.dark table.highlight th {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> border-color: rgba(27,31,35,.3);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/style&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;script type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">text/javascript</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> charset=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">utf-8</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> defer&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> document.addEventListener(&#39;DOMContentLoaded&#39;, function() {</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> const defaultIndex = window.matchMedia(&#39;(prefers-color-scheme)&#39;).media !== &#39;not all&#39; ? &#39;1&#39; : &#39;0&#39;;</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;"> const activeIndex = JSON.parse(window.localStorage.getItem(&#39;activeIndex&#39;) || defaultIndex);</span><span style="color:#96b5b4;">\n\n</span><span style="color:#a3be8c;"> const themes = document.getElementById(&#39;themes&#39;).content.split(&#39;,&#39;);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> const stylesheet = document.getElementById(&#39;stylesheet&#39;);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> if (stylesheet.href !== themes[activeIndex]) {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> stylesheet.href = themes[activeIndex];</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> }</span><span style="color:#96b5b4;">\n\n</span><span style="color:#a3be8c;"> document.body.className = activeIndex === 0 ? &#39;light&#39; : &#39;dark&#39;;</span><span style="color:#96b5b4;">\n\n</span><span style="color:#a3be8c;"> const theSwitch = document.getElementById(&#39;switch&#39;);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> theSwitch.checked = Boolean(activeIndex);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> theSwitch.addEventListener(&#39;change&#39;, function() {</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> const newIndex = Number(this.checked);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> stylesheet.href = themes[newIndex];</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> document.body.className = newIndex === 0 ? &#39;light&#39; : &#39;dark&#39;;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> window.localStorage.setItem(&#39;activeIndex&#39;, newIndex);</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> });</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> });</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/script&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;/head&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;body&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;div class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">switch-wrapper</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div&gt;Light&lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">switch</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;input id=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">switch</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> type=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">checkbox</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> style=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">display: none</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> /&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;label for=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">switch</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div&gt;&lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/label&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div&gt;Dark&lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;/div&gt;</span><span style="color:#96b5b4;">\n\n</span><span style="color:#a3be8c;">&lt;div class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">container</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;header class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">grid -middle -center</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;p&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;a href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">http://localhost:1313/</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> title=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">none</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;img class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">show-on-light</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> src=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">/icons/logo-light.svg</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> alt=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle Logo</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> width=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">113</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> height=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">57</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;img class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">show-on-dark</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> src=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">/icons/logo-dark.svg</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> alt=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">James Doyle Logo</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> width=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">113</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> height=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">57</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/a&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/p&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/header&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">the-loop</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;article&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;h2&gt;404 Page Not Found&lt;/h2&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;div class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">post-intro</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;p&gt;The page you are looking for cannot be found. Please &lt;a href=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">/</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;"> title=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">Homepage</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">&gt;return to the homepage&lt;/a&gt;.&lt;/p&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/article&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;"> &lt;/div&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;/div&gt;</span><span style="color:#96b5b4;">\n\n\n</span><span style="color:#a3be8c;">&lt;script defer&gt;</span><span style="color:#96b5b4;">\n \n \n</span><span style="color:#a3be8c;"> console.info(&#39;on localhost - not loading service worker. Query: http:</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">/</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">/localhost:1313</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">/sw.js 2023-03-09&#39;);</span><span style="color:#96b5b4;">\n \n</span><span style="color:#a3be8c;">&lt;/script&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;/body&gt;</span><span style="color:#96b5b4;">\n</span><span style="color:#a3be8c;">&lt;/html&gt;</span><span style="color:#96b5b4;">\n\n</span><span>&quot; -</span><span> }, -</span><span> &quot;</span><span style="color:#a3be8c;">redirectURL</span><span>&quot;: &quot;&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">headersSize</span><span>&quot;: </span><span style="color:#d08770;">131</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">bodySize</span><span>&quot;: </span><span style="color:#d08770;">8341</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">_transferSize</span><span>&quot;: </span><span style="color:#d08770;">8472</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">_error</span><span>&quot;: </span><span style="color:#d08770;">null -</span><span> }, -</span><span> &quot;</span><span style="color:#a3be8c;">serverIPAddress</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">127.0.0.1</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">startedDateTime</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">2023-03-10T04:23:55.706Z</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">time</span><span>&quot;: </span><span style="color:#d08770;">5.828000001201872</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">timings</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">blocked</span><span>&quot;: </span><span style="color:#d08770;">3.4279999995124526</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">dns</span><span>&quot;: </span><span style="color:#d08770;">-1</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">ssl</span><span>&quot;: </span><span style="color:#d08770;">-1</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">connect</span><span>&quot;: </span><span style="color:#d08770;">-1</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">send</span><span>&quot;: </span><span style="color:#d08770;">0.08699999999999997</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">wait</span><span>&quot;: </span><span style="color:#d08770;">0.28199999806331477</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">receive</span><span>&quot;: </span><span style="color:#d08770;">2.0310000036261044</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">_blocked_queueing</span><span>&quot;: </span><span style="color:#d08770;">1.5889999995124526 -</span><span> } -</span><span> } -</span><span> ] -</span><span> } -</span><span>} -</span></code></pre> -<p>To read a HAR file that has already been exported, you can just <strong>drag it into the network panel</strong> of your devtools:</p> -<div class="center"> - <video width="100%" autoplay loop muted preload="auto" poster="/images/5-devtools-poster.jpg"> - <source src="/images/5-devtools.mp4" type="video/mp4"> - <p>Sorry, your browser doesn't support embedded videos, but don't worry, you can <a href="/images/5-devtools.mp4" download>download it</a> and watch it with your favourite video player!</p> - </video> -</div> -<p>This will recreate the state of the world when the HAR file was captured.</p> -<p>You can also use a tool like the <a href="https://toolbox.googleapps.com/apps/har_analyzer/">Google Admin Toolbox HAR Analyzer</a> to view HAR files on a webpage:</p> -<div class="center"> - <a href="/images/4-devtools.png" target="_blank" title="save console output to a file"> - <img src="/images/4-devtools.png" alt="save console output to a file" /> - </a> -</div> -<p>If you want to read more about capturing HAR files, just check out <a href="https://www.ibm.com/support/pages/how-generate-har-file-troubleshoot-issues">this great list of steps on the IBM site</a> - of all places.</p> -<h3 id="in-summation">In Summation</h3> -<p>Hopefully these simple tips help improve your bug reports and make it easier for your team members to report them but also recreate them. Happy bug hunting!</p> - - - - - Versioning Service Workers In Hugo - 2022-08-21T00:00:00+00:00 - 2022-08-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/versioning-service-workers-in-hugo/ - - <p>I have been running this blog on <a href="https://gohugo.io/">Hugo</a> for quite some time. It is fast, well supported, and full of features. Until recently, one of the features I was not taking advantage of was the <a href="https://gohugo.io/hugo-pipes/introduction/">pipes feature</a>.</p> -<p>For all intents and purposes, pipes are used for processing strings and templates. They are &quot;assets&quot; that are used on your site. They live outside your theme at the top level <code>/assets</code> folder in your Hugo project. But you can also pull in remote assets via a URL and Hugo will pull that in when you build your site.</p> -<p>From the day I switched to Hugo, I was always <a href="https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-only">using a service worker to cache my sites static assets</a>. The challenge I had was how to bust the cache. The old way I was doing this was to add a query string on the end of my <code>sw.js</code> URL. Something like this:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span>navigator.</span><span style="color:#bf616a;">serviceWorker</span><span>.</span><span style="color:#8fa1b3;">register</span><span>(&#39;</span><span style="color:#a3be8c;">sw.js?{{ now.Format &quot;2006-01-02&quot; }}</span><span>&#39;); -</span></code></pre> -<p>This seemed to work <em>just OK</em>. I think maybe in the last couple years the browser's changed and they would still cache this file even when the URL was different. It would not reload the service worker and therefore update the cache with new articles making my site seem stale even though there was new content.</p> -<p>I can't say for sure but it used to work in the past and then one day it did not...</p> -<p>Now that this is a known issue in my site, I need a way to bust the cache in the service worker file itself instead of relying on the file's URL.</p> -<p>What I need to do is treat the <code>sw.js</code> file as a template so I can pass in a variable and tell the file it is new each time I deploy.</p> -<p>In this case, it is pipes to the rescue! Here is the code that runs in my <code>footer.html</code> file:</p> -<pre data-lang="handlebars" style="background-color:#2b303b;color:#c0c5ce;" class="language-handlebars "><code class="language-handlebars" data-lang="handlebars"><span style="color:#65737e;">&lt;!-- Get the file in &quot;assets/service-worker.js&quot; --&gt; -</span><span>{{ </span><span style="color:#bf616a;">$jsTemplate</span><span> := </span><span style="color:#bf616a;">resources.Get </span><span>&quot;</span><span style="color:#a3be8c;">service-worker.js</span><span>&quot; }} -</span><span style="color:#65737e;">&lt;!-- We are making a new file called &quot;sw.js&quot; --&gt; -</span><span>{{ </span><span style="color:#bf616a;">$js</span><span> := $</span><span style="color:#bf616a;">jsTemplate</span><span> | </span><span style="color:#bf616a;">resources.ExecuteAsTemplate </span><span>&quot;</span><span style="color:#a3be8c;">sw.js</span><span>&quot; . }} -</span><span>&lt;</span><span style="color:#bf616a;">script</span><span> defer&gt; -</span><span> </span><span style="color:#65737e;">// register the service worker only when not in development -</span><span>{{ </span><span style="color:#b48ead;">if </span><span style="color:#bf616a;">eq </span><span>(</span><span style="color:#bf616a;">printf </span><span>&quot;</span><span style="color:#a3be8c;">%v</span><span>&quot; </span><span style="color:#bf616a;">$</span><span>.Site.BaseURL) &quot;</span><span style="color:#a3be8c;">http://localhost:1313/</span><span>&quot; }} -</span><span> console.</span><span style="color:#96b5b4;">info</span><span>(&#39;</span><span style="color:#a3be8c;">on localhost - not loading service worker. Query: {{ $js.Permalink }} {{ now.Format &quot;2006-01-02&quot; }}</span><span>&#39;); -</span><span>{{ </span><span style="color:#b48ead;">else </span><span>}} -</span><span> </span><span style="color:#b48ead;">if </span><span>(&#39;</span><span style="color:#a3be8c;">serviceWorker</span><span>&#39; in navigator) { -</span><span> navigator.serviceWorker.</span><span style="color:#bf616a;">register</span><span>(&#39;</span><span style="color:#a3be8c;">{{ $js.Permalink }}</span><span>&#39;); -</span><span> } -</span><span>{{ </span><span style="color:#bf616a;">end </span><span>}} -</span><span>&lt;/</span><span style="color:#bf616a;">script</span><span>&gt; -</span></code></pre> -<p>That is all you need to read that asset file and create a URL so a &quot;compiled&quot; version. If you want to see this file, you can just visit the URL it creates: <code>http://localhost:1313/sw.js</code></p> -<p>And here is the code in <code>assets/service-worker.js</code> that gets parsed as a go html template:</p> -<pre data-lang="handlebars" style="background-color:#2b303b;color:#c0c5ce;" class="language-handlebars "><code class="language-handlebars" data-lang="handlebars"><span>// cache name will change to the date of my last deploy -</span><span>const CACHE_NAME = &#39;ODR-{{ </span><span style="color:#bf616a;">now.Format </span><span>&quot;</span><span style="color:#a3be8c;">2006-01-02</span><span>&quot; }}&#39;; -</span><span>const expectedCaches = [CACHE_NAME]; -</span><span> -</span><span>// the list of files that need to be cached -</span><span>const staticFiles = [ -</span><span> &#39;./&#39;, -</span><span> &#39;./css/site.css&#39;, -</span><span> &#39;./icons/logo.svg&#39;, -</span><span> &#39;./manifest.json&#39;, -</span><span> &#39;./favicon.ico&#39;, -</span><span>]; -</span><span> -</span><span>/** -</span><span> * Performs install steps. -</span><span> */ -</span><span>addEventListener(&#39;install&#39;, (event) =&gt; { -</span><span> // install this service worker as soon as a new one is available -</span><span> skipWaiting(); -</span><span> event.waitUntil(caches.open(CACHE_NAME).then(cache =&gt; cache.addAll(staticFiles))); -</span><span>}); -</span><span> -</span><span>/** -</span><span> * Handles requests: responds with cache or else network. -</span><span> */ -</span><span>addEventListener(&#39;fetch&#39;, (event) =&gt; { -</span><span> event.respondWith(caches.match(event.request).then(response =&gt; response || fetch(event.request))); -</span><span>}); -</span><span> -</span><span>/** -</span><span> * Cleans up static cache and activates the Service Worker. -</span><span> */ -</span><span>addEventListener(&#39;activate&#39;, (event) =&gt; { -</span><span> event.waitUntil(caches.keys().then(keys =&gt; Promise.all(keys.map((key) =&gt; { -</span><span> if (!expectedCaches.includes(key)) { -</span><span> return caches.delete(key); -</span><span> } -</span><span> }))).then(() =&gt; { -</span><span> console.log(`${CACHE_NAME} now ready to handle fetches!`); -</span><span> return clients.claim(); -</span><span> })); -</span><span>}); -</span></code></pre> -<p>As you will be able to see if you visited the file that gets generated at <code>http://localhost:1313/sw.js</code>, the <code>CACHE_NAME</code> is now being defined with a value that includes todays date!</p> -<p>Something like this:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// you should see a real string being assigned now and not a go template interpolation -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">CACHE_NAME </span><span>= &#39;</span><span style="color:#a3be8c;">ODR-2022-08-21</span><span>&#39;; -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">expectedCaches </span><span>= [</span><span style="color:#bf616a;">CACHE_NAME</span><span>]; -</span><span> -</span><span style="color:#65737e;">//... the rest of the file... -</span></code></pre> -<p>Now when you build the site, this new piped/resource file will generated too and properly linked in your templates.</p> -<p>Once I got this all setup, I can now deploy my site and know for sure that my service worker cache will be busted since it will be created with a key that includes the date of my deploy.</p> - - - - - Using Laravel `when` Method To Support Multiple Queries - 2022-08-14T00:00:00+00:00 - 2022-08-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/using-laravel-when-method-to-support-multiple-queries/ - - <p>There is a method on Laravel collections called <a href="https://laravel.com/docs/8.x/collections#method-when"><code>when</code></a> that allows you to create a condition on your code without using an <code>if</code> statement. This can be really handy given we often have conditions on queries to deal with missing or present data, the session-specific environment, or even information in the config.</p> -<p>Let's look at the common pattern I was using in the past to put conditions on my queries:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** </span><span style="color:#b48ead;">@var</span><span style="color:#65737e;"> \Illuminate\Database\Eloquent\Builder|\App\Models\User */ -</span><span>$</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">query</span><span>(); -</span><span> -</span><span style="color:#65737e;">// get the filter from the request -</span><span>$</span><span style="color:#bf616a;">filter </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;); -</span><span> -</span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#bf616a;">blank</span><span>($</span><span style="color:#bf616a;">filter</span><span>)) { -</span><span> $</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">basic</span><span>&#39;); -</span><span>} </span><span style="color:#b48ead;">else </span><span>{ -</span><span> $</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, $</span><span style="color:#bf616a;">filter</span><span>); -</span><span>} -</span></code></pre> -<p>This is a contrived example but it should remind you of code you have seen or written in the past. Now we can rewrite it using the <code>when</code> method:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** </span><span style="color:#b48ead;">@var</span><span style="color:#65737e;"> \Illuminate\Database\Eloquent\Builder|\App\Models\User */ -</span><span>$</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">query</span><span>(); -</span><span> -</span><span style="color:#65737e;">// get the filter from the request -</span><span>$</span><span style="color:#bf616a;">filter </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;); -</span><span> -</span><span>$</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">when</span><span>( -</span><span> </span><span style="color:#bf616a;">blank</span><span>($</span><span style="color:#bf616a;">filter</span><span>), -</span><span> </span><span style="color:#65737e;">// case when the condition is true (the filter is blank) -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">basic</span><span>&#39;); -</span><span> }, -</span><span> </span><span style="color:#65737e;">// case when condition is false (the filter is NOT blank) -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use</span><span>($</span><span style="color:#bf616a;">filter</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, $</span><span style="color:#bf616a;">filter</span><span>); -</span><span> }); -</span></code></pre> -<p>This is a lot nicer in my mind. It will allow us to encapsulate code under the closure and not pollute the top level workspace. Sweet!</p> -<p>How about a more complicated example?</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** </span><span style="color:#b48ead;">@var</span><span style="color:#65737e;"> \Illuminate\Database\Eloquent\Builder|\App\Models\User */ -</span><span>$</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">query</span><span>(); -</span><span> -</span><span style="color:#65737e;">// get the search query from the request -</span><span>$</span><span style="color:#bf616a;">search </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">query</span><span>&#39;); -</span><span> -</span><span style="color:#65737e;">// when the database is not postgresql, use &quot;like&quot; -</span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#bf616a;">config</span><span>(&#39;</span><span style="color:#a3be8c;">database.default</span><span>&#39;) !== &#39;</span><span style="color:#a3be8c;">pgsql</span><span>&#39;) { -</span><span> </span><span style="color:#65737e;">// replace any spaces with the SQL wildcard `%` character -</span><span> $</span><span style="color:#bf616a;">likeReady </span><span>= </span><span style="color:#ebcb8b;">Str</span><span>::</span><span style="color:#bf616a;">of</span><span>($</span><span style="color:#bf616a;">search</span><span>)-&gt;</span><span style="color:#bf616a;">replace</span><span>(&#39; &#39;, &#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">append</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">prepend</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;); -</span><span> $</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">like</span><span>&#39;, $</span><span style="color:#bf616a;">likeReady</span><span>); -</span><span>} -</span><span> -</span><span style="color:#65737e;">// use advanced postgresql features for searching text -</span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#bf616a;">config</span><span>(&#39;</span><span style="color:#a3be8c;">database.default</span><span>&#39;) === &#39;</span><span style="color:#a3be8c;">pgsql</span><span>&#39;) { -</span><span> </span><span style="color:#65737e;">// use the function from the pg_trgm extension that adds special text search features -</span><span> $</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">whereRaw</span><span>(&#39;</span><span style="color:#a3be8c;">SIMILARITY(&quot;name&quot;::text, ?) &gt; 0.07</span><span>&#39;, [$</span><span style="color:#bf616a;">search</span><span>]); -</span><span>} -</span></code></pre> -<p>This is actual code a wrote for an app that uses <code>sqlite</code> when testing but uses <code>postgresql</code> when running locally. This means some features just don't work unless the right code is running.</p> -<p>Let's rewrite this one as well to clean-up the top level:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** </span><span style="color:#b48ead;">@var</span><span style="color:#65737e;"> \Illuminate\Database\Eloquent\Builder|\App\Models\User */ -</span><span>$</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">query</span><span>(); -</span><span> -</span><span style="color:#65737e;">// get the search query from the request -</span><span>$</span><span style="color:#bf616a;">search </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">query</span><span>&#39;); -</span><span> -</span><span style="color:#65737e;">// nicer way without using if statements -</span><span>$</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">when</span><span>( -</span><span> </span><span style="color:#bf616a;">config</span><span>(&#39;</span><span style="color:#a3be8c;">database.default</span><span>&#39;) === &#39;</span><span style="color:#a3be8c;">pgsql</span><span>&#39;, -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use </span><span>($</span><span style="color:#bf616a;">search</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> </span><span style="color:#65737e;">// use the function from the pg_trgm extension that adds special text search features -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">whereRaw</span><span>(&#39;</span><span style="color:#a3be8c;">SIMILARITY(&quot;name&quot;::text, ?) &gt; 0.07</span><span>&#39;, [$</span><span style="color:#bf616a;">search</span><span>]); -</span><span> }, -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use </span><span>($</span><span style="color:#bf616a;">search</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> </span><span style="color:#65737e;">// replace any spaces with the SQL wildcard `%` character -</span><span> $</span><span style="color:#bf616a;">likeReady </span><span>= </span><span style="color:#ebcb8b;">Str</span><span>::</span><span style="color:#bf616a;">of</span><span>($</span><span style="color:#bf616a;">search</span><span>)-&gt;</span><span style="color:#bf616a;">replace</span><span>(&#39; &#39;, &#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">append</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">prepend</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;); -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">like</span><span>&#39;, $</span><span style="color:#bf616a;">likeReady</span><span>); -</span><span> }); -</span></code></pre> -<p>Great! Now we have the <code>pgsql</code> query nicely wrapped up.</p> -<p>This pattern is not just great for controllers. It works well in scoped queries too. I've used this in a scoped query to change the conditions based on the data in the model. Very handy!</p> -<p>If we use chaining, we can make a very nice flow:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** </span><span style="color:#b48ead;">@var</span><span style="color:#65737e;"> \Illuminate\Database\Eloquent\Builder|\App\Models\User */ -</span><span>$</span><span style="color:#bf616a;">query </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">query</span><span>(); -</span><span> -</span><span>$</span><span style="color:#bf616a;">search </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">query</span><span>&#39;); -</span><span>$</span><span style="color:#bf616a;">filter </span><span>= $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">basic</span><span>&#39;); -</span><span>$</span><span style="color:#bf616a;">ordering </span><span>= [$</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">order_by</span><span>&#39;), $</span><span style="color:#bf616a;">request</span><span>-&gt;</span><span style="color:#bf616a;">input</span><span>(&#39;</span><span style="color:#a3be8c;">direction</span><span>&#39;)]; -</span><span> -</span><span>$</span><span style="color:#bf616a;">query</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">role</span><span>&#39;, $</span><span style="color:#bf616a;">filter</span><span>) -</span><span> -&gt;</span><span style="color:#bf616a;">when</span><span>( -</span><span> </span><span style="color:#bf616a;">config</span><span>(&#39;</span><span style="color:#a3be8c;">database.default</span><span>&#39;) === &#39;</span><span style="color:#a3be8c;">pgsql</span><span>&#39;, -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use </span><span>($</span><span style="color:#bf616a;">search</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> </span><span style="color:#65737e;">// use the function from the pg_trgm extension that adds special text search features -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">whereRaw</span><span>(&#39;</span><span style="color:#a3be8c;">SIMILARITY(&quot;name&quot;::text, ?) &gt; 0.07</span><span>&#39;, [$</span><span style="color:#bf616a;">search</span><span>]); -</span><span> }, -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use </span><span>($</span><span style="color:#bf616a;">search</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> </span><span style="color:#65737e;">// replace any spaces with the SQL wildcard `%` character -</span><span> $</span><span style="color:#bf616a;">likeReady </span><span>= </span><span style="color:#ebcb8b;">Str</span><span>::</span><span style="color:#bf616a;">of</span><span>($</span><span style="color:#bf616a;">search</span><span>)-&gt;</span><span style="color:#bf616a;">replace</span><span>(&#39; &#39;, &#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">append</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;)-&gt;</span><span style="color:#bf616a;">prepend</span><span>(&#39;</span><span style="color:#a3be8c;">%</span><span>&#39;); -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">like</span><span>&#39;, $</span><span style="color:#bf616a;">likeReady</span><span>); -</span><span> } -</span><span> )-&gt;</span><span style="color:#bf616a;">when</span><span>( -</span><span> </span><span style="color:#96b5b4;">empty</span><span>($</span><span style="color:#bf616a;">ordering</span><span>), -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">orderBy</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;); -</span><span> }, -</span><span> </span><span style="color:#b48ead;">function </span><span>($</span><span style="color:#bf616a;">q</span><span>) </span><span style="color:#b48ead;">use </span><span>($</span><span style="color:#bf616a;">ordering</span><span>): </span><span style="color:#ebcb8b;">void </span><span>{ -</span><span> $</span><span style="color:#bf616a;">q</span><span>-&gt;</span><span style="color:#bf616a;">orderBy</span><span>($</span><span style="color:#bf616a;">ordering</span><span>[</span><span style="color:#d08770;">0</span><span>], $</span><span style="color:#bf616a;">ordering</span><span>[</span><span style="color:#d08770;">1</span><span>]); -</span><span> } -</span><span> ); -</span></code></pre> -<p>Hopefully this inspires you to find some code and wrap it up to be a little clearer or cleaner.</p> - - - - - Bitbucket Weekly Reports Using Make (Integromat) - 2022-08-06T00:00:00+00:00 - 2022-08-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/bitbucket-weekly-reports-using-integromat-make/ - - <p>At my job, we like to keep our team updated with all the dev work we do each week. This means we have meetings every Monday to review the work that has happened since the previous week.</p> -<p>I often like to review the work done over that time frame by looking at the projects git log for all the pull-requests merged during that previous week.</p> -<p>Now, I could open up the <code>develop</code> branch and run a <code>git shortlog --since &quot;1 week ago&quot;</code> every Monday, but I would much rather automate this task so I don't have to remember to do anything and it stays consistent. I can just wake up on Monday and see a pretty little report printed in the project Slack channel.</p> -<p>The way I accomplished this is using <a href="https://www.make.com/en?pc=jamesdoyle&amp;fromImt=1">Make.com (formerly Integromat)</a>. This is a &quot;no-code&quot; tool that allows you to use visual programming to build tools and apps.</p> -<p>Let's break down the solution I came up with:</p> -<div class="center"> - <a href="/images/bb-integromat-make-01.png" target="_blank" title="Bitbucket integromat make 01"> - <img src="/images/bb-integromat-make-01.png" alt="Bitbucket integromat make 01" /> - </a> -</div> -<p>With a final output something like this:</p> -<pre> -<h5><strong>BitBucket Reporter [APP] 9:01 AM</strong></h5> -<strong>List of BitBucket PRs since 2022-07-25 [1 of 2]</strong> -#824 - Feature/events [MERGED] by James Doyle -#825 - Bugfix/appointments and invoices [MERGED] by Sr. Developer -#826 - Feature/auto submit changes [MERGED] by Sr. Developer -#827 - Feature/events [MERGED] by James Doyle -#828 - Fixed: case where missing was not correct [MERGED] by James Doyle -#829 - Fixed: another issue with the wrong conditions showing up [MERGED] by James Doyle -#830 - Bugfix/appointment and admin notes [MERGED] by Sr. Developer -#831 - Feature/user-profile-header [MERGED] by Jr. Developer -#832 - Feature/user profile treatment plan [OPEN] by Sr. Developer -#833 - Fixed: loading wrong classes and incorrect state [MERGED] by James Doyle -<strong>List of BitBucket PRs since 2022-07-25 [2 of 2]</strong> -#834 - Fix/users documents style tweaks [MERGED] by Jr. Developer -#835 - Feature/insurance claims [OPEN] by Jr. Developer -#836 - Feature/forms [MERGED] by James Doyle -#837 - Bugfix/user tickets [OPEN] by Jr. Developer -</pre> -<p>Here is the overall solution laid out. As you can see, it wasn't as simple as it might seem.</p> -<p>I will go through the solution step-by-step and explain each node. I will assume you already have Slack and BitBucket connected.</p> -<h3 id="step-1">Step 1</h3> -<div class="center"> - <a href="/images/bb-integromat-make-02.png" target="_blank" title="Bitbucket integromat make 02"> - <img src="/images/bb-integromat-make-02.png" alt="Bitbucket integromat make 02" /> - </a> -</div> -<p>First we need set the schedule to run each Monday. Nothing fancy here. I set it to 9:01 just because I am usually on the computer by then so I will see the channel notification.</p> -<h3 id="step-2">Step 2</h3> -<div class="center"> - <a href="/images/bb-integromat-make-03.png" target="_blank" title="Bitbucket integromat make 03"> - <img src="/images/bb-integromat-make-03.png" alt="Bitbucket integromat make 03" /> - </a> -</div> -<p>Here is the reason this is so complicated: the BitBucket API is paginated and there is no way to easily page through the results. So we need to loop through each page from the results and append them to a variable we eventually send to Slack.</p> -<p>In this request, we ask BitBucket for the &quot;merged&quot; and &quot;open&quot; pull-requests that happened last week. There was no nice way to calculate the previous weeks date, so we have this fun code to get the date of the previous Monday.</p> -<p>You can copy the text below for the query:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>(state=&quot;merged&quot; OR state=&quot;open&quot;) AND created_on&gt;=&quot;{{formatDate(setDay(parseDate(timestamp - 604800; &quot;X&quot;); &quot;monday&quot;); &quot;YYYY-MM-DD&quot;)}}&quot; -</span></code></pre> -<p>The only reason we run this first request just so we can get the page count that we will then use to create a loop to make the real requests.</p> -<h3 id="step-3">Step 3</h3> -<div class="center"> - <a href="/images/bb-integromat-make-04.png" target="_blank" title="Bitbucket integromat make 04"> - <img src="/images/bb-integromat-make-04.png" alt="Bitbucket integromat make 04" /> - </a> -</div> -<p>Here is the loop that we use to iterate over the pages in the results. We check the page size and make sure we repeat the right amount of times.</p> -<div class="center"> - <a href="/images/bb-integromat-make-04a.png" target="_blank" title="Bitbucket integromat make 04 A"> - <img src="/images/bb-integromat-make-04a.png" alt="Bitbucket integromat make 04 A" /> - </a> -</div> -<p>Our first route (the top one) is the condition that the loop continues to run in. You can see the condition is a basic for loop that repeats for each page in the loop.</p> -<h3 id="step-4">Step 4</h3> -<div class="center"> - <a href="/images/bb-integromat-make-05.png" target="_blank" title="Bitbucket integromat make 05"> - <img src="/images/bb-integromat-make-05.png" alt="Bitbucket integromat make 05" /> - </a> -</div> -<p>Here is the request that happens within the loop. It is identical to the first one but we have a page parameter so that we can get each page of the results.</p> -<h3 id="step-5">Step 5</h3> -<div class="center"> - <a href="/images/bb-integromat-make-06.png" target="_blank" title="Bitbucket integromat make 06"> - <img src="/images/bb-integromat-make-06.png" alt="Bitbucket integromat make 06" /> - </a> -</div> -<p>All we are doing in this node is sorting the pull requests by their title. We do this because the PR number is in the title and not sorting it would look off.</p> -<h3 id="step-6">Step 6</h3> -<div class="center"> - <a href="/images/bb-integromat-make-07.png" target="_blank" title="Bitbucket integromat make 07"> - <img src="/images/bb-integromat-make-07.png" alt="Bitbucket integromat make 07" /> - </a> -</div> -<p>Here we are defining the string template for the PR titles that will go into our bulleted list. The output of this will be a single variable that contains all the row strings.</p> -<h3 id="step-7">Step 7</h3> -<div class="center"> - <a href="/images/bb-integromat-make-08.png" target="_blank" title="Bitbucket integromat make 08"> - <img src="/images/bb-integromat-make-08.png" alt="Bitbucket integromat make 08" /> - </a> -</div> -<p>Nothing fancy here. Just getting the variable the we will be pushing the results of our text aggregation to.</p> -<h3 id="step-8">Step 8</h3> -<div class="center"> - <a href="/images/bb-integromat-make-09.png" target="_blank" title="Bitbucket integromat make 09"> - <img src="/images/bb-integromat-make-09.png" alt="Bitbucket integromat make 09" /> - </a> -</div> -<p>Here we are updating our actual results that will be sent to Slack. We break up the list with a nice header that is chunked by page.</p> -<p>Here is the code in the box for setting the variable:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>{{21.results}}{{newline}}*List of Project PRs since {{formatDate(setDay(parseDate(timestamp - 604800; &quot;X&quot;); &quot;monday&quot;); &quot;YYYY-MM-DD&quot;)}} [{{13.i}} of {{ceil(1.body.size / 1.body.pagelen)}}]*{{newline}}{{11.text}} -</span></code></pre> -<h3 id="step-9">Step 9</h3> -<div class="center"> - <a href="/images/bb-integromat-make-10.png" target="_blank" title="Bitbucket integromat make 10"> - <img src="/images/bb-integromat-make-10.png" alt="Bitbucket integromat make 10" /> - </a> -</div> -<p>Here is the other condition in the router that will run at the end of the loop. It checks that the loop variable is larger that the page count.</p> -<div class="center"> - <a href="/images/bb-integromat-make-10a.png" target="_blank" title="Bitbucket integromat make 10 A"> - <img src="/images/bb-integromat-make-10a.png" alt="Bitbucket integromat make 10 A" /> - </a> -</div> -<p>Our first step in the end case is to get the variable. We are passing this to the Slack body.</p> -<h3 id="step-10">Step 10</h3> -<div class="center"> - <a href="/images/bb-integromat-make-11.png" target="_blank" title="bb integromat make 11"> - <img src="/images/bb-integromat-make-11.png" alt="bb integromat make 11" /> - </a> -</div> -<p>Our last step is to just shoot off that text to Slack. We have already formatted the text we are sending so we just slap it in.</p> -<h3 id="all-done">All Done!</h3> -<p>That is it! Hopefully this helps anyone that is trying to use the BitBucket in <a href="https://www.make.com/en?pc=jamesdoyle&amp;fromImt=1">Make.com (Integromat)</a>.</p> - - - - - Autocomplete TailwindCSS In Custom Attributes/Strings - 2022-06-06T00:00:00+00:00 - 2022-06-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/autocomplete-tailwind-classes/ - - <p>If you are using <a href="https://tailwindcss.com/">TailwindCSS</a> along with <a href="https://github.com/tailwindlabs/tailwindcss-intellisense">their extension for completing tailwind classes</a> but you are using styled components, custom attributes/props for class names, or packages like <a href="https://github.com/ben-rogerson/twin.macro">twin.macro</a>, then autocomplete for class names might not work properly for you.</p> -<p>There is a setting inside the language server for tailwind that let's you provide a custom regex for when/where you want the tailwind autocomplete to work. By default it works inside <code>class</code> and <code>className</code>. But what if we want to change that? For, say, a tagged template literal?</p> -<p>You can use the following config for various types/styles of solutions for writing tailwind classes:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>&quot;</span><span style="color:#a3be8c;">tailwindCSS.experimental.classRegex</span><span>&quot;: [ -</span><span> [&quot;</span><span style="color:#a3be8c;">classnames</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">(([^)]*)</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">)</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">&#39;([^&#39;]*)&#39;</span><span>&quot;], -</span><span> &quot;</span><span style="color:#a3be8c;">class=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">([^</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">]*)</span><span>&quot;, </span><span style="color:#65737e;">// &lt;div class=&quot;...&quot; /&gt; -</span><span> &quot;</span><span style="color:#a3be8c;">tw`([^`]*)</span><span>&quot;, </span><span style="color:#65737e;">// tw`...` -</span><span> &quot;</span><span style="color:#a3be8c;">tw=</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">([^</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">]*)</span><span>&quot;, </span><span style="color:#65737e;">// &lt;div tw=&quot;...&quot; /&gt; -</span><span> &quot;</span><span style="color:#a3be8c;">tw={</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">([^</span><span style="color:#96b5b4;">\&quot;</span><span style="color:#a3be8c;">}]*)</span><span>&quot;, </span><span style="color:#65737e;">// &lt;div tw={&quot;...&quot;} /&gt; -</span><span> &quot;</span><span style="color:#a3be8c;">tw</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">.</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">w+`([^`]*)</span><span>&quot;, </span><span style="color:#65737e;">// tw.xxx`...` -</span><span> &quot;</span><span style="color:#a3be8c;">tw</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">(.*?</span><span style="color:#96b5b4;">\\</span><span style="color:#a3be8c;">)`([^`]*)</span><span>&quot; </span><span style="color:#65737e;">// tw(Component)`...` -</span><span>], -</span></code></pre> -<p>The above rules are what I am using with React and the twin.macro package. I can complete under various <code>tw</code> props or tagged templates which is exactly what I need for the project I'm on.</p> -<p>If you would like to see some of the other use cases for this setting, <a href="https://github.com/tailwindlabs/tailwindcss-intellisense/search?q=classRegex&amp;type=issues">you can browse the issues on Github</a>. As you can see, it is a common feature that is reached for when you need to customize the location of the autocomplete trigger.</p> - - - - - Setup A Raspberry Pi with PHP And Lighttpd - 2022-06-05T00:00:00+00:00 - 2022-06-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/raspberry-pi-php-and-lighttpd/ - - <p>I recently got a Raspberry Pi model 4+. I'm using it to <a href="https://pimylifeup.com/raspberrypi-minidlna/">run a minidlna server</a> that loads music and movies from an old external hard drive that I have. On top of that, I wanted to run a local web server so that I can write scripts and pages that I can access locally on my network without having to have my laptop open and running.</p> -<p>I wanted to setup <code>lighttpd</code> as it is much more efficient than the default installed <code>apache2</code>. Since I'm already running <code>minidlna</code>, I wanted a web server that was more performant and used less memory/resources.</p> -<p>Finally, I wanted the latest PHP installed given I use it a lot in my day job and it will be great for writing small websites that are only ever accessed through my local network. Unfortunately, the default installed PHP version is quite a bit behind and you need to setup new sources in order to install the latest version of PHP.</p> -<p>Here is a short list of all the steps you need to setup a newer version of PHP 8.1 as well as the <code>lighttpd</code> server.</p> -<h4 id="instructions">Instructions</h4> -<p><em>These instructions assume you're using the default debian-based raspberry pi operating system!</em></p> -<p>Update the local packages already installed:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> apt-get update -</span></code></pre> -<p>Install the dependencies needed to add the additional services:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> apt-get install lsb-release apt-transport-https ca-certificates -</span></code></pre> -<p>Add the repository for installing the latest version of PHP:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> wget</span><span style="color:#bf616a;"> -O</span><span> /etc/apt/trusted.gpg.d/php.gpg https://origin.sury.org/php/apt.gpg -</span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">deb https://packages.sury.org/php/ </span><span>$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">lsb_release -sc</span><span style="color:#a3be8c;">) main</span><span>&quot; | </span><span style="color:#bf616a;">sudo</span><span> tee /etc/apt/sources.list.d/php.list -</span></code></pre> -<p>Now we can install all the PHP packages we will need for basic PHP applications:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> apt-get update -</span><span style="color:#bf616a;">sudo</span><span> apt-get install</span><span style="color:#bf616a;"> -y</span><span> php8.1 php8.1-cli php8.1-cgi php8.1-intl php8.1-zip -</span></code></pre> -<p>If you need additional PHP extensions then the above command is where you would add those in.</p> -<p>Since we now have the latest PHP version, we can setup <code>composer</code>. The <code>sha384</code> code may different for your installation based on whatever version is out when you are following these instructions. <a href="https://getcomposer.org/download/">You can find the latest download/install instructions on the Composer download page</a>.</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">php -r </span><span>&quot;</span><span style="color:#a3be8c;">copy(&#39;https://getcomposer.org/installer&#39;, &#39;composer-setup.php&#39;);</span><span>&quot; -</span><span style="color:#bf616a;">php -r </span><span>&quot;</span><span style="color:#a3be8c;">if (hash_file(&#39;sha384&#39;, &#39;composer-setup.php&#39;) === &#39;55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae&#39;) { echo &#39;Installer verified&#39;; } else { echo &#39;Installer corrupt&#39;; unlink(&#39;composer-setup.php&#39;); } echo PHP_EOL;</span><span>&quot; -</span><span style="color:#bf616a;">php</span><span> composer-setup.php -</span><span style="color:#bf616a;">sudo</span><span> mv composer.phar /usr/local/bin/composer -</span><span style="color:#bf616a;">php -r </span><span>&quot;</span><span style="color:#a3be8c;">unlink(&#39;composer-setup.php&#39;);</span><span>&quot; -</span></code></pre> -<p>We can remove <code>apache2</code>, given we are not going to use it:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> systemctl stop apache2.service -</span><span style="color:#bf616a;">sudo</span><span> apt remove apache2 -</span></code></pre> -<p>The last thing to install will be <code>lighttpd</code>:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> apt-get install lighttpd lighttpd-doc -</span></code></pre> -<p>We also want to enable the PHP modules inside <code>lighttpd</code> so that we can process <code>.php</code> files:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> lighttpd-enable-mod fastcgi fastcgi-php -</span></code></pre> -<p>In order for the web server to properly run, serve our files, and store logs and caches, we need to make sure the folders have the right ownership rules:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> chown</span><span style="color:#bf616a;"> -R</span><span> www-data:www-data /var/log/lighttpd -</span><span style="color:#bf616a;">sudo</span><span> chown</span><span style="color:#bf616a;"> -R</span><span> www-data:www-data /var/cache/lighttpd -</span><span style="color:#bf616a;">sudo</span><span> chown</span><span style="color:#bf616a;"> -R</span><span> www-data:www-data /var/www/html -</span></code></pre> -<p>To test out the server once we are finished, we can setup this simple project that emulates the apache directory listing module. This will just list everything in our <code>/var/www/html</code> folder in a nice display:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">git</span><span> clone https://github.com/halgatewood/file-directory-list /var/www/html/listing -</span></code></pre> -<p>If you are looking for a more efficient directory browser, or you don't want to use PHP for this, you can <a href="https://redmine.lighttpd.net/projects/lighttpd/wiki/Docs_ModDirlisting">use the built-in <code>dirlisting</code> module</a> that comes with <code>lighttpd</code>.</p> -<p>We can now make sure the <code>lighttpd</code> service is running so it will always start when we restart our pi:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">sudo</span><span> systemctl start lighttpd.service -</span></code></pre> -<p>If you want to test the server on the command line, you can just <code>curl</code> your localhost and see what happens. You should get the source code for the listing page we installed:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl</span><span> http://localhost/listing -</span></code></pre> -<p>Done! That should be all you need to have PHP 8.1 and a light weight web server setup!</p> - - - - - Vue stateful form component - 2021-10-24T00:00:00+00:00 - 2021-10-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/vue-stateful-form-component/ - - <p>Recently, I needed to create a app that could recreate a form from static JSON and then fill it with values from another source. This was pretty difficult as storing a form in JSON is very hard. You can't add handlers or events given you only can store string, numbers, booleans, and arrays. No functions.</p> -<p>I ended up coming up with <a href="https://james2doyle.github.io/vue-stateful-form/">a component that uses a render function in order to recreate the form stored in JSON</a>.</p> -<div class="center"> - <a href="/images/vue-stateful-form-demo.gif" target="_blank" > - <img src="/images/vue-stateful-form-demo.gif" width="720" /> - </a> -</div> -<h4 id="features">Features</h4> -<ul> -<li><label><input type="checkbox" checked readonly />  uses event delegation from the top level <code>form</code> element</label></li> -<li><label><input type="checkbox" checked readonly />  2 way binding with proper <code>v-model</code> support</label></li> -<li><label><input type="checkbox" checked readonly />  unstyled but includes lots of classes to target</label></li> -<li><label><input type="checkbox" checked readonly />  built-in debounce function</label></li> -<li><label><input type="checkbox" checked readonly />  still allows <code>submit</code> handler</label></li> -<li><label><input type="checkbox" checked readonly />  encodes &quot;multiple&quot; inputs (<code>select[multiple]</code>, <code>radio</code>, <code>checkbox</code>)</label></li> -<li><label><input type="checkbox" checked readonly />  no hacky &quot;mounted&quot; calls</label></li> -<li><label><input type="checkbox" checked readonly />  supports most input elements (no <code>file</code>/<code>image</code> support)</label></li> -<li><label><input type="checkbox" checked readonly />  sets <code>ref</code> for each input automatically</label></li> -<li><label><input type="checkbox" checked readonly />  supports custom components and passing props/attrs</label></li> -</ul> -<p>I made a whole site on this and posted the code to NPM. It currently only works in Vue 2. So keep that in mind. You can <a href="https://james2doyle.github.io/vue-stateful-form/">find the source code and instructions here</a>.</p> - - - - - Lodash i18n (translation) function - 2021-10-22T00:00:00+00:00 - 2021-10-22T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/lodash-translation-function/ - - <p>One of the great things about <a href="https://lodash.com/">lodash</a> is that it gives you all the building blocks to create some really powerful functions.</p> -<p>I was working on a site that needed to use dynamic translations. The way this is done today is usually through a function that is loaded in your components and called with a key that then gets mapped to whatever language you are using.</p> -<p>You can pass in variables to the function in order to translate sentences that include placeholders. For example, you might want to have a welcome message like &quot;Hello {name_of_user}!&quot;. Pretty common use case as you can imagine.</p> -<p>I managed to get a simple version of an <code>intl</code> function working by using a combination of <code>get</code>, <code>template</code>, and <code>memoize</code> functions in lodash.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/d00e06a5f4963a539e3aa0b2d5283d11.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;d00e06a5f4963a539e3aa0b2d5283d11.js'></script>`"></iframe> -<p>What we are doing here is using first getting a value from our translation file. We use the <code>template</code> function to parse the value we find from our translation function using a pattern that looks for single words wrapped with curly braces. Like <code>{this}</code>. We can then pass in any variables we wanted to replace in that string. Finally, we use <code>memoize</code> to avoid recompiling the template on each additional call. This will just return the cached results of any translations instead of grabbing the value and parsing the string again.</p> -<p>If you find yourself needing a simple translation function, this could be a good option if you already have lodash in your project.</p> - - - - - Lodash memoize with a timeout - 2021-10-20T00:00:00+00:00 - 2021-10-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/lodash-memo-with-timeout/ - - <p>If you are familiar with <a href="https://lodash.com/docs/4.17.15">lodash</a> you may also be familiar with one of the very handy functions called <code>[memoize](https://lodash.com/docs/4.17.15#memoize)</code>.</p> -<p>The definition of <code>memoize</code> on the lodash site is quite verbose. So I will use the <a href="https://en.wikipedia.org/wiki/Memoization">definition on wikipedia</a>:</p> -<blockquote> -<p>In computing, &quot;memoization&quot; or &quot;memoisation&quot; is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.</p> -</blockquote> -<p>The default <code>memo</code> function in lodash uses a local Map to cache the results of each call. This means the results of the calls to <code>memo</code> will be cached for the entire browser session. The way you would clear the cache is to refresh or trigger a full navigation.</p> -<p>A function that returns the same result given the same input, is called a <a href="https://en.wikipedia.org/wiki/Pure_function">pure function</a>. So if you have a &quot;pure function&quot; that gets called a lot with the same arguments, and should have the same output given the same arguments, then this is a perfect candidate for memoization. Of course, it is perfectly fine to reach for memoization in order to keep things like expensive HTTP requests from being repeated.</p> -<p>But is a &quot;session&quot; based cache really the best cache for <code>memo</code>? Personally, I think that a time-based caching mechanism is better. What I mean is that the cache will only live for a specific amount of time and then expire.</p> -<p>Imagine a case where you just fetched a users account from your API. If for some reason your code calls that endpoint again just a few seconds later, is it worth redoing the request or should you return cached results given you just called that endpoint earlier?</p> -<p>If you are using the default cache, the request will never be run again unless you refresh. But there may have been changes to the results of the call but you can't rerun it. This is where a TTL cache comes in.</p> -<p>TTL (time to live) is the amount of time that needs to elapse before the cache needs to be refreshed. If you are familiar with the HTTP protocol, you may have come across this term when learning about cache headers.</p> -<p>So how do you implement a custom cache backend for lodash <code>memoize</code>? Easy! The <code>memoize</code> function allows you to write your own resolver function that lets you decide when to fetch from the cache or run the function again.</p> -<p>I've done the work for you and made a simple version in TypeScript that uses the current minute as a tracker for when the last call was:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/2a7428e6e740279f8cc7fbd2dd7b4f75.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;2a7428e6e740279f8cc7fbd2dd7b4f75.js'></script>`"></iframe> -<p>You can see in this function that we take in the arguments and add a time to the end. We then serialize that object as JSON and use that as our cache key. When lodash runs our new <code>memo</code> function, it will first compare the cached keys and see if they are different. If they are, then the function will actually run and the cached results will not be used and instead our original function will run, and the result of that run, will be cached. Subsequent calls repeat the whole process.</p> -<p>In this case, we only cache the results for one minute. So any calls to our new <code>memo</code> function that are over a minute old will run. This will allow some inefficient code that calls an endpoint too often to only make those calls if at least a minute has passed.</p> -<p>In this case, our new <code>memo</code> is almost like the <code>throttle</code> function in lodash except we get the results back when we call our <code>memo</code>. But if you need to control the mechanism of caching (maybe you want to use localstorage, the URL, or global state) then you can write your own <code>memoize</code>.</p> - - - - - Fastmod Codemod For Refactoring - 2021-02-06T00:00:00+00:00 - 2021-02-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/fastmod-codemod-for-refactoring/ - - <p>If you have ever encounter a big refactor, you were probably dreading the steps it would take to get all the changes done. You have to find patterns, replace them, remove old code, rename variables, so much work! Well, like most things in the development world, there are tools to help you do this. Yes, you can use find-and-replace, but that approach is very naïve (as in simple) and doesn't take in some of the more nuanced cases that you will come across.</p> -<p>The tool that I have been using for the last couple of years is <a href="https://github.com/facebookincubator/fastmod">fastmod</a>. <code>fastmod</code> is a flavour of <code>codemod</code> but written in Rust - so it has to be cool right? Here is a description from the <code>codemod</code> repo:</p> -<blockquote> -<p>codemod is a tool/library to assist you with large-scale codebase refactors that can be partially automated but still require human oversight and occasional intervention.</p> -</blockquote> -<p>What you do is write a &quot;match pattern&quot; and then a &quot;replace pattern&quot;. When you run the tool you get a prompt for each match and you can decide what you want to do with the proposed changes. This is a lot like <code>git add -p</code> (which is also a great trick) where you only accept the changes you know are valid.</p> -<p>One of the best things about this tool is how it provides the interface to the changes you need to apply:</p> -<ul> -<li><input disabled="" type="checkbox" checked=""/> -filter files by extension</li> -<li><input disabled="" type="checkbox" checked=""/> -use regex to create matches</li> -<li><input disabled="" type="checkbox" checked=""/> -named matches so they can be reordered</li> -<li><input disabled="" type="checkbox" checked=""/> -preview of the changes before you commit them</li> -<li><input disabled="" type="checkbox" checked=""/> -ability to accept or decline any single change</li> -<li><input disabled="" type="checkbox" checked=""/> -ability to edit the changes using <code>$EDITOR</code> (like <code>git commit --verbose</code>)</li> -<li><input disabled="" type="checkbox" checked=""/> -accept all changes without previews (&quot;fast mode&quot;)</li> -</ul> -<p><strong>Help With Regex</strong></p> -<p>If you are terrible at written regex patterns, you're not alone. There are lots of people who despise regex but it isn't going anyway. I actually made a conscious effort a few years ago to acquire a better understanding of how regex works and what the patterns and special characters do. I used <a href="https://regexr.com/">this tool call regexr</a> to help practice as well as learn new patterns. Check it out if you need help building and testing regex patterns.</p> -<p>Like most purpose-built tools, there is some learning curve to finding how to best use the interface provided. You really need to consider your match strategy and your replace strategy. Let's write some examples based on some real world examples I've come across.</p> -<h4 id="css">CSS</h4> -<p>Here we have a CSS example for a button component. This component can be grouped or have an icon inside it. The naming of the component in CSS is kinda redundant.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button-group-component </span><span>{ -</span><span> display: flex; -</span><span> align-items: center; -</span><span> justify-content: space-between; -</span><span>} -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button-component </span><span>{ -</span><span> color: white; -</span><span> background-color: black; -</span><span> display: inline-block; -</span><span> padding: </span><span style="color:#d08770;">1rem 2rem</span><span>; -</span><span>} -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button-component </span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button-icon-component </span><span style="color:#bf616a;">svg </span><span>{ -</span><span> fill: currentColor; -</span><span>} -</span></code></pre> -<p>Let's remove the <code>-component</code> part of the CSS declaration. We don't need it. So we are going to turn <code>.button-group-component</code> into <code>.button-group</code> and so on for the other 2 class declarations. All we need to do is find <code>-component</code> and replace it with nothing/null/empty string.</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">fastmod -m --extensions</span><span> css &#39;</span><span style="color:#a3be8c;">(\-component)</span><span>&#39; &#39;&#39; -</span></code></pre> -<p>Running the above code on in our target folder will prompt us with this:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>./example.css:1 -</span><span style="color:#bf616a;">- .button-group-component { -</span><span style="color:#a3be8c;">+ .button-group { -</span><span> display: flex; -</span><span> align-items: center; -</span><span>Accept change (y = yes [default], n = no, e = edit, A = yes to all, E = yes+edit, q = quit)? -</span></code></pre> -<p>Here you can see all the options for the code that is going to be changed as well as a nice diff too. If we move from left to right in the options we get the following choices:</p> -<ul> -<li><code>y</code> or <code>enter</code> accepts the changes being shown</li> -<li><code>n</code> will not change anything and will go to the next change</li> -<li><code>e</code> will open your <code>$EDITOR</code> <em>without</em> any changes applied</li> -<li><code>A</code> will accept this change and all of the future changes with reprompting you</li> -<li><code>E</code> will open your <code>$EDITOR</code> <em>with</em> any changes applied</li> -<li><code>q</code> will no accept the changes and stop the process</li> -</ul> -<p>In this case we can hit <code>enter</code> for each change. We will be prompted 4 times: once for each occurrence of the string <code>-component</code> or we can skip all that and just hit <code>A</code> and everything will be changed.</p> -<h4 id="webpack-magic-comments">Webpack Magic Comments</h4> -<p>Recently I was working on a Vue project that had a lot of components in it. I wanted to start using <a href="https://router.vuejs.org/guide/advanced/lazy-loading.html#grouping-components-in-the-same-chunk">webpack chunking</a> in order to split the components up and have single chunks for each component instead of them being wrapped up together. In order to do that, I need to rewrite all my <code>import</code> statements to use &quot;dynamic imports&quot; with a special magic comment that webpack picks up.</p> -<p>You can see the example below:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># old code: `import MyComponent from &#39;Components/MyComponent.vue&#39;;` -</span><span style="color:#65737e;"># new code: `const MyComponent = () =&gt; import(/* webpackChunkName: &quot;MyComponent&quot; */ &#39;Components/MyComponent.vue&#39;);` -</span><span style="color:#65737e;"># note: you may need to reorder your imports when using a dynamic import -</span><span> -</span><span style="color:#bf616a;">fastmod -m -d</span><span> ./</span><span style="color:#bf616a;"> --extensions</span><span> vue \ -</span><span> &#39;</span><span style="color:#a3be8c;">import (.*?) from \</span><span>&#39;(.*?)</span><span style="color:#96b5b4;">\&#39;</span><span>;&#39;</span><span style="color:#a3be8c;"> \ -</span><span style="color:#a3be8c;"> </span><span>&#39;</span><span style="color:#bf616a;">const </span><span>${</span><span style="color:#bf616a;">1</span><span>} = () =&gt; import(</span><span style="color:#bf616a;">/*</span><span> webpackChunkName: &quot;$</span><span style="color:#a3be8c;">{</span><span style="color:#bf616a;">1</span><span style="color:#a3be8c;">}</span><span>&quot; */ </span><span style="color:#96b5b4;">\&#39;</span><span>${</span><span style="color:#bf616a;">2</span><span>}</span><span style="color:#96b5b4;">\&#39;</span><span>);&#39; -</span></code></pre> -<p>Here you can see the snippet I wrote to do the refactor. The great thing about using <code>fastmod</code> over find-and-replace, is that it allowed me to accept or deny the changes. I didn't want all the components updated this way. Especially imports that were not Vue components. So this approach was ideal.</p> -<h4 id="more-use-cases">More Use Cases</h4> -<p>Those are two decent examples but I have used this tool quite a lot for refactoring code. Here are some more examples of what I used it for:</p> -<ul> -<li>add/remove classes in HTML</li> -<li>rename bad variable names on multiple projects</li> -<li>quickly fix the misspelling of various words in multiple projects</li> -<li>ran a replace on an exported SQL database backup to change some values in the data (faster than running a query)</li> -<li><a href="https://gist.github.com/james2doyle/a94542b8ef6f07a689f23698424d1763">refactor <code>isset</code> into <code>array_get</code></a> on a Laravel (PHP) project</li> -<li>merge down multiple <code>use</code> statement in PHP to a single <code>use</code> line</li> -<li>rewrite <code>{{ var }}</code> interpolation to <code>v-text=&quot;var&quot;</code> directives in a Vue project</li> -<li>replace imports with other versions</li> -<li>upgrade old code to new methods or APIs</li> -</ul> -<p>As you can probably already tell, the possibilities are endless! I have used this on database exports and even CSV files. It is a much nicer alternative to find-and-replace. Best of all? It's editor agnostic. So you can use it with any other tools.</p> - - - - - Vue Omnibar Component - 2020-12-05T00:00:00+00:00 - 2020-12-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/vue-omnibar-component/ - - <p>I'm working on a project right now that requires us to have a search modal. The feature is actually inspired by the <a href="https://www.notion.so/Searching-with-Quick-Find-af945b6e69b64437afba2d143e4b546f">&quot;Quick Search&quot; experience in Notion</a>. I looked around for a component that was already created that would do this for me. I couldn't find one, so I wrote my own!</p> -<p>This component allows you to create modal popups that emulate omnibar, command palette, open anywhere, or other &quot;search and act&quot; functions/features. It is really simple and uses slots to make it easier to customize. It comes with some basic styling so you don't have to fight with it too much.</p> -<p>One of the cool things about the search box in the modal is that it is using <a href="https://fusejs.io/">Fuse.js</a> for the filtering part. This means you can search complex objects really easy. You can even rank-order properties that are being searched! Pretty slick, right?</p> -<h2 id="demo">Demo</h2> -<div class="center"> - <a href="https://james2doyle.github.io/vue-omnibar" target="_blank" title="demo of the omnibar modal"><img src="https://james2doyle.github.io/vue-omnibar/demo.gif" alt="demo of the omnibar modal"></a> -</div> -<p>Check out the <a href="https://james2doyle.github.io/vue-omnibar">website for the component</a> in order to view the demo.</p> -<h2 id="features">Features</h2> -<ul> -<li><input disabled="" type="checkbox" checked=""/> -built-in filtering using <a href="https://fusejs.io/">Fuse.js</a></li> -<li><input disabled="" type="checkbox" checked=""/> -custom key combo support</li> -<li><input disabled="" type="checkbox" checked=""/> -listens for <code>esc</code> key</li> -<li><input disabled="" type="checkbox" checked=""/> -bring your own styling (basic styles included)</li> -<li><input disabled="" type="checkbox" checked=""/> -arrow key support</li> -<li><input disabled="" type="checkbox" checked=""/> -uses slots for best flexibility</li> -<li><input disabled="" type="checkbox" checked=""/> -&quot;off-click&quot; closes the modal</li> -</ul> -<h2 id="installation">Installation</h2> -<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> install vue-omnibar -</span></code></pre> -<h3 id="global-usage">Global Usage</h3> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">import </span><span style="color:#bf616a;">Vue </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">vue</span><span>&#39;; -</span><span style="color:#b48ead;">import </span><span style="color:#bf616a;">Omnibar </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">vue-omnibar</span><span>&#39;; -</span><span> -</span><span style="color:#bf616a;">Vue</span><span>.</span><span style="color:#8fa1b3;">component</span><span>(&#39;</span><span style="color:#a3be8c;">omnibar</span><span>&#39;, </span><span style="color:#bf616a;">Omnibar</span><span>); -</span></code></pre> -<h3 id="in-single-file-components">In Single File Components</h3> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">import </span><span style="color:#bf616a;">Omnibar </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">vue-omnibar</span><span>&#39;; -</span><span> -</span><span style="color:#b48ead;">export default </span><span>{ -</span><span> </span><span style="color:#65737e;">// ... -</span><span> components: { -</span><span> </span><span style="color:#bf616a;">Omnibar</span><span>, -</span><span> }, -</span><span> </span><span style="color:#65737e;">// ... -</span><span>}; -</span></code></pre> -<p>To learn more, check out the <a href="https://james2doyle.github.io/vue-omnibar">website for the component</a>.</p> - - - - - Vue Toggle Component - 2020-12-04T00:00:00+00:00 - 2020-12-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/vue-toggle-component/ - - <p>Have you ever created a switch component that just shows and hides an element? How about an accordion? Or maybe a slider? If you distill down these components into their core offering, it is really just a simple state toggle that is either a <code>boolean</code> or a index/key value that is being used.</p> -<p>I created this component because it is something I use all the time. I wanted to share it with other people so I released it as a package. The state can be either a <code>boolean</code> or a <code>string</code>. This means you can use it to create experiences that are not just on/off and show/hide.</p> -<p>With a simple toggle, you can build almost any UI experience. Think about experiences like show/hide, accordions, nested menus, and even sliders, they mostly revolve around a single piece of state.</p> -<h2 id="demo">Demo</h2> -<p>Check out <a href="https://james2doyle.github.io/vue-toggle">the website for demos</a>.</p> -<h2 id="installation">Installation</h2> -<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#bf616a;">npm</span><span> install</span><span style="color:#bf616a;"> -S</span><span> vue-ui-toggle -</span></code></pre> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">const </span><span style="color:#bf616a;">Toggle </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">vue-ui-toggle</span><span>&#39;); </span><span style="color:#65737e;">// es5/node -</span><span style="color:#65737e;">// import Toggle from &#39;vue-ui-toggle&#39;; // es6 -</span><span> -</span><span style="color:#bf616a;">Vue</span><span>.</span><span style="color:#8fa1b3;">component</span><span>(&#39;</span><span style="color:#a3be8c;">toggle</span><span>&#39;, </span><span style="color:#bf616a;">Toggle</span><span>); -</span></code></pre> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">import </span><span style="color:#bf616a;">Toggle </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">vue-ui-toggle</span><span>&#39;; -</span><span> -</span><span style="color:#b48ead;">export default </span><span>{ -</span><span> </span><span style="color:#65737e;">// ... -</span><span> components: { -</span><span> </span><span style="color:#bf616a;">Toggle</span><span>, -</span><span> }, -</span><span> </span><span style="color:#65737e;">// ... -</span><span>}; -</span></code></pre> -<p>To learn more, check out the <a href="https://james2doyle.github.io/vue-toggle">website for the component</a>.</p> - - - - - Tailwind Screens In JS - 2020-09-05T00:00:00+00:00 - 2020-09-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/tailwind-screens-in-js/ - - <p>If you haven't heard of <a href="https://tailwindcss.com/">Tailwindcss</a> before, what is going on? It is the hot new CSS framework for building custom designs.</p> -<p>From the site:</p> -<blockquote> -<p>Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.</p> -</blockquote> -<p>One of the things Tailwindcss gives you is a configuration file (named <code>tailwind.config.js</code>) for setting the configuration of the output CSS. This may be a little strange to people who have never used this before but there are some advantages. The output CSS can be configured based on the build environment or your own specifications.</p> -<p>One of the configuration settings is for what screen sizes and breakpoints are supported. One of the benefits of these values being defined in JS is that you can then use them in JS as well. You can <a href="https://tailwindcss.com/docs/breakpoints">read more about them in the docs</a>.</p> -<p>So why might you want to use these values in JS? Well, if you are building components that have conditional content based on the screen size, having access to these values can be super handy. You can check the screen size and show/hide different content, conditional load different sizes elements, or even disable an entire swath of features/functionality. All while keep your JS code in sync with your CSS code. You can reuse the variable names.</p> -<p>Here is the code I used to accomplish this:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/c1306ace82cfe9e22a4ecfff13c6595b.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;c1306ace82cfe9e22a4ecfff13c6595b.js'></script>`"></iframe> -<p>You can use the function like so:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// es6 -</span><span style="color:#65737e;">// import screenIs from &#39;screenIs&#39;; -</span><span style="color:#65737e;">// commonjs -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">screenIs </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">screenIs</span><span>&#39;); -</span><span> -</span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#8fa1b3;">screenIs</span><span>(&#39;</span><span style="color:#a3be8c;">md</span><span>&#39;)) { -</span><span> </span><span style="color:#65737e;">// we are on an &quot;md&quot; screen... -</span><span>} -</span><span> -</span><span style="color:#b48ead;">const </span><span>{ </span><span style="color:#bf616a;">md</span><span>, </span><span style="color:#bf616a;">lg </span><span>} = </span><span style="color:#8fa1b3;">screenIs</span><span>(); -</span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#bf616a;">md </span><span>|| </span><span style="color:#bf616a;">lg</span><span>) { -</span><span> </span><span style="color:#65737e;">// we are on an &quot;md&quot; or an &quot;lg&quot; screen... -</span><span>} -</span></code></pre> -<p>Basically, we load up the config, use <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia"><code>window.matchMedia</code></a> to test all the media queries in the config, then check if the key we pass in matches an existing key in our config and return the results.</p> -<p>Pretty simple but a nice way to share the config into your JS code.</p> -<p>If you are familiar with Tailwind, you might be asking, <em>why not just use CSS with md:show and md:hide?</em></p> -<p>While that does work perfectly for visual content, it does not stop the browser from creating the DOM for the hidden content or stop and event listeners from being created or removed. If you are using the detection of the screen in JS, you can make sure to not render or add events for code that is not used. The opposite is true for when the screen changes and you need to cleanup any listeners or DOM elements you don't want to be there.</p> - - - - - Vuex Crosstab Plugin - 2020-09-03T00:00:00+00:00 - 2020-09-03T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/vuex-crosstab/ - - <p>Did you know that you can use localstorage as an event bus between same-origin tabs?</p> -<p>Yep! Local storage is shared across same-origin domains. Which means you can share information between tabs on the same domain. Other cool thing is that the <code>storage</code> event that is fired on the <code>window</code> when localstorage changes, it <em>only fired on non-focused tabs</em>. This means you can write an event bus to sync the tabs you don't have focused with the one you do! Neat.</p> -<p>When browsing around GitHub, like I do, I found this project called <a href="https://github.com/storeon/storeon">storeon</a>. It is a framework agnostic state manager. It's aim is to be small and flexible and provide additional functionality through plugins. One such plugin is called <a href="https://github.com/storeon/crosstab">crosstab</a>.</p> -<p>I am a Vue.js user who likes VueX. So I wanted to recreate this plugin in VueX. VueX also provides a <a href="https://vuex.vuejs.org/guide/plugins.html">plugin API</a> so you can write plugins as well. So I wrote the plugin for VueX, published it to <a href="https://www.npmjs.com/package/vuex-crosstab">npm</a>, and posted the source on <a href="https://github.com/james2doyle/vuex-crosstab">GitHub</a>.</p> -<p>Here is the demo of the plugin working:</p> -<div class="center"> - <a href="/images/vuex-crosstab.gif" target="_blank" title="vuex crosstab demo"><img alt="laravel multi-lingual site demo" src="/images/vuex-crosstab.gif"></a> -</div> -<p>You can use the plugin like so:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// es6 -</span><span style="color:#65737e;">// import CrossTab from &#39;vuex-crosstab&#39;; -</span><span style="color:#65737e;">// commonjs -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">CrossTab </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">vuex-crosstab</span><span>&#39;); -</span><span> -</span><span style="color:#b48ead;">export default </span><span>new Vuex.Store({ -</span><span> </span><span style="color:#65737e;">// ... -</span><span> plugins: [ -</span><span> </span><span style="color:#65737e;">// your other plugins... -</span><span> </span><span style="color:#8fa1b3;">CrossTab</span><span>({ recover: </span><span style="color:#d08770;">true </span><span>}) -</span><span> ], -</span><span> </span><span style="color:#65737e;">// ... -</span><span>}); -</span></code></pre> -<p>There is a simple config object you can pass to control some things like the &quot;recovery&quot; which will check the localstorage to see if there was state already in there. You can <a href="https://github.com/james2doyle/vuex-crosstab#options">read more on the Github page</a>.</p> - - - - - Vuex Stateful URL Plugin - 2020-09-03T00:00:00+00:00 - 2020-09-03T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/vuex-stateful-url/ - - <p>Have you ever used permalinks on a site? These links store the state of the page in the URL. They can be super useful for simple single-page-apps (SPAs) for bringing users back to where they were when they left the page or just adding the ability to share that page state with someone else without requiring logins or anything like that.</p> -<p>I'm a Vue.js user who likes VueX. So I wanted to create a plugin in VueX that would send the state of the store into the URL.</p> -<p>VueX provides a <a href="https://vuex.vuejs.org/guide/plugins.html">plugin API</a> so you can write plugins that can extend the basic functionality. So I wrote the plugin for VueX that would sync the store to the URL.</p> -<p>I used a great package called <a href="https://github.com/masotime/json-url">json-url</a> which allows you to encode/compress JSON objects in a URL-safe way.</p> -<p>It is now published to <a href="https://www.npmjs.com/package/vuex-stateful-url">npm</a> and the source is on <a href="https://github.com/james2doyle/vuex-stateful-url">GitHub</a>.</p> -<p>Here is the demo of the plugin working:</p> -<div class="center"> - <a href="/images/vuex-stateful-url.gif" target="_blank" title="vuex stateful url demo"><img alt="laravel multi-lingual site demo" src="/images/vuex-stateful-url.gif"></a> -</div> -<p>You can use the plugin like so:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// es6 -</span><span style="color:#65737e;">// import StatefulURL from &#39;vuex-stateful-url&#39;; -</span><span style="color:#65737e;">// commonjs -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">StatefulURL </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">vuex-stateful-url</span><span>&#39;); -</span><span> -</span><span style="color:#b48ead;">export default </span><span>new Vuex.Store({ -</span><span> </span><span style="color:#65737e;">// ... -</span><span> plugins: [ -</span><span> </span><span style="color:#65737e;">// your other plugins... -</span><span> </span><span style="color:#8fa1b3;">StatefulURL</span><span>() -</span><span> ], -</span><span> </span><span style="color:#65737e;">// ... -</span><span>}); -</span></code></pre> -<p>You can <a href="https://github.com/james2doyle/vuex-crosstab">read more on the Github page</a>.</p> - - - - - Using slots in Vue js - 2020-05-18T00:00:00+00:00 - 2020-05-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/using-slots-in-vue-js/ - - <p>If you are working with server-rendered apps (your view is compiled on the server down to HTML), and you are a <a href="https://vuejs.org/">Vue.js</a> user, then you should definitely learn to use a <a href="https://vuejs.org/v2/guide/components-slots.html">Vue feature called slots</a>! Not only will it allow you to make more reusable and flexible components, but you will also improve the rendering performance of your apps as well.</p> -<h2 id="basic-example">Basic Example</h2> -<p>Here is the example we will be refactoring later. It is a &quot;profile list&quot; which takes in an array of users from a prop, and then creates &quot;profile cards&quot; for each one. Finally, it adds a button in each card that emits an event with the user data when someone clicks on it.</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#65737e;">&lt;!-- -</span><span style="color:#65737e;"> This is the way you might typically build a component like this -</span><span style="color:#65737e;"> --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-list</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- loop over all the users and create cards for them --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">li </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-card</span><span>&quot; </span><span style="color:#d08770;">v-for</span><span>=&quot;</span><span style="color:#a3be8c;">user in users</span><span>&quot; </span><span style="color:#d08770;">:key</span><span>=&quot;</span><span style="color:#a3be8c;">user.id</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-details</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">img </span><span style="color:#d08770;">:src</span><span>=&quot;</span><span style="color:#a3be8c;">user.profile_image</span><span>&quot; </span><span style="color:#d08770;">:alt</span><span>=&quot;</span><span style="color:#a3be8c;">`${user.username}` profile image</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">h4 </span><span style="color:#d08770;">v-text</span><span>=&quot;</span><span style="color:#a3be8c;">user.username</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">h4</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- we have a button event here --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">button </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">button</span><span>&quot; </span><span style="color:#d08770;">@click.prevent</span><span>=&quot;</span><span style="color:#a3be8c;">buttonHandler(user)</span><span>&quot;&gt;View&lt;/</span><span style="color:#bf616a;">button</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">li</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">script</span><span>&gt; -</span><span style="color:#b48ead;">export default </span><span>{ -</span><span> name: &#39;</span><span style="color:#a3be8c;">ProfileList</span><span>&#39;, -</span><span> props: { -</span><span> </span><span style="color:#65737e;">// we take in our list of users -</span><span> users: { -</span><span> type: </span><span style="color:#ebcb8b;">Array</span><span>, -</span><span> required: </span><span style="color:#d08770;">true</span><span>, -</span><span> } -</span><span> }, -</span><span> methods: { -</span><span> </span><span style="color:#8fa1b3;">buttonHandler</span><span>(user) { -</span><span> </span><span style="color:#bf616a;">this</span><span>.</span><span style="color:#bf616a;">$emit</span><span>(&#39;</span><span style="color:#a3be8c;">profile-change</span><span>&#39;, </span><span style="color:#bf616a;">user</span><span>); -</span><span> } -</span><span> } -</span><span>}; -</span><span>&lt;/</span><span style="color:#bf616a;">script</span><span>&gt; -</span></code></pre> -<p>This is how you might use this component in your server-rendered templates:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#65737e;">&lt;!-- liquid or twig example --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">profile-list </span><span style="color:#d08770;">v-bind:users</span><span>=&quot;</span><span style="color:#a3be8c;">{{ users | json | escape }}</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">profile-list</span><span>&gt; -</span><span style="color:#65737e;">&lt;!-- plain php, short echo --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">profile-list </span><span style="color:#d08770;">v-bind:users</span><span>=&quot;</span><span style="color:#a3be8c;">&lt;?= htmlspecialchars(json_encode($users), ENT_QUOTES, &#39;UTF-8&#39;) ?&gt;</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">profile-list</span><span>&gt; -</span><span style="color:#65737e;">&lt;!-- laravel blade example, note single quotes --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">profile-list </span><span style="color:#d08770;">v-bind:users</span><span>=&#39;</span><span style="color:#a3be8c;">@json($users)</span><span>&#39;&gt;&lt;/</span><span style="color:#bf616a;">profile-list</span><span>&gt; -</span></code></pre> -<p>As you can see the main approach here is to encode the users as JSON and pass them to the components <code>users</code> prop. This can create quite a mess of JSON if you look at the source of your pages if you have lots of components that do this.</p> -<h2 id="why-use-slots">Why Use Slots?</h2> -<p>As I mentioned, with slots you can create more reusable and flexible components and improve the rendering performance of your apps. I'll outline some of the details below.</p> -<h4 id="on-reusability-and-flexibility">On Reusability and Flexibility</h4> -<p>Normally, you would send data to your Vue component using props, events, or a store. But using slots, you can essentially skip a rendering step and send your pre-compiled Vue into your Vue components template. This allows you to create components with <em>flexible templates</em> instead of adding tons of props and lots of switches in the templates.</p> -<p>What I've typically seen in the past with larger apps, developers end up creating super-components with tons and tons of props. The reason this happens is usually because the components require flexibility. If you have lots of props, that is <a href="https://en.wikipedia.org/wiki/Code_smell">probably a code smell</a>. A way to reduce the number of props is to use events, new components, and of course, slots, to split things up!</p> -<h4 id="on-passing-data">On Passing Data</h4> -<p>Since a slot is rendered on the server, you can also do some handy things with the template. Say you want to show or hide different content based on whether the user is authenticated. Well, this is super easy when working in server-rendered templates since the session is so easy to access. You might even have entire blocks of the UI that are not shown if the user is not logged in or is logged out.</p> -<p>This can help to dramatically reduce the work you need to do on your frontend to access data or building token-based APIs.</p> -<h4 id="on-rendering">On Rendering</h4> -<p>Since a slot is prerended HTML, your component ends up changing to really only encapsulating <em>the logic</em> of your component and less &quot;template&quot; of your component. You'll notice that when using slots instead of props, your page with show the content inside the slot even before Vue has finished loading. This is great because it can help reduce <a href="https://en.wikipedia.org/wiki/Flash_of_unstyled_content">flashes of unstyled content</a> that make your app look a little janky.</p> -<p>Although <a href="https://developers.google.com/search/docs/guides/javascript-seo-basics">browers will run your javascript</a>, a by-product of using slots is that you could improve the SEO of your site, as you are trying to fill the page with the most HTML possible.</p> -<p>Mostly slots are good for making more flexible components. The performance benefit is super helpful though.</p> -<h2 id="refactored-to-slots">Refactored to Slots</h2> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#65737e;">&lt;!-- -</span><span style="color:#65737e;"> All we need to do is cut that inside layout and replace it with a &quot;default&quot; slot -</span><span style="color:#65737e;"> --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-list</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- this is a default slot with a binding to the `buttonHandler` function --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">slot </span><span style="color:#d08770;">:buttonHandler</span><span>=&quot;</span><span style="color:#a3be8c;">buttonHandler</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">slot</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">script</span><span>&gt; -</span><span style="color:#b48ead;">export default </span><span>{ -</span><span> name: &#39;</span><span style="color:#a3be8c;">ProfileList</span><span>&#39;, -</span><span> methods: { -</span><span> </span><span style="color:#8fa1b3;">buttonHandler</span><span>(user) { -</span><span> </span><span style="color:#bf616a;">this</span><span>.</span><span style="color:#bf616a;">$emit</span><span>(&#39;</span><span style="color:#a3be8c;">profile-change</span><span>&#39;, </span><span style="color:#bf616a;">user</span><span>); -</span><span> } -</span><span> } -</span><span>}; -</span><span>&lt;/</span><span style="color:#bf616a;">script</span><span>&gt; -</span></code></pre> -<p>You can now see how we completely remove our inner template and props and replace it with the <code>slot</code> tag. We don't need the props now since we won't be passing in any data. We are going to be passing in the entire block of rendered HTML on our server, and when Vue loads on the client side, the functionality will be hooked up!</p> -<p>This is how you might use this component in your server-rendered templates:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">profile-list </span><span style="color:#d08770;">v-slot</span><span>=&quot;</span><span style="color:#a3be8c;">slotProps</span><span>&quot;&gt; -</span><span> @foreach ($users as $user) -</span><span> &lt;</span><span style="color:#bf616a;">li </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-card</span><span>&quot; </span><span style="color:#d08770;">key</span><span>=&quot;</span><span style="color:#a3be8c;">{{ $user.id }}</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">profile-details</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">img </span><span style="color:#d08770;">src</span><span>=&quot;</span><span style="color:#a3be8c;">{{ $user.profile_image }}</span><span>&quot; </span><span style="color:#d08770;">alt</span><span>=&quot;</span><span style="color:#a3be8c;">{{ $user.username }} profile image</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">h4</span><span>&gt;{{ $user.username }}&lt;/</span><span style="color:#bf616a;">h4</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- we have a button event here --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">button </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">button</span><span>&quot; </span><span style="color:#d08770;">@click.prevent</span><span>=&#39;</span><span style="color:#a3be8c;">slotProps.buttonHandler(@json($user))</span><span>&#39;&gt;View&lt;/</span><span style="color:#bf616a;">button</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">li</span><span>&gt; -</span><span> @endforeach -</span><span>&lt;/</span><span style="color:#bf616a;">profile-list</span><span>&gt; -</span></code></pre> -<p>Now if you view the source of your page in your browser, you will see that the HTML looks more like Vue is <em>decorating</em> your HTML instead of generating it.</p> -<h4 id="another-example">Another Example</h4> -<p>Let's create a modal with slots!</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">modal-wrapper</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">button </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">button</span><span>&quot; </span><span style="color:#d08770;">@click.prevent</span><span>=&quot;</span><span style="color:#a3be8c;">toggle</span><span>&quot; </span><span style="color:#d08770;">v-text</span><span>=&quot;</span><span style="color:#a3be8c;">openLabel</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">button</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">modal-overlay</span><span>&quot; </span><span style="color:#d08770;">v-if</span><span>=&quot;</span><span style="color:#a3be8c;">open</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">modal-header</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">h3</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- here is a named slot, we can force content right into this spot as long as we use named slots --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">slot </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">header</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">slot</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">h3</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">modal-body</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">slot </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">body</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">slot</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">div </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">modal-footer</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">button </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">button</span><span>&quot; </span><span style="color:#d08770;">@click.prevent</span><span>=&quot;</span><span style="color:#a3be8c;">toggle</span><span>&quot; </span><span style="color:#d08770;">v-text</span><span>=&quot;</span><span style="color:#a3be8c;">closeLabel</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">button</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">script</span><span>&gt; -</span><span style="color:#b48ead;">export default </span><span>{ -</span><span> name: &quot;</span><span style="color:#a3be8c;">Modal</span><span>&quot;, -</span><span> props: { -</span><span> openLabel: { -</span><span> type: </span><span style="color:#ebcb8b;">String</span><span>, -</span><span> required: </span><span style="color:#d08770;">false</span><span>, -</span><span> default: &quot;</span><span style="color:#a3be8c;">Open</span><span>&quot; -</span><span> }, -</span><span> closeLabel: { -</span><span> type: </span><span style="color:#ebcb8b;">String</span><span>, -</span><span> required: </span><span style="color:#d08770;">false</span><span>, -</span><span> default: &quot;</span><span style="color:#a3be8c;">Close</span><span>&quot; -</span><span> } -</span><span> }, -</span><span> </span><span style="color:#8fa1b3;">data</span><span>() { -</span><span> </span><span style="color:#b48ead;">return </span><span>{ -</span><span> open: </span><span style="color:#d08770;">false -</span><span> }; -</span><span> }, -</span><span> methods: { -</span><span> </span><span style="color:#8fa1b3;">toggle</span><span>() { -</span><span> </span><span style="color:#bf616a;">this</span><span>.open = !</span><span style="color:#bf616a;">this</span><span>.open; -</span><span> } -</span><span> } -</span><span>}; -</span><span>&lt;/</span><span style="color:#bf616a;">script</span><span>&gt; -</span></code></pre> -<p>Here we have a component that has 2 slots! One for the header of the modal and another for the body. We are still using props though. We don't want to overwrite the main button that opens the modal, or the <code>modal-footer</code> content that includes our button and the handler.</p> -<p>Now we can use this component as follows:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span style="color:#65737e;">&lt;!-- set our custom labels --&gt; -</span><span>&lt;</span><span style="color:#bf616a;">modal </span><span style="color:#d08770;">openLabel</span><span>=&quot;</span><span style="color:#a3be8c;">View Details</span><span>&quot; </span><span style="color:#d08770;">closeLabel</span><span>=&quot;</span><span style="color:#a3be8c;">Dismiss</span><span>&quot;&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- pass in our title --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">template </span><span style="color:#d08770;">v-slot:header</span><span>&gt;Details&lt;/</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- pass in our body content that will be displayed --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">template </span><span style="color:#d08770;">v-slot:body</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">h3</span><span>&gt;This is my title&lt;/</span><span style="color:#bf616a;">h3</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">p</span><span>&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ornare ipsum ligula, id pharetra neque lobortis nec. Mauris varius, felis eget interdum ultricies, libero nulla varius tortor, nec semper tortor leo id nisl. Fusce a est augue. In hac habitasse platea dictumst. Sed pretium egestas vestibulum. Nunc pellentesque aliquam justo, eu rutrum nunc vehicula ac. Nunc feugiat sed ipsum in dapibus. Quisque finibus, dolor at consequat.&lt;/</span><span style="color:#bf616a;">p</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">template</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">modal</span><span>&gt; -</span></code></pre> -<p>You can actually conditionally show slots as well. Let's add that:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>@@ -2,7 +2,7 @@ -</span><span> &lt;div class=&quot;modal-wrapper&quot;&gt; -</span><span> &lt;button type=&quot;button&quot; @click.prevent=&quot;toggle&quot; v-text=&quot;openLabel&quot;&gt;&lt;/button&gt; -</span><span> &lt;div class=&quot;modal-overlay&quot; v-if=&quot;open&quot;&gt; -</span><span style="color:#bf616a;">- &lt;div class=&quot;modal-header&quot;&gt; -</span><span style="color:#a3be8c;">+ &lt;div class=&quot;modal-header&quot; v-if=&quot;hasHeaderSlot&quot;&gt; -</span><span> &lt;h3&gt; -</span><span> &lt;!-- here is a named slot, we can force content right into this spot as long as we use named slots --&gt; -</span><span> &lt;slot name=&quot;header&quot;&gt;&lt;/slot&gt; -</span><span>@@ -38,6 +38,11 @@ -</span><span> open: false -</span><span> }; -</span><span> }, -</span><span style="color:#a3be8c;">+ computed: { -</span><span style="color:#a3be8c;">+ hasHeaderSlot() { -</span><span style="color:#a3be8c;">+ return !this.$slots[&quot;header&quot;]; -</span><span style="color:#a3be8c;">+ } -</span><span style="color:#a3be8c;">+ }, -</span><span> methods: { -</span><span> toggle() { -</span><span> this.open = !this.open; -</span></code></pre> -<p>What we've done here is hide a slot (the <code>header</code> one) if the slot is not filled in. This allows us to use the <code>body</code> slot even more effectively! We can provide a full content replacement if we want. Nice!</p> -<h4 id="accordion-example">Accordion Example</h4> -<p>Here are a couple more example but done in the Codesandbox editor. They have code comments as well as some more organization:</p> -<p><strong>Accordion</strong></p> -<iframe - src="https://codesandbox.io/embed/slots-accordion-32c18?fontsize=14&hidenavigation=1&theme=dark" - style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;" - title="Slots - Accordion" - allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" - sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" - ></iframe> -<p><strong>Image Picker</strong></p> -<iframe - src="https://codesandbox.io/embed/slots-image-picker-3yvlh?fontsize=14&hidenavigation=1&initialpath=%2Fsrc%2Fcomponents%2FImagePicker.vue&theme=dark" - style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 1rem" - title="Slots - Image Picker" - allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr" - sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" - ></iframe> -<h2 id="in-conclusion">In Conclusion</h2> -<p>So next time you find yourself making tons of props or duplicating component just because they have slightly different templates, just reach for slots!</p> - - - - - Create JSON Sections In Shopify - 2020-01-05T00:00:00+00:00 - 2020-01-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/create-json-sections-in-shopify/ - - <p>If you're like me, you're probably constantly running into issues with the flexibility of Shopify themes. There are a lot of issues that come up when trying to squeeze in features and functionality that are &quot;shop-adjacent&quot;. Shopify does a good job at running a store but it can be difficult to reach beyond that and make some more interactive experiences.</p> -<p>Recently, I wanted to share the content from one section on one page to another section on another page. This isn't really possible using liquid because you can't pass variables to sections. So even including them both on the same page wouldn't work. You also can't include sections in other sections. So that also won't work.</p> -<p>So how can you pass data between sections? Well, I managed to get this done by using the <a href="https://help.shopify.com/en/themes/development/sections/section-rendering-api">Section Rendering API</a>. Now, this is not something in liquid, but it can be used as a JS API. Specifically, you can render a section in isolation and without the surrounding page content.</p> -<p>Now, since you can call this API in JS, how would you use this content? You can just go ahead and use it as HTML, but I wanted to use it as JSON. How do we do that? How can we make sure the section works normally when rendered on a page but returns &quot;JSON&quot; when rendered through the section API?</p> -<p>Well, this is possible by taking advantage of the &quot;page state&quot; when being called via the API.</p> -<p>Here is an example of the if statement required:</p> -<pre data-lang="jinja2" style="background-color:#2b303b;color:#c0c5ce;" class="language-jinja2 "><code class="language-jinja2" data-lang="jinja2"><span>{% </span><span style="color:#b48ead;">comment </span><span>%}https://help.shopify.com/en/themes/development/sections/section-rendering-api{% </span><span style="color:#b48ead;">endcomment </span><span>%} -</span><span>{% </span><span style="color:#b48ead;">comment </span><span>%}request.page_type will be &quot;index&quot; when using the section rendering API{% </span><span style="color:#b48ead;">endcomment </span><span>%} -</span><span>{% </span><span style="color:#b48ead;">comment </span><span>%}content_for_header will contain the query string in the request{% </span><span style="color:#b48ead;">endcomment </span><span>%} -</span><span>{%- </span><span style="color:#b48ead;">if </span><span style="color:#bf616a;">request</span><span>.</span><span style="color:#bf616a;">page_type </span><span>== &#39;</span><span style="color:#a3be8c;">index</span><span>&#39; </span><span style="color:#b48ead;">and </span><span style="color:#bf616a;">content_for_header contains </span><span>&#39;</span><span style="color:#a3be8c;">section_id</span><span>&#39; -%} -</span><span>{ -</span><span> {%- </span><span style="color:#b48ead;">for block </span><span style="color:#bf616a;">in section</span><span>.</span><span style="color:#bf616a;">blocks </span><span>-%} -</span><span> {%- </span><span style="color:#b48ead;">assign </span><span style="color:#bf616a;">slug </span><span>= </span><span style="color:#bf616a;">block</span><span>.</span><span style="color:#bf616a;">settings</span><span>.</span><span style="color:#bf616a;">title </span><span>| </span><span style="color:#bf616a;">downcase </span><span>-%} -</span><span> &quot;{{ </span><span style="color:#bf616a;">slug </span><span>}}&quot;: {{- </span><span style="color:#bf616a;">block</span><span>.</span><span style="color:#bf616a;">settings </span><span>| </span><span style="color:#bf616a;">json </span><span>-}}{%- </span><span style="color:#b48ead;">unless </span><span style="color:#bf616a;">forloop</span><span>.</span><span style="color:#bf616a;">last </span><span>-%},{%- </span><span style="color:#b48ead;">endunless </span><span>-%} -</span><span> {%- </span><span style="color:#b48ead;">endfor </span><span>-%} -</span><span>} -</span><span>{%- </span><span style="color:#b48ead;">else </span><span>-%} -</span><span> {%- </span><span style="color:#b48ead;">for block </span><span style="color:#bf616a;">in section</span><span>.</span><span style="color:#bf616a;">blocks </span><span>-%} -</span><span> {%- </span><span style="color:#b48ead;">assign </span><span style="color:#bf616a;">slug </span><span>= </span><span style="color:#bf616a;">block</span><span>.</span><span style="color:#bf616a;">settings</span><span>.</span><span style="color:#bf616a;">title </span><span>| </span><span style="color:#bf616a;">downcase </span><span>-%} -</span><span> &lt;div id=&quot;{{ </span><span style="color:#bf616a;">slug </span><span>}}&quot;&gt;{{ </span><span style="color:#bf616a;">block</span><span>.</span><span style="color:#bf616a;">settings</span><span>.</span><span style="color:#bf616a;">title </span><span>}}&lt;/div&gt; -</span><span> {%- </span><span style="color:#b48ead;">endfor </span><span>-%} -</span><span>{%- </span><span style="color:#b48ead;">endif </span><span>-%} -</span></code></pre> -<p>As you can see from the comment, <code>request.page_type</code> will be equal to <code>&quot;index&quot;</code> and <code>content_for_header</code> will contain <code>&quot;section_id&quot;</code> when rendered via the API. This means, we can use that state to return a string of JSON.</p> -<p>When this is returned from the section API, it will still be treated as HTML and wrapped with markup. It will look something like this:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">div </span><span style="color:#8fa1b3;">id</span><span>=&quot;</span><span style="color:#a3be8c;">my-section-id</span><span>&quot; </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">shopify-section</span><span>&quot;&gt; -</span><span>{ -</span><span> &quot;my-block-title&quot;: {&quot;title&quot;: &quot;My Block Title&quot;, &quot;subtitle&quot;: &quot;This is my subtitle&quot;}, -</span><span> &quot;second-block-title&quot;: {&quot;title&quot;: &quot;Second Block Title&quot;, &quot;subtitle&quot;: &quot;This is my second subtitle&quot;} -</span><span>} -</span><span>&lt;/</span><span style="color:#bf616a;">div</span><span>&gt; -</span></code></pre> -<p>As you can tell, you can't just <code>JSON.parse</code> this. It has text inside that will bust up our JSON parser. How can we confidently parse the content inside the <code>div</code> and get that sweet schema in the center?</p> -<p>Let me introduce the <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMParser">DOMParser API</a>. This is a <a href="https://caniuse.com/#feat=xml-serializer">well-supported</a> API that will allow you to parse different types of structured documents.</p> -<p>In this case we are going to treat this content as <code>text/html</code> (which it is) and then use other DOM APIs to extract the JSON from the wrapper <code>div</code> surrounding it.</p> -<p>Here is the little function I came up with to fetch the rendered section, parse the content as HTML, extract the JSON inside, and return the object it contains.</p> -<p>See below:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">/** -</span><span style="color:#65737e;"> * Get section from the rendering API -</span><span style="color:#65737e;"> * </span><span style="color:#b48ead;">@see </span><span style="color:#bf616a;">https://help.shopify.com/en/themes/development/sections/section-rendering-api -</span><span style="color:#65737e;"> * -</span><span style="color:#65737e;"> * </span><span style="color:#b48ead;">@param </span><span style="color:#65737e;">{string} </span><span style="color:#bf616a;">section -</span><span style="color:#65737e;"> * -</span><span style="color:#65737e;"> * </span><span style="color:#b48ead;">@returns </span><span style="color:#65737e;">{Promise&lt;Object&gt;} -</span><span style="color:#65737e;"> */ -</span><span style="color:#b48ead;">async function </span><span style="color:#8fa1b3;">getSection</span><span>(</span><span style="color:#bf616a;">section</span><span>) { -</span><span> </span><span style="color:#b48ead;">try </span><span>{ -</span><span> </span><span style="color:#65737e;">// call the rendering API -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">data </span><span>= </span><span style="color:#b48ead;">await </span><span>(</span><span style="color:#b48ead;">await </span><span style="color:#8fa1b3;">fetch</span><span>(`</span><span style="color:#a3be8c;">/?section_id=${</span><span style="color:#bf616a;">section</span><span style="color:#a3be8c;">}</span><span>`)).</span><span style="color:#8fa1b3;">text</span><span>(); -</span><span> </span><span style="color:#65737e;">// create a DOMParser and get the inside content of the section -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">parser </span><span>= new DOMParser(); -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">node </span><span>= </span><span style="color:#bf616a;">parser</span><span>.</span><span style="color:#8fa1b3;">parseFromString</span><span>(</span><span style="color:#bf616a;">data</span><span>.</span><span style="color:#96b5b4;">toString</span><span>(), &#39;</span><span style="color:#a3be8c;">text/html</span><span>&#39;).</span><span style="color:#96b5b4;">querySelector</span><span>(&#39;</span><span style="color:#a3be8c;">.shopify-section</span><span>&#39;); -</span><span> </span><span style="color:#b48ead;">if </span><span>(!</span><span style="color:#bf616a;">node</span><span>) { -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">error</span><span>(&#39;</span><span style="color:#a3be8c;">No node found in</span><span>&#39;, </span><span style="color:#bf616a;">data</span><span>); -</span><span> -</span><span> </span><span style="color:#b48ead;">return </span><span>{}; -</span><span> } -</span><span> -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">content </span><span>= (</span><span style="color:#bf616a;">node</span><span>.</span><span style="color:#bf616a;">textContent </span><span>|| &#39;&#39;).</span><span style="color:#8fa1b3;">trim</span><span>(); -</span><span> -</span><span> </span><span style="color:#65737e;">// parse that content as JSON -</span><span> </span><span style="color:#b48ead;">return </span><span>JSON.</span><span style="color:#96b5b4;">parse</span><span>(</span><span style="color:#bf616a;">content</span><span>); -</span><span> } </span><span style="color:#b48ead;">catch </span><span>(</span><span style="color:#bf616a;">e</span><span>) { -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">error</span><span>(</span><span style="color:#bf616a;">e</span><span>); -</span><span> } -</span><span> -</span><span> </span><span style="color:#b48ead;">return </span><span>{}; -</span><span>} -</span></code></pre> -<p>Nice, right? Pretty simple! We pass a string that represents the file name of the section we want to render, pass that to our DOMParser parse method, fetch the wrapper <code>div</code>, grab the text content inside, and pass that all to <code>JSON.parse</code>.</p> -<p>This seems strange <em>but it really works!</em> It works really really well!</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// usage: -</span><span style="color:#8fa1b3;">getSection</span><span>(&#39;</span><span style="color:#a3be8c;">name-of-section-template-file</span><span>&#39;) -</span><span>.</span><span style="color:#96b5b4;">then</span><span>((</span><span style="color:#bf616a;">data</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#65737e;">// do stuff with the object... -</span><span>}); -</span></code></pre> -<p>That is how you use this function. Just pass in the filename of the section template (without the <code>.liquid</code> on the end) and you will get JSON back! The best part about this is that it still works with the normal HTML. So you can use this in both cases without any issues. This whole setup allows website admins to visually design JSON output for your more complex features.</p> -<p><em>Side Note</em></p> -<p>Shopify likes to change things up every once in a while. They recently <a href="https://developers.shopify.com/changelog/deprecating-the-include-liquid-tag-and-introducing-the-render-tag">deprecated the <code>include</code> tag in favour of a new <code>render</code> tag</a>. So there is no promise this will work forever.</p> -<p><em>You have been warned!</em></p> - - - - - Detect Theme Editor In Shopify Liquid Templates - 2020-01-04T00:00:00+00:00 - 2020-01-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/detect-theme-editor-in-shopify-liquid-templates/ - - <p>So you may already know that you cannot detect if an liquid page is running in the theme editor. You can however, <a href="https://help.shopify.com/en/themes/development/theme-editor/other-theme-files#detecting-the-theme-editor-with-javascript">detect this in Javascript</a>.</p> -<p>But what if you want conditional content? What if you want to use this &quot;switch&quot; in a liquid template?</p> -<p>There are a couple good reasons to do this. The first one being that sometimes you need to show the content the website admin is editing in a different way.</p> -<p>I needed this feature when I had a repeating section of blocks that only showed when certain filters where applied to a form. This made it really hard to edit the content. You couldn't see all the blocks at once while you were editing. I wanted to just show all the blocks, with no filtering applied, but only when the theme editor was being used to create and edit blocks.</p> -<p>Shopify says you can't do this though. I'm here to tell you they are incorrect.</p> -<p>Before I continue, you will need to understand <a href="http://freakdesign.com.au/blogs/news/get-the-url-querystring-values-with-liquid-in-shopify">this hack used to detect query strings in liquid</a>. Once read up on that snippet, you'll see how we build on that to detect the theme editor.</p> -<pre data-lang="jinja2" style="background-color:#2b303b;color:#c0c5ce;" class="language-jinja2 "><code class="language-jinja2" data-lang="jinja2"><span>{%- </span><span style="color:#b48ead;">comment </span><span>-%} -</span><span> http://freakdesign.com.au/blogs/news/get-the-url-querystring-values-with-liquid-in-shopify -</span><span> Capture the content for header containing the tracking data -</span><span>{%- </span><span style="color:#b48ead;">endcomment </span><span>-%} -</span><span>{%- </span><span style="color:#b48ead;">capture </span><span style="color:#bf616a;">contentForQuerystring </span><span>-%}{{- </span><span style="color:#bf616a;">content_for_header </span><span>-}}{%- </span><span style="color:#b48ead;">endcapture </span><span>-%} -</span><span> -</span><span>{% </span><span style="color:#b48ead;">comment </span><span>%}Use string splitting to pull the value from content_for_header and apply some string clean up{% </span><span style="color:#b48ead;">endcomment </span><span>%} -</span><span>{%- </span><span style="color:#b48ead;">assign </span><span style="color:#bf616a;">pageUrl </span><span>= </span><span style="color:#bf616a;">contentForQuerystring </span><span>| </span><span style="color:#bf616a;">split</span><span>:&#39;</span><span style="color:#a3be8c;">&quot;pageurl&quot;:&quot;</span><span>&#39; | </span><span style="color:#bf616a;">last -</span><span> | </span><span style="color:#bf616a;">split</span><span>:&#39;</span><span style="color:#a3be8c;">&quot;</span><span>&#39; -</span><span> | </span><span style="color:#bf616a;">first -</span><span> | </span><span style="color:#bf616a;">split</span><span>:&#39;</span><span style="color:#a3be8c;">.myshopify.com</span><span>&#39; -</span><span> | </span><span style="color:#bf616a;">last -</span><span> | </span><span style="color:#bf616a;">replace</span><span>:&#39;</span><span style="color:#a3be8c;">\/</span><span>&#39;,&#39;</span><span style="color:#a3be8c;">/</span><span>&#39; -</span><span> | </span><span style="color:#bf616a;">replace</span><span>:&#39;</span><span style="color:#a3be8c;">%20</span><span>&#39;,&#39; &#39; -</span><span> | </span><span style="color:#bf616a;">replace</span><span>:&#39;</span><span style="color:#96b5b4;">\u0026</span><span>&#39;,&#39;</span><span style="color:#a3be8c;">&amp;</span><span>&#39; -</span><span> | </span><span style="color:#bf616a;">strip -</span><span>-%} -</span></code></pre> -<p>The code above is a snippet of the <a href="http://freakdesign.com.au/blogs/news/get-the-url-querystring-values-with-liquid-in-shopify">trick used to detect query strings in liquid</a>. We only need this piece. So let's continue.</p> -<pre data-lang="jinja2" style="background-color:#2b303b;color:#c0c5ce;" class="language-jinja2 "><code class="language-jinja2" data-lang="jinja2"><span>{% </span><span style="color:#b48ead;">comment </span><span>%}When in the theme editor, the pageUrl variable is malformed/empty{% </span><span style="color:#b48ead;">endcomment </span><span>%} -</span><span>{% </span><span style="color:#b48ead;">if </span><span style="color:#bf616a;">pageUrl contains page</span><span>.</span><span style="color:#bf616a;">handle </span><span>%} -</span><span> We are in the frontend of the website -</span><span>{% </span><span style="color:#b48ead;">else </span><span>%} -</span><span> We are in the theme editor of the website -</span><span>{% </span><span style="color:#b48ead;">endif </span><span>%} -</span></code></pre> -<p>That's all we need now. I'll explain what is going on.</p> -<p>Basically, when the liquid template is being accessed in the theme editor, the <code>pageUrl</code> variable will not properly formed. Since that is the case, we just compare that value with the value that is in <code>page.handle</code>.</p> -<p>When the template is being loaded properly on the frontend of the site, the <code>pageUrl</code> will be equal to the <code>page.handle</code> value.</p> -<p>In summation, we can now detect if the theme is running in the section editor! Yay!</p> -<p><em>Side Note</em></p> -<p>Shopify likes to change things up every once in a while. They recently <a href="https://developers.shopify.com/changelog/deprecating-the-include-liquid-tag-and-introducing-the-render-tag">deprecated the <code>include</code> tag in favour of a new <code>render</code> tag</a>. So there is no promise this will work forever.</p> -<p><em>You have been warned!</em></p> - - - - - Using Zapier Webhooks for HTML forms - 2019-09-28T00:00:00+00:00 - 2019-09-28T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/zapier-webhooks-for-html-forms/ - - <p>If you don't know about <a href="https://zapier.com/">Zapier</a>, it's a service that allows you to &quot;automate everything&quot;. I've been using it for over 4 years to automate tons of different things like posting daily Trello tasks to Slack, activity in Intercom to Emails, and even a custom scheduled job that gives me task information for the current day.</p> -<p>One of the best built-in zaps (that's what they call the tasks) is the <a href="https://zapier.com/app-directory/webhook/integrations">webhook zap</a> which let's you send a <code>POST</code> request to Zapier and then connect that with the 100s of other integrations. You can use this for all types of things. One thing I have found it to be good for is handling form submissions on static sites.</p> -<p>As the concept of static sites and &quot;serverless&quot; keeps getting more and more popular, so do the services surrounding those concepts. But if you know where to look, and how to set things up, most of those &quot;services&quot; that supplement static sites can be replace with some savvy Zapier setups.</p> -<p>There are tons of &quot;form&quot; apps out there to allow you to create endpoints for your site to submit to. A lot of them even suggest using Zapier to handle the post-submission step. It's funny, because you can get a pretty robust setup just using Zapier on it's own and not having to use these services. Also, Zapier supports file uploads! So you can even handle attachments in your forms or <code>POST</code> request. Handy!</p> -<p>Anyway, on to the form. Here is an example of an HTML form that will successfully:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">form </span><span style="color:#d08770;">action</span><span>=&quot;</span><span style="color:#a3be8c;">https://hooks.zapier.com/hooks/catch/1234567/abcd123/</span><span>&quot; -</span><span> </span><span style="color:#d08770;">method</span><span>=&quot;</span><span style="color:#a3be8c;">post</span><span>&quot; -</span><span> </span><span style="color:#d08770;">enctype</span><span>=&quot;</span><span style="color:#a3be8c;">multipart/form-data</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">label </span><span style="color:#d08770;">for</span><span>=&quot;</span><span style="color:#a3be8c;">name</span><span>&quot;&gt;Name:&lt;/</span><span style="color:#bf616a;">label</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">text</span><span>&quot; </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">name</span><span>&quot; </span><span style="color:#8fa1b3;">id</span><span>=&quot;</span><span style="color:#a3be8c;">name</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">label </span><span style="color:#d08770;">for</span><span>=&quot;</span><span style="color:#a3be8c;">email</span><span>&quot;&gt;Email:&lt;/</span><span style="color:#bf616a;">label</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">email</span><span>&quot; </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">email</span><span>&quot; </span><span style="color:#8fa1b3;">id</span><span>=&quot;</span><span style="color:#a3be8c;">email</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">label </span><span style="color:#d08770;">for</span><span>=&quot;</span><span style="color:#a3be8c;">attachment</span><span>&quot;&gt;Attachment:&lt;/</span><span style="color:#bf616a;">label</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">file</span><span>&quot; </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">attachment</span><span>&quot; </span><span style="color:#8fa1b3;">id</span><span>=&quot;</span><span style="color:#a3be8c;">attachment</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">input </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">submit</span><span>&quot; </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">submit</span><span>&quot; </span><span style="color:#d08770;">value</span><span>=&quot;</span><span style="color:#a3be8c;">Submit</span><span>&quot;&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">form</span><span>&gt; -</span></code></pre> -<p>Let's break down the content above and talk about the details.</p> -<h5 id="action">Action</h5> -<p>Pretty straightforward. This is the URL of your webhook. You get this after you create the zap for the first time.</p> -<h5 id="method">Method</h5> -<p>Obviously, we need this to be a <code>POST</code> since that is what the webhook is expecting. Nothing special here.</p> -<h5 id="enctype">Enctype</h5> -<p>Here is the weird part. If you are familiar with file uploads, you already know that in order to properly handling files, you need to make sure your request is using the correct encoding. Here is a description from the <a href="https://www.w3.org/TR/1999/REC-html401-19991224/interact/forms.html#adef-enctype">HTML Spec</a>:</p> -<blockquote> -<p>This attribute specifies the content type used to submit the form to the server (when the value of method is &quot;post&quot;). The default value for this attribute is &quot;application/x-www-form-urlencoded&quot;. The value &quot;multipart/form-data&quot; should be used in combination with the INPUT element, type=&quot;file&quot;.</p> -</blockquote> -<p>So there you go. Use this for files.</p> -<h5 id="caveats">Caveats</h5> -<p>One thing to keep in mind is, like all forms, the <code>name</code> of each field will be assign that value that was used on submission. So you can't really handle dynamic fields with Zapier. You also don't have any validation other than the HTML validation (or javascript validation, if applied) to rely on. People could submit values you don't expect or mess with the rules and submit other things you aren't expecting.</p> -<p>Something to keep in mind. I wouldn't use this method for anything other than simple contact forms that send emails or maybe forms that fill rows in a spreadsheet or something. Basic stuff.</p> - - - - - Use Laravel Valet for WordPress Multisites - 2019-09-01T00:00:00+00:00 - 2019-09-01T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/use-laravel-valet-for-wordpress-multisites/ - - <p>Working with WordPress multisites can sometimes be a pain. Especially if you are not using the amazing <a href="https://laravel.com/docs/6.0/valet">Laravel Valet</a> tool to manage your local sites.</p> -<p>From the site:</p> -<blockquote> -<p>Valet is a Laravel (or any site really) development environment for Mac minimalists. No Vagrant, no /etc/hosts file.</p> -</blockquote> -<p>I would add that everything can be managed from the command line as well. Very handy.</p> -<p>I'm going to make some assumptions for the rest of the post. Firstly, you already have a WordPress multisite setup at <code>~/Sites/my-blog</code> (or wherever your root folder is) that is already resolving with <code>my-blog.localhost</code>. The TLD (in my case <code>.localhost</code>) is not important either. Just imagine yours there instead.</p> -<p>We are going to want to add a new domain name that resolves to the exact same folder as our current blog setup. We can do that with <code>valet link</code>.</p> -<p>So let's run some commands:</p> -<ul> -<li><code>cd ~/Sites/my-blog</code></li> -<li><code>valet link my-other-blog.localhost</code></li> -</ul> -<p>Now we have 2 domains pointing to the same folder:</p> -<ul> -<li><code>my-blog.localhost</code></li> -<li><code>my-other-blog.localhost</code></li> -</ul> -<p>They are both going to resolve to the same site right now. That's OK. We need to tweak our <code>wp-config.php</code> to work properly with the requests.</p> -<p>If we have multisite already setup, we should see some sites in the <code>wp_blogs</code> table of our database. We want each of these &quot;sites&quot; to reflect the domains on our system. You can update those now and make sure that the domains match the ones we are setting up.</p> -<p>Then we need to update our <code>wp-config.php</code> with some changes:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">// wp-config.php -</span><span> -</span><span style="color:#65737e;">// ... there is going to be other stuff in here at the top... -</span><span> -</span><span style="color:#65737e;">// check for HTTPS since valet makes it easy to add HTTPS locally... -</span><span>$</span><span style="color:#bf616a;">protocol </span><span>= </span><span style="color:#96b5b4;">isset</span><span>($</span><span style="color:#bf616a;">_SERVER</span><span>[&#39;</span><span style="color:#a3be8c;">HTTPS</span><span>&#39;]) &amp;&amp; $</span><span style="color:#bf616a;">_SERVER</span><span>[&#39;</span><span style="color:#a3be8c;">HTTPS</span><span>&#39;] === &#39;</span><span style="color:#a3be8c;">on</span><span>&#39; ? &#39;</span><span style="color:#a3be8c;">https://</span><span>&#39; : &#39;</span><span style="color:#a3be8c;">http://</span><span>&#39;; -</span><span> -</span><span style="color:#65737e;">// set the home vars based on the domain that is being used for this request -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">WP_HOME</span><span>&#39;, $</span><span style="color:#bf616a;">protocol </span><span>. $</span><span style="color:#bf616a;">_SERVER</span><span>[&#39;</span><span style="color:#a3be8c;">HTTP_HOST</span><span>&#39;]); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">WP_SITEURL</span><span>&#39;, </span><span style="color:#d08770;">WP_HOME</span><span>); -</span><span> -</span><span style="color:#65737e;">// multisite settings, should be the same -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">WP_ALLOW_MULTISITE</span><span>&#39;, </span><span style="color:#d08770;">true</span><span>); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">MULTISITE</span><span>&#39;, </span><span style="color:#d08770;">true</span><span>); -</span><span> -</span><span style="color:#65737e;">// grab the domain from the request and just set it as the current site -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">DOMAIN_CURRENT_SITE</span><span>&#39;, $</span><span style="color:#bf616a;">_SERVER</span><span>[&#39;</span><span style="color:#a3be8c;">HTTP_HOST</span><span>&#39;]); -</span><span> -</span><span style="color:#65737e;">// we get the IDs from `wp_blogs` table -</span><span style="color:#65737e;">// and use them here as our domain =&gt; wp_blog.id mappings -</span><span>$</span><span style="color:#bf616a;">site </span><span>= [ -</span><span> &#39;</span><span style="color:#a3be8c;">my-blog.localhost</span><span>&#39; =&gt; </span><span style="color:#d08770;">1</span><span>, -</span><span> &#39;</span><span style="color:#a3be8c;">my-other-blog.localhost</span><span>&#39; =&gt; </span><span style="color:#d08770;">2</span><span>, -</span><span>][$</span><span style="color:#bf616a;">_SERVER</span><span>[&#39;</span><span style="color:#a3be8c;">HTTP_HOST</span><span>&#39;]]; -</span><span> -</span><span style="color:#65737e;">// we set the active ID here now that we have the right one -</span><span style="color:#65737e;">// based on the domain we are on in this request -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">SITE_ID_CURRENT_SITE</span><span>&#39;, $</span><span style="color:#bf616a;">site</span><span>); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">BLOG_ID_CURRENT_SITE</span><span>&#39;, $</span><span style="color:#bf616a;">site</span><span>); -</span><span> -</span><span style="color:#65737e;">// the rest of the regular stuff -</span><span style="color:#65737e;">// up to you what you want here -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">WP_DEBUG</span><span>&#39;, </span><span style="color:#d08770;">false</span><span>); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">WP_CACHE</span><span>&#39;, </span><span style="color:#d08770;">false</span><span>); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">PATH_CURRENT_SITE</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">/</span><span>&#39;); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">COOKIE_DOMAIN</span><span>&#39;, &#39;&#39;); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">ADMIN_COOKIE_PATH</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">/</span><span>&#39;); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">COOKIEPATH</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">/</span><span>&#39;); -</span><span style="color:#96b5b4;">define</span><span>(&#39;</span><span style="color:#a3be8c;">SITECOOKIEPATH</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">/</span><span>&#39;); -</span></code></pre> -<p>So what we are doing here is taking advantage of the way PHP handles requests. Each request runs the entire application. So we can use the request itself as the state of our application. We use the domain to figure out which site we should be loading. Pretty simple.</p> -<p>Now we don't have to make multiple copies of the WordPress setup just to test the basic multisite features.</p> -<p>I understand that you probably don't want to do this as multisite is usually because you want to administrate multiple instances of WordPress at the same time. But when you are just using multisite for managing content. In my case, it was just that the site was multilingual and so we wanted the sites to be identical but the content is swapping in and out. So this is a setup for that instead of having two completely separate folders and copies.</p> -<p>The nice thing about this is that it also scales nicely. If you have new sites, just <code>valet link</code> the domain, and add to the array of <code>$sites</code>. Done!</p> - - - - - Build A Multi-lingual Laravel Site With Subdomains - 2019-08-25T00:00:00+00:00 - 2019-08-25T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/build-a-multi-lingual-laravel-site-with-subdomains/ - - <p>OK, so you want to make Laravel a multi-lingual (or just multi-functional) site based off the subdomain? Cool. Must be an interesting project.</p> -<p>In order to test this on local development, we are going to use <a href="https://laravel.com/docs/5.8/valet">Valet</a>. Let's say our Laravel project folder is in <code>~/Sites/multisite</code>. We can use that as our base path moving forward. With Valet, we can set up subdomains with <code>valet link</code>.</p> -<p>I'm going to pretend that we want to add support for a new subdomain for the French version of our site. I'm also going to assume that your valet TLD is <code>.localhost</code> and your project is called <code>multisite.localhost</code>. The domain we are adding in this case, it will be <code>fr.multisite.localhost</code>.</p> -<p>So in order to get that subdomain setup locally, we just go to our site folder and run <code>valet link fr.multisite</code>. This will allow <code>fr.multisite.localhost</code> to route to the same Laravel setup as <code>multisite.localhost</code>. Beauty!</p> -<p>Well, first things first. Let's make a helper to grab the subdomain from the request. This will come in handy for a lot of the future logic switching or setting application state on a per-request basis.</p> -<p>Let's use a <code>macro</code> to add a new method to the <code>Request</code>:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- a/app/Providers/AppServiceProvider.php -</span><span>+++ b/app/Providers/AppServiceProvider.php -</span><span>@@ -3,6 +3,7 @@ -</span><span> namespace App\Providers; -</span><span> -</span><span> use Illuminate\Support\ServiceProvider; -</span><span style="color:#a3be8c;">+use Illuminate\Http\Request; -</span><span> -</span><span> class AppServiceProvider extends ServiceProvider -</span><span> { -</span><span>@@ -13,7 +14,14 @@ </span><span style="color:#8fa1b3;">class AppServiceProvider extends ServiceProvider -</span><span> */ -</span><span> public function register() -</span><span> { -</span><span style="color:#bf616a;">- // -</span><span style="color:#a3be8c;">+ // nice helper for getting the current subdomain -</span><span style="color:#a3be8c;">+ Request::macro(&#39;subdomain&#39;, function () { -</span><span style="color:#a3be8c;">+ $domain = request()-&gt;server-&gt;get(&#39;HTTP_HOST&#39;); -</span><span style="color:#a3be8c;">+ $split = explode(&#39;.&#39;, $domain, 3); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ // get the subdomain or return null -</span><span style="color:#a3be8c;">+ return array_get($split, &#39;0&#39;, &#39;&#39;); -</span><span style="color:#a3be8c;">+ }); -</span><span> } -</span></code></pre> -<p>Now any &quot;request&quot; instance will have a new <code>subdomain</code> method that we can call.</p> -<p>We need a way to organize the details of each domain. We can use a <code>config</code> for this. So we are going to make a new one and fill it with some details:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- /dev/null -</span><span>+++ b/config/multisite.php -</span><span>@@ -0,0 +1,25 @@ -</span><span style="color:#a3be8c;">+&lt;?php -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+return [ -</span><span style="color:#a3be8c;">+ // we are going to set this in a future middleware -</span><span style="color:#a3be8c;">+ &#39;active&#39; =&gt; null, -</span><span style="color:#a3be8c;">+ &#39;sites&#39; =&gt; [ -</span><span style="color:#a3be8c;">+ &#39;en&#39; =&gt; [ -</span><span style="color:#a3be8c;">+ &#39;default&#39; =&gt; true, -</span><span style="color:#a3be8c;">+ &#39;locale&#39; =&gt; &#39;en&#39;, -</span><span style="color:#a3be8c;">+ &#39;domain&#39; =&gt; &#39;http://multisite.localhost&#39;, -</span><span style="color:#a3be8c;">+ &#39;label&#39; =&gt; &#39;English&#39;, -</span><span style="color:#a3be8c;">+ ], -</span><span style="color:#a3be8c;">+ &#39;fr&#39; =&gt; [ -</span><span style="color:#a3be8c;">+ &#39;locale&#39; =&gt; &#39;fr&#39;, -</span><span style="color:#a3be8c;">+ &#39;domain&#39; =&gt; &#39;http://fr.multisite.localhost&#39;, -</span><span style="color:#a3be8c;">+ &#39;label&#39; =&gt; &#39;Français&#39;, -</span><span style="color:#a3be8c;">+ ], -</span><span style="color:#a3be8c;">+ ], -</span><span style="color:#a3be8c;">+]; -</span></code></pre> -<p>Now, we need a way to map the requested domain to the correct locale. We take in our subdomain and then map it to the correct config item. In order to make this happen, we need to make a middleware. A middleware manipulates the request as it moves through our app. We aren't going to manipulate the request, we are just going to use the details in the request to set up more config settings.</p> -<p>Here we go:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- /dev/null -</span><span>+++ b/app/Http/Middleware/MultisiteMiddleware.php -</span><span>@@ -0,0 +1,36 @@ -</span><span style="color:#a3be8c;">+&lt;?php -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+namespace App\Http\Middleware; -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+use Illuminate\Support\Facades\App; -</span><span style="color:#a3be8c;">+use Closure; -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+class MultisiteMiddleware -</span><span style="color:#a3be8c;">+{ -</span><span style="color:#a3be8c;">+ /** -</span><span style="color:#a3be8c;">+ * Handle an incoming request. -</span><span style="color:#a3be8c;">+ * -</span><span style="color:#a3be8c;">+ * @param \Illuminate\Http\Request $request -</span><span style="color:#a3be8c;">+ * @param \Closure $next -</span><span style="color:#a3be8c;">+ * @return mixed -</span><span style="color:#a3be8c;">+ */ -</span><span style="color:#a3be8c;">+ public function handle($request, Closure $next) -</span><span style="color:#a3be8c;">+ { -</span><span style="color:#a3be8c;">+ $sites = collect(config(&#39;multisite.sites&#39;)); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ $defaultSite = $sites-&gt;firstWhere(&#39;default&#39;, true); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ $currentSite = $sites-&gt;get($request-&gt;subdomain(), $defaultSite); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ // put this subdomain in the `env` -</span><span style="color:#a3be8c;">+ putenv(&#39;SUBDOMAIN=&#39; . $currentSite[&#39;domain&#39;]); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ // make it easier to access the current site config -</span><span style="color:#a3be8c;">+ config()-&gt;set(&#39;multisite.active&#39;, $currentSite); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ // finally, set the app locale so translations load correctly -</span><span style="color:#a3be8c;">+ App::setLocale($currentSite[&#39;locale&#39;]); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+ return $next($request); -</span><span style="color:#a3be8c;">+ } -</span><span style="color:#a3be8c;">+} -</span></code></pre> -<p>We got our new middleware, so we can enable it in the HTTP kernel array:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- a/app/Http/Kernel.php -</span><span>+++ b/app/Http/Kernel.php -</span><span>@@ -14,6 +14,7 @@ </span><span style="color:#8fa1b3;">class Kernel extends HttpKernel -</span><span> * @var array -</span><span> */ -</span><span> protected $middleware = [ -</span><span style="color:#a3be8c;">+ \App\Http\Middleware\MultisiteMiddleware::class, -</span><span> \App\Http\Middleware\TrustProxies::class, -</span><span> \App\Http\Middleware\CheckForMaintenanceMode::class, -</span><span> \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, -</span></code></pre> -<p>Great! When we make requests now, the <code>config('multisite.active')</code> is going to return a matched value from the multisite config. If the subdomain doesn't match, it will just return the default site config. In our case, the <code>en</code> one.</p> -<p>Since we are setting the locale, we are going to need to make sure there are translations available for all our text. We do this by adding new language files. These files are PHP files that just return plain arrays. These files go under <code>resources/lang/{locale}</code>. So for English (locale is <code>en</code>) we put our files under <code>resources/lang/en</code> and for French (or <code>fr</code>), we put the files under <code>resources/lang/fr</code>.</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- /dev/null -</span><span>+++ b/resources/lang/en/multisite.php -</span><span>@@ -0,0 +1,11 @@ -</span><span style="color:#a3be8c;">+&lt;?php -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+return [ -</span><span style="color:#a3be8c;">+ &#39;site_title&#39; =&gt; &#39;English Multisite&#39;, -</span><span style="color:#a3be8c;">+ &#39;site_label&#39; =&gt; &#39;English&#39;, -</span><span style="color:#a3be8c;">+ &#39;welcome&#39; =&gt; &#39;Welcome&#39;, -</span><span style="color:#a3be8c;">+ &#39;welcome_message&#39; =&gt; &#39;Welcome to the site. You can change the locale by changing to a supported subdomain.&#39;, -</span><span style="color:#a3be8c;">+ &#39;switch_site&#39; =&gt; &#39;Switch site&#39;, -</span><span style="color:#a3be8c;">+]; -</span></code></pre> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- /dev/null -</span><span>+++ b/resources/lang/fr/multisite.php -</span><span>@@ -0,0 +1,11 @@ -</span><span style="color:#a3be8c;">+&lt;?php -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+return [ -</span><span style="color:#a3be8c;">+ &#39;site_title&#39; =&gt; &#39;Multisite Français&#39;, -</span><span style="color:#a3be8c;">+ &#39;site_label&#39; =&gt; &#39;Français&#39;, -</span><span style="color:#a3be8c;">+ &#39;welcome&#39; =&gt; &#39;Bienvenue&#39;, -</span><span style="color:#a3be8c;">+ &#39;welcome_message&#39; =&gt; &#39;Bienvenue sur le site. Vous pouvez changer les paramètres régionaux avec le sous-domaine.&#39;, -</span><span style="color:#a3be8c;">+ &#39;switch_site&#39; =&gt; &#39;Changer de site&#39;, -</span><span style="color:#a3be8c;">+]; -</span></code></pre> -<p>We access these files in a similar way to a <code>config</code> value. We can use the <code>trans</code> function, or we can use the <code>@lang</code> blade directive. Either way, they take a string that represents the path to our array value that we want.</p> -<p>So, if I want to display the <code>site_title</code> from our <code>resources/lang/{locale}/multisite.php</code>, I would need to run <code>trans('multisite.site_title')</code>. Laravel will take care of the rest. If we are in a valid locale, great! We will get the correct language. If we are missing a translation for that key, it will fallback to whatever locale we have set in the <code>config/app.php</code> under the <code>fallback_locale</code> key. The default is <code>en</code>.</p> -<p>So how about the javascript side? How do we deal with JSON when working with this setup?</p> -<h4 id="javascript-and-json">JavaScript and JSON</h4> -<p>Well, if we set things up correctly, the domain should set the locale for us and as long as the URL is correct, we should be good to go!</p> -<p>We need to add an endpoint to get our JSON from. Let's just toss it into <code>routes/web.php</code> because we are animals like that:</p> -<pre data-lang="diff" style="background-color:#2b303b;color:#c0c5ce;" class="language-diff "><code class="language-diff" data-lang="diff"><span>--- a/routes/web.php -</span><span>+++ b/routes/web.php -</span><span>@@ -14,3 +14,7 @@ -</span><span> Route::get(&#39;/&#39;, function () { -</span><span> return view(&#39;welcome&#39;); -</span><span> }); -</span><span style="color:#a3be8c;">+ -</span><span style="color:#a3be8c;">+Route::get(&#39;/locale&#39;, function () { -</span><span style="color:#a3be8c;">+ return response()-&gt;json(config(&#39;multisite.active&#39;), 200); -</span><span style="color:#a3be8c;">+}); -</span></code></pre> -<p>Here, you can see we are just returning whatever the active site is. This will be a JSON object of the active site config. Nice!</p> -<h4 id="in-summation">In Summation</h4> -<p>Finally, let's update the <code>welcome.blade.php</code> just so we can see our changes in progress:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;!</span><span style="color:#b48ead;">DOCTYPE </span><span style="color:#d08770;">html</span><span>&gt; -</span><span>&lt;</span><span style="color:#bf616a;">html </span><span style="color:#d08770;">lang</span><span>=&quot;</span><span style="color:#a3be8c;">{{ str_replace(&#39;_&#39;, &#39;-&#39;, app()-&gt;getLocale()) }}</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">head</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">charset</span><span>=&quot;</span><span style="color:#a3be8c;">utf-8</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">viewport</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">width=device-width, initial-scale=1</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">title</span><span>&gt;@lang(&#39;multisite.site_title&#39;)&lt;/</span><span style="color:#bf616a;">title</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">head</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">body</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">p</span><span>&gt; -</span><span> @foreach (config(&#39;multisite.sites&#39;) as $site) -</span><span> &lt;</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">{{ $site[&#39;domain&#39;] }}</span><span>&quot;&gt;{{ $site[&#39;label&#39;] }}&lt;/</span><span style="color:#bf616a;">a</span><span>&gt; -</span><span> @endforeach -</span><span> &lt;/</span><span style="color:#bf616a;">p</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">h1</span><span>&gt;@lang(&#39;multisite.welcome&#39;)&lt;/</span><span style="color:#bf616a;">h1</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">p</span><span>&gt;@lang(&#39;multisite.welcome_message&#39;)&lt;/</span><span style="color:#bf616a;">p</span><span>&gt; -</span><span> -</span><span> &lt;</span><span style="color:#bf616a;">pre </span><span style="color:#8fa1b3;">id</span><span>=&quot;</span><span style="color:#a3be8c;">ajax</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">pre</span><span>&gt; -</span><span> -</span><span> &lt;</span><span style="color:#bf616a;">script</span><span>&gt; -</span><span> document.</span><span style="color:#bf616a;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">DOMContentLoaded</span><span>&#39;, () </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#65737e;">// call that endpoint we created in our route -</span><span> </span><span style="color:#bf616a;">fetch</span><span>(&#39;</span><span style="color:#a3be8c;">/locale</span><span>&#39;, { -</span><span> credentials: &#39;</span><span style="color:#a3be8c;">include</span><span>&#39;, -</span><span> headers: { -</span><span> accept: &#39;</span><span style="color:#a3be8c;">application/json</span><span>&#39;, -</span><span> </span><span style="color:#65737e;">// make sure we set the language explicitly -</span><span> &#39;</span><span style="color:#a3be8c;">accept-language</span><span>&#39;: document.documentElement.lang -</span><span> } -</span><span> }) -</span><span> </span><span style="color:#65737e;">// do the fetch dance... -</span><span> .</span><span style="color:#bf616a;">then</span><span>((res) </span><span style="color:#b48ead;">=&gt; </span><span style="color:#bf616a;">res</span><span>.</span><span style="color:#bf616a;">json</span><span>()) -</span><span> .</span><span style="color:#bf616a;">then</span><span>((data) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">ajaxField </span><span>= document.</span><span style="color:#bf616a;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">ajax</span><span>&#39;); -</span><span> -</span><span> </span><span style="color:#65737e;">// prepend this to the output -</span><span> </span><span style="color:#bf616a;">ajaxField</span><span>.innerHTML = &#39;</span><span style="color:#a3be8c;">// loaded via ajax</span><span style="color:#96b5b4;">\n</span><span>&#39;; -</span><span> </span><span style="color:#65737e;">// put our JSON in but format it with 2 spaces -</span><span> </span><span style="color:#bf616a;">ajaxField</span><span>.innerHTML += JSON.</span><span style="color:#96b5b4;">stringify</span><span>(</span><span style="color:#bf616a;">data</span><span>, </span><span style="color:#d08770;">null</span><span>, </span><span style="color:#d08770;">2</span><span>); -</span><span> }); -</span><span> }); -</span><span> &lt;/</span><span style="color:#bf616a;">script</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">body</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">html</span><span>&gt; -</span></code></pre> -<p>We should see something like this when we load up <code>multisite.localhost</code>:</p> -<div class="center"> - <a href="/images/multisite-demo.gif" target="_blank" title="laravel multi-lingual site demo"><img alt="laravel multi-lingual site demo" src="/images/multisite-demo.gif"></a> -</div> -<p>Wow! Amazing. So it works! Very nice. We now have the basis for a multilingual site based completely off subdomains and we didn't need to install any packages or do any weird magic!</p> - - - - - Simple Slack Slash Supplier - 2019-05-13T00:00:00+00:00 - 2019-05-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/simple-slash-commands/ - - <p><em>This is a reprint of the README from my project <a href="https://github.com/james2doyle/simple-slack-slash-supplier">simple-slack-slash-supplier</a>.</em></p> -<p>This examples project showcases a simple way to create new slash commands without having to work too hard.</p> -<p>This example assumes you have access to <a href="https://aws.amazon.com/lambda/">AWS Lambda</a> as well as have <a href="https://api.slack.com/slash-commands">created a Slash command in Slack</a>.</p> -<h3 id="handling-application-x-www-form-urlencoded">Handling <code>application/x-www-form-urlencoded</code></h3> -<p>Slack sends their <code>POST</code> requests using <code>Content-Type: application/x-www-form-urlencoded</code>. This is not a content type that Lambda supports out-of-the-box. We need to do some legwork in order to map that raw request body to <code>JSON</code> which we can understand more easily in our Node.js handler function.</p> -<p>Preliminary setup:</p> -<ul> -<li>You must create a standalone <code>POST</code> resource action</li> -<li>Be sure &quot;Integration type&quot; is set to &quot;Lambda Function&quot;</li> -<li>Make sure <code>Use Lambda Proxy integration</code> is checked</li> -<li>Remove the original <code>ANY</code> resource action to be sure it doesn't conflict</li> -</ul> -<h4 id="mapping-templates">Mapping templates</h4> -<p>In order to properly handle <code>application/x-www-form-urlencoded</code>, we need to make a mapping template for it.</p> -<p>Here are the steps:</p> -<ul> -<li>Click on &quot;Integration Request&quot; when viewing the details of your <code>POST</code> resource action</li> -<li>Pop open the &quot;Mapping templates&quot; accordion</li> -<li>Click &quot;Add mapping template&quot;</li> -<li>Use <code>application/x-www-form-urlencoded</code> in the field that appears</li> -<li>Scroll down and see the new form to enter in your template details</li> -<li>Select &quot;Method request passthrough&quot; from the &quot;Generate template&quot; dropdown</li> -<li>Put the following content in the textarea:</li> -</ul> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>## Ripped from &quot;https://stackoverflow.com/a/52705985/1170664&quot; -</span><span>{ -</span><span> &quot;body-json&quot; : $input.json(&#39;$&#39;), -</span><span> &quot;params&quot; : { -</span><span> #foreach( $token in $input.path(&#39;$&#39;).split(&#39;&amp;&#39;) ) -</span><span> #set( $keyVal = $token.split(&#39;=&#39;) ) -</span><span> #set( $keyValSize = $keyVal.size() ) -</span><span> #if( $keyValSize &gt;= 1 ) -</span><span> #set( $key = $util.urlDecode($keyVal[0]) ) -</span><span> #if( $keyValSize &gt;= 2 ) -</span><span> #set( $val = $util.urlDecode($keyVal[1]) ) -</span><span> #else -</span><span> #set( $val = &#39;&#39; ) -</span><span> #end -</span><span> &quot;$key&quot;: &quot;$util.escapeJavaScript($val)&quot;#if($foreach.hasNext),#end -</span><span> #end -</span><span> #end -</span><span> }, -</span><span> &quot;stage-variables&quot; : { -</span><span> #foreach($key in $stageVariables.keySet()) -</span><span> &quot;$key&quot; : &quot;$util.escapeJavaScript($stageVariables.get($key))&quot; -</span><span> #if($foreach.hasNext),#end -</span><span> #end -</span><span> }, -</span><span> &quot;context&quot; : { -</span><span> &quot;account-id&quot; : &quot;$context.identity.accountId&quot;, -</span><span> &quot;api-id&quot; : &quot;$context.apiId&quot;, -</span><span> &quot;api-key&quot; : &quot;$context.identity.apiKey&quot;, -</span><span> &quot;authorizer-principal-id&quot; : &quot;$context.authorizer.principalId&quot;, -</span><span> &quot;caller&quot; : &quot;$context.identity.caller&quot;, -</span><span> &quot;cognito-authentication-provider&quot; : &quot;$context.identity.cognitoAuthenticationProvider&quot;, -</span><span> &quot;cognito-authentication-type&quot; : &quot;$context.identity.cognitoAuthenticationType&quot;, -</span><span> &quot;cognito-identity-id&quot; : &quot;$context.identity.cognitoIdentityId&quot;, -</span><span> &quot;cognito-identity-pool-id&quot; : &quot;$context.identity.cognitoIdentityPoolId&quot;, -</span><span> &quot;http-method&quot; : &quot;$context.httpMethod&quot;, -</span><span> &quot;stage&quot; : &quot;$context.stage&quot;, -</span><span> &quot;source-ip&quot; : &quot;$context.identity.sourceIp&quot;, -</span><span> &quot;user&quot; : &quot;$context.identity.user&quot;, -</span><span> &quot;user-agent&quot; : &quot;$context.identity.userAgent&quot;, -</span><span> &quot;user-arn&quot; : &quot;$context.identity.userArn&quot;, -</span><span> &quot;request-id&quot; : &quot;$context.requestId&quot;, -</span><span> &quot;resource-id&quot; : &quot;$context.resourceId&quot;, -</span><span> &quot;resource-path&quot; : &quot;$context.resourcePath&quot; -</span><span> } -</span><span>} -</span></code></pre> -<ul> -<li>Paste the content above in the textarea</li> -<li>Click &quot;Save&quot; to confirm the content of the textarea</li> -<li>From the &quot;Actions&quot; dropdown select &quot;Deploy API&quot;</li> -<li>Deploy the API changes as you normally would</li> -</ul> -<p>Now you should now be able to handle <code>POST</code> requests with content type of <code>application/x-www-form-urlencoded</code> and they will be converted right into <code>JSON</code> automatically!</p> -<h3 id="testing">Testing</h3> -<p>You can edit <code>hello-command.json</code> to add details about your command in order to test the different handlers. Add new files and new tests scripts to <code>package.json</code> to keep going.</p> -<p>Run the commands with:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>npm install -</span><span>npm run test -</span></code></pre> - - - - - SQL As An API - 2019-05-12T00:00:00+00:00 - 2019-05-12T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/sql-as-an-api/ - - <p>There has been a lot of fanfare around the idea of <a href="https://graphql.org/">GraphQL</a> lately. For good reason in my opinion.</p> -<p>GraphQL allows you to essentially define your API with your query. For example, if you want to only include a single field then you can do that. If you want to nest related items, or not, you can do that as well.</p> -<p>If you take a quick look at the GraphQL website (as it is today) you can see how they lay this out:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># Describe your data -</span><span>type Project { -</span><span> name: String -</span><span> tagline: String -</span><span> created_at: Int -</span><span>} -</span><span> -</span><span># Ask for what you want -</span><span>{ -</span><span> project(name: &quot;My Project&quot;) { -</span><span> tagline -</span><span> } -</span><span>} -</span><span> -</span><span># Get predictable results -</span><span>{ -</span><span> &quot;project&quot;: { -</span><span> &quot;tagline&quot;: &quot;A query language for APIs&quot; -</span><span> } -</span><span>} -</span></code></pre> -<p>Pretty straightforward. You create a model of your data that describes it, write a query that matches the model, and get the results.</p> -<p>Although, this should already look familiar to anyone that has used a relational database before. What if we massage this example so it fits a more classic datastore:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># Describe your data -</span><span>CREATE TABLE &quot;project&quot; -</span><span>( -</span><span> [name] NVARCHAR(40), -</span><span> [tagline] NVARCHAR(120), -</span><span> [created_at] INTEGER NOT NULL -</span><span>); -</span><span> -</span><span># Ask for what you want -</span><span>SELECT `tagline` FROM `projects` WHERE `name` = &quot;My Project&quot; -</span><span> -</span><span># Get predictable results -</span><span>[ -</span><span> { -</span><span> &quot;tagline&quot;: &quot;A query language for APIs&quot; -</span><span> } -</span><span>] -</span></code></pre> -<p>So this really is just another way to store and query your data. We describe the structure, query it using the specified query language with our expected structure, and print out the results.</p> -<blockquote> -<p>So can we create a GraphQL experience using just SQL?</p> -</blockquote> -<p>So can we create a GraphQL experience using just SQL? Short answer, yes. This isn't a new concept though. There is a tool called <a href="https://simonwillison.net/2017/Nov/13/datasette/#Arbitrary_SQL_55">datasette</a> that does just this.</p> -<p>So where do we start? Well, we don't want users to be able to modify our database. We want to safely expose SQL to the internet. Crazy idea but it just might work.</p> -<p>So what we want to do is open a Sqlite database using the &quot;ro&quot; (read only) mode (see <a href="http://www.sqlite.org/draft/c3ref/open.html">mode details here</a>). This mode means that you <em>cannot modify the database</em>. You can only run queries that are reads. This is a great feature as we need to expose this database to the public internet if we are going to use it as an API.</p> -<p>Once we have a mounted sqlite database that is set to &quot;read only&quot; mode, we can then pass queries right to it and return the results.</p> -<h3 id="basic-example">Basic Example</h3> -<p>We are going to whip up this example using PHP and the <code>chinook</code> database <a href="http://www.sqlitetutorial.net/sqlite-sample-database/">from this site</a>. It is insanely easy to get this working using the <code>SQLite3</code> class and a single <code>json_encode</code> call.</p> -<p>Let's see how to get this done:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">// be sure to open with `SQLITE3_OPEN_READONLY` -</span><span>$</span><span style="color:#bf616a;">db </span><span>= </span><span style="color:#b48ead;">new </span><span style="color:#ebcb8b;">SQLite3</span><span>(&#39;</span><span style="color:#a3be8c;">chinook.sqlite</span><span>&#39;, SQLITE3_OPEN_READONLY); -</span><span style="color:#65737e;">// pull out the query from the POST request -</span><span>$</span><span style="color:#bf616a;">rows </span><span>= $</span><span style="color:#bf616a;">db</span><span>-&gt;</span><span style="color:#bf616a;">query</span><span>($</span><span style="color:#bf616a;">_POST</span><span>[&#39;</span><span style="color:#a3be8c;">query</span><span>&#39;]); -</span><span>$</span><span style="color:#bf616a;">results </span><span>= []; -</span><span style="color:#b48ead;">while </span><span>($</span><span style="color:#bf616a;">row </span><span>= $</span><span style="color:#bf616a;">rows</span><span>-&gt;</span><span style="color:#bf616a;">fetchArray</span><span>(SQLITE3_ASSOC)) { -</span><span> $</span><span style="color:#bf616a;">results</span><span>[] = $</span><span style="color:#bf616a;">row</span><span>; -</span><span>} -</span><span style="color:#96b5b4;">echo json_encode</span><span>($</span><span style="color:#bf616a;">results</span><span>); -</span></code></pre> -<p>OK. That was easy. This is all we need to support calls to this &quot;server&quot;. Let's save this to <code>index.php</code>. And start a webserver on port <code>8000</code>. If you didn't know this, PHP has a built-in server just like the one python has. We can start it like so:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">php -S</span><span> localhost:8000 -</span></code></pre> -<p>You should see some output information about what the server is doing. Now we can make calls to this API:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl --request</span><span> POST \ -</span><span style="color:#bf616a;"> --url</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> --header </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/x-www-form-urlencoded</span><span>&#39; \ -</span><span style="color:#bf616a;"> --data </span><span>&#39;</span><span style="color:#a3be8c;">query=SELECT LastName FROM employees</span><span>&#39; -</span></code></pre> -<p>We should see the following output in our console:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>[ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Adams</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Peacock</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Park</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Johnson</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Mitchell</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">King</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Callahan</span><span>&quot; -</span><span> } -</span><span>] -</span></code></pre> -<h3 id="getting-fancier">Getting fancier</h3> -<p>Nice! So this works. Let's try to do something a little more fancy:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl --request</span><span> POST \ -</span><span style="color:#bf616a;"> --url</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> --header </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/x-www-form-urlencoded</span><span>&#39; \ -</span><span style="color:#bf616a;"> --data </span><span>&#39;</span><span style="color:#a3be8c;">query=SELECT printf(&quot;%s %s&quot;, FirstName, LastName) AS FullName, FirstName, LastName FROM employees</span><span>&#39; -</span></code></pre> -<p>Our output:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>[ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Andrew Adams</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Andrew</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Adams</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Nancy Edwards</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Nancy</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Jane Peacock</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Jane</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Peacock</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Margaret Park</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Margaret</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Park</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Steve Johnson</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Steve</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Johnson</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Michael Mitchell</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Michael</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Mitchell</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Robert King</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Robert</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">King</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">FullName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Laura Callahan</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">FirstName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Laura</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Callahan</span><span>&quot; -</span><span> } -</span><span>] -</span></code></pre> -<p>How about a little example that searches a users name?</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl --request</span><span> POST \ -</span><span style="color:#bf616a;"> --url</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> --header </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/x-www-form-urlencoded</span><span>&#39; \ -</span><span style="color:#bf616a;"> --data </span><span>&#39;</span><span style="color:#a3be8c;">query=SELECT printf (&quot;%s %s&quot;, employees.FirstName, employees.LastName) AS full_name FROM employees WHERE full_name LIKE &quot;an%&quot;</span><span>&#39; -</span></code></pre> -<p>We see that we found the user:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>[ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Andrew Adams</span><span>&quot; -</span><span> } -</span><span>] -</span></code></pre> -<p>Let's do an even more sophisticated example:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl --request</span><span> POST \ -</span><span style="color:#bf616a;"> --url</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> --header </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/x-www-form-urlencoded</span><span>&#39; \ -</span><span style="color:#bf616a;"> --data </span><span>&#39;</span><span style="color:#a3be8c;">query=SELECT e2.EmployeeId AS employee_id, e2.ReportsTo AS reports_to, printf (&quot;%s %s&quot;, e2.FirstName, e2.LastName) AS employee_full_name, printf (&quot;%s %s&quot;, e1.FirstName, e1.LastName) AS reports_to_full_name FROM employees e1 INNER JOIN employees e2 ON e1.EmployeeId = e2.ReportsTo WHERE e1.ReportsTo IS NOT NULL</span><span>&#39; -</span></code></pre> -<p>And our output:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>[ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">employee_id</span><span>&quot;: </span><span style="color:#d08770;">3</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to</span><span>&quot;: </span><span style="color:#d08770;">2</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">employee_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Jane Peacock</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Nancy Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">employee_id</span><span>&quot;: </span><span style="color:#d08770;">4</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to</span><span>&quot;: </span><span style="color:#d08770;">2</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">employee_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Margaret Park</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Nancy Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">employee_id</span><span>&quot;: </span><span style="color:#d08770;">5</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to</span><span>&quot;: </span><span style="color:#d08770;">2</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">employee_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Steve Johnson</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Nancy Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">employee_id</span><span>&quot;: </span><span style="color:#d08770;">7</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to</span><span>&quot;: </span><span style="color:#d08770;">6</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">employee_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Robert King</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Michael Mitchell</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">employee_id</span><span>&quot;: </span><span style="color:#d08770;">8</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to</span><span>&quot;: </span><span style="color:#d08770;">6</span><span>, -</span><span> &quot;</span><span style="color:#a3be8c;">employee_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Laura Callahan</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">reports_to_full_name</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Michael Mitchell</span><span>&quot; -</span><span> } -</span><span>] -</span></code></pre> -<p>Awesome! As you can see this is pretty great! We have a basic API that we can use not only to read but to handle relationships as well. Sweet!</p> -<h3 id="handling-malicious-queries">Handling malicious queries</h3> -<p>Let's try to execute a bad command:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl --request</span><span> POST \ -</span><span style="color:#bf616a;"> --url</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> --header </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/x-www-form-urlencoded</span><span>&#39; \ -</span><span style="color:#bf616a;"> --data </span><span>&#39;</span><span style="color:#a3be8c;">query=INSERT INTO &quot;artists&quot; (&quot;Name&quot;) VALUES (&quot;Philip Glass Ensemble&quot;)</span><span>&#39; -</span></code></pre> -<p>We should see this output from the server:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">br </span><span>/&gt; -</span><span>&lt;</span><span style="color:#bf616a;">b</span><span>&gt;Warning&lt;/</span><span style="color:#bf616a;">b</span><span>&gt;: SQLite3::query(): Unable to execute statement: attempt to write a readonly database in &lt;</span><span style="color:#bf616a;">b</span><span>&gt;/Users/james/Sites/sqliteapi/index.php&lt;/</span><span style="color:#bf616a;">b</span><span>&gt; on line &lt;</span><span style="color:#bf616a;">b</span><span>&gt;6&lt;/</span><span style="color:#bf616a;">b</span><span>&gt;&lt;</span><span style="color:#bf616a;">br </span><span>/&gt; -</span><span>&lt;</span><span style="color:#bf616a;">br </span><span>/&gt; -</span><span>&lt;</span><span style="color:#bf616a;">b</span><span>&gt;Fatal error&lt;/</span><span style="color:#bf616a;">b</span><span>&gt;: Uncaught Error: Call to a member function fetchArray() on bool in /Users/james/Sites/sqliteapi/index.php:8 -</span><span>Stack trace: -</span><span>#0 {main} -</span><span> thrown in &lt;</span><span style="color:#bf616a;">b</span><span>&gt;/Users/james/Sites/sqliteapi/index.php&lt;/</span><span style="color:#bf616a;">b</span><span>&gt; on line &lt;</span><span style="color:#bf616a;">b</span><span>&gt;8&lt;/</span><span style="color:#bf616a;">b</span><span>&gt;&lt;</span><span style="color:#bf616a;">br </span><span>/&gt; -</span></code></pre> -<p>As you can see, we try to execute a bad query that would manipulate the data and we get blocked.</p> -<h3 id="super-example">Super example</h3> -<p>Here is a much more robust example with some error handling, proper status codes, parsing of JSON input, and it also allows you to put the <code>query</code> in a <code>GET</code> or a <code>POST</code> request.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/9e4b2b4f17e33bfb236fbdaf96c41a4c.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;9e4b2b4f17e33bfb236fbdaf96c41a4c.js'></script>`"></iframe> -<p>Try running the example above and giving things a test. Here is our new request that uses JSON instead:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">curl -X</span><span> POST \ -</span><span> http://localhost:8000 \ -</span><span style="color:#bf616a;"> -H </span><span>&#39;</span><span style="color:#a3be8c;">Content-Type: application/json</span><span>&#39; \ -</span><span style="color:#bf616a;"> -d </span><span>&#39;</span><span style="color:#a3be8c;">{ &quot;query&quot;: &quot;SELECT LastName FROM employees&quot; }</span><span>&#39; -</span></code></pre> -<p>Now we have the following output:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{ -</span><span> &quot;</span><span style="color:#a3be8c;">data</span><span>&quot;: [ -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Adams</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Edwards</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Peacock</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Park</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Johnson</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Mitchell</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">King</span><span>&quot; -</span><span> }, -</span><span> { -</span><span> &quot;</span><span style="color:#a3be8c;">LastName</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">Callahan</span><span>&quot; -</span><span> } -</span><span> ], -</span><span> &quot;</span><span style="color:#a3be8c;">meta</span><span>&quot;: { -</span><span> &quot;</span><span style="color:#a3be8c;">query</span><span>&quot;: &quot;</span><span style="color:#a3be8c;">SELECT LastName FROM employees</span><span>&quot;, -</span><span> &quot;</span><span style="color:#a3be8c;">total</span><span>&quot;: </span><span style="color:#d08770;">8 -</span><span> } -</span><span>} -</span></code></pre> -<p>So there you go. A pretty robust solution that has excellent performance, a simple and well-known query language, and supports almost any combination of data.</p> -<h3 id="in-summation">In summation</h3> -<p>So this could be a great option for a lot of applications. You can quickly imagine how this could be used for something like a simple search API. You could have an application hook to add data to this special database when your data is changed. Sqlite is a really viable option for a lot of things as it can grow to 140 TB, supports <a href="https://www.sqlite.org/json1.html"><code>json_</code> functions</a>, and even <a href="https://effbot.org/zone/sqlite-blob.htm">binary data</a>.</p> -<p>Keep in mind that you can't really nest the same way you can in GraphQL. But you might be ok with that depending on your use case.</p> - - - - - Laravel Scout Sonic Driver - 2019-04-12T00:00:00+00:00 - 2019-04-12T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/laravel-scout-sonic-driver/ - - <p>If you haven't heard about the cool new search indexer <a href="https://github.com/valeriansaliou/sonic">Sonic</a> you must be living under a rock! If you want to read about Sonic directly from the author, <a href="https://journal.valeriansaliou.name/announcing-sonic-a-super-light-alternative-to-elasticsearch/">check out his blog post</a> on why and how they went on building the tool.</p> -<p>According to the Sonic page, it is:</p> -<blockquote> -<p>Fast, lightweight &amp; schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.</p> -</blockquote> -<p>There are some main points on the site that outline some of the main features:</p> -<ul> -<li>Search terms are stored in collections, organized in buckets</li> -<li>Search results return object identifiers</li> -<li>Search query typos are corrected</li> -<li>Insert and remove items in the index at the same time</li> -<li>Auto-complete any word in real-time via the suggest operation</li> -<li>Full Unicode compatibility on 80+ languages</li> -<li>Built as a TCP protocol</li> -</ul> -<p>All these items make this an ideal candidate for the <a href="https://laravel.com/docs/5.8/scout">Laravel Scout search tool</a>. Scout expects your search engine to return IDs that then get mapped to your query builder which will apply further transformations like <code>WHERE</code> clauses and pagination.</p> -<p>I took it upon myself to write a driver for Sonic. You can find it on my Github at <a href="https://github.com/james2doyle/laravel-scout-sonic">Laravel Scout Sonic Driver</a> and you can also install it via composer with the following command:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>composer require james2doyle/laravel-scout-sonic -</span></code></pre> -<p>You will need to add some extra config information to <code>config/scout.php</code> before you can use everything correctly. Just checkout the <code>README.md</code> because all the instructions are in there.</p> - - - - - Function Currying To Make Reusable Code - 2019-02-17T00:00:00+00:00 - 2019-02-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/function-currying-to-make-reusable-code/ - - <p>Function currying. If you haven't heard of it before, let me introduce you to this magical pattern that can help reduce duplicate code and improve readability.</p> -<p>Currying is just a fancy way to describe a <em>function that returns a function</em>. Pretty simple right? I mean this literally. There are no gotchas here. A <em>function that returns a function</em> is a curried function. Enough words though. I will show you some common code samples and then refactor them to use curried functions.</p> -<h3 id="cleaning-up-prototype-chains">Cleaning up prototype chains</h3> -<p>I'm sure everyone has written code like this before:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">const </span><span style="color:#bf616a;">items </span><span>= [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]; -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>((</span><span style="color:#bf616a;">num</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>`</span><span style="color:#a3be8c;">item-${</span><span style="color:#bf616a;">num</span><span style="color:#a3be8c;">}</span><span>`); -</span><span style="color:#65737e;">// returns [&#39;item-1&#39;, &#39;item-2&#39;, &#39;item-3&#39;, &#39;item-4&#39;] -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>((</span><span style="color:#bf616a;">num</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>`</span><span style="color:#a3be8c;">key-${</span><span style="color:#bf616a;">num</span><span style="color:#a3be8c;">}</span><span>`); -</span><span style="color:#65737e;">// returns [&#39;key-1&#39;, &#39;key-2&#39;, &#39;key-3&#39;, &#39;key-4&#39;] -</span></code></pre> -<p>You can see that this function is very specific to the area it's being used. We support a variable number of items via our array and they don't even have to be numbers. We can build new results with either numbers or strings. That's great. But we do have repeated code that essentially does the same thing. The only difference is the string we are prepending.</p> -<p>Here is an example using currying to <em>D.R.Y</em> (don't repeat yourself) this up:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">labelMaker </span><span>= (</span><span style="color:#bf616a;">label</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span>(</span><span style="color:#bf616a;">num</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>`</span><span style="color:#a3be8c;">${</span><span style="color:#bf616a;">label</span><span style="color:#a3be8c;">}-${</span><span style="color:#bf616a;">num</span><span style="color:#a3be8c;">}</span><span>`; -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">items </span><span>= [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]; -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#8fa1b3;">labelMaker</span><span>(&#39;</span><span style="color:#a3be8c;">item</span><span>&#39;)); -</span><span style="color:#65737e;">// returns [&#39;item-1&#39;, &#39;item-2&#39;, &#39;item-3&#39;, &#39;item-4&#39;] -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#8fa1b3;">labelMaker</span><span>(&#39;</span><span style="color:#a3be8c;">key</span><span>&#39;)); -</span><span style="color:#65737e;">// returns [&#39;key-1&#39;, &#39;key-2&#39;, &#39;key-3&#39;, &#39;key-4&#39;] -</span></code></pre> -<p>When refactoring to curried functions, the thing to keep in mind is <em>what is unique about each function and what is the same?</em></p> -<p>Things that are unique should be the arguments to the first function call. The things that are the same should be the second function that is called. At least when using this pattern of currying.</p> -<p>We can actually go a step further again and make this function very flexible:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">labelMaker </span><span>= (</span><span style="color:#bf616a;">label</span><span>, </span><span style="color:#bf616a;">splitter </span><span>= &#39;</span><span style="color:#a3be8c;">-</span><span>&#39;) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span>(</span><span style="color:#bf616a;">num</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>`</span><span style="color:#a3be8c;">${</span><span style="color:#bf616a;">label</span><span style="color:#a3be8c;">}${</span><span style="color:#bf616a;">splitter</span><span style="color:#a3be8c;">}${</span><span style="color:#bf616a;">num</span><span style="color:#a3be8c;">}</span><span>`; -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">items </span><span>= [</span><span style="color:#d08770;">1</span><span>, </span><span style="color:#d08770;">2</span><span>, </span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">4</span><span>]; -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#8fa1b3;">labelMaker</span><span>(&#39;</span><span style="color:#a3be8c;">item</span><span>&#39;)); -</span><span style="color:#65737e;">// returns [&#39;item-1&#39;, &#39;item-2&#39;, &#39;item-3&#39;, &#39;item-4&#39;] -</span><span style="color:#bf616a;">items</span><span>.</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#8fa1b3;">labelMaker</span><span>(&#39;</span><span style="color:#a3be8c;">key</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">_</span><span>&#39;)); -</span><span style="color:#65737e;">// returns [&#39;key_1&#39;, &#39;key_2&#39;, &#39;key_3&#39;, &#39;key_4&#39;] -</span></code></pre> -<p>Now that you have seen this in action under a simple example. I think it is useful to chat about using currying to create &quot;constructors&quot;.</p> -<h3 id="using-currying-to-create-constructors">Using currying to create &quot;constructors&quot;</h3> -<p>Take this example:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">logger </span><span>= (</span><span style="color:#bf616a;">level </span><span>= &#39;</span><span style="color:#a3be8c;">info</span><span>&#39;) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#ebcb8b;">console</span><span>[</span><span style="color:#bf616a;">level</span><span>].</span><span style="color:#8fa1b3;">bind</span><span>(</span><span style="color:#ebcb8b;">console</span><span>); -</span><span>}; -</span></code></pre> -<p>In this example, we have a function called <code>logger</code> which returns an instance of <code>console</code> but bound to whichever <code>level</code> you want. Here is how we might use it:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">logger </span><span>= (</span><span style="color:#bf616a;">level </span><span>= &#39;</span><span style="color:#a3be8c;">info</span><span>&#39;) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#ebcb8b;">console</span><span>[</span><span style="color:#bf616a;">level</span><span>].</span><span style="color:#8fa1b3;">bind</span><span>(</span><span style="color:#ebcb8b;">console</span><span>); -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">infoLogger </span><span>= </span><span style="color:#8fa1b3;">logger</span><span>(); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">errorLogger </span><span>= </span><span style="color:#8fa1b3;">logger</span><span>(&#39;</span><span style="color:#a3be8c;">error</span><span>&#39;); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">traceLogger </span><span>= </span><span style="color:#8fa1b3;">logger</span><span>(&#39;</span><span style="color:#a3be8c;">trace</span><span>&#39;); -</span></code></pre> -<p>See what I mean about creating &quot;constructors&quot;? We can use the first function almost like a constructor that will return a function that will be called with that first set of arguments applied in some way. Pretty useful right? Instead of making classes or global variables, we can make curried functions that can store those values for use in the returned function.</p> -<h3 id="currying-event-handlers">Currying event handlers</h3> -<p>Given the &quot;constructor&quot; nature of function currying, I typically use these for event handlers. I think of it like tagging. I'll show you why here:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">/* &lt;input type=&quot;text&quot; id=&quot;username&quot;&gt; */ -</span><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">handleInput </span><span>= (</span><span style="color:#bf616a;">name</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span>({ </span><span style="color:#bf616a;">target</span><span>: { </span><span style="color:#bf616a;">value </span><span>} }) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(`</span><span style="color:#a3be8c;">${</span><span style="color:#bf616a;">name</span><span style="color:#a3be8c;">} input was set to &quot;${</span><span style="color:#bf616a;">value</span><span style="color:#a3be8c;">}&quot;</span><span>`); -</span><span> }; -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">username </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;); -</span><span style="color:#bf616a;">username</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">input</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;)); -</span></code></pre> -<p>In this example, we are adding an event handler to an input and basically giving it a label of &quot;username&quot;. Not too special at the moment. If we wanted just the name that would be easy. So let's make this function handle more input types:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">/** -</span><span style="color:#65737e;"> * &lt;input type=&quot;text&quot; id=&quot;username&quot;/&gt; -</span><span style="color:#65737e;"> * &lt;input type=&quot;checkbox&quot; id=&quot;accept&quot; value=&quot;1&quot; /&gt; -</span><span style="color:#65737e;"> * &lt;select id=&quot;age&quot;&gt; -</span><span style="color:#65737e;"> * &lt;option value=&quot;below 18&quot;&gt;below 18&lt;/option&gt; -</span><span style="color:#65737e;"> * &lt;option value=&quot;above 18&quot;&gt;above 18&lt;/option&gt; -</span><span style="color:#65737e;"> * &lt;/select&gt; -</span><span style="color:#65737e;"> **/ -</span><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">handleInput </span><span>= (</span><span style="color:#bf616a;">name</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span>({ </span><span style="color:#bf616a;">target</span><span>: { </span><span style="color:#bf616a;">type</span><span>, </span><span style="color:#bf616a;">value</span><span>, </span><span style="color:#bf616a;">checked </span><span>} }) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(`</span><span style="color:#a3be8c;">${</span><span style="color:#bf616a;">name</span><span style="color:#a3be8c;">} input was set to &quot;${</span><span style="color:#bf616a;">type </span><span>!== &#39;</span><span style="color:#a3be8c;">checkbox</span><span>&#39; ? </span><span style="color:#bf616a;">value </span><span>: </span><span style="color:#bf616a;">checked</span><span style="color:#a3be8c;">}&quot;</span><span>`); -</span><span> }; -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">username </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;); -</span><span style="color:#bf616a;">username</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">input</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;)); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">accept </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">accept</span><span>&#39;); -</span><span style="color:#bf616a;">accept</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">change</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">accept</span><span>&#39;)); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">age </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">age</span><span>&#39;); -</span><span style="color:#bf616a;">age</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">change</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">age</span><span>&#39;)); -</span></code></pre> -<p>Ok, so now we see 3 different input types, all using the same function but the output is &quot;tagged&quot; via that first function's argument.</p> -<p>We can actually clean this up more though:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">/** -</span><span style="color:#65737e;"> * &lt;input type=&quot;text&quot; id=&quot;username&quot;/&gt; -</span><span style="color:#65737e;"> * &lt;input type=&quot;checkbox&quot; id=&quot;accept&quot; value=&quot;1&quot; /&gt; -</span><span style="color:#65737e;"> * &lt;select id=&quot;age&quot;&gt; -</span><span style="color:#65737e;"> * &lt;option value=&quot;below 18&quot;&gt;below 18&lt;/option&gt; -</span><span style="color:#65737e;"> * &lt;option value=&quot;above 18&quot;&gt;above 18&lt;/option&gt; -</span><span style="color:#65737e;"> * &lt;/select&gt; -</span><span style="color:#65737e;"> **/ -</span><span style="color:#b48ead;">const </span><span style="color:#8fa1b3;">handleInput </span><span>= (</span><span style="color:#bf616a;">name</span><span>, </span><span style="color:#bf616a;">prop </span><span>= &#39;</span><span style="color:#a3be8c;">value</span><span>&#39;) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#b48ead;">return </span><span>({ </span><span style="color:#bf616a;">target </span><span>}) </span><span style="color:#b48ead;">=&gt; </span><span>{ -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(`</span><span style="color:#a3be8c;">${</span><span style="color:#bf616a;">name</span><span style="color:#a3be8c;">} input was set to &quot;${</span><span style="color:#bf616a;">target</span><span style="color:#a3be8c;">[</span><span style="color:#bf616a;">prop</span><span style="color:#a3be8c;">]}&quot;</span><span>`); -</span><span> }; -</span><span>}; -</span><span> -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">username </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;); -</span><span style="color:#bf616a;">username</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">input</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;)); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">accept </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">accept</span><span>&#39;); -</span><span style="color:#bf616a;">accept</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">change</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">accept</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">checked</span><span>&#39;)); -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">age </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">age</span><span>&#39;); -</span><span style="color:#bf616a;">age</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(&#39;</span><span style="color:#a3be8c;">change</span><span>&#39;, </span><span style="color:#8fa1b3;">handleInput</span><span>(&#39;</span><span style="color:#a3be8c;">age</span><span>&#39;)); -</span></code></pre> -<p>Now you can see our function handles the one special exception on the &quot;checkbox&quot; via an optional argument instead of a ternary. <strong>Slick!</strong></p> -<p>This becomes a really nice pattern when working in React or other virtual DOMs since your function can really only access the data directly available to it. Here is the same thing wrapped up to use just a single class method to manage the state for 3 different inputs:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#b48ead;">import </span><span style="color:#bf616a;">React </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">react</span><span>&#39;; -</span><span style="color:#b48ead;">import </span><span style="color:#bf616a;">ReactDOM </span><span style="color:#b48ead;">from </span><span>&#39;</span><span style="color:#a3be8c;">react-dom</span><span>&#39;; -</span><span> -</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Form </span><span style="color:#b48ead;">extends </span><span style="color:#eff1f5;">React.</span><span style="color:#a3be8c;">Component </span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> </span><span style="color:#bf616a;">state </span><span>= </span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> username: </span><span style="color:#d08770;">null</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> accept: </span><span style="color:#d08770;">false</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> age: </span><span>&#39;</span><span style="color:#a3be8c;">below 18</span><span>&#39;</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> } -</span><span style="color:#eff1f5;"> -</span><span style="color:#eff1f5;"> </span><span style="color:#8fa1b3;">handleInput </span><span>= (</span><span style="color:#bf616a;">name</span><span style="color:#eff1f5;">, </span><span style="color:#bf616a;">prop </span><span>= &#39;</span><span style="color:#a3be8c;">value</span><span>&#39;) </span><span style="color:#b48ead;">=&gt; </span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span>(</span><span style="color:#eff1f5;">{ </span><span style="color:#bf616a;">target </span><span style="color:#eff1f5;">}</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> </span><span style="color:#bf616a;">this</span><span style="color:#eff1f5;">.</span><span style="color:#8fa1b3;">setState</span><span style="color:#eff1f5;">({ -</span><span style="color:#eff1f5;"> [</span><span style="color:#bf616a;">name</span><span style="color:#eff1f5;">]: </span><span style="color:#bf616a;">target</span><span style="color:#eff1f5;">[</span><span style="color:#bf616a;">prop</span><span style="color:#eff1f5;">] -</span><span style="color:#eff1f5;"> }); -</span><span style="color:#eff1f5;"> } -</span><span style="color:#eff1f5;"> } -</span><span style="color:#eff1f5;"> -</span><span style="color:#eff1f5;"> </span><span style="color:#8fa1b3;">render</span><span>() </span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">return </span><span style="color:#eff1f5;">(&lt;div className=</span><span>&#39;</span><span style="color:#a3be8c;">form</span><span>&#39;</span><span style="color:#eff1f5;">&gt; -</span><span style="color:#eff1f5;"> </span><span>&lt;</span><span style="color:#bf616a;">input type</span><span>=&#39;</span><span style="color:#a3be8c;">text</span><span>&#39; </span><span style="color:#bf616a;">onInput</span><span>=</span><span style="color:#eff1f5;">{this.handleInput(</span><span>&#39;</span><span style="color:#a3be8c;">username</span><span>&#39;</span><span style="color:#eff1f5;">)} /&gt; -</span><span style="color:#eff1f5;"> &lt;input type=</span><span>&#39;</span><span style="color:#a3be8c;">checkbox</span><span>&#39;</span><span style="color:#eff1f5;"> onChange={this.handleInput(</span><span>&#39;</span><span style="color:#a3be8c;">accept</span><span>&#39;</span><span style="color:#eff1f5;">, </span><span>&#39;</span><span style="color:#a3be8c;">checked</span><span>&#39;</span><span style="color:#eff1f5;">)} /&gt; -</span><span style="color:#eff1f5;"> &lt;select onChange={this.handleInput(</span><span>&#39;</span><span style="color:#a3be8c;">age</span><span>&#39;</span><span style="color:#eff1f5;">)}&gt; -</span><span style="color:#eff1f5;"> &lt;option value=</span><span>&#39;</span><span style="color:#a3be8c;">below 18</span><span>&#39;</span><span style="color:#eff1f5;">&gt;below 18&lt;/option&gt; -</span><span style="color:#eff1f5;"> &lt;option value=</span><span>&#39;</span><span style="color:#a3be8c;">above 18</span><span>&#39;</span><span style="color:#eff1f5;">&gt;above 18&lt;/option&gt; -</span><span style="color:#eff1f5;"> &lt;/select&gt; -</span><span style="color:#eff1f5;"> {</span><span style="color:#65737e;">/* shows the state in the document body - not required */</span><span style="color:#eff1f5;">} -</span><span style="color:#eff1f5;"> &lt;pre&gt;&lt;code&gt;{JSON.stringify(this.state)}&lt;/code&gt;&lt;/pre&gt; -</span><span style="color:#eff1f5;"> &lt;/div&gt;); -</span><span style="color:#eff1f5;"> } -</span><span style="color:#eff1f5;">} -</span><span style="color:#eff1f5;"> -</span><span style="color:#eff1f5;">ReactDOM.render(&lt;Form /&gt;, document.body); -</span></code></pre> -<p><em><a href="https://6nv8q86yk3.codesandbox.io/">View this example on codesandbox.</a></em></p> -<p>This is where I think curried functions really shine. This is a very compact way to support a lot of different form items but they can all use the same function even though they may all be unique items. Obviously, you can use this for any type of event handlers. Hopefully you can see the flexibility here and the potential to make simpler code that is more usable.</p> -<h3 id="in-summation">In summation</h3> -<p>So next time you see some of these patterns pop up in your code (or some smells with global variables and repeated code) maybe reach for a curried function instead. <em>Delicious</em>.</p> - - - - - When And Where To Compromise Your App Design - 2018-10-21T00:00:00+00:00 - 2018-10-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/when-and-where-to-compromise-your-app-design/ - - <p><em><strong>You won't believe number 3!</strong></em></p> -<p>When initially developing an application or a new feature we are constantly trying to balance best practices in system/application design with delivering a finished product. Usually, things like scaling or flexibility become a secondary thought or something that would be <em>&quot;nice to have, if we had the time&quot;</em>.</p> -<p>Given those restrictions or that type of scenario, we need to pick places to <em>compromise on purpose</em>. We need to actively choose a section of the app to cut corners and hide garbage. Fun! Right?</p> -<blockquote> -<p>Should we hardcode this? Should we store this in a column instead of a pivot table? Can we stick it in the HTML for now?</p> -</blockquote> -<p>We have all heard (or spoken) these types of phrases at some point. If you haven't, well it must be nice to live in a fantasy land full of rainbows, puppies, and perfectly designed systems.</p> -<p>So here is my list of the top 3 places you should not compromise when getting stuck in a situation where compromise is necessary.</p> -<h3 id="3-http-json-api-design">3. HTTP JSON API design</h3> -<p>Building a crappy API for you service or application can be crippling. It can stifle updates, cause a lot of &quot;patch&quot; code inside consumers and clients to fix issues, create uncomfortable conversations with customers who need to update (because you didn't think about versioning), and even force you to maintain multiple implementations of similar end-points that are almost identical because you had to fill in gaps after the fact.</p> -<p>Think of HTTP APIs as contracts. I mean literal contracts not just &quot;code contracts&quot; or interfaces.</p> -<p>What happens if you enter into an agreement with someone and the contract is missing key information? You could end up really missing out on a lot, or worse still, <em>being sucked into something that you didn't understand you were getting into with no feasible escape</em> beyond writing a new contract and getting every single party to sign again. Gross.</p> -<p>My advice would be to spend more time on designing a robust and verbose API with lots of tests and less time on organizing things behind the scenes. Organization can always come later. I would edge more towards including more information than you need in a response than you are actually using. Maybe even including keys that are empty if you know you are going to need to use them in the near future. Consider using &quot;501 Not Implemented&quot; responses for endpoints that are coming in the future.</p> -<p>Changing an API once it's in the wild is difficult and annoying. If you have a single-page app, this could mean refactoring major parts of the front-end to work with new datasets and responses. So consider what you need now, and in the near future, when designing that JSON response object. Also, try to keep your types consistent. Having to deal use with mixed types and constant variation of types is incredibly painful and adds a lot of type checking for API consumers!</p> -<h3 id="2-leveraging-vendor-code-tools">2. Leveraging vendor code/tools</h3> -<p>As developers we like making stuff. Sometimes that stuff is mixed quality, quickly assembled, or just plain buggy. In the last 10 years or so, the quality and volume of excellent production-grade open source projects has been phenomenal. Unless you are on the bleeding edge of some burgeoning industry or technology, there is probably an open source equivalent of pretty much everything you need.</p> -<p>I've typically seen people develop their own solutions and spend a lot of energy thinking about problems that someone has already solved. On top of that, instead of choosing a package or service that provides all they need, they decided on a DIY solution or a patchwork of different offerings. Too many times I've seen a project rapidly go from a bespoke group of loose packages to a company's main offering with a handful of developers charged to take care of it.</p> -<p>It's not just server-side apps, I've seen this a lot in front-end development. I've done it to myself a dozen times. I can't even count the number of hours I spent building my own sliders that were never really perfect and often had bugs in some specific browser because I didn't test it enough. Whoops!</p> -<p>When it comes to support infrastructure like queues, caches, CDNs, and databases, unless you have the time or need, you shouldn't be spinning up your own on a dedicated host. There are a lot of services that are reasonably priced that can manage all that for you. It's hard to set things up correctly (secure defaults, scalability, interoperability, etc) with tight time frames. So don't bother. Choose your flavour of hosting and pick the service that fits your needs and move on. Use those saved hours for more development time. Like organization of code, you can always swap things later when you find the time or you grow to a place where it matters.</p> -<h3 id="1-database-design">1. Database design</h3> -<p>This is my number one place to spend all your time. Doing database tweaks early on is not too hard. But once you start getting a few hundred thousand rows, data in the gigabytes, or distributed databases, migrations become super painful. They need to be meticulously programmed, tested, and then timed properly in order not to break things or take the service down.</p> -<p>I don't have much experience with no-SQL databases at scale. So I can't really speak to much on them. But generally, I think the advice is similar to relational database design: big migrations are painful. So try to make sure you have a scalable and sustainable design is important.</p> -<p>Regarding relation data stores, my advice is mostly about relationships. Too often I see things like <code>address1</code>, <code>address2</code>, <code>address3</code>, etc. You get the deal. Instead of using a pivot table, someone decided it was too much work. So now you are growing the table horizontally and with each row instead of, get this, <em>being relational</em>.</p> -<p>Another one I've been bit by is using one-to-many relationships instead of designing for many-to-many with a <code>LIMIT 1</code> by default. It is so much easier to turn a many-to-many into a one-to-many than it is to do the reverse. How many times have you made something and then a week or a month later someone from product development or business asks <em>&quot;how hard it would be to make it so that X could have many Y's?&quot;</em> Only to be met with you screaming at the top of your lungs because you need to not only migrate the existing database but update all the code as well.</p> -<h3 id="in-summation">In Summation</h3> -<p>I've really become a fan of &quot;where should we hide our garbage?&quot;. It's always going to happen so we should figure out where to put the mess so that it's either easy to clean up or it isn't somewhere critical.</p> - - - - - Using Sqlite As A Cache In Laravel - 2018-10-04T00:00:00+00:00 - 2018-10-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/laravel-sqlite-cache/ - - <p>I was playing with a new project using <a href="https://laravel.com/docs/5.7">Laravel 5.7</a> and I wanted to use <a href="https://www.sqlite.org/index.html">sqlite</a> for the <a href="https://laravel.com/docs/5.7/cache">cache feature</a> that comes with the framework. If you didn't know, Laravel allows you to choose a cache &quot;driver&quot; and Laravel will handle writes, reads, and even locks, using that cache.</p> -<p>By default, Laravel includes drivers for &quot;database&quot;, &quot;redis&quot;, &quot;memcached&quot;, &quot;file&quot;, and &quot;array&quot;. In typical Laravel fashion, you can even <a href="https://laravel.com/docs/5.7/cache#adding-custom-cache-drivers">write your own drivers</a>. All you need to do is implement the <code>Illuminate\Contracts\Cache\Store</code> interface using the technology you wish to turn into a cache driver.</p> -<p>To my surprise, the &quot;database&quot; cache driver does not support multiple database connections. Which means whatever <code>DB_CONNECTION</code> you are using is also going to be used for the cache database driver when it is selected. I took a look to see how the &quot;database&quot; driver was implemented (it's in the <code>Illuminate\Cache\DatabaseStore</code> class) to see if there is any way I can make the database driver use a different connection than the one set in <code>DB_CONNECTION</code>. Looking deeper, it turns out that by extending <code>Illuminate\Cache\DatabaseStore</code>, I was able to put together a &quot;sqlite&quot; cache driver really quickly.</p> -<p>Before you set this all up, make sure you create the empty database. From the root of your project run: <code>touch database/cache.sqlite</code>. This creates an empty file that sqlite will mount as the database. To learn more about sqlite and how the file works, check out the page at the sqlite site about the <a href="https://www.sqlite.org/onefile.html">Single-file Cross-platform Database</a>.</p> -<p>For the next step, you will need to make sure that the <code>sqlite3</code> command line tool is installed. If you need help getting that going, <a href="https://www.sqlite.org/download.html">check out the download page for the tool</a>. Once that is installed, or if you already have it, you can then connect to the database file via the sqlite cli: <code>sqlite3 database/cache.sqlite</code>. If you want more info about the CLI tool, <a href="https://www.sqlite.org/cli.html">check out the sqlite docs</a>.</p> -<p>Finally, you need to setup that sqlite database by executing the following SQL statement:</p> -<pre data-lang="sql" style="background-color:#2b303b;color:#c0c5ce;" class="language-sql "><code class="language-sql" data-lang="sql"><span style="color:#b48ead;">CREATE TABLE </span><span>`</span><span style="color:#8fa1b3;">cache</span><span>` ( -</span><span> `</span><span style="color:#a3be8c;">key</span><span>` STRING </span><span style="color:#b48ead;">PRIMARY KEY</span><span>, -</span><span> `</span><span style="color:#a3be8c;">value</span><span>` </span><span style="color:#b48ead;">TEXT </span><span>NOT </span><span style="color:#d08770;">NULL</span><span>, -</span><span> `</span><span style="color:#a3be8c;">expiration</span><span>` </span><span style="color:#b48ead;">INT DEFAULT </span><span style="color:#d08770;">0 -</span><span>); -</span></code></pre> -<p>This statement will update the sqlite database with the required table and columns. This SQL is the same statement that runs when the cache migrations are created via the <code>artisan</code> command. To see that the SQL statement worked, you can run the <code>.tables</code> command. You should see <code>cache</code> in the list. To close out of the sqlite cli, run <code>.exit</code>.</p> -<p>Now that everything is all setup, here is the class that implements the <code>SqliteStore</code> that allows for a sqlite driver:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">// put in app\Extensions -</span><span style="color:#b48ead;">namespace </span><span>App\Extensions; -</span><span> -</span><span style="color:#b48ead;">use </span><span>Illuminate\Cache\</span><span style="color:#ebcb8b;">DatabaseStore</span><span>; -</span><span style="color:#b48ead;">use </span><span>Illuminate\Support\Facades\</span><span style="color:#ebcb8b;">Config</span><span>; -</span><span> -</span><span style="color:#65737e;">/** -</span><span style="color:#65737e;"> * SqliteStore delegates to DatabaseStore but with an sqlite connection instead -</span><span style="color:#65737e;"> */ -</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">SqliteStore </span><span style="color:#b48ead;">extends </span><span style="color:#a3be8c;">DatabaseStore -</span><span style="color:#eff1f5;">{ -</span><span style="color:#eff1f5;"> </span><span style="color:#b48ead;">public function </span><span style="color:#96b5b4;">__construct</span><span style="color:#eff1f5;">() -</span><span style="color:#eff1f5;"> { -</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">// load the config or use the default -</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">config </span><span>= </span><span style="color:#bf616a;">config</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">cache.stores.sqlite</span><span>&#39;</span><span style="color:#eff1f5;">, [ -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">driver</span><span>&#39; =&gt; &#39;</span><span style="color:#a3be8c;">sqlite</span><span>&#39;</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">table</span><span>&#39; =&gt; &#39;</span><span style="color:#a3be8c;">cache</span><span>&#39;</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">database</span><span>&#39; =&gt; </span><span style="color:#bf616a;">env</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">CACHE_DATABASE</span><span>&#39;</span><span style="color:#eff1f5;">, </span><span style="color:#bf616a;">database_path</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">cache.sqlite</span><span>&#39;</span><span style="color:#eff1f5;">)), -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">prefix</span><span>&#39; =&gt; &#39;&#39;</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> ]); -</span><span style="color:#eff1f5;"> -</span><span style="color:#eff1f5;"> </span><span style="color:#65737e;">// Set the temporary configuration -</span><span style="color:#eff1f5;"> </span><span style="color:#ebcb8b;">Config</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">set</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">database.connections.sqlite_cache</span><span>&#39;</span><span style="color:#eff1f5;">, [ -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">driver</span><span>&#39; =&gt; &#39;</span><span style="color:#a3be8c;">sqlite</span><span>&#39;</span><span style="color:#eff1f5;">, -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">database</span><span>&#39; =&gt; $</span><span style="color:#bf616a;">config</span><span style="color:#eff1f5;">[</span><span>&#39;</span><span style="color:#a3be8c;">database</span><span>&#39;</span><span style="color:#eff1f5;">], -</span><span style="color:#eff1f5;"> </span><span>&#39;</span><span style="color:#a3be8c;">prefix</span><span>&#39; =&gt; $</span><span style="color:#bf616a;">config</span><span style="color:#eff1f5;">[</span><span>&#39;</span><span style="color:#a3be8c;">prefix</span><span>&#39;</span><span style="color:#eff1f5;">], -</span><span style="color:#eff1f5;"> ]); -</span><span style="color:#eff1f5;"> -</span><span style="color:#eff1f5;"> </span><span>$</span><span style="color:#bf616a;">connection </span><span>= </span><span style="color:#bf616a;">app</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">db</span><span>&#39;</span><span style="color:#eff1f5;">)-&gt;</span><span style="color:#bf616a;">connection</span><span style="color:#eff1f5;">(</span><span>&#39;</span><span style="color:#a3be8c;">sqlite_cache</span><span>&#39;</span><span style="color:#eff1f5;">); -</span><span style="color:#eff1f5;"> </span><span style="color:#bf616a;">parent</span><span style="color:#eff1f5;">::</span><span style="color:#bf616a;">__construct</span><span style="color:#eff1f5;">(</span><span>$</span><span style="color:#bf616a;">connection</span><span style="color:#eff1f5;">, </span><span>$</span><span style="color:#bf616a;">config</span><span style="color:#eff1f5;">[</span><span>&#39;</span><span style="color:#a3be8c;">table</span><span>&#39;</span><span style="color:#eff1f5;">], </span><span>$</span><span style="color:#bf616a;">config</span><span style="color:#eff1f5;">[</span><span>&#39;</span><span style="color:#a3be8c;">prefix</span><span>&#39;</span><span style="color:#eff1f5;">]); -</span><span style="color:#eff1f5;"> } -</span><span style="color:#eff1f5;">} -</span></code></pre> -<p>You may need to run <code>composer dumpautoload</code> in order for the new class to be picked up if you are creating the <code>app/Extensions</code> folder for the first time.</p> -<p>Then, add the following to your <code>AppServiceProvider@boot</code>:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>// AppServiceProvider@boot -</span><span>\Cache::extend(&#39;sqlite&#39;, function ($app) { -</span><span> return \Cache::repository(new \App\Extensions\SqliteStore); -</span><span>}); -</span></code></pre> -<p>That's it for the code side. But we still need to setup the config so the driver details exist. Open up the <code>config/cache.php</code> file. Add these details so the config can be properly loaded:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>// ... the rest of config/cache.php -</span><span>&#39;sqlite&#39; =&gt; [ -</span><span> &#39;driver&#39; =&gt; &#39;sqlite&#39;, -</span><span> &#39;table&#39; =&gt; &#39;cache&#39;, -</span><span> &#39;database&#39; =&gt; env(&#39;CACHE_DATABASE&#39;, database_path(&#39;cache.sqlite&#39;)), -</span><span> &#39;prefix&#39; =&gt; &#39;&#39;, -</span><span>], -</span></code></pre> -<p>You can then update your <code>.env</code> to have <code>CACHE_DRIVER=sqlite</code> and everything should be good!</p> - - - - - Angular (v1.x) Through iFrame - 2018-04-29T00:00:00+00:00 - 2018-04-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/angular-through-iframe/ - - <p><strong>Note</strong>: This is an older project/repo that uses the previous version of Angular (v1.x) so keep that in mind.</p> -<p>I was working on a project that required users to generate content that had dynamic templates. The goal was to make the entire thing was just a javascript client application and not have to worry about adding a server for handling the updates. Even better, if we could avoid AJAX requests for each keypress to update the content of the template, that would be even better.</p> -<p>This was a perfect problem for an iframe to solve as we had remote HTML files that would be loaded into an editor and then you can edit the data insde them. When you were finished, you could generate a PDF from the content inside the iframe. It was pretty sweet and worked really well.</p> -<p>While writing the app, I had to find a way to get data from the parent window into the iframe. If you didn't know this, you can actually call <code>window</code> functions on an iframe you own (one that has <code>sandbox=&quot;allow-same-origin allow-scripts&quot;</code> set on the tag and is the same domain) which will allow you to send data down to the iframe and have it run events, change code, or in my case, trigger <code>$scope</code> updates.</p> -<div class="center"> - <img src="/images/angular-iframe.gif" alt="angular through iframe demo"> - <p><small>Angular Through iFrame Preview</small></p> -</div> -<p>All we need to do is access the <code>contentWindow</code> (on our parent iframe) property and call functions that exist in the <code>window</code> of the child iframe.</p> -<p>This looks like the following:</p> -<p><strong>Parent page:</strong></p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// somewhere in the parent main js file probably another controller -</span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">iframe </span><span>= document.</span><span style="color:#96b5b4;">getElementById</span><span>(&#39;</span><span style="color:#a3be8c;">iframe</span><span>&#39;); -</span><span style="color:#bf616a;">iframe</span><span>.</span><span style="color:#bf616a;">contentWindow</span><span>.</span><span style="color:#8fa1b3;">update</span><span>(</span><span style="color:#bf616a;">dataFromParent</span><span>); -</span></code></pre> -<p><strong>Child iframe page:</strong></p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// inside the iframe controller -</span><span>window.</span><span style="color:#8fa1b3;">update </span><span>= </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">dataFromParent</span><span>) { -</span><span> </span><span style="color:#bf616a;">$scope</span><span>.</span><span style="color:#8fa1b3;">$apply</span><span>(</span><span style="color:#b48ead;">function</span><span>() { -</span><span> </span><span style="color:#65737e;">// replace the scope with an object from the parent -</span><span> </span><span style="color:#bf616a;">$scope </span><span>= </span><span style="color:#bf616a;">dataFromParent</span><span>; -</span><span> }); -</span><span>}; -</span></code></pre> -<p>As you can see, this is incredibly simple and allows for <em>write-only</em> control of an iframe.</p> -<p>What I did to integrate with Angular was use that window function inside the iframes controller to call <code>$scope.$apply</code> with the new data.</p> -<p>You can <a href="https://james2doyle.github.io/angular-through-iframe">see a live demo</a> of the technique or <a href="https://github.com/james2doyle/angular-through-iframe">visit the demo project on Github</a>.</p> - - - - - Nuxt Firebase Starter - 2017-12-21T00:00:00+00:00 - 2017-12-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/nuxt-firebase-starter/ - - <p>Over the past few weeks, I have been working on <a href="https://github.com/james2doyle/nuxt-firebase-auth">a boilerplate/starting template for using the Nuxt.js framework with Firebase</a>.</p> -<p>I plan to use this boilerplate for easily creating apps that require authentication, real-time feedback from the database (chats, threads, account balances, etc.), and proper modern support for the new PWA (progressive web app) conventions (service worker, offline, code-splitting, etc.) without having to worry too much about laying the ground work each time.</p> -<p>If you already know about Nuxt and Firebase, I suggest just checking out the project and playing around with it. By default, I have setup the Nuxt PWA module, social login support for Google and Github, and also setup a database convention called “accounts” where users manage their public profile inside the application.</p> -<p>If you are unfamiliar with the tools, keep reading!</p> -<p>+++</p> -<h3 id="nuxt">Nuxt</h3> -<p>As you may be able to tell from this post and some of the other posts on this site, I am also a <a href="https://vuejs.org/">Vue.js</a> fan. I have been using it since before version 1 was released.</p> -<p>About four months ago, I started using a relatively new framework created on top of Vue.js (and stolen from the <a href="https://github.com/zeit/next.js/">Next.js React project</a>) called <a href="https://nuxtjs.org/">Nuxt.js</a>.</p> -<p>Essentially, Nuxt (and Next) setup conventions for routing pages, creating components, adding “stores” (flux, redux, etc.) based on a directory setup.</p> -<p>So, for example (these are taken from the Nuxt documentation), you have a structure as follows:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>pages/ -</span><span>--| _slug/ -</span><span>-----| comments.vue -</span><span>-----| index.vue -</span><span>--| users/ -</span><span>-----| _id.vue -</span><span>--| index.vue -</span></code></pre> -<p>And this will generate the following routes automatically:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>router: { -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">routes</span><span>: [ -</span><span> { -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">name</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;index&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">path</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;/&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">component</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;pages/index.vue&#39;</span><span> -</span><span> }, -</span><span> { -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">name</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;users-id&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">path</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;/users/</span><span>:</span><span style="background-color:#bf616a;color:#2b303b;">id?&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">component</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;pages/users/_id.vue&#39;</span><span> -</span><span> }, -</span><span> { -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">name</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;slug&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">path</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;/</span><span>:</span><span style="background-color:#bf616a;color:#2b303b;">slug&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">component</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;pages/_slug/index.vue&#39;</span><span> -</span><span> }, -</span><span> { -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">name</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;slug-comments&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">path</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;/</span><span>:</span><span style="background-color:#bf616a;color:#2b303b;">slug/comments&#39;,</span><span> -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">component</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;pages/_slug/comments.vue&#39;</span><span> -</span><span> } -</span><span> ] -</span><span>} -</span></code></pre> -<p>You can then call <code>this.$route.params.slug</code> (for slug-comments) or <code>this.$route.params.id</code> (for users-id) inside the components or pages that are on that route.</p> -<p>Pretty slick right?</p> -<p>So Nuxt is cool. It lets me quickly create an SPA (single-page app) without worrying about setting up an elaborate route object or worrying about how to organize my projects folders and logic. You just toss files in folders, and everything pretty much works.</p> -<p>Some people <strong>hate this</strong> but I find the structure liberating. I plan to use that additional time/energy to focus on building my app and not organizing configs or fiddling with route logic.</p> -<p>Now onto the Firebase portion of the project:</p> -<hr /> -<h3 id="firebase">Firebase</h3> -<p>If you have some experience with <a href="https://firebase.google.com/">Firebase</a> you probably know that it is an excellent service offering some nice features for dealing with &quot;real-time&quot; data as well as some other features like storage, push notifications, serverless functions, and authentication. The best part is that it is usually free for most small projects as they don't use enough resources to qualify for the paid tiers of service.</p> -<p>When I first started using Firebase, I didn't like it. The concept of <code>snapshots</code> and paths was confusing for someone coming from a key-value store or a more traditional relational database. It also lacks higher order sorting and querying. For example, you can query for ranges, but you can't query for <code>LIKE</code> or &quot;matches/patterns”.</p> -<p>Queries like that can be limiting, but once you shift your mentality to something more like reducing and filtering, these issues disappear. You also need to be conscious about how you structure your database, <em>but I digress</em>.</p> -<p>As I got more comfortable with it, I began to see how powerful and easy it is to build things like chats, threads/commenting systems, atomic counters (“like” systems, ratings, etc.), and even used it to do browser push notifications. I am using it right now to build an API rate limiter!</p> -<hr /> -<h3 id="combining-forces">Combining Forces!</h3> -<p>Using Nuxt and Firebase together has been easy. I was able to create a nice login flow (with support for Google and Github OAuth!) within about a day.</p> -<p>I also added support for this convention called “accounts”. When a new user signs up, we create an account on the Firebase database that is read-write for that user. This object contains their display name and profile image path.</p> -<p>Since we have a profile image (either pulled from the social login or assigned a default), I figured; <em>why not add support for uploading a new profile image to the Firebase storage?</em></p> -<p>So I did! And now you can easily manage a mini-profile on the app without any extra configuration. It is there by default:</p> -<div class="center"> - <img src="/images/nuxt-firebase-account-preview.gif" alt="nuxt firebase account preview"> - <p><small>Nuxt Firebase Account Preview</small></p> -</div> -<p>As you can see from the animation above, super simple interface with live updating thanks to the bindings from our application store to the Firebase database!</p> -<p>Again, you can check out the project <a href="https://github.com/james2doyle/nuxt-firebase-auth">at the repo on Github</a>.</p> - - - - - Using DigitalOcean Spaces - 2017-11-18T00:00:00+00:00 - 2017-11-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/using-digitalocean-spaces/ - - <p>If you are a fan of <a href="https://m.do.co/c/802f151adea5">DigitalOcean</a>, or you keep an eye on DevOps news, then you probably heard about the new <a href="https://www.digitalocean.com/products/object-storage/">DigitalOcean Spaces</a> offering.</p> -<p>Spaces is essentially an AWS S3-compatible service but with that special DigitalOcean touch.</p> -<p>Now Spaces is not a 1:1 replacement for S3. There are quite a few features that have not yet been implemented. They also don't have any GUI interfaces for things like managing bucket policies or CORS configurations.</p> -<p>I made an example project for how to use Spaces with an S3 node module (<a href="https://github.com/james2doyle/digitalocean-spaces-example">which you can find here</a>) to see what the differences were when actually using the service.</p> -<p>I was really surprised to find that it was almost identical.</p> -<p>Here is an example of how you might tweak your config from an S3 project:</p> -<pre data-lang="json" style="background-color:#2b303b;color:#c0c5ce;" class="language-json "><code class="language-json" data-lang="json"><span>{ -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">region</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;nyc3&#39;,</span><span> </span><span style="color:#65737e;">// very familiar setting -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">endpoint</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;https</span><span>:</span><span style="color:#65737e;">//nyc3.digitaloceanspaces.com&#39;, // something you probably ignored before -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">signatureVersion</span><span>: </span><span style="background-color:#bf616a;color:#2b303b;">&#39;v4&#39;,</span><span> </span><span style="color:#65737e;">// spaces supports the V4 API -</span><span> </span><span style="background-color:#bf616a;color:#2b303b;">s3DisableBodySigning</span><span>: </span><span style="color:#d08770;">true </span><span style="color:#65737e;">// not sure if this is always required... -</span><span>} -</span></code></pre> -<p>As you can see there is really nothing different. The main thing is <code>endpoint</code>. That controls the service basically. By default in S3 it is set to <code>{service}.{region}.amazonaws.com</code>.</p> -<p>Other than that, there was really nothing much different from the regular S3 usage. I found that DigitalOcean was <em>much easier</em> to get started with. I created a bucket in a few seconds and generated an API key right away.</p> -<p>With S3, you usually have a few steps to create the bucket, then create an IAM user for the bucket, then update the policy, and finally, update the CORS configuration to allow the referrers and methods you want. That's a lot of steps.</p> -<p>If you need that level of control I would still recommend S3. But if you have a simple use-case like a public CDN, I think DigitalOcean Spaces would be a great option.</p> - - - - - 3 Ways A Website Project Falls Apart - 2017-07-29T00:00:00+00:00 - 2017-07-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/3-ways-a-website-project-falls-apart/ - - <div class="center"> - <img src="/images/broken-glass.jpg" alt="Mens shoes on broken glass. Photo by veeterzy on Unsplash"> - <p><small>Photo by <a href="https://unsplash.com/photos/afq5-t0ZGtQ?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">veeterzy</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p> -</div> -<p>Over the last seven years, I have been involved in hundreds of website projects.</p> -<p>I've worked with a ton of different clients on various types and sizes. From design/branding projects all the way to sophisticated web applications. From a couple of thousand dollars to hundreds of thousands of dollars.</p> -<p>While working on these projects, I noticed that whenever a project started to go sideways, I can trace back the timeline to a single point, or a single reason, where the pain began to manifest.</p> -<p>So, here are the top 3 reasons (in my opinion) that will cause a website project to fall apart.</p> -<h2 id="3-confusion-around-responsibilities">3. Confusion Around Responsibilities</h2> -<p>Now, this may sound vague, and you can probably assign all these reasons below to this category - but - let me lay out some sub-reasons that will break this down a little better.</p> -<h3 id="technical-misunderstandings">Technical Misunderstandings</h3> -<p>Who owns the domain? Who owns the hosting? Where is the email managed? Which company (yours or mine) pays the hosting bill? How do updates work? What about maintenance?</p> -<p>All these questions fall under this sub-reason of responsibilities. These are vital points that need to be answered <em>before a project starts</em>. In my experience, these issues tend to arise when you have a client that has no experience with online projects or lacks a general understanding of how a website hierarchy works. They probably don't understand how a domain works or how their email is setup.</p> -<p>These are usually non-issues if you are working with a client that has a technical team in place for any existing web properties.</p> -<p>For me, these issues tend to become quite a pain as hosting can stop a project in its tracks. Without a place to put the site, we cannot finish. Same rules apply to a domain. Without one, we cannot continue.</p> -<p>For email, it is typical for a website to have a contact form of some sort or maybe they have a public email listed in the footer or on a contact page. Finding out that the email doesn't exist or that the access to that email is lost, will crush a client and make you seem completely incompetent.</p> -<blockquote> -<p>Finding out that the email doesn't exist or that the access to that email is lost, will crush a client and make you seem completely incompetent.</p> -</blockquote> -<p>You may say that you &quot;don't control the email&quot; or &quot;it is managed with the hosting company&quot;. You can even say &quot;they already had a hosting company when they came to us&quot;.</p> -<p>The problem is that the client doesn't usually care about the reason why the problem occurred. Even if you can say, it is their fault. They care that <em>you let it slip by</em> and the fact <em>that it became an issue at all</em>. It means you missed something.</p> -<h3 id="assumptions">Assumptions</h3> -<p>A lot of project managers will encounter this one.</p> -<p>The client believes that there are a certain number of things that &quot;should just be included&quot;. They will never vocalise or acknowledge these things until they come up. I call these &quot;Oh, By The Way&quot; moments. Mainly, they will say they forgot something, or they will slide something in last minute under the veil of it being obvious or assumed it would be included.</p> -<p>One of the most common ones that I have seen fall under this umbrella would be things like setting up email for a new domain, providing hosting (for free or not), and on-going support.</p> -<p>You should make a list of items that you find people believe will be included and make those deliverables explicitly included or not.</p> -<p>I suggest having a section in your scope of work (S.O.W) for &quot;included items&quot; and &quot;excluded items&quot;. Be sure to walk through these things with the client. If they point out something that they don't agree with, then that is the perfect time to discuss it. Don't assume anything.</p> -<blockquote> -<p>Be sure to walk through these things with the client. If they point out something that they don't agree with, then that is the perfect time to discuss it</p> -</blockquote> -<p>The includes/excludes discussion can sometimes be painful and even uncomfortable. Especially if you have a client that is already sensitive to budget. You are pointing out all the things they are not getting. Depending on the size of the list, it can easily be longer than the list of deliverables.</p> -<h2 id="2-arbitrary-deadlines">2. Arbitrary deadlines</h2> -<p>This one kills me. Always question deadlines. A time limit must be tied to a real-world (physical event or the business opening) reason. Do not make deadlines because you think you will be done at a particular time or just because someone asks you to set a date for when it will be done.</p> -<p>Also, be wary of a deadline that is tied to an item that can be easily moved. I once had a client who wanted their website done in time for a newsletter. Just a newsletter. It wasn't an annual report or anything, just a general newsletter. And guess what? They moved the date back at least three times.</p> -<blockquote> -<p>Also, be wary of a deadline that is tied to an item that can be easily moved.</p> -</blockquote> -<p>We rushed to get it done for the initial date, but the date passed, and there were no consequences. Instead, we just waited and waited to go live. Eventually, they managed to get their newsletter done, and we went live. But it was three weeks later. We could have used that time for something else other than just waiting.</p> -<p>When a future date becomes established, the pressure is on. It can be a great milestone moment when you hit a deadline (early or right on). But it can become a real challenge when the client cannot hold up their end of the deal. Whether that is paying on time, providing information (logins, hosting access, domain details, etc.) or attending important meetings/status updates. The project requires both parties to be on the ball and bringing their A-game all the time.</p> -<blockquote> -<p>The project requires both parties to be on the ball and bringing their A-game all the time.</p> -</blockquote> -<p>The only caveat here is if you charge for time. Then you have to set a deadline or discuss the monthly/hourly cost. But in the billable-time scenario, the only person that is watching and monitoring time is the client. They become the advocate for speed and efficiency. You just work away until the budget runs out or the project becomes finalised.</p> -<p>Everyone understands that concept of cutting corners. When you promised to get something done at a particular time and things didn't quite move as quickly as you would have hoped, or you encountered some friction with a piece of the project, you rush. You reduce certain things to their bare essentials or you slash hours in one area to compensate in another. You miss things (remember the email section?), and then you fall behind.</p> -<p>Another downside to this is that if there are delays for either party or just general hiccups, the problem becomes compounded. Now both sides are rushing since one side held up the other. This is not a great place to be.</p> -<p>A general rule to follow is to establish the deadline on when the client will be satisfied with the project and within a timeframe that you know you can deliver. That could mean 100%, or that could mean 75%. Either way, do it right and don't rush because of some misplaced trust in &quot;setting deadlines&quot; or having an obvious &quot;finish&quot; date in mind.</p> -<h2 id="1-content">1. Content</h2> -<p>Content is by far the biggest culprit for project failures. Who is generating the content? Who enters the content? What happens if it is missing or incomplete? What if the content isn't ready in time? How does the blog look if we have no posts? When are we replacing the placeholder images?</p> -<p>All these questions should, again, be answered <em>before the project starts</em>. I have never encountered so much anxiety and frustration when talking to a client about content. These feelings come from both sides.</p> -<p>Often, they believe they can write the content themselves. That can sometimes be the case. But it is a lot more complicated than people understand. Unless you have a marketing team or have an experienced copy writer, do not rely on the word of the client that the content will be done on time and in perfect format.</p> -<blockquote> -<p>Imagine the first thing a potential client sees on your site is mistakes, placeholder copy, or missing/broken links. That would not be a great first impression.</p> -</blockquote> -<p>Another concern when it comes to content is that there are a lot of people out there that do not want to pay someone to write their content. I have no idea where this mindset comes from. But you can see it in the result. When people describe their own business, it is often too verbose, too deep, and littered with industry speak. Sometimes that is great (looking at you, lawyers!) but it usually makes the site difficult to read and will affect the performance of the site in search rankings.</p> -<p>The solution is to hire a copy writer or at least hire someone to run through the content after you believe it to be complete. Not only will you have the peace of mind that it is correct (both grammatically and in the communication sense) but you can publish it without getting an email the day of the launch where someone points out errors in your site.</p> -<p>Imagine the first thing a potential client sees on your site is mistakes, placeholder copy, or missing/broken links. That would not be a great first impression.</p> -<h2 id="conclusion">Conclusion</h2> -<p>The pattern here is that <em>discovery is your friend</em>. What is a discovery? Discovery is a service (paid or included/unpaid) that will take an inventory and assessment of the client, the project, the business, the competitors, and the target industry for that project.</p> -<p>Even a simple checklist or survey can act as a project discovery. You can ask the questions about email and hosting, content, timelines or deadlines, and the holy grail: <em>budget</em>.</p> -<p>I don’t know how many times I have said, &quot;we should have found this in discovery&quot; when lamenting the fact that we rushed to start a project and skipped some of the upfront due diligence.</p> -<p>If you are a client, and you are reading this to educate yourself, then hear this: a discovery <em>will save you money</em>. Why? Well, you can reduce timeline (get ahead of problems), understand your role and the required tasks for you and your team, and accept or deny any recommendations from the service provider before its too late.</p> -<p>How about just generally auditing the competence of the people who are about to become entangled with you and your business for months or possibly years?</p> -<p>If that doesn't sound like a good idea, then I don’t know what is.</p> - - - - - Handle spaces and no-spaces in MySQL where-like clauses - 2017-07-08T00:00:00+00:00 - 2017-07-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/handle-spaces-and-no-spaces-in-mysql-where-like-clauses/ - - <p>Have you ever been writing a search for MySQL and had an issue where the search wouldn't handle spaces properly?</p> -<p>I was writing a search for a <code>users</code> table and wanted to find a user by their first name or last name or a combination of both.</p> -<p>I started with a query like this:</p> -<pre data-lang="sql" style="background-color:#2b303b;color:#c0c5ce;" class="language-sql "><code class="language-sql" data-lang="sql"><span style="color:#b48ead;">SELECT </span><span style="color:#bf616a;">* </span><span style="color:#b48ead;">FROM </span><span>`</span><span style="color:#a3be8c;">users</span><span>` -</span><span> </span><span style="color:#b48ead;">WHERE </span><span style="color:#96b5b4;">LOWER</span><span>(`</span><span style="color:#a3be8c;">users</span><span>`.`</span><span style="color:#a3be8c;">first_name</span><span>`) -</span><span> LIKE </span><span style="color:#96b5b4;">LOWER</span><span>(:searchTerm) -</span><span> OR </span><span style="color:#96b5b4;">LOWER</span><span>(`</span><span style="color:#a3be8c;">users</span><span>`.`</span><span style="color:#a3be8c;">last_name</span><span>`) -</span><span> LIKE </span><span style="color:#96b5b4;">LOWER</span><span>(:searchTerm) -</span></code></pre> -<p>Here is the list for the matches, given that there is a user with the <code>first_name</code> of &quot;James&quot; and <code>last_name</code> of &quot;Doyle&quot;:</p> -<ul> -<li>&quot;%james%&quot; - <strong>match</strong></li> -<li>&quot;%doyle%&quot; - <strong>match</strong></li> -<li>&quot;%james d%&quot; - <em>no match</em></li> -<li>&quot;%j doyle%&quot; - <em>no match</em></li> -<li>&quot;%james doyle%&quot; - <em>no match</em></li> -</ul> -<p>The issue comes in when you add spaces into the search query. I didn't want to split the word into an array and do a search for each word. That would require querying the database multiple times. And I don't want to try to do <code>RLIKE</code> and all these string hacks to get this to match more accurately.</p> -<p>Well, I found this trick where you can create fake columns using the <code>CONCAT</code> and then replace any space character with <code>%</code>.</p> -<p>So if I queried like this: <code>%james doyle%</code>, that will actually become <code>%james%doyle%</code> when it gets to the actual SQL WHERE query.</p> -<p>This allows you to <em>get a better match more often</em> if the user types in more content in a query with a space.</p> -<pre data-lang="sql" style="background-color:#2b303b;color:#c0c5ce;" class="language-sql "><code class="language-sql" data-lang="sql"><span style="color:#b48ead;">SELECT </span><span style="color:#bf616a;">* </span><span style="color:#b48ead;">FROM </span><span>`</span><span style="color:#a3be8c;">users</span><span>` -</span><span> </span><span style="color:#b48ead;">WHERE -</span><span> </span><span style="color:#96b5b4;">LOWER</span><span>(CONCAT(`</span><span style="color:#a3be8c;">users</span><span>`.`</span><span style="color:#a3be8c;">first_name</span><span>`,`</span><span style="color:#a3be8c;">users</span><span>`.`</span><span style="color:#a3be8c;">last_name</span><span>`)) -</span><span> LIKE </span><span style="color:#96b5b4;">LOWER</span><span>(REPLACE(:searchTerm, &quot; &quot;, &quot;</span><span style="color:#a3be8c;">%</span><span>&quot;)) -</span></code></pre> -<p>Here is a list of terms that will be matched in this query:</p> -<ul> -<li>&quot;%james%&quot; - <strong>match</strong></li> -<li>&quot;%doyle%&quot; - <strong>match</strong></li> -<li>&quot;%james d%&quot; - <strong>match</strong></li> -<li>&quot;%j doyle%&quot; (becomes &quot;%j%doyle%&quot; due to <code>REPLACE</code>) - <strong>match</strong></li> -<li>&quot;%james doyle%&quot; (becomes &quot;%james%doyle%&quot; due to <code>REPLACE</code>) - <strong>match</strong></li> -</ul> -<h4 id="sidenote">Sidenote</h4> -<p>The only downside of this query is that you may get more matches if the string you are searching for is too small. Like 2 - 3 characters. At that point though, you should notify the user that they should enter in more characters to get more accurate results.</p> -<p>Another great thing is that if you added a <code>middle_name</code> column, it will handle searches where someone is searching for a known first and middle name as well. It can still match as the <code>CONCAT</code> builds a nice string to match against.</p> - - - - - WordPress Browser Body Class - 2017-06-21T00:00:00+00:00 - 2017-06-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/wordpress-browser-body-class/ - - <p>Sometimes, browsers just don't behave. While working on a WordPress site, I had a particular styling issue that was affecting Safari. I was wondering what the best way to target the browser was.</p> -<p>If you didn't know, there are some primitive <a href="https://codex.wordpress.org/Global_Variables#Browser_Detection_Booleans">browser detection booleans</a> that are built into WordPress.</p> -<p>Using those primitives, I was able to whip up a little snippet that adds classes to the body tag depending on the browser - no plugins required.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/979b09fc7c676baf3283bdb113b3db6d.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;979b09fc7c676baf3283bdb113b3db6d.js'></script>`"></iframe> -<p>As you can see, we simply loop over each one of those browser booleans and append them to the output.</p> -<p>Easy, and no plugin needed. Just add that code to your <code>functions.php</code> file.</p> - - - - - Update Laravel Pagination With New Collection - 2017-06-04T00:00:00+00:00 - 2017-06-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/update-laravel-pagination-with-new-collection/ - - <p>Have you even done a database search (using the <code>DB</code> facade) and got back an array of results that wasn't wrapped in lovely little Eloquent Models? After some googling, I am sure you probably found out about the <a href="http://commandz.io/create-models-from-query-builder/">hydrate</a> method. Using <code>hydrate</code>, you can turn an array of results from the database into real models from your application. This works phenomenally.</p> -<p>But what if you want to return paginated results? How do you used the results after they are already paginated? Well there is a method called <code>setCollection</code> on the <a href="https://laravel.com/api/5.4/Illuminate/Pagination/LengthAwarePaginator.html">LengthAwarePaginator</a> class. What it will let you do actually set the results of the pagination to a new collection.</p> -<p>Here is an example of how that works in practice:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span style="color:#ab7967;">&lt;?php -</span><span style="color:#65737e;">/** -</span><span style="color:#65737e;"> * Hydrates the paginated results for the query -</span><span style="color:#65737e;"> */ -</span><span style="color:#b48ead;">public function </span><span style="color:#8fa1b3;">simpleSearchQuery</span><span>() -</span><span>{ -</span><span> </span><span style="color:#65737e;">// contrived example of a &quot;DB search&quot; -</span><span> $</span><span style="color:#bf616a;">results </span><span>= </span><span style="color:#ebcb8b;">DB</span><span>::</span><span style="color:#bf616a;">table</span><span>(&#39;</span><span style="color:#a3be8c;">users</span><span>&#39;) -</span><span> -&gt;</span><span style="color:#bf616a;">select</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;) -</span><span> -&gt;</span><span style="color:#bf616a;">where</span><span>(&#39;</span><span style="color:#a3be8c;">email</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">LIKE</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">%@gmail.com%</span><span>&#39;) -</span><span> -&gt;</span><span style="color:#bf616a;">orderBy</span><span>(&#39;</span><span style="color:#a3be8c;">created_at</span><span>&#39;) -</span><span> -&gt;</span><span style="color:#bf616a;">paginate</span><span>(</span><span style="color:#d08770;">15</span><span>); -</span><span> -</span><span> </span><span style="color:#65737e;">// we can hydrate a model with the results from a DB call -</span><span> $</span><span style="color:#bf616a;">hydrated </span><span>= </span><span style="color:#ebcb8b;">User</span><span>::</span><span style="color:#bf616a;">hydrate</span><span>($</span><span style="color:#bf616a;">results</span><span>-&gt;</span><span style="color:#bf616a;">all</span><span>()); -</span><span> -</span><span> </span><span style="color:#65737e;">// updates the pagination with a new collection of models instead of raw DB array results -</span><span> </span><span style="color:#b48ead;">return </span><span>$</span><span style="color:#bf616a;">results</span><span>-&gt;</span><span style="color:#bf616a;">setCollection</span><span>($</span><span style="color:#bf616a;">hydrated</span><span>); -</span><span>} -</span></code></pre> -<p>Now this is a pretty simple example of how this could be used. I used this feature to search across a couple tables that didn't have models. The results were for a specific model type, but I wanted the results to be real models so that I can use nice methods on them to get relationships, trigger mutations, etc.</p> - - - - - Color Helpers In Fish Shell - 2017-06-03T00:00:00+00:00 - 2017-06-03T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/color-helpers-in-fish-shell/ - - <p>I am one of those people who likes a lot of colors in my shell. When there is a failure, I like to see red. If there is something stuccessful, I like to see green.</p> -<p>Working with the prompt to output the correct colors can be a bit of a pain. I manage to find a nice way to handle both colorizing backgrounds for the text as well as the actual text color. You can see an example below:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/acb8c065c8b4d63f557d44a77a356d59.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;acb8c065c8b4d63f557d44a77a356d59.js'></script>`"></iframe> -<p>Once you add the code into your <code>fish config</code>. You will then be able to colorize your output using this simple helper function:</p> -<pre data-lang="fish" style="background-color:#2b303b;color:#c0c5ce;" class="language-fish "><code class="language-fish" data-lang="fish"><span style="color:#bf616a;">color_print </span><span>$</span><span style="color:#bf616a;">COLOR_R </span><span>&quot;</span><span style="color:#a3be8c;">I am red text.</span><span>&quot; -</span></code></pre> -<p>You can also do a background color:</p> -<pre data-lang="fish" style="background-color:#2b303b;color:#c0c5ce;" class="language-fish "><code class="language-fish" data-lang="fish"><span style="color:#bf616a;">color_print </span><span>$</span><span style="color:#bf616a;">BG_G </span><span>&quot;</span><span style="color:#a3be8c;">I am on a green background.</span><span>&quot; -</span></code></pre> -<p>I use this helper function in my other functions to colorize output when showing different results or handling different exit codes.</p> -<p>Here is an exmple of a little function that moves a file and timestamps it:</p> -<pre data-lang="fish" style="background-color:#2b303b;color:#c0c5ce;" class="language-fish "><code class="language-fish" data-lang="fish"><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">amv -</span><span> </span><span style="color:#bf616a;">set </span><span>source $</span><span style="color:#bf616a;">argv</span><span>[</span><span style="color:#d08770;">1</span><span>] -</span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">[ </span><span>$</span><span style="color:#bf616a;">source </span><span style="color:#96b5b4;">] -</span><span> </span><span style="color:#bf616a;">set </span><span>stamp (</span><span style="color:#bf616a;">date </span><span>+%Y-%m-%d-%H-%M-%S) -</span><span> </span><span style="color:#bf616a;">set </span><span>dest &quot;$</span><span style="color:#bf616a;">stamp</span><span style="color:#a3be8c;">-</span><span>$</span><span style="color:#bf616a;">source</span><span>&quot; -</span><span> </span><span style="color:#bf616a;">mv </span><span>$</span><span style="color:#bf616a;">source </span><span>$</span><span style="color:#bf616a;">dest -</span><span> </span><span style="color:#65737e;"># output will be blue text -</span><span> </span><span style="color:#bf616a;">color_print </span><span>$</span><span style="color:#bf616a;">COLOR_B </span><span>&quot;</span><span style="color:#a3be8c;">Moved </span><span>$</span><span style="color:#bf616a;">source</span><span style="color:#a3be8c;"> to an archive at </span><span>$</span><span style="color:#bf616a;">dest</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">else -</span><span> </span><span style="color:#65737e;"># output will be yellow text -</span><span> </span><span style="color:#bf616a;">color_print </span><span>$</span><span style="color:#bf616a;">COLOR_Y </span><span>&quot;</span><span style="color:#a3be8c;">Help: amv source.zip {timestamp}-source.zip</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">end -</span><span style="color:#b48ead;">end -</span></code></pre> -<p>So you can see where this can be really helpful for when you just want to simply color some text. Instead of worrying about the color codes and escape sequences, you can use this handy helper function now.</p> - - - - - Conditional Vue.js Mixins - 2017-05-25T00:00:00+00:00 - 2017-05-25T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/conditionall-vuejs-mixins/ - - <p>When building more traditional Vue.js applications, I tend to use mixins a lot. It helps split up code, speparate concerns, and can allow for some cool tricks. One that i keep using is conditionally requiring a mixin based on the details of the page.</p> -<p>Here is an example. The first module is a function that, like <code>require</code>, will load a file into the script it is being called from. The thing is, this <code>require</code> function also takes a new parameter for checking if the page URL contains a particular string or <em>array of strings</em>.</p> -<h4 id="conditionalrequire-js">conditionalRequire.js</h4> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// loads mixins based on the current URL -</span><span>module.exports = </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">name</span><span>, </span><span style="color:#bf616a;">filename</span><span>) { -</span><span> </span><span style="color:#65737e;">// name can be an array or single string value -</span><span> </span><span style="color:#bf616a;">name </span><span>= (typeof(</span><span style="color:#bf616a;">name</span><span>) === &#39;</span><span style="color:#a3be8c;">object</span><span>&#39;) ? </span><span style="color:#ebcb8b;">Array</span><span>.</span><span style="color:#8fa1b3;">from</span><span>(</span><span style="color:#bf616a;">name</span><span>) : [</span><span style="color:#bf616a;">name</span><span>]; -</span><span> </span><span style="color:#65737e;">// are there any matches? -</span><span> </span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">check </span><span>= </span><span style="color:#bf616a;">name</span><span>.</span><span style="color:#8fa1b3;">filter</span><span>((</span><span style="color:#bf616a;">n</span><span>) </span><span style="color:#b48ead;">=&gt; </span><span>window.location.pathname.</span><span style="color:#8fa1b3;">includes</span><span>(</span><span style="color:#bf616a;">n</span><span>)).length; -</span><span> -</span><span> </span><span style="color:#65737e;">// return a no-op function for better compatibility with traditional require -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#bf616a;">check </span><span>? </span><span style="color:#96b5b4;">require</span><span>(</span><span style="color:#bf616a;">filename</span><span>) : </span><span style="color:#b48ead;">function</span><span>() { </span><span style="color:#b48ead;">return </span><span>{}; }; -</span><span>}; -</span></code></pre> -<p>The next code block below would be for you main app file. This would be the file included in all of the footers on your site. Some things to assume is these modules exist in a folder called <code>mixins/</code> and that the source files in there are returning a valid object that works as a mixin.</p> -<h4 id="index-js">index.js</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>// include the module from the above snippet -</span><span>const conditionalRequire = require(&#39;conditionalRequire&#39;); -</span><span> -</span><span>// create our Vue app -</span><span>const app = new Vue({ -</span><span> el: &quot;#app&quot;, -</span><span> data: { -</span><span> // ... -</span><span> }, -</span><span> mixins: [ -</span><span> require(&#39;mixins/global&#39;), // a regular module - loaded always -</span><span> require(&#39;mixins/navigation&#39;), // a regular module - loaded always -</span><span> conditionalRequire(&#39;home&#39;, &#39;mixins/home&#39;), // only load when on the &quot;/home&quot; page -</span><span> conditionalRequire(&#39;profile&#39;, &#39;mixins/profile&#39;), // only load when on the &quot;/profile&quot; page -</span><span> conditionalRequire([&#39;about&#39;, &#39;contact&#39;], &#39;mixins/forms&#39;) // load on &quot;/about&quot; and &quot;/contact&quot; -</span><span> ] -</span><span>}); -</span></code></pre> -<p>So you can see this can be a handy trick. You don't load code on pages you don't need it. There are ways to do this using components and <code>v-if</code> but it is a lot &quot;softer&quot; than simply never running/including code that you know is not needed.</p> - - - - - Install the latest Node.js on Amazon Linux - 2017-04-29T00:00:00+00:00 - 2017-04-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/install-latest-nodejs-on-amazon-linux/ - - <p>Installing the latest version of Node.js on the Amazon linux AMI can actually be a little painful.</p> -<p>Here is a script for doing just that.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/a1f0b415dee4e69b3595b7af1d07e7c1.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;a1f0b415dee4e69b3595b7af1d07e7c1.js'></script>`"></iframe> -<p>Hopefully, sometime soon, Amazon will stop using <code>0.10.*</code> and start using a <code>x.*</code> version by default.</p> - - - - - Running Go (golang) in Docker - 2017-04-28T00:00:00+00:00 - 2017-04-28T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/running-go-in-docker/ - - <p>Lately, I have been trying to learn golang. This means playing with a lot of tools and busting up my local environment.</p> -<p>In order to keep things simple, I have been using Docker container to run my applications when I am ready to deploy or build them.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/6489d3e60d994222ce0404c8cd500a93.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;6489d3e60d994222ce0404c8cd500a93.js'></script>`"></iframe> -<p>I use <a href="https://zeit.co/now">now.sh</a> to deploy the applications. Since now.sh support deploying Docker, using the Dockerfile approach makes it really simple to deploy.</p> - - - - - How To Use Template Strings As Modules - 2017-02-26T00:00:00+00:00 - 2017-02-26T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/es6-template-strings-as-modules/ - - <p>I had a project the other day that needed to make some HTML strings based on some other data in my code.</p> -<p>This is a standard approach when you need to write to the <code>innerHTML</code> of an element, or if you want to populate a string before writing it to the DOM or some HTML attribute.</p> -<p>I was trying to find a nice way to split up my JS - that had a bunch of functions in it - and my template string which had a bunch of parts that needed to be filled/replaced while being looped over.</p> -<p>Another thing I wanted to accomplish is to be able to find the string in my code quickly. Just in case I needed to locate it and make some more changes to it in the future. Previously, the code was sitting is some variable or some function like <code>buildElementTemplate</code> or something. But that still seemed a little gross for me.</p> -<p>Well, I found a nice way to do all of these things. By sticking the string code in a module, and returning a function, I can pass the data in as I needed. Plus, it was easy to use <code>require</code> whenever I needed that template.</p> -<h4 id="template-js">template.js</h4> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span>module.exports = </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">name</span><span>, </span><span style="color:#bf616a;">age</span><span>) { -</span><span> </span><span style="color:#b48ead;">return </span><span>`</span><span style="color:#a3be8c;">Hello ${</span><span style="color:#bf616a;">name</span><span style="color:#a3be8c;">}! You look good for ${</span><span style="color:#bf616a;">age</span><span style="color:#a3be8c;">}!</span><span>`; -</span><span>} -</span><span> -</span><span style="color:#65737e;">// or as an object -</span><span>module.exports = </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">data</span><span>) { -</span><span> </span><span style="color:#b48ead;">return </span><span>`</span><span style="color:#a3be8c;">Hello ${</span><span style="color:#bf616a;">data</span><span style="color:#a3be8c;">.name}! You look good for ${</span><span style="color:#bf616a;">data</span><span style="color:#a3be8c;">.</span><span style="color:#bf616a;">age</span><span style="color:#a3be8c;">}!</span><span>`; -</span><span>} -</span></code></pre> -<h4 id="application-js">application.js</h4> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// myTemplate will be the compiled string -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">myTemplate </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">./template</span><span>&#39;)(&#39;</span><span style="color:#a3be8c;">Billy</span><span>&#39;, </span><span style="color:#d08770;">85</span><span>); -</span><span> -</span><span style="color:#65737e;">// object style -</span><span style="color:#b48ead;">const </span><span style="color:#bf616a;">myTemplate </span><span>= </span><span style="color:#96b5b4;">require</span><span>(&#39;</span><span style="color:#a3be8c;">./template</span><span>&#39;)({ name: &#39;</span><span style="color:#a3be8c;">Billy</span><span>&#39;, age: </span><span style="color:#d08770;">85</span><span>}); -</span></code></pre> -<p>There we go! A nice way to split up some template logic and some app logic. You could probably even return an object of methods that would compile different strings for your app - think translations or different personal messages for the user.</p> - - - - - How To Use LC_MONETARY In Laravel - 2017-02-24T00:00:00+00:00 - 2017-02-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/setting-money-locale-laravel/ - - <p>If you are using the awesome <code>money_format</code> function in PHP, you may have noticed a difference between servers or environments regarding the output.</p> -<p>Sometimes you do get the trailing zeros. Sometimes not.</p> -<p>If you are ever trying to figure out what is going on here, it usually has to do with the current locale of the application. In Laravel, you can make this change in the <code>AppServiceProvider</code>:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>class AppServiceProvider extends ServiceProvider -</span><span>{ -</span><span> /** -</span><span> * Bootstrap any application services. -</span><span> * -</span><span> * @return void -</span><span> */ -</span><span> public function boot() -</span><span> { -</span><span> // set the money locale for money_format to work nicely -</span><span> setlocale(LC_MONETARY, &#39;en_US.UTF-8&#39;); -</span><span> } -</span><span> -</span><span> // ... rest of the provider -</span></code></pre> -<p>And that should be it. Now when using <code>money_format</code>, the output should be normalized.</p> - - - - - OpenSSL Passwd Without Prompt - 2016-11-22T00:00:00+00:00 - 2016-11-22T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/openssl-passwd-without-prompt/ - - <p>Have you ever wanted to generate a password using the <code>openssl passwd</code> command, but didn't want the prompt?</p> -<p>I encountered this problem when I was writing <a href="https://github.com/invokemedia/ansible-basicauth-nginx">an Ansible role for setting up Nginx basic auth</a>. I didn't want the prompt since I had no way for ansible to handle that gracefully. After some Googling, and some <code>man</code> page grepping, I found the answer.</p> -<p>You can generate a password without a prompt by piping text into <code>openssl</code> and passing a new flag. For example:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>echo &quot;password&quot; | openssl passwd -apr1 -stdin -</span></code></pre> -<p>This will echo to <code>stdout</code>. This way you can write a script or something instead of having to use the prompt to type in the password.</p> -<p>In my case of generating a basic auth password, I had to append the output to the <code>/etc/nginx/.htpasswd</code> file. That was done using the following command:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>echo &quot;password&quot; | openssl passwd -apr1 -stdin &gt;&gt; /etc/nginx/.htpasswd -</span></code></pre> -<p>There ya go. It's that easy. I have actually used this trick a couple times for generating passwords and piping to <code>pbcopy</code> (the clipboard) on my mac.</p> -<p>Pretty useful stuff!</p> - - - - - Use Nginx for A/B Testing - 2016-08-28T00:00:00+00:00 - 2016-08-28T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/use-nginx-for-a-b-testing/ - - <p>I was starting a new project the other day that had a heavy marketing influence. The team was wondering about possibly A/B testing the main content section of the website.</p> -<p>It got me thinking about doing A/B test with content and how that works.</p> -<p>There are a couple of ways that A/B testing can be accomplished. After some quick Google-ing, I found this cool feature of Nginx called <code>split_clients</code>.</p> -<p>Here is a little breakdown of that module:</p> -<blockquote> -<p>The <code>ngx_http_split_clients_module</code> module creates variables suitable for A/B testing, also known as split testing.</p> -</blockquote> -<p>There is a <a href="https://www.digitalocean.com/community/tutorials/how-to-target-your-users-with-nginx-analytics-and-a-b-testing">great article at DigitalOcean</a> about getting <code>split_clients</code> setup.</p> -<p>Nginx will allow you to apply certain variables to a segment of your traffic. For example:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>http { -</span><span> split_clients &quot;${remote_addr}&quot; $variant { -</span><span> 0.5% .one; -</span><span> 2.0% .two; -</span><span> * &quot;&quot;; -</span><span> } -</span><span> -</span><span> server { -</span><span> location / { -</span><span> index index${variant}.html; -</span></code></pre> -<p>You can break this down as:</p> -<blockquote> -<p>0.5% of the traffic will see <code>index.one.html</code>, 2.0% of the traffic will see <code>index.two.html</code>, and the rest will see <code>index.html</code>.</p> -</blockquote> -<p>Although this is perfect for serving static content, what about dynamic content? I figured this would be better if it set a <em>custom header</em> that could then be handled in a response middleware and applied to my views. Here is the resulting code for that:</p> -<h4 id="etc-nginx-conf-d-split-clients-conf-file"><code>/etc/nginx/conf.d/split-clients.conf</code> file</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>split_clients &quot;${remote_addr}&quot; $ab_test { -</span><span> # 50% of the traffic is &quot;A&quot; traffic -</span><span> 50% &quot;A&quot;; -</span><span> # the remaining traffic (the other 50%) will be set to &quot;B&quot; -</span><span> * &quot;B&quot;; -</span><span>} -</span></code></pre> -<p>If your Nginx is setup with the default <code>nginx.conf</code>, then there is an include that will autoload all <code>.conf</code> files in <code>/etc/nginx/conf.d/</code> folder.</p> -<p>So you can put files in there that you want to be globally loaded in the main <code>http {}</code> block.</p> -<h4 id="etc-nginx-sites-enabled-default-or-etc-nginx-sites-enabled-somewebsite-file"><code>/etc/nginx/sites-enabled/default</code> or <code>/etc/nginx/sites-enabled/somewebsite</code> file</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>server { -</span><span> # probably some `listen 80;` code above, and some other things -</span><span> -</span><span> # this will come out in our response headers -</span><span> add_header X-AB-Testcase $ab_test; -</span><span> -</span><span> # the rest of the code below for `location / {}` and all that... -</span><span>} -</span></code></pre> -<p>This would be the resulting response from any HTTP request:</p> -<div class="center"> - <a href="/images/nginx-ab-header.png" title="Nginx example of the custom a/b header" target="_blank"><img alt="Nginx example of the custom a/b header" src="/images/nginx-ab-header.png" ></a> -</div> -<p>Awesome! Now half of all traffic will be tagged with either <em>X-AB-Testcase: A</em> or <em>X-AB-Testcase: B</em>.</p> -<p>Finally, we could add a middleware to collect that information from the header and pass it to variables in your views, or you could use AJAX to check the headers of a piece of conditionally content.</p> -<p>The <code>split_clients</code> directive uses the IP of the request to assign it to either pool. Given this functionality, <em>if you are using shared internet connection in the office, this can be difficult to test</em>.</p> -<p>I used <code>curl</code> requests from 2 different external servers to make sure that we were getting both A and B set by various IPs.</p> - - - - - Backup MySQL And Email It - 2016-03-24T00:00:00+00:00 - 2016-03-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/backup-mysql-and-email-it/ - - <p>Recently, a friend of mine asked me what we use for managing the backups for our clients. I mentioned that we use <code>mysqldump</code> running on a <code>CRON</code> schedule. He said that he used a paid service for managing all the servers and their backups. He mentioned it sends to an Amazon S3 bucket, and also sends a notification.</p> -<p>With my setup, he noted that I could be in trouble if the hard drive failed, or the site gets wiped because my backups are stored locally right beside the site itself.</p> -<p>I thought to myself, &quot;Hmmm. What would help me back these files up, let me know that it is done, access them quickly, and also cost no money?&quot;. Email + <code>CRON</code>. Email is a pretty reasonable solution for this.</p> -<p>With Email, you get the following:</p> -<ul> -<li>A notification of when the backup is done</li> -<li>An &quot;offsite&quot; backup of the file</li> -<li>A searchable history of the files/backups</li> -<li>Forwarding of the backup to someone else</li> -<li>CC multiple accounts and have the backup available to multiple people</li> -<li>Send a copy to the client, so they can have one too</li> -</ul> -<p>So I whipped up a script with <code>mysqldump</code>, <code>curl</code>, and <code>CRON</code>.</p> -<p>Although you can send email <code>sendmail</code> or <code>mail</code>, I opted for <a href="http://www.mailgun.com/">Mailgun</a>. It is something we are already using, so hooking it up took no time at all. They also have an excellent API, which is faster than <em>SMTP</em>.</p> -<p>On top of the regular email features I listed above, with Mailgun, I also get:</p> -<ul> -<li>read receipts</li> -<li>delivery reports</li> -<li>logging</li> -<li>web hooks (you can catch emails with individual subjects or recipients)</li> -</ul> -<p>So, without spending any money, I can get a SaaS experience using some built-in tools.</p> -<p>You can see the script here:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/6e471dee73124eddda8c.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;6e471dee73124eddda8c.js'></script>`"></iframe> -<p>I included some rules for the <code>crontab</code> settings. I used <code>0 0 * * 1 *</code> which is once a week on Monday at midnight.</p> -<p>You can still run the script above <em>without using <code>CRON</code></em>. Just put it somewhere on your server and run it like you normally would: <code>./backup-mysql-db.sh</code>.</p> - - - - - Add A Counter For Duplicate Uploads - 2016-02-12T00:00:00+00:00 - 2016-02-12T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/add-a-counter-for-duplicate-file-uploads/ - - <p>Wouldn't it be nice if, when you uploaded a file, the duplicate filenames just get a counter added in front?</p> -<p>Some people add timestamps on their files, and add some type of date, but I always found that to be very cumbersome.</p> -<p>How about something like this?</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>some-file.jpg -</span><span>1-some-file.jpg -</span><span>2-some-file.jpg -</span><span>3-some-file.jpg -</span></code></pre> -<p>Well, now you can! I have used this little script a couple times to remove any conflicting filenames when uploading files with the same name.</p> -<p>I use this in <a href="https://laravel.com">Laravel</a> (with an instance of <code>Symfony\Component\HttpFoundation\File\UploadedFile</code> which extends <a href="http://php.net/SplFileInfo">SplFileInfo</a>) but it can easily be modified for any other framework or system.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/516483af423d4643ac83.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;516483af423d4643ac83.js'></script>`"></iframe> -<p>If you are OCD, and wanted to all files to start with a number, just remove the if statement for the <code>$client_name</code>, only leaving the string concatenate line, and it will always add a the counter when the file is new.</p> -<p>So give it a shot, and if you modify it for some other framework, post the link in the comments.</p> - - - - - Add $.getStylesheet To jQuery - 2016-02-05T00:00:00+00:00 - 2016-02-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/add-getstylesheet-to-jquery/ - - <p>Have you ever wanted to do this?</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">$</span><span>.</span><span style="color:#8fa1b3;">when</span><span>(</span><span style="color:#bf616a;">$</span><span>.</span><span style="color:#8fa1b3;">getStylesheet</span><span>(&#39;</span><span style="color:#a3be8c;">css/main.css</span><span>&#39;), </span><span style="color:#bf616a;">$</span><span>.</span><span style="color:#8fa1b3;">getScript</span><span>(&#39;</span><span style="color:#a3be8c;">js/main.js</span><span>&#39;)) -</span><span>.</span><span style="color:#96b5b4;">then</span><span>(</span><span style="color:#b48ead;">function </span><span>() { -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(&#39;</span><span style="color:#a3be8c;">the css and js loaded successfully and are both ready</span><span>&#39;); -</span><span>}, </span><span style="color:#b48ead;">function </span><span>() { -</span><span> </span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(&#39;</span><span style="color:#a3be8c;">an error occurred somewhere</span><span>&#39;); -</span><span>}); -</span></code></pre> -<p>Well, now you can! I have created an alternative to <code>$.getScript</code> that handles stylesheets. I called it <a href="https://gist.github.com/james2doyle/9456c3e145f8d0afbe25">$.getStylesheet</a> for obvious reasons.</p> -<p>It implements the <a href="https://api.jquery.com/jQuery.Deferred/">$.Deferred</a> object, which allows for chaining and pseudo-promises-style code. Just like <code>$.ajax</code>, <code>$.post</code>, and <code>$.get</code>. This also means you would have access to all the other methods on that object too, this means: <code>.then()</code>, <code>.always()</code>, <code>.done()</code>, and <code>.fail()</code>.</p> -<p>Here is the little function for <a href="https://gist.github.com/james2doyle/9456c3e145f8d0afbe25">$.getStylesheet</a>. It is just hosted on Github gist, so I can update it if I need to:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/9456c3e145f8d0afbe25.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;9456c3e145f8d0afbe25.js'></script>`"></iframe> -<p>You can see this is pretty simple. Just add the function after your jQuery script, or somewhere in your main script file.</p> - - - - - Slack Meta Data For URLs and Links - 2015-10-31T00:00:00+00:00 - 2015-10-31T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/slack-url-meta-data/ - - <p>If you use <a href="https://slack.com/">Slack</a>, then you have probably noticed the awesome feature they have for generating nice meta data whenever you paste a URL or an image.</p> -<div class="center"> - <a href="/images/slack-example.png" title="Slack meta tag fetching example" target="_blank"><img alt="Slack meta tag fetching example" src="/images/slack-example.png" ></a> -</div> -<p>That is pretty slick! So how does it know what to grab for us?</p> -<p>From the Slack website:</p> -<blockquote> -<p>It fetches as little of the page as it can (using HTTP Range headers) to extract meta tags about the content. Specifically, we are looking for <a target="_blank" href="http://oembed.com/">oEmbed</a> and <a target="_blank" href="https://dev.twitter.com/docs/cards">Twitter Card</a> / <a target="_blank" href="http://ogp.me/">Open Graph</a> tags. If a page's tags refer to an image, video, or audio file, we will fetch that file as well to check validity and extract other metadata.</p> -</blockquote> -<p>I've never used <em>oEmbed</em>, so I am going to skip that.</p> -<p>Let's break down each part of the resulting meta fetch by Slack. Here is what we have, and the order it appears.</p> -<ul> -<li>WARPAINT Media (Site Title)</li> -<li>Homepage | WARPAINT Media (Page Title)</li> -<li>WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development. (Meta Description)</li> -</ul> -<p>Now we can look at the Open Graph meta tags that are being used to generate these results:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">property</span><span>=&quot;</span><span style="color:#a3be8c;">og:site_name</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">WARPAINT Media</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">property</span><span>=&quot;</span><span style="color:#a3be8c;">og:title</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">Homepage | WARPAINT Media</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">property</span><span>=&quot;</span><span style="color:#a3be8c;">og:description</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development.</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">property</span><span>=&quot;</span><span style="color:#a3be8c;">og:image</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">http://warpaintmedia.ca/img/meta-image.jpg</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">property</span><span>=&quot;</span><span style="color:#a3be8c;">og:url</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">http://warpaintmedia.ca</span><span>&quot;&gt; -</span></code></pre> -<p>Open Graph tags are pretty popular. The biggest user of Open Graph is Facebook. Here is the result when the link is shared on Facebook:</p> -<div class="center"> - <a href="/images/warpaint-facebook.png" title="Facebook meta tag fetching example" target="_blank"><img alt="Facebook meta tag fetching example" src="/images/warpaint-facebook.png" ></a> -</div> -<p>Make sure you test your tags using the <a href="https://developers.facebook.com/tools/debug/og/object/">Facebook Open Graph Object Debugger</a>. This will help you spot errors in your tags. If there are errors, <strong>Slack will not load the content</strong>. You need to have valid tags to make Slack work nicely.</p> -<p>How about the <a href="https://dev.twitter.com/cards/overview">Twitter Card</a> meta tags?</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:card</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">summary_large_image</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:title</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">Homepage | WARPAINT Media</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:description</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development.</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:site</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">@warpaintmedia</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:creator</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">@warpaintmedia</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:url</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">http://warpaintmedia.ca</span><span>&quot;&gt; -</span><span>&lt;</span><span style="color:#bf616a;">meta </span><span style="color:#d08770;">name</span><span>=&quot;</span><span style="color:#a3be8c;">twitter:image:src</span><span>&quot; </span><span style="color:#d08770;">content</span><span>=&quot;</span><span style="color:#a3be8c;">http://warpaintmedia.ca/img/meta-image.jpg</span><span>&quot;&gt; -</span></code></pre> -<p>As you noticed, there is no &quot;site title&quot; being set. You can also see in the screenshot that Twitter doesn't really care about the site title, they just show the page title. If that was something you wanted to adjust, then you would tweak the <code>twitter:title</code> tag to reflect what you wanted for a page title.</p> -<p>Now you may have copied these Twitter meta tags and pasted them in your site and filled in your information. That <strong>will not work</strong>. You need to use the <a href="https://cards-dev.twitter.com/validator">Twitter Card Validator</a> to test, and then register, your account and URL.</p> -<p>When you</p> - - - - - Fixing Wordpress wp-content 500 Errors - 2015-09-29T00:00:00+00:00 - 2015-09-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/wordpress-wp-content-500-errors/ - - <p>Yesterday, a friend emailed me about her Wordpress site acting crazy. For some reason all the site assets weren't loading and were returning 500 errors.</p> -<h4 id="troubleshooting">Troubleshooting</h4> -<p>I had her check the usual things: <code>.htaccess</code> is there, the asset actually existed, some plugin wasn't busting the site. Each one was throwing a 500 error, so it was something else.</p> -<p>Eventually she gave me her log in, which had a <em>super simple password</em>, and I logged in to see what was up. Nothing seemed to be wrong, no crazy plugins or weird issues.</p> -<p>I then logged into the FTP server to sniff around. Checked the <code>.htaccess</code>, nothing was wrong. I checked the <code>index.php</code>, didn't see anything weird.</p> -<p>I opened the <code>wp-config.php</code> and added all the <a href="https://codex.wordpress.org/Debugging_in_WordPress#Example_wp-config.php_for_Debugging">debugging constants</a>, here there are for laziness:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>// Enable WP_DEBUG mode -</span><span>define(&#39;WP_DEBUG&#39;, true); -</span><span> -</span><span>// Enable Debug logging to the /wp-content/debug.log file -</span><span>define(&#39;WP_DEBUG_LOG&#39;, true); -</span><span> -</span><span>// Disable display of errors and warnings -</span><span>define(&#39;WP_DEBUG_DISPLAY&#39;, false); -</span><span>@ini_set(&#39;display_errors&#39;, 0); -</span><span> -</span><span>// Use dev versions of core JS and CSS files (only needed if you are modifying these core files) -</span><span>define(&#39;SCRIPT_DEBUG&#39;, true); -</span></code></pre> -<p>I then navigated around the site. The content (text, database data) was loading fine, but all the assets were busted. There was a map plugin that was hitting Google Maps and that loaded fine. I then knew it was this specific site.</p> -<p>After poking around some more, I noticed an <code>index.php</code> and a <code>.htaccess</code> in the root of <code>/wp-content</code>.</p> -<h4 id="the-fix">The Fix</h4> -<p>Well, I have come across this problem before. <strong>Wordpress was hacked</strong>. Hacked might be a strong word, since the password was really weak. A bot probably had that password in it's script and guessed it without issue.</p> -<p>The &quot;hacker&quot; then uploaded a file to <code>/wp-content/.htaccess</code> and <code>/wp-content/index.php</code>. These files were intercepting request to the <code>wp-content</code> folder.</p> -<p>When I removed the files, the site came back to life!</p> -<p>So if your Wordpress site is suddenly getting 500 errors, check for rogue <code>index.php</code> or <code>.htaccess</code> files.</p> -<h4 id="create-a-better-password">Create A Better Password</h4> -<p>If you are looking to create a strong (but human readable) password, you should take a look at <a href="https://en.wikipedia.org/wiki/Diceware">Diceware</a>. In short, it is a huge list of uncommon words that get randomly combined to give you a strong password you can read.</p> -<p>Here is the definition from Wikipedia:</p> -<blockquote> -<p>Diceware is a method for creating passphrases, passwords, and other cryptographic variables using an ordinary die from a pair of dice as a hardware random number generator. For each word in the passphrase, five rolls of the dice are required. The numbers from 1 to 6 that come up in the rolls are assembled as a five-digit number, e.g. 43146. That number is then used to look up a word in a word list.</p> -</blockquote> -<p>Here is an <a href="http://www.ethanresnick.com/diceware/">online tool that can generate passwords for you</a>. This site has the default set to word count set to <em>5</em>. As of today, you should probably use <em>6</em> words to generate your password.</p> -<h4 id="has-your-wordpress-been-hacked">Has Your Wordpress Been Hacked?</h4> -<p>Do you know any common Wordpress hacks that occur?</p> - - - - - Simple Benchmarks With Apache AB - 2015-09-22T00:00:00+00:00 - 2015-09-22T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/simple-benchmarks-with-apache-ab/ - - <h3 id="why-benchmark">Why Benchmark?</h3> -<p>Did you know that Apache has a benchmarking tool for testing the HTTP server? It is called <a href="http://httpd.apache.org/docs/2.4/programs/ab.html" title="Apache ab">ab</a>, and it is pretty great!</p> -<p>As your site grows in popularity, complexity, or size, you will want to test the site to see how it preforms. Having the site crash or lock up during peak time can be devastating for a small blog or e-commerce site. It means lost revenue, and can leave a visitor with a bad impression of your site. This can drive them to generate a bad referral, or worse, go to your competitor.</p> -<p>Users aren't going to care if your site is being bombarded with traffic constantly, they really only care about <em>their own personal experience</em>.</p> -<p><a href="https://blog.kissmetrics.com/loading-time/?wide=1" title="Kiss Metrics loading time infographic">Studies and analytics show</a> that the slower your site is, the impact on your sales or target actions is affected exponentially.</p> -<h3 id="getting-ab">Getting ab</h3> -<p>The <code>ab</code> tool can be found in most default Apache (httpd 2.2 and 2.4) setups. If you don't have it, you can install the <code>apache2-utils</code> package and get it from there.</p> -<h3 id="testing">Testing</h3> -<p>I found <a href="https://www.devside.net/wamp-server/load-testing-apache-with-ab-apache-bench" title="Load testing apache with ab apache bench">a great article on this site</a> that explains a lot of details about using ab, setting up Apache, configuring PHP, and information about the results. You should check it out.</p> -<p>I used the following script, based on that article above, that tests a site in succession and prints the results to a file.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/1b77386317af93a0e5b2.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;1b77386317af93a0e5b2.js'></script>`"></iframe> -<p>To run the script, download it and unzip it. Then run <code>chmod +x bench.sh</code> to allow the script to run. Then you can use <code>./bench</code> and the script will begin.</p> -<p>The article I linked to above mentioned using a <a href="https://www.devside.net/wamp-server/load-testing-apache-with-ab-apache-bench" title="Load testing apache with ab apache bench">delay of 4 minutes</a> between running <code>ab</code>.</p> -<p>Be careful using <code>ab</code>, as it essentially emulates a DDOS attack, in that it generates as many requests as possible as fast as possible. There is no delay option in ab, so there is no way to emulate something like &quot;10 hits every 10 seconds&quot; or anything like that.</p> -<h3 id="results">Results</h3> -<p>Here are the things you are going to want to pay attention to. These definitions are taken from the <a href="http://httpd.apache.org/docs/2.4/programs/ab.html" title="Apache ab">ab site</a>.</p> -<ul> -<li><strong>Failed requests:</strong> The number of requests that were considered a failure. If the number is greater than zero, another line will be printed showing the number of requests that failed due to connecting, reading, incorrect content length, or exceptions.</li> -<li><strong>Non-2xx responses:</strong> The number of responses that were not in the 200 series of response codes. If all responses were 200, this field is not printed.</li> -<li><strong>Requests per second:</strong> This is the number of requests per second. This value is the result of dividing the number of requests by the total time taken.</li> -<li><strong>Time per request:</strong> The average time spent per request. The first value is calculated with the formula (concurrency * timetaken * 1000 / done) while the second value is calculated with the formula (timetaken * 1000 / done).</li> -</ul> -<p>Failures and errors are generally not good. You should check your logs for anything that happened during your requests.</p> -<p>For <em>Requests Per Second</em>, this tells you how quickly your site was able to process all those requests. Higher is better because it means that your site was able to serve the content without hiccups.</p> -<p>The <em>Time Per Request</em>, although that article says it isn't important in the context of ab, I think it is still important to watch. This metric tells you how long the average request takes. Keep in mind we don't have javascript running, or</p> - - - - - Varnish For Static Sites - 2015-09-20T00:00:00+00:00 - 2015-09-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/varnish-for-static-sites/ - - <p>Recently, <a href="http://warpaintmedia.ca">my company</a> had a request to build a series of sites that could handle huge bursts of traffic. I asked some friends of mine, what a good solution for this would be. All of them said <a href="https://www.varnish-cache.org/">Varnish</a>.</p> -<p>If you don't know what Varnish is, check out this definition from their documentation:</p> -<blockquote> -<p>Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy. You install it in front of any server that speaks HTTP and configure it to cache the contents. Varnish Cache is really, really fast. It typically speeds up delivery with a factor of 300 - 1000x, depending on your architecture.</p> -</blockquote> -<p>So you can see that having this tool would be really nice. A simple way to get started with Varnish is to set it up on a flat-file site. Maybe something like <a href="http://philecms.com/">PhileCMS</a> perhaps? Here is <a href="https://www.staticgen.com/">a nice curated list</a> of flat-file site generators.</p> -<h3 id="setting-up">Setting Up</h3> -<p>This tutorial assumes the following:</p> -<ul> -<li>You are using Ubuntu 14.04</li> -<li>You have <a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-varnish-with-apache-on-ubuntu-12-04--3">Varnish and Apache installed</a> *</li> -</ul> -<p>* This tutorial is for Ubuntu 12.04. Replace this command: <code>deb http://repo.varnish-cache.org/ubuntu/ lucid varnish-3.0</code> with this one <code>deb http://repo.varnish-cache.org/ubuntu/ trusty varnish-4.0</code></p> -<h3 id="apache-setup">Apache Setup</h3> -<p>First, you will want to serve Apache on a different port, because Varnish is going to act as our &quot;web server&quot; and Apache will only be used if the cache is stale or there is no item in the Varnish cache.</p> -<p>We can open <code>/etc/apache2/ports.conf</code> and make the following change:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># Listen 80 -</span><span>Listen 127.0.0.1:8080 -</span></code></pre> -<p>We commented out the original listening port, and added our own.</p> -<p>If we have any sites setup, we should change their virtual host files as well. These files live in <code>/etc/apache2/sites-available</code> and end in <code>.conf</code>, so this demo file might be called <code>example.com.conf</code></p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;VirtualHost *:8080&gt; -</span><span> ServerAdmin hello@example.com -</span><span> ServerName www.example.com -</span><span> ServerAlias example.com -</span><span> DocumentRoot /var/www/html/example.com -</span><span>&lt;/VirtualHost&gt; -</span></code></pre> -<p>If this site is not enabled, it would be done with the command <code>a2ensite example.com</code>.</p> -<h4 id="remove-caching-and-header-changes">Remove Caching And Header Changes</h4> -<p>You need to <strong>disable all caching in Apache</strong>!</p> -<p>Varnish works by reading headers from any files served from our normal web server. Having caching in Apache might seem like killing 2 birds with 1 stone, but it doesn't work that way.</p> -<p>Rules you might want to check for in your <code>apache.conf</code> files:</p> -<ul> -<li>mod_headers - used for modifying headers, use with caution</li> -<li>mod_deflate - for setting compression details</li> -<li>mod_filter - used with mod_deflate for setting compression</li> -<li>mod_expires - used for setting how long to cache files, use with caution</li> -</ul> -<p>These different rule sets usually contain settings that would be great if we were <em>not</em> using Varnish. In this case, we are going to trust Varnish to manage all the headers for us.</p> -<p>When everything is good to go, restart Apache with <code>service apache2 restart</code>.</p> -<h3 id="varnish-setup">Varnish Setup</h3> -<p>First, we need to tell Varnish to live on port 80. We do that by editing the settings for the Varnish daemon.</p> -<p>The file we need to edit is <code>/etc/default/varnish</code>. Scroll down until you see the uncommented lines with code that looks like it does below. We need to it like this:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>DAEMON_OPTS=&quot;-a :80 \ -</span><span> -T localhost:6082 \ -</span><span> -f /etc/varnish/default.vcl \ -</span><span> -S /etc/varnish/secret \ -</span><span> -s malloc,256m&quot; -</span></code></pre> -<p>Most likely, you will only change the <code>-a</code> part to <code>80</code>.</p> -<p>Our Varnish configuration for our site lives at <code>/etc/varnish/default.vcl</code>, here is the one I am using:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/0feec6ab77078ad3fdce.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;0feec6ab77078ad3fdce.js'></script>`"></iframe> -<p>It is very basic. I really only want to cache text files (HTML, CSS, Javascript/JSON) and images.</p> -<p>To restart Varnish, use <code>service varnish restart</code>.</p> -<h4 id="restarting-varnish">Restarting Varnish</h4> -<p>If you restart or run out of memory, Varnish will rebuild the cache. This isn't great because you are trying to keep Varnish alive and the cache enabled.</p> -<h3 id="testing">Testing</h3> -<p>At this point we are ready to test.</p> -<p>I would suggest running the command <code>vanrnishstat</code> on your remote server so you can see things happening in the Varnish cache. Pressing the arrow keys up and down will give you a description of the item.</p> -<p>Then you can go to you site and click around. You should see the Varnish Stat table getting updated. You will want to watch the <em>MAIN.cache_hit</em> and <em>MAIN.cache_miss</em> numbers.</p> -<p>You want the <em>MAIN.cache_hit</em> to be as high as possible. This means that your Apache is not getting tapped for information, but Varnish is serving it straight to the client.</p> -<p>For the <em>MAIN.cache_miss</em>, you want that to be as low as possible. This number represents the number of times that Varnish had to hit Apache. Having a low <em>MAIN.cache_miss</em> means that we are only tapping Apache when we must.</p> -<h4 id="troubleshooting">Troubleshooting</h4> -<p>Since we added that line in our <code>default.vcl</code> file for <code>X-Cache</code>, we can see which files are being served by Varnish. Using dev tools in Chrome/Safari or Firefox, we can look for a header in our request called <code>X-Cache</code>.</p> -<div class="center"> - <a href="/images/varnish-x-cache.png" title="Varnish x-cache header example" target="_blank"><img alt="Varnish x-cache header example" src="/images/varnish-x-cache.png" ></a> -</div> -<p>You can see that this item was <em>HIT</em>. This means that it will be counted in the <em>MAIN.cache_hit</em> column and not <em>MAIN.cache_miss</em> column. Good!</p> -<p>Things that mess up Varnish are headers and cookie headers especially. Sometimes you want or need some headers though. This setup does not allow any cookies to get through. If you had a normal CMS with this setup, you would find you wouldn't be able to log in, or there might be some CSRF Token (Cross Site Request Forgery) issues with form submissions if you use CSRF.</p> -<p>Varnish will let you control different areas where cookies or other things can change. You will want to refer to the <a href="https://www.varnish-cache.org/trac/wiki/VCLExampleRemovingSomeCookies">Varnish Documentation</a> for these advanced features.</p> - - - - - Validate Email With Lua - 2015-09-06T00:00:00+00:00 - 2015-09-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/validate-email-with-lua/ - - <p>Checking if an email is valid should be easy, right? WRONG.</p> -<p>This took about 3-4 hours to finally get. I scoured the web for a good email validation function. But, I was unable to find any that actually handled all the valid email variations.</p> -<p>If you didn't know, the <a href="https://en.wikipedia.org/wiki/Email_address#Examples">spec for email</a> is actually pretty complex. It allows a lot more than just a web-safe prefix and a domain sandwiched between an <code>@</code> symbol. You can use quotes, brackets, escapes, and more <code>@</code> symbols.</p> -<p>Looking at the results that Wikipedia gives me for emails that should fail or pass a validation, I knew this would require more than just a simple pattern match.</p> -<p>Here is the final product. I added some nice comments to explain some of the rules as well.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/67846afd05335822c149.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;67846afd05335822c149.js'></script>`"></iframe> -<p>You can see there is a lot more logic than expected. I also made this function have multiple returns.</p> -<p>If the email passes the function returns <code>true</code> and <code>nil</code>, but if it fails, it will return <code>nil</code> and the reason that the validation failed. Pretty slick!</p> -<p>Anyway, this was a tedious task. So go forth and leverage my pain in your next form submission.</p> - - - - - Simple PHP JSON Response - 2015-08-04T00:00:00+00:00 - 2015-08-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/simple-php-json-response/ - - <p>This is little snippet I use all the time when I am building simple flat sites that need a single route for an AJAX request.</p> -<p>There are a couple of things you need to do in order to create a proper JSON response.</p> -<h4 id="use-json-encode">Use json_encode</h4> -<p>If you are really new to PHP, you may not have found <a href="http://php.net/manual/en/function.json-encode.php#refsect1-function.json-encode-examples">json_encode</a>. If that is the case, then look it up right now.</p> -<p>This function converts PHP arrays, strings, and objects, into a JSON safe string. This makes it simple for you to create safe responses that can be handled by your javascript.</p> -<h4 id="use-content-type">Use Content-Type</h4> -<p>This is usually the magic command that allows you to <em>receive</em> JSON from your script. If you don't set the header, PHP will simple return a string. Then in your javascript, you have to use <code>JSON.parse</code> in order to get the object that javascript can use.</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>header(&#39;Content-Type: application/json&#39;); -</span></code></pre> -<p>No more strings! The request is now treated accordingly and is parsed for you. I am sure everyone has encountered this in the beginning.</p> -<h4 id="use-status">Use Status</h4> -<p>Here is another header that you need to set in order for things to work smoothly. By setting the status, you can tell your javascript, as well as your browser, what the status of the request it. By default, the requests are treated as <code>200 OK</code>. So although you may have sent a response with a failing message, with setting a failed status (usually 300 codes and above) the browser, and your javascript, think everything is fine.</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>header(&#39;Status: 400 Bad Request&#39;); -</span></code></pre> -<p>This becomes very apparent in jQuery when using <code>$.get</code> or <code>$.post</code>. If you are using a Promise style request (using <code>$.get().then( ... )</code>), your error handler never gets called.</p> -<p>If you want to find the perfect header for your fantastic error, check out the Wikipedia for the <a href="https://en.wikipedia.org/wiki/List_of_HTTP_status_codes">HTTP status codes</a>.</p> -<h4 id="example">Example</h4> -<p>Here is an example of a simple script I might use when testing an AJAX response, or when building a flat-file site. I would place this somewhere in my websites directory, and then hit it directly with an AJAX request.</p> -<p>If my local path to my site was <code>http://localhost:8888/website</code>, I would save this script as <code>json.php</code>. Then I would then use the following jQuery to test the script:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$.get(&quot;http://localhost:8888/website/json.php&quot;) -</span><span>.done(function(response){ -</span><span> console.log(response); -</span><span>}); -</span></code></pre> -<p>If everything was organized correctly, I should get a successful response with the information I wanted.</p> -<p>Here is the script:</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/33794328675a6c88edd6.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;33794328675a6c88edd6.js'></script>`"></iframe> -<p>You could always add more HTTP status codes, or use different key names for the array response. I like <code>status</code> and <code>message</code> because I may have a successful response, but I know something else went wrong when the <code>status</code> is false. I usually put whatever data I need in <code>message</code>. If message is a string, I know there is actually a message and not data I need to handle.</p> - - - - - Disallowed Characters In URI - 2015-03-31T00:00:00+00:00 - 2015-03-31T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/disallowed-characters-in-uri/ - - <p>How to fix &quot;The URI you submitted has disallowed characters&quot; error.</p> -<p>Does this screenshot look familiar?</p> -<div class="center"> - <a href="/images/error-400-disallowed-characters.png" target="_blank"><img alt="codeigniter 400 error for disallowed characters" src="/images/error-400-disallowed-characters.png" ></a> -</div> -<p>Well, it has been killing me for the last 2 hours. I have encountered this error before, but I never realized what caused it, or how it was fixed. I would just try random stuff, entering in different content, moving different functions. Eventually it would go away...</p> -<h3 id="symptoms">Symptoms</h3> -<p>In this situation, whenever I would click on a particular link I got the error. I copied and pasted the URL from the browser into Sublime Text. It looked like this:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>http://localhost:8080/website/page%E2%80%8B -</span></code></pre> -<p>What the heck? I never added those characters. They never showed in the browser URL, and trying to replace them in my code was not working.</p> -<p>I tried editing the entries in my database in order to remove the character. I deleted and edited each field. I even tried converting the character encoding and trimming the URL. <em>Nothing worked!</em></p> -<p>I then read a post on the <a href="https://forum.pyrocms.com/discussion/24142/does-pagesurl-return-with-disallowed-characters-for-you-too">PyroCMS Forum</a> that said there is probably a hidden character somewhere in the code. Well, I looked in my PHP and I didn't find anything, looking in my HTML there was nothing there either.</p> -<p>So what to do? When in doubt, <em>use vim</em>. I dusted off my vim and looked at the PHP file, nothing! Then I opened the HTML file...</p> -<p><strong>HEY! There is a tiny little hidden character in my HTML!!</strong></p> -<h3 id="the-fix">The Fix</h3> -<p>I removed the character and everything worked perfectly. Here is the diff after I removed the character:</p> -<div class="center"> - <a href="/images/hidden-character-diff.png" target="_blank"><img alt="git diff for hidden character" src="/images/hidden-character-diff.png" ></a> -</div> -<p>You can see there is a little trailing character there. I don't know exactly how this got in there, but god damn was it driving me crazy.</p> -<h3 id="in-summation">In Summation</h3> -<p>The real problem is, this <em>didn't show in Sublime Text</em> or in the <em>Chrome elements panel</em>. Even the commit on <em>Github</em> didn't show the hidden character I removed.</p> -<div class="center"> - <a href="/images/github-hidden-character-diff.png" target="_blank"><img alt="github diff for hidden character" src="/images/github-hidden-character-diff.png" ></a> -</div> -<p>The only way it would show up is when I opened the file in <strong>vim</strong>!</p> - - - - - PhalconPHP Completions - 2015-03-20T00:00:00+00:00 - 2015-03-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/phalconphp-completions/ - - <p>I have created a package of Sublime Text completions for <a href="http://phalconphp.com/en/">Phalcon PHP</a> 1.3.*.</p> -<p>There are <strong>414</strong> total right now. This is pretty much a copy-paste from my <a href="https://github.com/james2doyle/sublime-node-snippets">sublime-node-snippets</a> repo.</p> -<div class="center"> - <img src="https://raw.githubusercontent.com/james2doyle/phalconphp-completions/master/testing.gif" alt="PhalconPHP Completions in action" /> - <p>PhalconPHP Completions in action</p> -</div> -<h2 id="installing">Installing</h2> -<h4 id="via-package-control">Via Package Control</h4> -<p>Just look for <code>phalconphp-completions</code> on <a href="https://packagecontrol.io/packages/PhalconPHP%20Completions">Package Control</a>.</p> -<h4 id="manually-adding-repo">Manually Adding Repo</h4> -<ul> -<li>Open the Commands Palette (command+shift+p)</li> -<li>Package Control: Add Repository</li> -<li>Past in this repos URL</li> -<li>Press Enter</li> -<li>Open the palette again</li> -<li>press enter on &quot;phalconphp-completions&quot;</li> -<li>watch it install</li> -</ul> -<h4 id="by-download">By Download</h4> -<p>Drop this folder in your Sublime Text packages directory.</p> -<h2 id="using">Using</h2> -<p>Pressing <code>\</code> (backslash) or <code>:</code> will end the snippet lookup.</p> -<p>Therefore, you will have better results if you <em>pretend the slashes and colons aren't needed</em>. So if you are looking for <code>Phalcon\Text::increment</code>, you would type <code>phalcontextincrement</code> and you would see the results coming up.</p> -<p><em>See the GIF above!</em></p> -<h2 id="building">Building</h2> -<p>I went to each page of the PhalconPHP docs, and copied the functions. Then I wrote a converter to take each function and convert it to a snippet.</p> -<p>For Example, this line:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>Phalcon\Text::endsWith($str, $end, $ignoreCase) -</span></code></pre> -<p>Is going to get converted to:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>Phalcon\\Text::endsWith(\\$${1:str}, \\$${2:end}, \\$${3:ignoreCase});${0} -</span></code></pre> -<h2 id="sources-txt">sources.txt</h2> -<p>This file is cool. It is just a line-by-line output of the Phalcon docs functions. This is the file that is parsed to generate the snippets.</p> -<h2 id="running-the-build">Running The Build</h2> -<p>Just run <code>node build.js</code> and it will rake the <code>sources.txt</code> file and then write the new snippet in the snippets folder.</p> -<p>Everything before the first <code>(</code> will be used as the filename.</p> -<h2 id="adding-new-snippets">Adding New Snippets</h2> -<p>Here is how I quickly got all these snippets.</p> -<p>First, I went to the docs for the class, and I looked to see what the code examples were wrapped in. For the all the docs pages, the methods and properties are show in a <code>p.method-signature</code> element.</p> -<p>So to quickly get the list, I ran the following code:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#ebcb8b;">Array</span><span>.prototype.</span><span style="color:#bf616a;">slice</span><span>.</span><span style="color:#96b5b4;">call</span><span>(document.</span><span style="color:#96b5b4;">querySelectorAll</span><span>(&quot;</span><span style="color:#a3be8c;">.method-signature</span><span>&quot;), </span><span style="color:#d08770;">0</span><span>).</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">item</span><span>){ -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#bf616a;">item</span><span>.</span><span style="color:#bf616a;">textContent</span><span>.</span><span style="color:#8fa1b3;">trim</span><span>(); -</span><span>}).</span><span style="color:#96b5b4;">join</span><span>(&quot;</span><span style="color:#96b5b4;">\n</span><span>&quot;); -</span></code></pre> -<p>Then copied the output in the Chrome console, added the class in front (replacing the type info), and pasted it in the <code>sources.txt</code> file. Done!</p> - - - - - Easy FFmpeg Video Posters - 2015-03-19T00:00:00+00:00 - 2015-03-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/easy-ffmpeg-video-posters/ - - <p>A week ago I was tasked with uploading about 20 different videos to a CMS. Normally for the HTML5 Video element to look nice, you should upload a <a href="http://www.w3.org/TR/2012/WD-html5-20121025/the-video-element.html#attr-video-poster">poster image</a> so that there can be something showing before the video starts to play.</p> -<p>In my case, I had to generate a poster for each of these 20 videos. This would have taken a long time, so I scripted it using FFmpeg!</p> -<p>Here is the script:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;">#!/usr/bin/env bash -</span><span> -</span><span style="color:#65737e;"># take in mp4, take screenshot at 5 seconds -</span><span style="color:#65737e;"># output same filename, but with jpg extension -</span><span style="color:#b48ead;">for</span><span> FILE </span><span style="color:#b48ead;">in </span><span>*.mp4 -</span><span> </span><span style="color:#b48ead;">do -</span><span> </span><span style="color:#65737e;"># save the filename but swap the extension -</span><span> </span><span style="color:#bf616a;">NEWFILE</span><span>=&quot;$</span><span style="color:#a3be8c;">{</span><span style="color:#bf616a;">FILE</span><span>%</span><span style="color:#a3be8c;">.mp4}.jpg</span><span>&quot; -</span><span> </span><span style="color:#bf616a;">ffmpeg -y -i </span><span>$</span><span style="color:#bf616a;">FILE -f</span><span> mjpeg</span><span style="color:#bf616a;"> -vframes</span><span> 1</span><span style="color:#bf616a;"> -ss</span><span> 5 $</span><span style="color:#bf616a;">NEWFILE -</span><span style="color:#b48ead;">done -</span></code></pre> -<p><em>If your videos are not mp4 format, just change the extension.</em></p> -<p>To use this script:</p> -<ul> -<li>save this script as <code>poster.sh</code></li> -<li>put it in the folder with all your video files</li> -<li>own the script with <code>chmod +x poster.sh</code></li> -<li>run the script with <code>./poster.sh</code></li> -</ul> -<p>You should see a bunch of text fly in your command line, and a couple of seconds later, the conversion should be done. You will see some nice little posters right beside your videos! And, they will all be nicely named too!</p> - - - - - Minimal Raspberry Pi OS - 2015-03-14T00:00:00+00:00 - 2015-03-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/minimal-raspberry-pi-os/ - - <h2 id="introduction">Introduction</h2> -<p>I have had a <a href="http://www.raspberrypi.org/products/model-b/">Raspberry Pi B+</a> for a while now, and I was looking to setup a very minimal Linux OS. I am sure this would work fine with the Pi 2.</p> -<p>Although the other <a href="http://www.raspberrypi.org/downloads/">Raspberry Pi OSs</a> are great, a lot of them are too feature-full (read bloated) and have a packaged GUI that I would never use. Therefore, I wanted to install something much more <em>naked</em> than the ones on the Raspberry Pi website.</p> -<p>Enter, <a href="http://sourceforge.net/projects/moebiuslinux/">Moebius</a>. A few words from the Moebius site:</p> -<blockquote> -<p>[Moebius] is a Raspberry Pi armhf Debian based distribution targeted to have a minimal footprint. Size, speed and optimizations are main goals for this distro...</p> -</blockquote> -<p>With that said, the <em>unzipped <code>img</code></em> file is about <strong>110Mb</strong>. That is pretty small!</p> -<p>The other thing that Moebius does is remove the default Raspbian sources from <code>apt-get</code>. This means <em>you can't just download all the Rasbian packages</em> you want.</p> -<p>Moebius introduces the idea of containers. This isn't the same container technology like Docker. The &quot;containers&quot; are more like groups of packages. When installing a Moebius container, everything is installed in <code>/usr/bin</code> as if it came with the system.</p> -<p>I am going to provide a little walkthrough to get started with Moebius as a light-weight development environment, as well as how to install some other tools.</p> -<h3 id="initial-setup">Initial Setup</h3> -<p>First, visit the Moebius Sourceforge page and follow the instructions to <a href="http://sourceforge.net/projects/moebiuslinux/files/raspberry.stable/">download Moebius</a>. The basic instructions tell you how to copy the img to the SD Card. Once everything is setup and the Raspberry Pi has booted, complete the following:</p> -<ul> -<li>Either connect a screen and keyboard to the pi, or SSH to the pi</li> -<li>The default user is <code>root</code> and password is <code>moebius</code></li> -<li>When logged in, run <code>moebius</code></li> -</ul> -<div class="center"> - <a href="/images/moebius-tool.png" title="Moebius command line app" target="_blank"><img alt="Moebius command line app" src="/images/moebius-tool.png" ></a> - <p>Moebius command line app</p> -</div> -<p>Moebius is the name of the OS, but also the name of a sweet little built-in command line tool to setup the rest of the Pi.</p> -<h4 id="ssh-niceness">SSH Niceness</h4> -<p><strong>Optional:</strong> Add your public key to Moebius in order to ssh without a password. This is not required, but it does make popping in and out of the Pi nice and quick. Plus, no password to remember!</p> -<p>Moebius does not come with <code>nano</code>! If you are not familiar with the <code>vi</code> tool, you should <a href="http://www.washington.edu/computing/unix/vi.html">use this site to learn some basics</a>.</p> -<p>Just create <code>~/.ssh</code> and then use <code>vi ~/.ssh/authorized_keys</code> to create a new file, then paste in your public key.</p> -<h3 id="dev-environment-setup">Dev Environment Setup</h3> -<ul> -<li>Run <code>moebius</code> and select <code>Basic/Initial Setup</code></li> -<li>Choose <code>Autoresize SD partition</code>, follow the instructions</li> -<li>Run <code>apt-get update</code></li> -<li>Run <code>apt-get install tzdata</code></li> -<li>Run <code>moebius</code> and select <code>Basic/Initial Setup</code>, choose <code>System timezone setup</code> and follow instructions</li> -<li>In <code>moebius</code>, select <code>Software Management</code> and select <code>Update containers list from repository</code></li> -<li>Do the same as above except choose <code>Install a container</code> from the <code>Software Management</code> menu</li> -<li>Select the <code>lang.gcc</code> container</li> -</ul> -<div class="center"> - <a href="/images/moebius-container-tool.png" title="Moebius container command line app" target="_blank"><img alt="Moebius container command line app" src="/images/moebius-container-tool.png" ></a> - <p>Moebius container command line app</p> -</div> -<p><strong>You may get an error</strong> telling you to run <code>dpkg --configure -a</code>. If this happens, press any key to close the container installed and then run that command. When that completes, try to install the <code>lang.gcc</code> container again.</p> -<p>You may have to repeat the process above <em>multiple times</em>. I did it twice.</p> -<p>Once the container is installed, you should have <code>make</code> and <code>gcc</code>, and a bunch of other tools on your system.</p> -<hr /> -<h3 id="install-git">Install git</h3> -<p><strong>You must install the lang.gcc container first</strong>. That container provides the necessary compilers we need in order to build git.</p> -<ul> -<li>Get the required development files with <code>apt-get install openssl-dev curl-dev libexpat-dev dropbear-dev coreutils coreutils-dev</code></li> -<li>Download the latest zip archive <code>wget https://github.com/git/git/archive/v2.3.3.zip</code></li> -<li>Run <code>unzip v2.3.3.zip</code> and then <code>cd v2.3.3/</code></li> -<li>Allow the scripts to run with <code>chmod +x *.sh &amp;&amp; chmod +x check_bindir</code></li> -</ul> -<p>Following the official <a href="https://github.com/git/git/blob/master/INSTALL">INSTALL</a> in the git source code repository, we want to leave out some of the features in order to build without some of the required libraries.</p> -<p>To do this, we need to run:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>make NO_PERL=YesPlease NO_TCLTK=YesPlease NO_GETTEXT=YesPlease prefix=/usr install -</span></code></pre> -<p>This will take a while to build, so <em>grab a coffee or a beer</em>.</p> -<p><strong>Note:</strong> This command does not build the docs, so if you want those, you will have to consult the <a href="https://github.com/git/git/blob/master/INSTALL">INSTALL</a> file in the git repo.</p> -<h3 id="samba-setup">Samba setup</h3> -<p><a href="http://en.wikipedia.org/wiki/Samba_%28software%29">Samba</a> lets us access the Pi like a hard drive on our local network. Samba <em>works well with Windows and OSX</em>, and of course Linux as well.</p> -<p>If you are not familiar with the <code>vi</code> tool, you should <a href="http://www.washington.edu/computing/unix/vi.html">use this site to learn some basics</a>.</p> -<p>Complete the following to setup Samba:</p> -<ul> -<li>run <code>apt-get install samba</code></li> -<li>open the config with <code>vi /etc/samba/smb.conf</code></li> -<li>then complete the following:</li> -</ul> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># find in the top part of the file -</span><span>workgroup = your_workgroup_name -</span><span># find and uncomment this line -</span><span>wins support = yes -</span><span># add to the bottom of the file -</span><span>[pihome] -</span><span> comment= Pi Home -</span><span> path=/home/ -</span><span> browseable=Yes -</span><span> writeable=Yes -</span><span> only guest=no -</span><span> create mask=0777 -</span><span> directory mask=0777 -</span><span> public=no -</span></code></pre> -<ul> -<li>set the samba password with <code>smbpasswd -a root</code></li> -</ul> -<h3 id="install-lit-and-luvit">Install lit and luvit</h3> -<p><em>Again, have lang.gcc installed before continuing</em>.</p> -<p>I have been playing with <a href="https://github.com/luvit/lit">Lit</a> and <a href="https://github.com/luvit/luvit">Luvit</a>. They are like lightweight versions of Node.js, but written with Lua. So let's install them with a series of commands:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>apt-get install curl -</span><span>curl -L https://github.com/luvit/lit/raw/master/get-lit.sh | sh -</span><span>mkdir -p /usr/local/bin -</span><span>mv lit /usr/local/bin/lit -</span><span>lit make lit://luvit/luvit -</span><span>mv luvit /usr/local/bin/luvit -</span></code></pre> -<p>Try running <code>lit version</code> and then <code>luvit --version</code> to see if the frameworks are installed.</p> -<hr /> -<h3 id="finished">Finished</h3> -<p>You should now have everything setup to get around. If you find any problems with my instructions, <strong>please let me know in the comments</strong> and I will update them!</p> - - - - - Phalcon Micro App Starter - 2015-01-20T00:00:00+00:00 - 2015-01-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/phalcon-micro-starter/ - - <p>I created a <a href="https://github.com/james2doyle/phalcon-micro-start">simple application template</a> that helps people get started with <a href="http://phalconphp.com/" title="Phalcon PHP Homepage">Phalcon PHP</a> using a more practical example of the <a href="http://docs.phalconphp.com/en/latest/reference/micro.html">Micro application</a>.</p> -<p>There is already a <a href="https://github.com/phalcon/store">sample application created by the Phalcon team that uses the Micro class</a>, but I found it to be a little more specific than I would like. It uses things like the <a href="http://docs.phalconphp.com/en/latest/reference/volt.html">Volt template engine</a>, models, Database connections, and some other <a href="https://github.com/phalcon/store/blob/master/config/bootstrap.php">glossed over Bootstrapping</a>.</p> -<p><a href="https://github.com/james2doyle/phalcon-micro-start">My example application</a> contains very little. It has enough to get you started creating a simple JSON-based application, or just serving a static site with a few cached views.</p> -<h2 id="what-s-included">What's Included?</h2> -<h4 id="basic-page-example">Basic page example</h4> -<p>Just shows a simple GET route and serves a single view.</p> -<h4 id="partial-views-simple-view-engine">Partial views (<code>Simple</code> view engine)</h4> -<p>The templates use partials for the header and footer of the site.</p> -<h4 id="url-with-params">URL with params</h4> -<p>You can pass parameters into the URL, and they will be rendered on the page.</p> -<h4 id="json-return">JSON return</h4> -<p>An example of how to return JSON via a POST request. There is also a comment that tells you the jQuery test function to try.</p> -<h4 id="cached-view">Cached view</h4> -<p>This shows how you can serve a cached view, with an expiry. Good for those complicated pages that need to be refreshed every other day.</p> -<h4 id="redirect-url">Redirect URL</h4> -<p>This one is really simple. It just shows how you can redirect one URL request to another.</p> -<h3 id="other-niceness">Other Niceness</h3> -<p>I also included a simple grunt task that uses <code>livereload</code>. This will refresh the browser when view files, or the <code>app.php</code> file, changes.</p> -<h3 id="link">Link</h3> -<p>You can find the <a href="https://github.com/james2doyle/phalcon-micro-start">repositry on Github</a>. I will be updating and tweaking this project as I move along. It may become more feature-rich in the next few months. I would like to build a nice solid base for myself when using the Micro app.</p> -<p>There may be some need to add in some simple search examples, models, forms, validation, or even Database connections. But we will see if that is where it moved organically.</p> - - - - - PhalconPHP Crop Image To Fit - 2014-11-24T00:00:00+00:00 - 2014-11-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/phalconphp-crop-to-fit/ - - <p>I was trying to find out how to crop-to-fit an image with <code>GD</code>. But there were no examples with Phalcon. There was one post that mentioned using <code>Imagick</code>. The only problem was that you needed to compiled Imagick with <code>--lrf</code> in order to use <code>liquidRescale</code>. This may not be an option on many hosting platforms. For that reason, I wanted to use <code>GD</code> instead.</p> -<p>I <a href="http://salman-w.blogspot.com/2009/04/crop-to-fit-image-using-aspphp.html" title="Crop-To-Fit an Image Using ASP/PHP">found this post</a> after a quick Google search. It helped me create the base for my Phalcon version of the function. This may seem pretty easy for some people, but I found enough people asking, that I figured I would share the whole code.</p> -<iframe class="iframes" id="https://gist.github.com/james2doyle/13a36401d6249729d017.js" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;james2doyle&#x2F;13a36401d6249729d017.js'></script>`"></iframe> -<p>If you really wanted to use Imagick, then you could just replace GD in the constructor (<code>Phalcon\Image\Adapter\Imagick($source)</code>) and it should work fine. This way you don't need to compile Imagick from source in order to get <code>liquidRescale</code>.</p> - - - - - PyroCMS Module Generator 2.0 - 2014-11-16T00:00:00+00:00 - 2014-11-16T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-module-generator-2/ - - <p>Finally, I found a good excuse to re-write my old <a href="https://ohdoylerules.com/personal-project/pyro-module-generator">Pyro Module Generator</a>.</p> -<p>This tool was originally made when I was freelancing. I built it off the <a href="https://github.com/pyrocms/sample">Sample Module project on Github</a>. I wanted to be able to build modules quickly, since I wasn't using streams. In fact, streams wasn't even a thing when I made the first version of the module generator.</p> -<p>Again, I have made a <a href="http://dev.warpaintmedia.ca/pyro-module-generator/" title="PyroCMS Module Generator Website">live hosted version of the generator</a> which you can use without having to have anything setup locally. The generated module is zipped and then ready for download.</p> -<p>The <a href="https://github.com/james2doyle/pyro-module-generator" title="PyroCMS Module Generator On Github">source is also on Github</a> for people who want to patch issues or fork.</p> -<h3 id="about-version-2-0">About Version 2.0</h3> -<p>This new version is built with <a href="http://phalconphp.com/en/">PhalconPHP</a> because phalcon is crazy fast and easy to make small apps with. I managed to get the whole thing re-written in a day. Much of the code was a copy paste for the build process. But now the <code>Add Field</code> button is actually an AJAX call to generate a new partial for the new field. This is much nicer than the pervious version.</p> -<p>That being said, <strong>YOU MUST HAVE PHALCONPHP INSTALLED TO USE THIS APP!!</strong>.</p> -<p>There are a few little things I need to refactor, so that when 3.0 comes out, it will be easy to switch between the different versions of Pyro.</p> -<h3 id="usage">Usage</h3> -<p>Just throw it in your localhost root and point your browser to it. There is no database, since it just writes and renames files for you.</p> -<p>If you have <strong>used a custom folder name</strong> (and didn't just clone as <code>pyro-module-generator</code>), then open the <a href="https://github.com/james2doyle/pyro-module-generator/blob/master/config/config.php#L7">config/config.php and change the baseUri</a> to match that folder name.</p> -<h5 id="writeable-folders">Writeable Folders</h5> -<p>We need to run <code>chmod -R 777 cache/volt</code> and <code>chmod -R 777 public/generated</code> if you have write errors.</p> -<ul> -<li>cache/volt/</li> -<li>public/generated/</li> -</ul> -<h4 id="genrated-modules">Genrated Modules</h4> -<p><strong>Included in all generated modules is the following setup:</strong></p> -<ul> -<li><code>ID</code> field by default</li> -<li><code>order</code> field by default</li> -<li>functionality for drag and drop table order (add <code>ui-sortable-container</code> to <code>tbody</code> in admin index view)</li> -<li>basic function for files included but commented out</li> -<li><code>_form_data</code> function for passing data to form views</li> -<li>settings table included, but commented out</li> -</ul> -<p>The generated module gets put in the <code>public/generated/</code> folder. As well as the Zip file.</p> -<div class="center"> - <p>Screenshot of the current version of the app</p> - <a href="/images/pmg2.jpeg" title="PyroCMS Module Generator Screenshot" target="_blank"><img alt="PyroCMS Module Generator Screenshot" src="/images/pmg2.jpeg" ></a> -</div> - - - - - Kijiji Vector Logo - 2014-11-14T00:00:00+00:00 - 2014-11-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/kijiji-vector-logo/ - - <p>This logo was not only impossible to find, because searching for &quot;kijiji vector logo&quot; returns nothing but people wanting to sell vector logos <strong>on</strong> Kijiji, but it was difficult to trace as well.</p> -<div class="center"> - <a href="/images/kijiji.svg" title="kijiji svg vector logo" target="_blank"><img alt="kijiji svg vector logo" src="/images/kijiji.svg" ></a> -</div> -<p>The reason I had to find this logo, was that I made a new Chrome app called, &quot;<a href="http://goo.gl/8VXrlm" title="Kijiji Enhanced on the Chrome Web Store">Kijiji Enhanced</a>&quot;. It allows you to browse with larger thumbnails, rotate images in the image viewer, and also view inline maps at the bottom of posts.</p> - - - - - Migrate AllPasswords To 1Password - 2014-10-11T00:00:00+00:00 - 2014-10-11T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/migrate-allpasswords-to-1password/ - - <p><a href="https://itunes.apple.com/ca/app/allpasswords-handy-personal/id588258846?mt=12">AllPasswords</a> is an awesome, free, app for <a href="https://itunes.apple.com/ca/app/allpasswords/id578246311?mt=8">iPhone</a> and <a href="https://itunes.apple.com/ca/app/allpasswords-handy-personal/id588258846?mt=12">OSX</a>. It has a nice, simple interface, there is an awesome password generator, and it has iCloud sync.</p> -<p>The problem is, I recently bought an iPhone 6 and updated to iOS 8. It seems that the iCloud sync has busted for AllPasswords, at least on my device. With the advent of the <a href="http://bgr.com/2014/09/17/1password-for-ios-free-download/">1Password app getting a free version</a> I decided it might be time to switch.</p> -<p>I have about 130 logins in AllPasswords, so I wasn't about to manually enter in each account. Instead, I had to format the exported CSV from AllPasswords to be able to import into 1Password. Here is how I did it:</p> -<h3 id="allpasswords-export-format">AllPasswords Export Format</h3> -<blockquote> -<p>Title, Username, Password, URL, Notes</p> -</blockquote> -<p>The above line is the export format for AllPasswords.</p> -<p>Now this isn't going to work when you try to import it into 1Password. You will need to do some <em>massaging</em> of the CSV to get it to work properly.</p> -<p>Here is an export example:</p> -<blockquote> -<p>ODR PW,super_cool_guy,ilovepuppies5000,http://ohdoylerules.com,&quot;This is a fake entry&quot;</p> -</blockquote> -<p>Here is one that is less ideal, or maybe had some info missing:</p> -<blockquote> -<p>ODR PW,super_cool_guy,ilovepuppies5000,,</p> -</blockquote> -<h3 id="1password-import-expectations">1Password Import Expectations</h3> -<blockquote> -<p>&quot;Title&quot;,&quot;Location (URL)&quot;,&quot;Username&quot;,&quot;Password&quot;,&quot;Notes&quot;</p> -</blockquote> -<p>Now we need to put our CSV in this format. We need to <em>wrap the sections in quotes</em>, and we need to make sure that the <em>empty fields are just empty quotes</em>.</p> -<p>Here is how we would arrange those 2 examples from before.</p> -<blockquote> -<p>&quot;ODR PW&quot;,&quot;http://ohdoylerules.com&quot;,&quot;super_cool_guy&quot;,&quot;ilovepuppies5000&quot;,&quot;This is a fake entry&quot;</p> -</blockquote> -<p>Here is the ugly one, and how to fix it:</p> -<blockquote> -<p>&quot;ODR PW&quot;,&quot;&quot;,&quot;super_cool_guy&quot;,&quot;ilovepuppies5000&quot;,&quot;&quot;</p> -</blockquote> -<h3 id="finding-issues">Finding Issues</h3> -<p>When you are trying to import, you will notice that 1Passwords gives no feedback on what is wrong with the CSV, it will just deny the import.</p> -<p>The trick is to go through the file and make sure there are <em>5 sets of quotes</em>. That is what helped me.</p> -<p>So far I have been very happy with 1Password. It seems like a really solid app. I opted for Dropbox as the sync storage, and I downloaded the Chrome extension that will prompt me for pasting or saving of logins.</p> - - - - - Always Connect To Starbucks WiFi - 2014-09-20T00:00:00+00:00 - 2014-09-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/tricks/always-connect-to-starbucks-wifi/ - - <p>Lately, it has been quite difficult for me to connect to Starbucks WiFi. About a year ago, the network would connect pretty quickly, but now it seems like the network is powered by voodoo magic and the will of Satan.</p> -<p>Well, I have discovered the <em>ultimate way</em> to connect to the network <strong>every time</strong>. Now, this is still complete bullshit, considering the year is <strong>2014</strong> and we have a rover on Mars, but we still can't connect to a wireless network. Don't even get me started on wireless printers.</p> -<p>Here is the process I use to connect quickly:</p> -<h3 id="1-connect-to-the-starbucks-network-with-your-phone">1. Connect to the Starbucks network with your phone</h3> -<p>Yes. Use your cell phone to connect to the Starbucks wireless network. I have never had a problem connecting any phone to the Starbucks wifi. It seems like phones have some sort of special powers.</p> -<h3 id="2-turn-on-tethering-and-connect-to-your-computer-to-your-phone">2. Turn on tethering and connect to your computer to your phone</h3> -<p>Once you connect <em>your phone</em> to the Starbucks network, turn on <strong>tethering</strong> or <strong>wifi hotspot</strong>. Then, on your computer, connect to your phones network.</p> -<p>Here are instructions for turning on wireless hotspot on <a href="http://www.wikihow.com/Activate-Internet-Tethering-on-the-iPhone">iOS</a> and <a href="http://www.wikihow.com/Tether-With-Android">android</a>.</p> -<h3 id="3-load-the-starbucks-connection-page">3. Load the Starbucks connection page</h3> -<p>Now you will go to the <a href="http://starbucks.allstreamwifi.ca/00246C3ECB90/Welcome">Starbucks connection page</a> and <strong>wait until it is finished loading</strong>.</p> -<p>If it doesn't load all the way, that is fine. Just hit the stop button when you think it has reached a point of not going any further.</p> -<h4 id="do-not-press-the-green-submit-button">DO NOT PRESS THE GREEN SUBMIT BUTTON!</h4> -<p>Control yourself. We will do this later.</p> -<h3 id="4-switch-your-computer-to-the-starbucks-network">4. Switch your computer to the Starbucks network</h3> -<p>While that connection page <strong>is still open</strong>, switch your computers wifi from the <em>phones hotspot connection</em> to the <em>Starbucks network</em>.</p> -<h3 id="5-click-the-big-green-button-to-connect">5. Click the big green button to connect</h3> -<p>Now that you have the <em>connection page loaded</em> and your <em>computer connected to the Starbucks wifi</em>, you can press the big green submit button. This will complete the authentication and it should redirect you to the Starbucks homepage.</p> -<h3 id="why-does-this-work">Why does this work?</h3> -<p>I am not really sure. I <em>think</em> the reason might be that an unauthenticated connection to the network (a connection that has not made it past the connection page) has limited bandwidth.</p> -<p>This means that all pages are trying to load with a throttled speed. Typically, the browser will only try to connect for so long before it gives up and assumes the site is unreachable (a timeout occurs).</p> -<p>With my process, you are using a connection that is already authenticated, so you have the full speed you need in order to load the necessary page without it timing out on you.</p> -<h3 id="side-note">Side Note</h3> -<p>You can probably use your phones <em>data network</em> to load the initial Starbucks connection page. Then switch from the phone to Starbucks and press the green button.</p> -<h3 id="did-this-work">Did this work?</h3> -<p>This always works for me. It is a hell of a lot better than waiting a solid 30 minutes before the network finally connects. If this works for you, please <strong>leave a comment</strong> and let me know. If it doesn't work, let me know <strong>what you did</strong> in order to get it to work.</p> - - - - - Lico - 2014-08-04T00:00:00+00:00 - 2014-08-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/lico-luvit-cms/ - - <p>I have re-created Pico (<a href="https://github.com/picocms/Pico">github</a> or <a href="http://picocms.org/">homepage</a>) using the awesome <a href="https://github.com/luvit/luvit">Luvit</a> framework. Luvit is a <a href="http://luajit.org/">LuaJIT</a> wrapper for <a href="https://github.com/joyent/libuv">libuv</a>, of <a href="http://nodejs.org/">node.js</a> fame. It is called <a href="https://github.com/james2doyle/Lico" title="Lico On Github">Lico</a>!</p> -<h3 id="what-is-lico">What is Lico?</h3> -<p>Pico says, <em>&quot;Pico is a stupidly simple, blazing fast, flat file CMS&quot;</em>. Lico aims for the same thing. There is a very close parity with Pico even though this is very early.</p> -<p>I used the static server from the Luvit examples as a base and went from there.</p> -<h3 id="how-to-create-content">How to create content?</h3> -<p>You can understand the basics by looking at the included content directory and just running the <code>server.lua</code> file and hitting the index page.</p> -<p>If you need more information you can see the <a href="http://picocms.org/docs.html">Pico docs</a> and understand what is happening and how it works.</p> -<h3 id="what-features-are-implemented">What features are implemented?</h3> -<ul> -<li>Markdown Parsing using <a href="https://github.com/mneudert/luvit-markdown">luvit-markdown</a> -- <em>looking to switch to <a href="https://github.com/torch/sundown-ffi/tree/hoedown">Hoedown</a></em></li> -<li>HTML Templating (using my own modified version of <a href="https://github.com/james2doyle/sltluv">SLT2</a></li> -<li>Flexible Meta schema (Uses HTML comments instead of PHP style)</li> -</ul> -<h3 id="whats-missing">Whats missing?</h3> -<p>Plugins. Although with the native of the <a href="https://github.com/luvit/luvit/blob/master/examples/event-emitters.lua">Event Emitter</a> inside Luvit, this should be rather easy to re-create.</p> -<p>You can use SLTLuv to add new functions and features to your templates. You can see the <code>modules/slt-extensions.lua</code> on how to add extensions to the templates. I also added in some examples in the <code>default/themes/index.html</code>, if you want to see how they work.</p> -<p>Check out the <a href="https://github.com/henix/slt2#example">slt2 examples</a> to see how to write proper templates.</p> -<p>The markdown engine is rather simple. There is no fenced code blocks, and sometimes it will wrap uncommon HTML tags with <code>&lt;p&gt;</code> tags (I tried using a <code>figure</code> element and it was wrapped in p tags). I want switch to <a href="https://github.com/torch/sundown-ffi/tree/hoedown">Hoedown</a> soon.</p> -<h3 id="performance">Performance</h3> -<p>Well, this is very interesting. Running the default setup for Pico and Lico, reveals Lico is twice as fast in at the browser level.</p> -<p>Using the Chrome Devtools Network Panel, I measured the index page of each system. I consistently got around 120ms for each request. For Pico, the results were varied quite a bit. They ranged from 200ms to as high as 500ms, but never going under 200ms.</p> -<p>There are a lot of factors here, but the default Pico has 3 pages and my Lico testing suite (same one as this repo) had 6 pages.</p> -<p>I did some other testing against my other project, <a href="https://github.com/PhileCMS/Phile#performance-with-20-pages">PhileCMS</a>. You can see that Pico doesn't handle large amounts of pages very well.</p> -<h3 id="there-are-issues">There are issues!</h3> -<p>Yeah I bet. I am not a Lua developer. I made this over a week-long period trying to learn Lua. If you notice some funky stuff or clean n00b issues, please create issues or pull requests.</p> -<h3 id="repo">Repo?</h3> -<p>Here is the <a href="https://github.com/james2doyle/Lico">link to the Github repository</a>.</p> - - - - - Typeform Vector Logo - 2014-07-31T00:00:00+00:00 - 2014-07-31T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/typeform-vector-logo/ - - <p>Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.</p> -<div class="center"> - <a href="/images/typeform.svg" title="typeform svg vector" target="_blank"><img alt="typeform svg vector" src="/images/typeform.svg" ></a> -</div> -<p>If you are a photoshopper, you can also <a href="/images/typeform.eps" title="typeform eps vector" target="_blank">grab the EPS file</a>.</p> -<h3 id="update">Update</h3> -<p>Here is the <a href="http://bit.ly/WPjXfZ">official repository of Typeform branding graphics</a>.</p> - - - - - Using Node.js in an AppleScript - 2014-07-12T00:00:00+00:00 - 2014-07-12T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/using-node-in-applescript/ - - <p>A few days ago, I wrote an article about <a href="https://ohdoylerules.com/snippets/copy-file-path-clipboard-osx" title="Copy filepath to clipboard in OSX">how to create a service in Automator to copy the selected file's path to the clipboard while in the Finder.app</a>.</p> -<p>I was playing around some more and thought it would be cool to be able to right click and convert a markdown file to HTML. This can be useful for lazy people who don't want to open and app or terminal just to convert.</p> -<blockquote> -<p>Here is the trick, you need absolute paths to node and the target module (or bin entry js file) file you are trying to run</p> -</blockquote> -<p>Here is the code:</p> -<pre data-lang="applescript" style="background-color:#2b303b;color:#c0c5ce;" class="language-applescript "><code class="language-applescript" data-lang="applescript"><span style="color:#65737e;">-- setup some valid extensions for markdown files -</span><span style="color:#b48ead;">property </span><span style="color:#bf616a;">validExtensions </span><span>: {&quot;</span><span style="color:#a3be8c;">md</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">markdown</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">mdown</span><span>&quot;} -</span><span style="color:#b48ead;">tell </span><span style="color:#ebcb8b;">application </span><span>&quot;</span><span style="color:#a3be8c;">Finder</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">theFile </span><span style="color:#b48ead;">to </span><span style="color:#96b5b4;">item </span><span style="color:#d08770;">1 </span><span style="color:#b48ead;">of </span><span>(</span><span style="color:#b48ead;">get</span><span> selection) -</span><span> </span><span style="color:#65737e;">-- check if the extension is correct -</span><span> </span><span style="color:#b48ead;">if </span><span>(</span><span style="color:#b48ead;">the </span><span style="color:#96b5b4;">name</span><span> extension </span><span style="color:#b48ead;">of</span><span> theFile is in </span><span style="color:#b48ead;">the</span><span> validExtensions) </span><span style="color:#b48ead;">then -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">selectedItem </span><span style="color:#b48ead;">to the</span><span> selection as </span><span style="color:#ebcb8b;">text -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">thePath </span><span style="color:#b48ead;">to </span><span style="color:#96b5b4;">POSIX path </span><span style="color:#b48ead;">of</span><span> selectedItem -</span><span> </span><span style="color:#65737e;">-- created a quoted path in case there are special characters -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">nicePath </span><span style="color:#b48ead;">to </span><span style="color:#96b5b4;">quoted form </span><span style="color:#b48ead;">of</span><span> thePath -</span><span> </span><span style="color:#65737e;">-- here is the trick, you need absolute paths to node and the target bin -</span><span> </span><span style="color:#65737e;">-- just tack on the extension for html -</span><span> </span><span style="color:#96b5b4;">do shell script </span><span>&quot;</span><span style="color:#a3be8c;">/usr/local/bin/node /usr/local/share/npm/bin/marked --gfm </span><span>&quot; &amp; nicePath &amp; &quot;</span><span style="color:#a3be8c;"> &gt; </span><span>&quot; &amp; nicePath &amp; &quot;</span><span style="color:#a3be8c;">.html</span><span>&quot; -</span><span> </span><span style="color:#65737e;">-- find out what the new file is called -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">outName </span><span style="color:#b48ead;">to </span><span>(</span><span style="color:#96b5b4;">do shell script </span><span>&quot;</span><span style="color:#a3be8c;">basename </span><span>&quot; &amp; nicePath &amp; &quot;</span><span style="color:#a3be8c;">.html </span><span>&quot;) </span><span style="color:#b48ead;">of </span><span style="color:#d08770;">result -</span><span> </span><span style="color:#65737e;">-- since there is no progress, let me know when your done -</span><span> display notification outName &amp; &quot;</span><span style="color:#a3be8c;"> created successfully</span><span>&quot; </span><span style="color:#b48ead;">with</span><span> title &quot;</span><span style="color:#a3be8c;">Markdown Conversion Finished</span><span>&quot; -</span><span> </span><span style="color:#65737e;">-- allow time for the notification to show -</span><span> </span><span style="color:#96b5b4;">delay </span><span style="color:#d08770;">2 -</span><span> </span><span style="color:#b48ead;">else -</span><span> </span><span style="color:#65737e;">-- wrong file so show this -</span><span> </span><span style="color:#96b5b4;">display dialog </span><span>&quot;</span><span style="color:#a3be8c;">the file is not a valid Markdown file</span><span>&quot; </span><span style="color:#b48ead;">with</span><span> title &quot;</span><span style="color:#a3be8c;">Conversion Error</span><span>&quot; -</span><span> </span><span style="color:#65737e;">-- allow time for the notification to show -</span><span> </span><span style="color:#96b5b4;">delay </span><span style="color:#d08770;">2 -</span><span> </span><span style="color:#b48ead;">end if -</span><span style="color:#b48ead;">end tell -</span></code></pre> -<p>Now this can be used in an <em>Automator Service</em>, which you can find out how to make in the <a href="https://ohdoylerules.com/snippets/copy-file-path-clipboard-osx" title="Copy filepath to clipboard in OSX">previous article</a>.</p> -<p>If you modify this for any other cool node tools, please let me know!</p> -<p><strong>PS</strong>: Keep in mind that this doesn't iterate through multiple files. Only single files.</p> - - - - - Copy filepath to clipboard in OSX - 2014-07-10T00:00:00+00:00 - 2014-07-10T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/copy-file-path-clipboard-osx/ - - <p>At <a href="http://warpaintmedia.ca" title="WARPAINT Media">WARPAINT</a>, we use <a href="https://www.dropbox.com" title="Dropbox Homepage">Dropbox</a> for collaborating on our files. This is awesome, but a lot of the times you get some pretty nasty file paths. Especially when you are trying to guide someone to a place where you saved a file.</p> -<p>I wanted to solve this problem by creating an AppleScript service that would allow everyone to <strong>Copy the selected file's path to the clipboard</strong>. Here is how I did it.</p> -<p>+++</p> -<p>We are going to be using <a href="http://en.wikipedia.org/wiki/Automator_(software)">Automator</a> to create a new service. Here is the description of Automator in case you don't know what it is:</p> -<blockquote> -<p>Automator is an application developed by Apple Inc. for OS X that implements point-and-click (or drag and drop) creation of workflows for automating repetitive tasks into batches for quicker alteration, thus saving time and effort over human intervention to manually change each file separately.</p> -</blockquote> -<p>So the first thing is to open Automator and create a new service. Like so:</p> -<div class="center"> - <a href="/images/clipboard-1.png" target="_blank" > - <img src="/images/clipboard-1.png" width="720" /> - </a> -</div> -<p>Then you need to select <code>files or folders</code> for &quot;Service receives selected&quot; and choose <code>Finder.app</code> for the second option. The do a search for <code>applescript</code> and drag the <code>Run AppleScript</code> choice into the window on the right.</p> -<p>You will need to paste the following code into the AppleScript window:</p> -<pre data-lang="applescript" style="background-color:#2b303b;color:#c0c5ce;" class="language-applescript "><code class="language-applescript" data-lang="applescript"><span style="color:#b48ead;">tell </span><span style="color:#ebcb8b;">application </span><span>&quot;</span><span style="color:#a3be8c;">Finder</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">set </span><span style="color:#bf616a;">sel </span><span style="color:#b48ead;">to the</span><span> selection as </span><span style="color:#ebcb8b;">text -</span><span> </span><span style="color:#b48ead;">set the</span><span> clipboard to </span><span style="color:#96b5b4;">POSIX path </span><span style="color:#b48ead;">of</span><span> sel -</span><span style="color:#b48ead;">end tell -</span></code></pre> -<p>When that is all done, it should look something like this.</p> -<div class="center"> - <a href="/images/clipboard-2.png" target="_blank" > - <img src="/images/clipboard-2.png" width="720" /> - </a> -</div> -<p>Go to <code>File &gt; Save</code> or press <code>⌘S</code>. <strong>Do not Save-As</strong>. Enter in <code>Copy Path To Clipboard</code> as the name. <em>It shouldn't ask for a location</em>, it will just show an input field. This is perfectly fine.</p> -<p>Now open a new finder window and go to <code>Finder &gt; Services &gt; Services Preferences...</code> or <code>System Preferences &gt; Keyboard &gt; Shortcuts</code>. Select services on the left menu if it isn't already and scroll down to find <code>Copy Path To Clipboard</code>. This will open a window like this:</p> -<div class="center"> - <a href="/images/clipboard-3.png" target="_blank" > - <img src="/images/clipboard-3.png" width="720" /> - </a> -</div> -<p>Click on that item and make sure it is checked off, it should be by default. Then add a shortcut by clicking on the right side where it says &quot;add shortcut&quot;. I made mine <code>⌃⌘\</code>. But if you have <a href="http://www.alfredapp.com/">Alfred.app</a> that might conflict with it's copy feature. So you choose.</p> -<div class="center"> - <a href="/images/clipboard-4.png" target="_blank" > - <img src="/images/clipboard-4.png" width="720" /> - </a> -</div> -<p>You can use these steps to run any AppleScript on a file you choose. Pretty slick!</p> -<p>Now when you have a file of folder selected in the Finder, you can right-click, go to Services, and select <code>Copy Path To Clipboard</code>!</p> -<div class="center"> - <a href="/images/clipboard-5.png" target="_blank" > - <img src="/images/clipboard-5.png" width="646" /> - </a> -</div> - - - - - The Simple Spam Stopper - 2014-05-27T00:00:00+00:00 - 2014-05-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/simple-spam-stopper/ - - <p>For the last year at <a href="http://warpaintmedia.ca">WARPAINT Media</a>, we have been getting assaulted with spam. Everything from &quot;Chinese Jerseys&quot; and &quot;Super SEO Ultra Elite Package Extreme&quot; offers.</p> -<p>We are using <a href="http://pyrocms.com">PyroCMS</a> for the website. The default contact plugin is pretty awesome. It has some really great features and couldn't be easier to use. There is a little <a href="http://www.sitepoint.com/forums/showthread.php?946120-Spam-Honey-Pot-trap&amp;s=9cfd3419319d5c9bd1f5d597cdfa6113&amp;p=5278832&amp;viewfull=1#post5278832">honeypot</a> for spam bots, but it seems to not be doing a great job, at least for us.</p> -<p>The great thing about the Pyro contact form is that it lets the developer define some validation without much work. In the past I have added questions like <em>&quot;what is one plus one? (use a number)&quot;</em>, other times I have tried <em>&quot;are you a human?&quot;</em> with a dropdown. Both seemed fine. But I wanted something a little more transparent and more conventional than strange questions about math or your species.</p> -<p>The solution that I came up with was, 2 email fields. That's it. I have one that is called &quot;email&quot; and another field that is called &quot;check&quot;. When the user submits the form, the email and check field and validated. The rules for them are that they need to be identical, <em>but</em> they also need to be valid emails.</p> -<p>So there is a sort of a double validation going on. They need to be putting in a real email and they need to know that the email needs to match the check field. The label for this second check field is just &quot;Enter Your Email Again&quot;. Since it comes after the first field with an Email label, people tend to figure it out.</p> -<p>How about the results? Well we used to get about 10-20 spam per day. Now we get about 1-3 a week and none of them are from our websites contact form.</p> -<h4 id="tl-dr">Tl;Dr</h4> -<p>I added a second email input and practically eliminated our spam.</p> - - - - - Flexible SVG Placeholder Images - 2014-05-24T00:00:00+00:00 - 2014-05-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/flexible-svg-placeholders/ - - <p>Do you use <a href="http://placehold.it" title="Placehold.it Homepage">placehold.it</a>? It is a great service. The only thing is when you are offline, or you are testing a page that needs a lot of placeholders, it may not be the greatest solution.</p> -<p><strong>Enter the SVG placeholder.</strong></p> -<p>Here are the properties you can set:</p> -<ul> -<li>width and height</li> -<li>background-fill</li> -<li>font-color</li> -<li>font-family</li> -<li>font-size</li> -</ul> -<p>Here is the actual SVG file. As you can see it is a PHP file, but you are serving it as an SVG (see the <code>Content-Type</code> part?). Here we grab the URL arguments and assign them to the SVG.</p> -<iframe class="iframes" id="https://gist.github.com/3aad1d22163c3c3e5cfd.js?file=placeholder-svg.php" src="about:blank" srcdoc="`<script src='https:&#x2F;&#x2F;gist.github.com&#x2F;3aad1d22163c3c3e5cfd.js?file=placeholder-svg.php'></script>`"></iframe> -<p>If you saved the file as <code>placeholder-svg.php</code> then it would be used like so:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">img </span><span style="color:#d08770;">src</span><span>=&quot;</span><span style="color:#a3be8c;">placeholder-svg.php?wh=400x400&amp;fill=bada55&amp;color=000000&amp;font=Georgia&amp;size=20</span><span>&quot; /&gt; -</span></code></pre> -<p>This would be the output:</p> -<div class="center"> - <img src="/images/placeholder.png" alt="Placeholder Example"> -</div> - - - - - Purge A File From A Github Repo - 2014-05-18T00:00:00+00:00 - 2014-05-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/purge-file-from-github/ - - <p>Ever wanted to permanently remove a file from a repo and it's history?</p> -<p>Add this snippet to the end of your <code>.bashrc</code> (or <code>.zshrc</code> if you are a cool guy developer):</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># remove a file from the repo and from the history -</span><span style="color:#8fa1b3;">git-purge</span><span>() { -</span><span> </span><span style="color:#bf616a;">FN</span><span>=&quot;</span><span style="color:#a3be8c;">git rm --cached --ignore-unmatch </span><span>$</span><span style="color:#bf616a;">1</span><span>&quot; -</span><span> </span><span style="color:#bf616a;">git</span><span> filter-branch</span><span style="color:#bf616a;"> --force --index-filter </span><span>$</span><span style="color:#bf616a;">FN --prune-empty --tag-name-filter</span><span> cat -- --all -</span><span>} -</span></code></pre> -<p>This was taken from the <a href="https://help.github.com/articles/remove-sensitive-data" title="Github - Remove Sensitive Data">Github article about removing files</a>. Here is what they said about the function:</p> -<blockquote> -<p>Run git filter-branch, forcing (--force) Git to process—but not check out (--index-filter)—the entire history of every branch and tag (--tag-name-filter cat -- --all), removing the specified file ('git rm --cached --ignore-unmatch MYFILE') and any empty commits generated as a result (--prune-empty). Note that you need to specify the path to the file you want to remove, not just its filename. Be careful! This will overwrite your existing tags.</p> -</blockquote> -<p>You should also add the file to your <code>.gitignore</code>:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">MYFILE</span><span>&quot; &gt;&gt; .gitignore -</span><span style="color:#bf616a;">git</span><span> add .gitignore -</span><span style="color:#bf616a;">git</span><span> commit</span><span style="color:#bf616a;"> -m </span><span>&quot;</span><span style="color:#a3be8c;">Add MYFILE to .gitignore</span><span>&quot; -</span></code></pre> -<p>Then to update the live repo, run <code>git push origin master --force</code>.</p> -<p>This process will remove the file from your repo, and from the history. This is in-case you committed a sensitive file. If you get in a real pickle, you can use the <a href="http://rtyley.github.io/bfg-repo-cleaner/" title="BFG Repo-Cleaner">BFG Repo-Cleaner</a>.</p> - - - - - Tips For Using SVGs - 2014-05-17T00:00:00+00:00 - 2014-05-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/tips-for-using-svgs/ - - <p>I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.</p> -<h3 id="use-viewbox">Use Viewbox</h3> -<p>This one is a little gem. The <code>viewBox</code> property allows you to set the dimensions of the image but it will also allow you can have responsive SVGs.</p> -<p>They maintain their ratios, but they will scale to 100% width and height. If you have an SVG that is 64 by 64, the syntax for the viewBox property would be <code>viewBox=&quot;0 0 64 64&quot;</code>. Pretty simple. Just make sure you remove the width and height on the base SVG tag when using viewBox.</p> -<p>If you open an SVG in a new window, like opening a new file in the browser, you will notice that when zooming in, if <code>viewBox</code> property set properly, the image stays the same size. It won't zoom.</p> -<h3 id="use-base64-images">Use base64 Images</h3> -<p>So there are a couple ways you can embed an image in an SVG. I have found that the best way is to use a <a href="http://b1nary.ch/base64/" title="embed base64 - easy, client side base64 encoder">base 64 encoded string</a> as the image href.</p> -<pre data-lang="xml" style="background-color:#2b303b;color:#c0c5ce;" class="language-xml "><code class="language-xml" data-lang="xml"><span>&lt;</span><span style="color:#bf616a;">svg </span><span style="color:#d08770;">viewBox</span><span>=&quot;</span><span style="color:#a3be8c;">0 0 64 64</span><span>&quot; </span><span style="color:#d08770;">xmlns</span><span>=&quot;</span><span style="color:#a3be8c;">http://www.w3.org/2000/svg</span><span>&quot; </span><span style="color:#d08770;">xmlns:svg</span><span>=&quot;</span><span style="color:#a3be8c;">http://www.w3.org/2000/svg</span><span>&quot; </span><span style="color:#d08770;">xmlns:xlink</span><span>=&quot;</span><span style="color:#a3be8c;">http://www.w3.org/1999/xlink</span><span>&quot;&gt; -</span><span> &lt;</span><span style="color:#bf616a;">defs</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">pattern </span><span style="color:#d08770;">id</span><span>=&quot;</span><span style="color:#a3be8c;">background-image</span><span>&quot; </span><span style="color:#d08770;">patternUnits</span><span>=&quot;</span><span style="color:#a3be8c;">userSpaceOnUse</span><span>&quot; </span><span style="color:#d08770;">width</span><span>=&quot;</span><span style="color:#a3be8c;">64</span><span>&quot; </span><span style="color:#d08770;">height</span><span>=&quot;</span><span style="color:#a3be8c;">64</span><span>&quot;&gt; -</span><span> </span><span style="color:#65737e;">&lt;!-- the width and height of the image should match the pattern, in most cases --&gt; -</span><span> &lt;</span><span style="color:#bf616a;">image </span><span style="color:#d08770;">xlink:href</span><span>=&quot;</span><span style="color:#a3be8c;">data:image/gif;base64,R0lGODdhQABAAOMAAMzMzJaWlr6+vqOjo8XFxZycnLGxsbe3t6qqqgAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAQABAAAAEpBDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyRwSQk+bYDA5IDbT6rVGKAgmhSim+5WEbYbBQWItTBDwLSC9BrRthcHgGnaD/WZ6fF1cAQBdBAEFAXUAcBOJh2GKjDUBBAdUkpAFZxKXmRKENQedZaMAAwZpVaaigE2xsrO0tba3uLm6u7y9vr/AwcLDxMXGxxURADs=</span><span>&quot; </span><span style="color:#d08770;">x</span><span>=&quot;</span><span style="color:#a3be8c;">0</span><span>&quot; </span><span style="color:#d08770;">y</span><span>=&quot;</span><span style="color:#a3be8c;">0</span><span>&quot; </span><span style="color:#d08770;">width</span><span>=&quot;</span><span style="color:#a3be8c;">64</span><span>&quot; </span><span style="color:#d08770;">height</span><span>=&quot;</span><span style="color:#a3be8c;">64</span><span>&quot; </span><span style="color:#d08770;">id</span><span>=&quot;</span><span style="color:#a3be8c;">svg-background</span><span>&quot; /&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">pattern</span><span>&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">defs</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">g</span><span>&gt; -</span><span> &lt;</span><span style="color:#bf616a;">ellipse </span><span style="color:#d08770;">ry</span><span>=&quot;</span><span style="color:#a3be8c;">32</span><span>&quot; </span><span style="color:#d08770;">rx</span><span>=&quot;</span><span style="color:#a3be8c;">32</span><span>&quot; </span><span style="color:#d08770;">id</span><span>=&quot;</span><span style="color:#a3be8c;">svg_1</span><span>&quot; </span><span style="color:#d08770;">cy</span><span>=&quot;</span><span style="color:#a3be8c;">32</span><span>&quot; </span><span style="color:#d08770;">cx</span><span>=&quot;</span><span style="color:#a3be8c;">32</span><span>&quot; </span><span style="color:#d08770;">fill</span><span>=&quot;</span><span style="color:#a3be8c;">url(#background-image)</span><span>&quot;/&gt; -</span><span> &lt;/</span><span style="color:#bf616a;">g</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">svg</span><span>&gt; -</span></code></pre> -<p>This is what the <a href="/images/placeholder.svg">outputted image</a> would look like.</p> -<p><em>I'm using a very small gif so that the base64 string isn't giant</em>.</p> -<p>You can also see <code>viewBox</code> in action (try zooming). The reason I use this technique, is because it cuts down on requests but also cuts down on file size (for the most part). When you need to have multiple images inside the SVG, this works very well. Instead of having multiple requests (SVG, image1.jpg, image2.jpg, etc.) you get one request.</p> -<h4 id="note">Note</h4> -<hr /> -<p>For some reason, Safari does not like the following scenario: <strong>an SVG, in an img tag, that has a base64 image in it</strong>. I solved this problem, and this is how I did it.</p> -<p>I changed this:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">img </span><span style="color:#d08770;">src</span><span>=&quot;</span><span style="color:#a3be8c;">img/fun.svg</span><span>&quot; </span><span style="color:#d08770;">height</span><span>=&quot;</span><span style="color:#a3be8c;">989</span><span>&quot; </span><span style="color:#d08770;">width</span><span>=&quot;</span><span style="color:#a3be8c;">989</span><span>&quot;&gt; -</span></code></pre> -<p>To this:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">object </span><span style="color:#d08770;">height</span><span>=&quot;</span><span style="color:#a3be8c;">989px</span><span>&quot; </span><span style="color:#d08770;">width</span><span>=&quot;</span><span style="color:#a3be8c;">989px</span><span>&quot; </span><span style="color:#d08770;">data</span><span>=&quot;</span><span style="color:#a3be8c;">img/fun.svg</span><span>&quot; </span><span style="color:#d08770;">type</span><span>=&quot;</span><span style="color:#a3be8c;">image/svg+xml</span><span>&quot;&gt;&lt;/</span><span style="color:#bf616a;">object</span><span>&gt; -</span></code></pre> -<p>Then it worked. In an <code>img</code> tag, the image worked fine in all browsers, yes even IE (checked 11 and 10), but in Safari (7.0.3) it would not render. If I opened the SVG directly in a new tab, it worked fine. So there was some sort of reason that it would not render inside of an <code>img</code> tag. Annoying.</p> -<hr /> -<h3 id="be-careful-with-masks">Be Careful With Masks</h3> -<p>Masking in SVG seems to be a magical mistress. I find sometimes it works wonderfully, and sometimes it is just wrong. Firefox is a pain with this one.</p> -<h3 id="simple-is-better">Simple Is Better</h3> -<p>Always check your files to make sure there aren't any hidden layers. Firefox, again, will punish you for this. I find a lot of SVG documents will have some strange layers that are just empty paths or unfilled objects. This usually is the product of using live trace.</p> -<h3 id="text-to-outlines">Text To Outlines</h3> -<p>Always outline your text. Enough said. Without it, the person may see a font that you didn't intend. I would only use a system font if I had to make sure it stayed as text. A case for this might be dynamically creating SVGs on the fly or something.</p> -<h3 id="use-css-fo">Use CSS Fo</h3> - - - - - Koding Interview - 2014-04-29T00:00:00+00:00 - 2014-04-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/koding-interview/ - - <p>A few months back I was interviewed by the developer socila network Koding.</p> -<p>The initiative was for them to showcase some of the people using their network to build their skill and circle of friends. It is a really cool social network that has been gaining a lot of traction lately.</p> -<p>Here is <a href="http://stories.koding.com/story/james-doyle/">the link to the interview</a>.</p> - - - - - Kaenon Vector Logo - 2014-04-10T00:00:00+00:00 - 2014-04-10T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/kaenon-logo/ - - <p>Here is another logo. This one is for the Kaenon sunglasses brand. This was very hard to find. It doesn't include the little gradient on the original logo. Maybe I will add that in the future.</p> -<div class="center"> - <a href="/images/kaenon.svg" target="_blank"><img alt="kaenon svg vector" src="/images/kaenon.svg" ></a> -</div> -<h5 id="hint">Hint</h5> -<p>If you are looking for these hard to find logos, sometimes you can find them in a lookbook or catalog PDF. The assets are usually high DPI because most of the time these documents are for printing.</p> - - - - - Bash select example - 2014-04-07T00:00:00+00:00 - 2014-04-07T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/bash-select-example/ - - <p>I recently bought 2 <a href="http://raspberrypi.org">raspberry pi computers</a>. One is for home, and one is for the office.</p> -<p>Since we have dynamic IPs setup in the office, and I have the same at my house, I needed to be able to connect using the MAC address of the pi. I ended up writing a little script to get the IP based on the MAC Address, and then ssh into the computer. Pretty slick.</p> -<p>To make my life easier I used the <code>select</code> command in bash. The <a href="http://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs">documentation for select</a> leaves a lot to be desired. So I had to fiddle with it until I got it right. Here is a simple boilerplate for a bash script using select:</p> -<h4 id="function">Function</h4> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;">#!/usr/bin/env bash -</span><span> -</span><span style="color:#8fa1b3;">speak</span><span>() { -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;$</span><span style="color:#bf616a;">1 </span><span>$</span><span style="color:#bf616a;">2</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">break</span><span>; </span><span style="color:#65737e;"># we are done -</span><span>} -</span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">What do you want me to say?</span><span>&quot; -</span><span style="color:#bf616a;">select</span><span> ab in &quot;</span><span style="color:#a3be8c;">Hello</span><span>&quot; &quot;</span><span style="color:#a3be8c;">Bonjour</span><span>&quot;; -</span><span style="color:#b48ead;">do -</span><span> </span><span style="color:#b48ead;">case </span><span>$</span><span style="color:#bf616a;">ab </span><span style="color:#b48ead;">in -</span><span> &quot;</span><span style="color:#a3be8c;">Hello</span><span>&quot;</span><span style="color:#b48ead;">) </span><span style="color:#bf616a;">speak </span><span>&quot;</span><span style="color:#a3be8c;">Hello</span><span>&quot; &quot;</span><span style="color:#a3be8c;">my friend</span><span>&quot;;; -</span><span> &quot;</span><span style="color:#a3be8c;">Bonjour</span><span>&quot;</span><span style="color:#b48ead;">) </span><span style="color:#bf616a;">speak </span><span>&quot;</span><span style="color:#a3be8c;">Bonjour</span><span>&quot; &quot;</span><span style="color:#a3be8c;">mon ami</span><span>&quot;;; -</span><span> *</span><span style="color:#b48ead;">) </span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">invalid option</span><span>&quot;;; </span><span style="color:#65737e;"># you picked anything but 1 or 2 -</span><span> </span><span style="color:#b48ead;">esac -</span><span style="color:#b48ead;">done -</span></code></pre> -<p>Save that in a file called <code>say-hello.sh</code> and change the rights to allow execution by using <code>chmod +x say-hello.sh</code>. Then you can run it:</p> -<h4 id="output">Output:</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ ./say-hello.sh -</span><span> 1) Hello -</span><span> 2) Bonjour -</span><span> #? 1 -</span><span> Hello my friend -</span><span>$ -</span></code></pre> -<p>You can see that in this example I push <code>1</code>. If I run it again and push <code>2</code>, you will see the French words show up.</p> - - - - - Sublime Node Snippets - 2014-03-25T00:00:00+00:00 - 2014-03-25T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/sublime-node-snippets/ - - <p>I created a <a href="https://sublime.wbond.net/packages/Node%20Completions">huge snippet library</a> based on the docs for node 10.26. There are <strong>783</strong> total right now (<em>2014-03-25</em>).</p> -<p>The way that I quickly made this big repository, was I wrote a script that would generate new sublime snippets based on a text file.</p> -<p>The <a href="https://github.com/james2doyle/sublime-node-snippets/blob/master/convert.php">converter</a> just reads <a href="https://github.com/james2doyle/sublime-node-snippets/blob/master/sources.txt">the text file</a> line by line and then generates a <code>.sublime-completions</code> file.</p> -<p>There is a template that is sort of setup. So you can actually just clone the repo, drop in a new sources file, and then generate a new snippets library with the converter.</p> -<p>Here is an excerpt from <a href="https://github.com/james2doyle/sublime-node-snippets">the github repo</a>:</p> -<h2 id="installing">Installing</h2> -<h4 id="package-control">Package Control</h4> -<p>Just look for <code>sublime-node-snippets</code> on <a href="https://sublime.wbond.net/packages/Node%20Completions">Package Control</a>. It is called &quot;Node Completions&quot; on the site, but comes up as &quot;sublime-node-snippets&quot;.</p> -<h4 id="manual-install">Manual Install</h4> -<ul> -<li>Open the Commands Palette (command+shift+p)</li> -<li>Package Control: Add Repository</li> -<li>Past in this repos URL</li> -<li>Press Enter</li> -<li>Open the palette again</li> -<li>press enter on &quot;sublime-node-snippets&quot;</li> -<li>watch it install</li> -</ul> -<h2 id="using">Using</h2> -<p>Pressing <code>.</code> (period) will end the snippet lookup.</p> -<p>You will have better results if you pretend the period isn't needed. So if you are looking for <code>fs.readdir</code>, you would type <code>fsread</code> and you would see the results coming up.</p> -<h2 id="snippet-categories">Snippet Categories</h2> -<p>Node Populars</p> -<ul> -<li>async</li> -<li>underscore</li> -<li>lodash</li> -</ul> -<p>Node Core</p> -<ul> -<li>Assert</li> -<li>Buffer</li> -<li>Child</li> -<li>Console</li> -<li>Cluster</li> -<li>Crypto</li> -<li>Decoder</li> -<li>Domain</li> -<li>Dns</li> -<li>Event</li> -<li>Http</li> -<li>Https</li> -<li>Fs</li> -<li>Global</li> -<li>Module</li> -<li>Net</li> -<li>Path</li> -<li>Punnycode</li> -<li>Process</li> -<li>Querystring</li> -<li>Readline</li> -<li>Repl</li> -<li>Timers</li> -<li>Tls Ssl</li> -<li>Tty</li> -<li>Udp</li> -<li>Util</li> -<li>Url</li> -<li>Os</li> -<li>Vm</li> -<li>Zlib</li> -</ul> -<h2 id="adding-new-snippets">Adding New Snippets</h2> -<p>Here is how I quickly got all these snippets.</p> -<p>I will use <a href="http://expressjs.com/3x/api.html">Express</a> as an example since it isn't in here.</p> -<p>First I went to the docs for the framework, and I looked to see what the code examples were wrapped in.</p> -<p>For the <a href="http://expressjs.com/3x/api.html">express</a> docs site, the codes are shown in <code>section h3</code> tags. So to quickly get the list, I ran the following code:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#ebcb8b;">Array</span><span>.prototype.</span><span style="color:#bf616a;">slice</span><span>.</span><span style="color:#96b5b4;">call</span><span>(document.</span><span style="color:#96b5b4;">querySelectorAll</span><span>(&quot;</span><span style="color:#a3be8c;">section h3</span><span>&quot;), </span><span style="color:#d08770;">0</span><span>).</span><span style="color:#8fa1b3;">map</span><span>(</span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">item</span><span>){ -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#bf616a;">item</span><span>.</span><span style="color:#bf616a;">textContent</span><span>.</span><span style="color:#8fa1b3;">trim</span><span>(); -</span><span>}).</span><span style="color:#96b5b4;">join</span><span>(&quot;</span><span style="color:#96b5b4;">\n</span><span>&quot;); -</span></code></pre> -<p>Then copied the output and pasted it in the <code>sources.txt</code> file. Done!</p> -<h5 id="cool-feature">Cool Feature</h5> -<p><strong>The word <code>callback</code> will automagically be converted into a function.</strong></p> -<h2 id="building">Building</h2> -<p>I went to each page of the <a href="http://nodejs.org/api/">node docs</a>, and copied the functions. Then I wrote a <a href="https://github.com/james2doyle/sublime-node-snippets/blob/master/convert.php">converter</a> to take each function and convert it to a snippet.</p> -<p>For Example, this line:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>setTimeout(fun, delay) -</span></code></pre> -<p>Is going to get converted to:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>setTimeout(${1:fun}, ${2:delay})${0} -</span></code></pre> -<p>When the word <code>callback</code> appears, it will convert it to the standard -<code>fun</code> snippet.</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#bf616a;">fs</span><span>.</span><span style="color:#8fa1b3;">readdir</span><span>(</span><span style="color:#bf616a;">path</span><span>, </span><span style="color:#bf616a;">callback</span><span>) -</span></code></pre> -<p>will become</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#bf616a;">fs</span><span>.</span><span style="color:#8fa1b3;">readdir</span><span>(</span><span style="color:#bf616a;">$</span><span>{</span><span style="color:#d08770;">1</span><span>:</span><span style="color:#bf616a;">path</span><span>}, </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">$</span><span>{</span><span style="color:#d08770;">2</span><span>:</span><span style="color:#bf616a;">args</span><span>}){ -</span><span> </span><span style="color:#bf616a;">$</span><span>{</span><span style="color:#d08770;">3</span><span>:</span><span style="color:#65737e;">// body} -</span><span>})</span><span style="color:#bf616a;">$</span><span>{</span><span style="color:#d08770;">0</span><span>} -</span></code></pre> -<h2 id="sources-txt">sources.txt</h2> -<p>This file is cool.</p> -<p>It is just a line-by-line output of the node docs functions. This is the file that is raked over to generate the snippets.</p> -<h2 id="running-the-build">Running The Build</h2> -<p>Just run <code>php convert.php</code> and it will rake the sources.txt file and then write the new snippet in the snippets folder.</p> -<p>Everything before the first <code>(</code> will be used as the filename and tab snippet.</p> -<h2 id="contributing">Contributing</h2> -<p>Just add (or edit) a line in the source file. Then run <code>php convert.php</code> to generate the new snippets.</p> -<h2 id="why-php">Why PHP?!</h2> -<p>Well, PHP is actually pretty good at manipulating strings and writing files. Maybe at some point I will convert the converter and release it as a separate tool.</p> -<h2 id="source">Source</h2> -<p>You can find the <a href="https://github.com/james2doyle/sublime-node-snippets">source code on github</a>. You can also install via package control by looking for <code>sublime-node-snippets</code>.</p> - - - - - Simple Binder - 2014-03-14T00:00:00+00:00 - 2014-03-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/simple-binder/ - - <p>The other day I was working on a custom form that had a lot of javascript interaction. It got a little too far before I realized I should have been using something like <a href="http://angularjs.org/">Angular.js</a>. I was looking for a simple one-way databinding library, but I couldn't find anything that wasn't overkill.</p> -<p>So I created <a href="http://james2doyle.github.io/simplebinder/">Simple Binder</a>. Simple Binder is a zero dependency one-way databinder for javascript. The great thing about it is that, not only is it very simple, but it is super small as well. No dependencies is also nice.</p> -<p>Using the lib is pretty straightforward. Here is the markup required for a simplebinder element:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;p data-model=&quot;number&quot;&gt;number&lt;/p&gt; -</span><span>&lt;input type=&quot;number&quot; data-controller=&quot;number&quot; /&gt; -</span></code></pre> -<p>As you can see, you must have a <em>data-model</em> and a <em>data-controller</em> set on your items. Models are like the destination for the data-controllers value.</p> -<p>This would be the javascript for this element:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>var sb = SimpleBinder(&#39;number&#39;, function(input, model) { -</span><span> console.log(input.value); -</span><span>}); -</span></code></pre> -<p>Now the <code>sb</code> variable it a simplebinder object. It has a few nice methods that you can use now:</p> -<p>Destroy a simplebinder element.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sb.destroy(); -</span></code></pre> -<p>Add a new controller to a simplebinder element.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sb.addController(&#39;new-controller-name&#39;); -</span></code></pre> -<p>Add a new model to a simplebinder element.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sb.addModel(&#39;new-model-name&#39;); -</span></code></pre> -<p>See all models on a simplebinder element. Returns an arrary of strings with querySelectorAll queries.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sb.models; -</span></code></pre> -<p>See all controllers on a simplebinder element. Returns an arrary of strings with querySelectorAll queries.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>sb.controllers; -</span></code></pre> -<p>Custom events and attributes</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>var sb = SimpleBinder(&#39;modelname&#39;, { -</span><span> watch: &#39;value&#39;, // what controller attribute are we watching? -</span><span> change: &#39;className&#39; // the attribute to change on the model, default = textContent -</span><span>}, function(input, model) { -</span><span> console.log(input.value); -</span><span>}); -</span></code></pre> -<p>That's it. I will be adding the ability to remove a Model or Controller in the future. I tested this on a variety of devices. This library uses <code>querySelectorAll</code>, so if you don't have that... well you're fucked.</p> -<p>Check out the <a href="https://github.com/james2doyle/simplebinder">source on github</a>.</p> - - - - - Vim Vector Logo - 2014-03-04T00:00:00+00:00 - 2014-06-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/vim-svg/ - - <p>Here is a beauty. People have been looking for this Vim logo in a vector format for quite some time. There is of course the <a href="http://commons.wikimedia.org/wiki/File:Vimlogo.svg">old logo</a>, but it looks pretty strange. It reminds me of Tron for some reason.</p> -<p>Anyway this was a pain to make because of all the layers and custom shaping of the V. Also I had to make a bunch of changes to everything once I tested it in the browser because it was all busted.</p> -<p>I usually test my SVGs in the browser as a final OK point. I know if it renders there, then everything should be fine. Also people want it for using as a retina icon now, since the advent of responsive design and development.</p> -<div class="center"> - <a href="/images/vim.svg" target="_blank"><img alt="vim svg vector" src="/images/vim.svg" ></a> -</div> -<p>If you happen to use this for anything, it would be nice to recieve some credit for it. I actually couldn't find who made the original Vim logo. It might just be lost in time.</p> -<h4 id="update">Update</h4> -<p>I made a icns version for OSX. You can <a href="/images/vim.icns">download it here</a>.</p> -<p>You can change the MacVim icon if you want. I happen to think this one is a little nicer.</p> -<h4 id="update-2">Update 2</h4> -<p>I have found another person who wanted a new vim icon. They added it to the fork of <a href="https://code.google.com/p/macvim/">MacVim</a>. Here is the <a href="https://github.com/kaishin/macvim/commit/fedcb4579b68439dba85b9d7fa4b076faac7ebad">commit from his repo</a> with the new icon.</p> - - - - - Atom Monokai Dark - 2014-02-27T00:00:00+00:00 - 2014-02-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/atom-monokai-dark/ - - <p>I made a <a href="http://atom.io/packages/monokai-dark">dark monokai</a> syntax theme for <a href="http://atom.io/">Atom</a>.</p> -<p>Originally converted from <a href="https://github.com/kevinsawicki/monokai">monokai</a> which in turn came from the <a href="http://www.monokai.nl/blog/wp-content/asdev/Monokai.tmTheme">TextMate</a> theme using the <a href="http://atom.io/docs/latest/converting-a-text-mate-theme">TextMate bundle converter</a>.</p> -<div class="center"> - <a href="/images/atom-monokai-dark.png" target="_blank"><img alt="atom monokai dark screenshot" src="/images/atom-monokai-dark.png" ></a> -</div> -<p>I would also suggest editing your main stylesheet and adding the following CSS:</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/* really nice smooth fonts */ -</span><span style="color:#bf616a;">body </span><span>{ -</span><span> -webkit-font-smoothing: antialiased; -</span><span> text-rendering: optimizeLegibility; -</span><span> -moz-osx-font-smoothing: grayscale; -</span><span>} -</span><span> -</span><span style="color:#65737e;">/* custom scrollbars */ -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">tree-view-resizer </span><span>{ -</span><span> ::-webkit-scrollbar { -</span><span> width: </span><span style="color:#d08770;">0.5em</span><span>; -</span><span> height: </span><span style="color:#d08770;">0.5em</span><span>; -</span><span> } -</span><span> -</span><span> </span><span style="color:#8fa1b3;">::</span><span style="color:#b48ead;">-webkit-scrollbar-track </span><span>{ -</span><span> background-color: </span><span style="color:#96b5b4;">#303030</span><span>; -</span><span> } -</span><span> -</span><span> </span><span style="color:#8fa1b3;">::</span><span style="color:#b48ead;">-webkit-scrollbar-thumb </span><span>{ -</span><span> background-color: lighten(</span><span style="color:#96b5b4;">#303030</span><span>, </span><span style="color:#d08770;">15%</span><span>); -</span><span> } -</span><span>} -</span><span> -</span><span style="color:#65737e;">/* fix website scroll styling flash */ -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">tree-view-scroller </span><span>{ -</span><span> overflow: hidden; -</span><span> &amp;:hover { -</span><span> overflow: auto; -</span><span> } -</span><span>} -</span></code></pre> -<p>This adds some nicer smoothing and also adds some custom scrollbars to both panes. This gets rid of the ugly strange white ones. I would also suggest checking out <a href="https://ohdoylerules.com/web/source-code-pro-sublime">Source Code Pro</a> for your font!</p> -<p>You can download the theme on <a href="http://atom.io/packages/monokai-dark">Atom</a>.</p> - - - - - The Alternative Cms - 2014-02-26T00:00:00+00:00 - 2014-02-26T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/the-alternative-cms/ - - <p>The Explode Code presentation was last night. I spoke about <a href="/images/the-alternative-cms.pdf" title="The Alternative CMS PDF">The Alternative CMS</a>. It was about the problems with Wordpress, why <em>WYSIWYG</em> is hard, and why Flat File and Markdown could be the solution you are looking for.</p> -<p>Pretty good reception overall. Some of the things people asked were about complexity. The real answer is if you are doing things that are super complex, you should'nt really use a flat-file cms.</p> -<blockquote> -<p>the idea is really more 60% client and 40% developer</p> -</blockquote> -<p>I don't mention this in the slides, but the idea is really more 60% client and 40% developer. Using that flat-file is much faster to develop, but it makes it easier to manage for the client.</p> -<p><a href="/images/the-alternative-cms.pdf" title="The Alternative CMS PDF">Here is the download</a>.</p> -<p>Here are some of the links to things I mentioned in the talk:</p> -<h4 id="flat-file-cmss">Flat File CMSs</h4> -<ul> -<li>https://github.com/PhileCMS/Phile</li> -<li>https://github.com/gilbitron/Pico</li> -<li>http://assemble.io/</li> -<li>https://ghost.org/features/</li> -<li>http://getkirby.com/</li> -<li>http://jekyllrb.com/</li> -<li>http://octopress.org/</li> -</ul> -<h4 id="markdown-editors">Markdown Editors</h4> -<ul> -<li>http://mouapp.com/ -- OSX Only</li> -<li>http://pad.haroopress.com/ -- Node Webkit</li> -<li>https://stackedit.io/ -- Online</li> -<li>http://dillinger.io/ -- Online</li> -<li>http://www.sublimetext.com/ -- Duh</li> -<li>http://macromates.com/ -- TextMate</li> -<li>https://github.com/plasticboy/vim-markdown -- Vim markdown</li> -<li>http://jblevins.org/projects/markdown-mode/ -- Emacs</li> -<li>https://gist.github.com/james2doyle/9045390 -- Markdown Cheatsheet</li> -<li>https://gist.github.com/james2doyle/6540193 -- Mou Markdown Cheatsheet</li> -</ul> -<h4 id="full-cmss-with-markdown-support">Full CMSs With Markdown Support</h4> -<ul> -<li>http://anchorcms.com/ -- markdown only</li> -<li>http://buildwithcraft.com/ -- wysiwyg, markdown, simple/text</li> -<li>https://www.pyrocms.com/ -- wysiwyg, markdown, simple/text</li> -<li>http://wordpress.org/plugins/wp-markdown/screenshots/ -- who knows</li> -<li>http://dropplets.com/ -- markdown and simple/text</li> -</ul> -<h4 id="inline-editors-content-editable">Inline Editors (Content Editable)</h4> -<ul> -<li>http://jakiestfu.github.io/Medium.js/docs/</li> -<li>http://www.zenpen.io/</li> -<li>http://mattduvall.com/grande.js/</li> -<li>http://ckeditor.com/demo#inline</li> -<li>http://sofish.github.io/pen/</li> -<li>http://goo.gl/b4ECsb -- pen, grande, medium breakdown</li> -</ul> -<p>Again you can find the <a href="/images/the-alternative-cms.pdf" title="The Alternative CMS PDF">PDF here</a>.</p> - - - - - Randomly Generate A Password In Bash - 2014-02-16T00:00:00+00:00 - 2014-02-16T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/bash-random-password-generation/ - - <p>When installing or setting up frameworks, in this case I was playing around with <a href="http://laravel.com/">Laravel</a>, you usually need to set a session/secret/encryption key.</p> -<p>I know why this is, but I always end up looking around for some random password generator so I can get a random string that is exactly 32 characters. Isn't there an easier way?!?!</p> -<p>Yes there is. If you have the magical <a href="https://www.openssl.org/" title="OpenSSL Website">OpenSSL</a> installed, which most do, you can use it to generate a random string.</p> -<p>I found a <a href="http://osxdaily.com/2011/05/10/generate-random-passwords-command-line/" title="Generate Random Passwords from the Command Line">article online</a> that uses base64 to generate a string of a certain length. The only thing is that base64 is padded with 8 bits. Which means that if you want 32 then you need to use 24. This goes up exponentially as the number gets bigger. So there is a trim part of the function that clips off the extra characters.</p> -<p>Here is the function broken down into steps:</p> -<ul> -<li>pass a number to the function</li> -<li>cut the resulting string</li> -<li>generate a base64 string using that number</li> -<li>echo out the result</li> -<li>copy the output to the clipboard with a newline</li> -<li>echo out a success</li> -</ul> -<p>I put a check in there if the argument is not a number. This is just for the dummies out there.</p> -<h4 id="pbcopy-is-not-defined">pbcopy is not defined</h4> -<p>You are probably on Linux. I found <a href="http://whereswalden.com/2009/10/23/pbcopy-and-pbpaste-for-linux/" title="pbcopy and pbpaste for Linux">this little snippet</a> for the lazy. This way you can forget about translating it each time.</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#96b5b4;">alias </span><span style="color:#8fa1b3;">pbcopy</span><span>=&quot;</span><span style="color:#a3be8c;">xsel --clipboard --input</span><span>&quot; -</span><span style="color:#96b5b4;">alias </span><span style="color:#8fa1b3;">pbpaste</span><span>=&quot;</span><span style="color:#a3be8c;">xsel --clipboard --output</span><span>&quot; -</span></code></pre> -<p>Now for the actual shell function:</p> -<h3 id="code">Code</h3> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># if the argument is a number -</span><span style="color:#65737e;"># cut the string so that there is no base64 padding -</span><span style="color:#65737e;"># generate a random password of the specified length -</span><span style="color:#65737e;"># then copy it to the clipboard without a newline -</span><span style="color:#65737e;"># usage: password 32 -</span><span style="color:#8fa1b3;">password</span><span>() { -</span><span> </span><span style="color:#bf616a;">LENGTH</span><span>=&quot;$</span><span style="color:#bf616a;">1</span><span>&quot; -</span><span> </span><span style="color:#bf616a;">REGEX</span><span>=&#39;</span><span style="color:#a3be8c;">^[0-9]+$</span><span>&#39; -</span><span> </span><span style="color:#b48ead;">if </span><span style="color:#96b5b4;">[[ </span><span>$</span><span style="color:#bf616a;">LENGTH </span><span>=~ $</span><span style="color:#bf616a;">REGEX </span><span style="color:#96b5b4;">]] </span><span>; </span><span style="color:#b48ead;">then -</span><span> </span><span style="color:#bf616a;">PASSWD</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">openssl</span><span style="color:#a3be8c;"> rand</span><span style="color:#bf616a;"> -base64 </span><span>$</span><span style="color:#bf616a;">LENGTH </span><span>| </span><span style="color:#bf616a;">head -c</span><span>$</span><span style="color:#bf616a;">LENGTH</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">PASSWD -</span><span> </span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">PASSWD </span><span>| </span><span style="color:#bf616a;">tr -d </span><span>&#39;</span><span style="color:#a3be8c;">\n</span><span>&#39; | </span><span style="color:#bf616a;">pbcopy -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">Password copied to clipboard</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">else -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">Argument must be a number</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">fi -</span><span>} -</span></code></pre> -<p>Here is how you would use it, and what the results would look like:</p> -<h3 id="output">Output</h3> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">~</span><span> ❯ password 32 -</span><span style="color:#bf616a;">gY4zLES+WWF5+iNWo0FYx+os6EmDwecf -</span><span style="color:#bf616a;">Password</span><span> copied to clipboard -</span><span style="color:#bf616a;">~</span><span> ❯ password fu -</span><span style="color:#bf616a;">Argument</span><span> must be a number -</span><span style="color:#bf616a;">~</span><span> ❯ -</span></code></pre> -<p>This function would be put in your <code>.bashrc</code> file, or you <code>.zshrc</code> file if you are a cool ZSH user.</p> - - - - - PHP WebSocket Chat - 2014-02-08T00:00:00+00:00 - 2014-02-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/php-websocket-chat/ - - <p>About 6 months ago, I made a little <a href="https://github.com/james2doyle/socket-chat-example">socket.io chat app</a>. At the time, this was really only possible with Node.js because the <a href="http://caniuse.com/#feat=websockets">HTML5 WebSocket support</a> was too low.</p> -<p>But now, months later, the support for WebSockets is actually very good.Looking at <a href="http://caniuse.com">caniuse.com</a> right now, there is better support for WebSocket than there is WebGL. I would argue that WebGL support is actually more important than the WebSocket support, but I digress. Here is a non-jargon-laden explanation from <a href="http://www.html5rocks.com/en/tutorials/websockets/basics/#toc-introduction-sockets">HTML5Rocks</a>:</p> -<blockquote> -<p>The WebSocket specification defines an API establishing &quot;socket&quot; connections between a web browser and a server. In plain words: There is an persistent connection between the client and the server and both parties can start sending data at any time.</p> -</blockquote> -<p>Here is a little more technical explanation.</p> -<blockquote> -<p>A WebSocket creates a TCP connection to server, and keeps it as long as needed. The Server or client can easily close it. It uses Bidirectional communication - so server and client can exchange data both directions at any time. It is very efficient if the application requires frequent messages. WebSockets have data framing that includes masking for each message sent from client to server so data is simply encrypted.</p> -</blockquote> -<p>If you want a technically in-depth overview, checkout <a href="http://www.websocket.org/quantum.html">websocket.org</a>.</p> -<p>Anyway, I made a <a href="https://github.com/james2doyle/php-socket-chat">little chat app</a> with <a href="http://socketo.me/">Ratchet</a>. People knock PHP for all the bad things it does. But getting the WebSocket example running, actually wasn't that bad. Apparently Apache doesn't play nice with Ratchet (not sure about <em>pure</em> WebSockets) so you have to use the <a href="http://www.php.net/manual/en/features.commandline.webserver.php">built-in PHP server</a> which comes with PHP 5.4.</p> -<div class="center"> - <a href="/images/php-socket-animation.gif" target="_blank" title="php ratchet socket server form example"><img alt="php ratchet socket server form example" src="/images/php-socket-animation.gif" width="252" height="246" ></a> -</div> -<p>The app I made is pretty much a copy paste from the <a href="http://socketo.me/docs/hello-world">Rachet Hello World Example</a> but tried to make the simplest chat app I could. The server is actually pretty close the Hello World code, just with a bunch of extra client-side javascript.</p> -<p>Once you <a href="https://github.com/james2doyle/php-socket-chat">download the app</a>, if you have PHP properly installed and in your path, you can use <code>php bin/chat-server.php</code> in the root folder to start the server. You can then hit the index page and see the green connection message. You will also see some information in your terminal.</p> -<p>You can then open a new browser (or incognito/private window) and &quot;create&quot; another user to chat with.</p> -<p>You can see your messages going back and forth. Pretty slick. With the way I develop things at <a href="http://warpaintmedia.ca">WARPAINT Media</a>, I really can't wait to create some sites and apps that use the WebSocket server.</p> - - - - - All About PhileCMS Video - 2014-02-07T00:00:00+00:00 - 2014-02-07T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/phile-intro-video/ - - <p><a href="https://github.com/PhileCMS/Phile">PhileCMS</a> now has a <a href="http://www.youtube.com/watch?v=8GLMe371RuI">new instructional video</a>.</p> -<p>Here are some of the points I hit on in the video:</p> -<ul> -<li>Installation</li> -<li>Differences To Pico</li> -<li>About Twig</li> -<li>Creating Content</li> -<li>Using Meta Data</li> -<li>Themes</li> -<li>Conditional Navigations</li> -<li>Plugins</li> -<li>Tricks</li> -</ul> -<p>The video is about 35 minutes. So there is quite a lot of stuff in there. I plan on making a few more videos. One about making themes and using the <a href="https://github.com/PhileCMS/phileGruntThemeing">phileGruntThemeing</a> project, and creating plugins with the events system.</p> -<p><a href="http://www.youtube.com/watch?v=8GLMe371RuI">Watch it on YouTube</a>.</p> - - - - - Font Awesome SVG Icons - 2014-02-06T00:00:00+00:00 - 2014-02-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/font-awesome-svg-icons/ - - <p>This one was kind of annoying. I was looking for all the <a href="http://fontawesome.io/">font-awesome icons</a> in a nice sheet so that <a href="https://ohdoylerules.com/web/font-awesome-svg-icons/warpaintmedia.ca">WARPAINT</a> could design some mockups for a client. Well, of course this sheet doesn't exists.</p> -<p>So I used the following code to grab the icons from the <a href="http://fontawesome.io/cheatsheet/">cheatsheet page</a>.</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">var </span><span style="color:#bf616a;">arr </span><span>= &quot;&quot;; -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">.container .col-md-4</span><span>&#39;).</span><span style="color:#8fa1b3;">each</span><span>(</span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">i</span><span>, </span><span style="color:#bf616a;">e</span><span>) { -</span><span> </span><span style="color:#bf616a;">arr </span><span>+= </span><span style="color:#bf616a;">e</span><span>.</span><span style="color:#bf616a;">innerText</span><span>.</span><span style="color:#96b5b4;">slice</span><span>(</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">1</span><span>) + &#39;</span><span style="color:#a3be8c;">, </span><span>&#39;; -</span><span>}); -</span><span style="color:#ebcb8b;">console</span><span>.</span><span style="color:#96b5b4;">log</span><span>(</span><span style="color:#bf616a;">arr</span><span>); -</span></code></pre> -<p>I then took that output and pasted it into Sublime Text. I split the output into multiple lines, and replaced the commas with double spaces. That was then pasted into Illustrator and exported as SVG.</p> -<p>Voila! There you have this sprite sheet.</p> -<p>You can <a href="/images/font-awesome-sheet.svg">download it here</a>.</p> -<p><em>These icons are from version 4.0.2</em></p> - - - - - Groupon SVG logo - 2014-01-24T00:00:00+00:00 - 2014-01-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/groupon-green-logo-svg/ - - <p>Lo and behold, a new vector. This one is for the Groupon logo. This is actually based on another logo that was black. It wasn't very nice, so I picked the Groupon greens from their site and applied them to the background gradient. I also removed the gaudy font gradient and left them as flat white.</p> -<p>Below is the source SVG for download.</p> -<div class="center"> - <a href="/images/groupon.svg" title="groupon svg vector" target="_blank"><img alt="groupon svg vector" src="/images/groupon.svg" ></a> -</div> - - - - - Chrome Reverse Geocode App - 2014-01-08T00:00:00+00:00 - 2014-01-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/chrome-reverse-geocode/ - - <p>I have created a new app (with the help of <a href="https://twitter.com/beatricelaw">Beatrice Law</a>) called <a href="http://goo.gl/Z15Teh" title="Reverse Geocode on the Chrome Web Store">Reverse Geocode</a>.</p> -<p>In the process of building the site for <a href="http://textbooksforchange.ca/">Textbooks For Change</a>, a <a href="http://warpaintmedia.ca">WARPAINT Media</a> client, I realized I needed an easy way for them to reverse geocode an address for their map listings.</p> -<p>The site is built on <a href="http://philecms.github.io/Phile/">PhileCMS</a> so it is very fast, but requires a little more savvy-ness than normal. I added the static Google Map that is based on the list of coordinates they enter in.</p> -<div class="center"> - <a href="http://goo.gl/Z15Teh" target="_blank"><img alt="chrome reverse geocode app screenshot" src="/images/reverse-geocode.png" ></a> -</div> -<p>It is very easy to update, but not quite that easy to get the coordinates. There is a Google tool to do this, but it is actually not as nice (but I have an obvious bias).</p> -<p>The app allows you to enter in an address, then they app goes and gets the latitude and longitude. Then, because this is a <a href="http://developer.chrome.com/apps/about_apps.html">Chrome Packaged App</a>, you can copy it right to your clipboard with a single click!</p> -<p>You can download the app on the <a href="http://goo.gl/Z15Teh" title="Reverse Geocode on the Chrome Web Store">Chrome Web Store</a>.</p> -<p>The app <a href="https://github.com/WARPAINTMedia/chrome-reverse-geocode" title="Reverse Geocode on Github">is also on github</a> so anyone can submit some changes and improvements.</p> - - - - - IICRC Vector Logo - 2013-12-18T00:00:00+00:00 - 2013-12-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/iicrc-vector-logo/ - - <p>Another lost logo, this one is for the IICRC (institute of inspection cleaning and restoration). They are an international cleaning certification institure. Clearly. Here is their vector logo.</p> -<div class="center"> - <a href="/images/iicrc.svg" title="iicrc svg vector" target="_blank"><img alt="iicrc svg vector" src="/images/iicrc.svg" ></a> -</div> -<p>Enjoy. This was a huge pain to get right.</p> - - - - - List File Permission Numbers - 2013-12-08T00:00:00+00:00 - 2013-12-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/list-file-permission-numbers/ - - <p>I wanted to see the chmod numbers for the files in a directory. So I can copy them to the other files. Since I don't want to do that dumb chmod math, I looked for a way to do it easily.</p> -<p>I found the following code:</p> -<h4 id="function">Function</h4> -<p>Add the following to your .bashrc (or .zshrc file if you are cool) and then reload the source of your terminal.</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">show-permissions</span><span>() { -</span><span> </span><span style="color:#bf616a;">ls -l </span><span>| </span><span style="color:#bf616a;">awk </span><span>&#39;</span><span style="color:#a3be8c;">{k=0;for(i=0;i&lt;=8;i++)k+=((substr($1,i+2,1)~/[rwx]/) \ -</span><span style="color:#a3be8c;"> *2^(8-i));if(k)printf(&quot;%0o &quot;,k);print}</span><span>&#39; -</span><span>} -</span></code></pre> -<h4 id="usage">Usage:</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>show-permissions -</span><span>644 -rw-r--r-- 1 james2doyle README.md -</span><span>644 -rw-r--r-- 1 james2doyle LICENSE -</span></code></pre> -<p>greping the output</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>show-permissions | grep README.md -</span><span>644 -rw-r--r-- 1 james2doyle README.md -</span></code></pre> -<p>Here is the <a href="http://goo.gl/HS9Ar3">stackoverflow question</a> where I stole this from.</p> - - - - - Easy Command Line Reverse Geocoding - 2013-11-27T00:00:00+00:00 - 2013-11-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/easy-command-line-reverse-geocoding/ - - <p>Using this function you can easily reverse geocode an address into a <em>lat and lang</em> position. This uses the <a href="http://stedolan.github.io/jq/">jq executable</a> and the <a href="https://developers.google.com/maps/documentation/geocoding/#GeocodingRequests">Google Maps API</a>.</p> -<h4 id="requirements">Requirements</h4> -<p>This little snippet <a href="http://stedolan.github.io/jq/">requires jq to be installed</a>. It is very easy to install.</p> -<p>From the site:</p> -<blockquote> -<p>jq is a lightweight and flexible command-line JSON processor</p> -</blockquote> -<p>It is multi-platform, so no worries for Windows users.</p> -<p>Here is the meat:</p> -<h4 id="function">Function</h4> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">reverse-geocode</span><span>() { -</span><span> </span><span style="color:#65737e;"># replace spaces with + signs -</span><span> </span><span style="color:#bf616a;">STRING</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">1 </span><span>| </span><span style="color:#bf616a;">tr </span><span>&#39; &#39; &#39;</span><span style="color:#a3be8c;">+</span><span>&#39;</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#65737e;"># save results -</span><span> </span><span style="color:#bf616a;">CURLED</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">curl </span><span>&quot;</span><span style="color:#a3be8c;">http://maps.googleapis.com/maps/api/geocode/json?address=</span><span>$</span><span style="color:#bf616a;">STRING</span><span style="color:#a3be8c;">&amp;sensor=true</span><span>&quot;</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#65737e;"># save lat and lng -</span><span> </span><span style="color:#bf616a;">LANG</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">CURLED </span><span>| </span><span style="color:#bf616a;">jq </span><span>&#39;</span><span style="color:#a3be8c;">.results[0].geometry.location.lng</span><span>&#39;</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#bf616a;">LAT</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">CURLED </span><span>| </span><span style="color:#bf616a;">jq </span><span>&#39;</span><span style="color:#a3be8c;">.results[0].geometry.location.lat</span><span>&#39;</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#65737e;"># echo them out -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;</span><span style="color:#a3be8c;">Lat: </span><span>$</span><span style="color:#bf616a;">LAT</span><span style="color:#a3be8c;">, Lang: </span><span>$</span><span style="color:#bf616a;">LANG</span><span>&quot; -</span><span>} -</span></code></pre> -<h4 id="usage">Usage:</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>reverse-geocode &quot;998 Oxford Street E, London ON, N5Y 3K7&quot; -</span></code></pre> -<p>This return the curl results as well as the Lat and Lang output for the location.</p> - - - - - Grunt Highlight Plugin - 2013-11-25T00:00:00+00:00 - 2013-11-25T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/grunt-highlight/ - - <p>Over the weekend, in a couple hours, I wrote this grunt plugin for <a href="http://highlightjs.org">Highlight.js</a>. I know that <a href="https://github.com/chjj/marked">marked</a> does an excellent job of parsing markdown, and can also use highlight, but I wanted something I could use in <a href="https://github.com/assemble/assemble/">assemble</a> for HTML parsing or full css/js files.</p> -<p>This was made much easier thanks to the <a href="https://github.com/yeoman/generator-gruntplugin">yeoman-gruntplugin</a> project.</p> -<h3 id="getting-started">Getting Started</h3> -<p>This plugin requires Grunt.</p> -<p>If you haven't used <a href="http://gruntjs.com/">Grunt</a> before, be sure to check out the <a href="http://gruntjs.com/getting-started">Getting Started</a> guide, as it explains how to create a <a href="http://gruntjs.com/sample-gruntfile">Gruntfile</a> as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">npm</span><span> install grunt-highlight</span><span style="color:#bf616a;"> --save-dev -</span></code></pre> -<p>Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">grunt</span><span>.</span><span style="color:#8fa1b3;">loadNpmTasks</span><span>(&#39;</span><span style="color:#a3be8c;">grunt-highlight</span><span>&#39;); -</span></code></pre> -<h3 id="the-highlight-task">The &quot;highlight&quot; task</h3> -<h4 id="overview">Overview</h4> -<p>In your project's Gruntfile, add a section named <code>highlight</code> to the data object passed into <code>grunt.initConfig()</code>.</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">grunt</span><span>.</span><span style="color:#8fa1b3;">initConfig</span><span>({ -</span><span> highlight: { -</span><span> task: { -</span><span> options: { -</span><span> </span><span style="color:#65737e;">// Task-specific options go here. -</span><span> }, -</span><span> your_target: { -</span><span> </span><span style="color:#65737e;">// Target-specific file lists and/or options go here. -</span><span> } -</span><span> } -</span><span> } -</span><span>}); -</span></code></pre> -<h4 id="options">Options</h4> -<h5 id="options-lang">options.lang</h5> -<p>Type: <code>Boolean</code> -Default value: <code>false</code></p> -<p>If you know the highlight language, use this.</p> -<h5 id="options-usecheerio">options.useCheerio</h5> -<p>Type: <code>Boolean</code> -Default value: <code>true</code></p> -<p>You target files are HTML and you want to parse over them and highlight code blocks. <em>Turn off for raw code input</em>.</p> -<h5 id="options-selector">options.selector</h5> -<p>Type: <code>Boolean</code> -Default value: <code>pre code</code></p> -<p>This is what cheerio will be looking for as code block in your HTML. <em>Only used when useCheerio is true</em>.</p> -<h4 id="usage-examples">Usage Examples</h4> -<h5 id="default-options">Default Options</h5> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">grunt</span><span>.</span><span style="color:#8fa1b3;">initConfig</span><span>({ -</span><span> highlight: { -</span><span> task: { -</span><span> options: {}, -</span><span> files: { -</span><span> &#39;</span><span style="color:#a3be8c;">dest/out.html</span><span>&#39;: [&#39;</span><span style="color:#a3be8c;">src/in.html</span><span>&#39;], -</span><span> } -</span><span> } -</span><span> } -</span><span>}); -</span></code></pre> -<h5 id="full-code-files">Full Code Files</h5> -<p>If you want to highlight an entire file then use the following:</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">grunt</span><span>.</span><span style="color:#8fa1b3;">initConfig</span><span>({ -</span><span> highlight: { -</span><span> task: { -</span><span> options: { -</span><span> useCheerio: </span><span style="color:#d08770;">false</span><span>, -</span><span> lang: &#39;</span><span style="color:#a3be8c;">javascript</span><span>&#39; </span><span style="color:#65737e;">// treat the file as a javascript file -</span><span> }, -</span><span> files: { -</span><span> &#39;</span><span style="color:#a3be8c;">dest/highlighted.html</span><span>&#39;: [&#39;</span><span style="color:#a3be8c;">src/bunch-o-javascript.js</span><span>&#39;], -</span><span> } -</span><span> } -</span><span> } -</span><span>}); -</span></code></pre> -<h5 id="many-files">Many Files</h5> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#bf616a;">grunt</span><span>.</span><span style="color:#8fa1b3;">initConfig</span><span>({ -</span><span> highlight: { -</span><span> scripts: { -</span><span> options: { -</span><span> useCheerio: </span><span style="color:#d08770;">false</span><span>, -</span><span> lang: &#39;</span><span style="color:#a3be8c;">javascript</span><span>&#39; -</span><span> }, -</span><span> files: { -</span><span> &#39;</span><span style="color:#a3be8c;">javascript.html</span><span>&#39;: [&#39;</span><span style="color:#a3be8c;">src/script.js</span><span>&#39;] -</span><span> } -</span><span> }, -</span><span> styles: { -</span><span> options: { -</span><span> useCheerio: </span><span style="color:#d08770;">false</span><span>, -</span><span> lang: &#39;</span><span style="color:#a3be8c;">css</span><span>&#39; -</span><span> }, -</span><span> files: { -</span><span> &#39;</span><span style="color:#a3be8c;">stylesheet.html</span><span>&#39;: [&#39;</span><span style="color:#a3be8c;">src/style.css</span><span>&#39;] -</span><span> } -</span><span> } -</span><span> } -</span><span>}); -</span></code></pre> -<p>Check out the project <a href="https://github.com/james2doyle/grunt-highlight">on github</a>.</p> - - - - - Decrypt-Encrypt Functions From Command Line - 2013-11-22T00:00:00+00:00 - 2013-11-22T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/openssl-encrypt-decrypt-functions/ - - <h3 id="preamble">Preamble</h3> -<p>I have been reading about encryption and security since the whole NSA/Edward Snowden thing. It is pretty intense stuff. Most of the security comes from the philosophy of &quot;security through obfuscation&quot;. What this means, is that you are making it extremely difficult (expensive, time-consuming) to try and look at your &quot;stuff&quot;.</p> -<p>I would suggest reading <a href="http://en.wikipedia.org/wiki/Key_size">this article on &quot;Key Size&quot;</a>. This is probably my favorite quote from the article:</p> -<blockquote> -<p>With a key of length <em>n</em> bits, there are <em>2n</em> possible keys. This number grows very rapidly as <em>n</em> increases. Moore's law suggests that computing power doubles roughly every 18 to 24 months, but even this doubling effect leaves the larger symmetric key lengths currently considered acceptable well out of reach.</p> -</blockquote> -<p>The best thing you can do for this type of encryption is <a href="https://tech.dropbox.com/2012/04/zxcvbn-realistic-password-strength-estimation/">pick a good password</a>.</p> -<h3 id="code">Code</h3> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># take in a file and output an encrypted one -</span><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">encrypt</span><span>() { -</span><span> </span><span style="color:#65737e;"># take in a file and output a new one with a `.enc` extension -</span><span> </span><span style="color:#bf616a;">openssl</span><span> rc4</span><span style="color:#bf616a;"> -in </span><span>&quot;$</span><span style="color:#bf616a;">1</span><span>&quot;</span><span style="color:#bf616a;"> -out </span><span>&quot;$</span><span style="color:#bf616a;">1</span><span>&quot;.enc -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;$</span><span style="color:#bf616a;">1</span><span style="color:#a3be8c;">.enc created</span><span>&quot; -</span><span>} -</span><span> -</span><span style="color:#65737e;"># reverse of encrypt() -</span><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">decrypt</span><span>() { -</span><span> </span><span style="color:#bf616a;">FILENAME</span><span>=&quot;$</span><span style="color:#bf616a;">1</span><span>&quot; </span><span style="color:#65737e;"># save the old filename -</span><span> </span><span style="color:#65737e;"># decrypt the file and save it to a file with no `.enc` extension -</span><span> </span><span style="color:#bf616a;">openssl</span><span> rc4</span><span style="color:#bf616a;"> -d -in </span><span>&quot;$</span><span style="color:#bf616a;">1</span><span>&quot;</span><span style="color:#bf616a;"> -out </span><span>${</span><span style="color:#bf616a;">FILENAME</span><span>%.*} -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&quot;$</span><span style="color:#bf616a;">1</span><span style="color:#a3be8c;"> decrypted</span><span>&quot; -</span><span>} -</span></code></pre> -<p>This still leave an &quot;open&quot; file when the file is encrypted. Remember to remove the file securely. You can use <code>shred</code> or <code>gshred</code> (for OSX). Here is the info from the <code>--help</code> output of gshred:</p> -<blockquote> -<p>Overwrite the specified FILE(s) repeatedly, in order to make it harder -for even very expensive hardware probing to recover the data.</p> -</blockquote> -<p>Here is the function that I found to be pretty good:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># overwrite &#39;my-unsafe-file.txt&#39; 3 times, with zeros (nulls) and then remove the file -</span><span style="color:#bf616a;">gshred --iterations</span><span>=3</span><span style="color:#bf616a;"> --zero --remove</span><span> my-unsafe-file.txt -</span></code></pre> -<p>Note: I used RC4 instead of 3DES because it is faster (95% slower than RC4), but it is not as secure.</p> -<h4 id="references">References:</h4> -<ul> -<li><a href="http://osxdaily.com/2012/01/30/encrypt-and-decrypt-files-with-openssl/">Encrypt &amp; Decrypt Files from the Command Line with OpenSSL</a></li> -<li><a href="http://blog.commandlinekungfu.com/2009/05/episode-32-wiping-securely.html">Wiping Securely</a></li> -<li><a href="http://zombe.es/post/4078724716/openssl-cipher-selection">OpenSSL Cipher Selection</a></li> -</ul> - - - - - Get Wordpress via Command Line - 2013-11-21T00:00:00+00:00 - 2013-11-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/wordpress-via-command-line/ - - <p>All the cool kids are using the command line these days. This allows you to run quick commands and little functions that would be too tedious to run with a GUI or just clicking around.</p> -<p>A while ago I added this little code snippet to be .bashrc file. It means I can run <code>download-wordpress</code> in an empty folder and then it will go and grab the latest archive, unzip it, and remove the junk.</p> -<pre data-lang="bash" style="background-color:#2b303b;color:#c0c5ce;" class="language-bash "><code class="language-bash" data-lang="bash"><span style="color:#8fa1b3;">download-wordpress </span><span>() { -</span><span> </span><span style="color:#bf616a;">wget</span><span> http://wordpress.org/latest.tar.gz </span><span style="color:#65737e;"># get wordpress -</span><span> </span><span style="color:#bf616a;">tar</span><span> xfz latest.tar.gz </span><span style="color:#65737e;"># unzip the archive -</span><span> </span><span style="color:#bf616a;">mv</span><span> wordpress/* ./ </span><span style="color:#65737e;"># move the files to the root of this directory -</span><span> </span><span style="color:#bf616a;">rmdir</span><span> ./wordpress/ </span><span style="color:#65737e;"># delete the empty directory -</span><span> </span><span style="color:#bf616a;">rm -f</span><span> latest.tar.gz </span><span style="color:#65737e;"># delete the archive -</span><span> </span><span style="color:#96b5b4;">echo </span><span>&#39;</span><span style="color:#a3be8c;">wordpress installed</span><span>&#39; </span><span style="color:#65737e;"># let me know we are done -</span><span>} -</span></code></pre> -<p>This is a handy function. You can really use it for any CMS or Zip file you have to grab on the regular. Just remember to run this in an <em>empty directory</em>, or it will overwrite everything and it will make a huge mess.</p> - - - - - HostMonster PHPMailer Settings - 2013-11-19T00:00:00+00:00 - 2013-11-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/hostmonster-phpmailer/ - - <p>God Damn!! This one was a b*tch to get right. I have a small plugin for a site that makes doing AJAX contact forms a breeze.</p> -<p>But, of course, it likes to be a pain in the ass when I am trying to set it up. Also you usually have to be on the correct domain to allow the script to send the right email. So doubly annoying.</p> -<p>Here is the code that worked for me:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>$mail = new PHPMailer; // basic class declaration -</span><span>$mail-&gt;isSMTP(); // duh! -</span><span>$mail-&gt;SMTPDebug = 0; // no debug -</span><span>$mail-&gt;SMTPAuth = true; // yes to auth please -</span><span>$mail-&gt;Port = 26; // nope not port 25, 26!! -</span><span>$mail-&gt;Host = &#39;host286.hostmonster.com&#39;; -</span></code></pre> -<p>Now your host may differ. I used <a href="http://mxtoolbox.com/">this tool</a> to check the MX records for the domain. After the check is complete, you will see a small table showing the hostname, IP address, TTL, and some links. <strong>Click &quot;SMTP Test&quot;</strong>.</p> -<p>Once that text completes, you will see another table. The first result is the &quot;SMTP Reverse Banner Check&quot;. Copy the hostname, which is the domain in that value field.</p> -<p>Hopefully this works for you. I had a hell of a time getting the correct settings. <em>My pain is your gain</em>.</p> - - - - - GoDaddy Email on Digital Ocean - 2013-11-18T00:00:00+00:00 - 2013-11-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/godaddy-digital-ocean/ - - <p>I was recently trying to send an email to a domain I had purchased on GoDaddy but had hosting on <a href="https://www.digitalocean.com/?refcode=802f151adea5">Digital Ocean</a>.</p> -<p>I sent the email and a couple hours later it bounced. This wasn't good. My email was going to GoDadddy but I want the site <em>hosted</em> on Digital Ocean.</p> -<p>So I had to find how to keep the domain hosted on Digital Ocean but the email needs to stay on GoDaddy servers.</p> -<p>Here are the DNS settings I used:</p> -<div class="center"> - <a href="/images/do-records1.png" title="GoDaddy Digital Ocean DNS Records" target="_blank"><img src="/images/do-records1.png" alt="GoDaddy Digital Ocean DNS Records" ></a> -</div> -<p>Your settings may differ, so please follow these instructions in order to check if your settings are correct:</p> -<ul> -<li>Log into GoDaddy</li> -<li>Launch the domain you are looking to check</li> -<li>Go to the email tab</li> -<li>Hover over tools and click &quot;Server Settings&quot;</li> -<li>You will see a popup showing all the settings</li> -</ul> -<p>There you can see that there are a bunch of records listed. These are the ones for your specific domain.</p> -<div class="center"> - <a href="/images/do-records2.png" title="GoDaddy Default MX and DNS Records" target="_blank"><img src="/images/do-records2.png" alt="GoDaddy Default MX and DNS Records" ></a> -</div> -<p>Hopefully this was helpful because it took a long time to figure out! It is even more painful because the records take a while to propogate. Boo!</p> - - - - - Github Wiki To HTML - 2013-11-17T00:00:00+00:00 - 2013-11-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/github-wiki-to-html/ - - <p>Have you ever wanted to convert a Github wiki to a set of HTML pages? This can be an easy way to generate new gh-pages (github web pages) based on the projects Wiki.</p> -<p>As of <a href="https://github.com/blog/699-making-github-more-open-git-backed-wikis">August 2010</a>, you can actually clone a repositories wiki to your local machine just by adding .wiki at the end.</p> -<p>This pulls down all the wiki pages in their current format, by default this is <code>.md</code> files.</p> -<p>Now what can you do with these files? Well how about converting them to HTML so that you can use them in your gh-pages repo?</p> -<p>After cloning the <em>.wiki</em> repo to your local, you can create a script to convert all the files to HTML.</p> -<ul> -<li>first <a href="https://github.com/chjj/marked">install marked globally</a> via NPM</li> -<li>make a directory called <code>html</code> in the root of the repo</li> -<li>create a file called <code>convert.sh</code></li> -<li>run <code>chmod +x convert.sh</code> on that file to allow execution</li> -<li>paste the following into the file:</li> -</ul> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;">#!/bin/bash -</span><span> -</span><span style="color:#65737e;"># for each md file in the directory -</span><span style="color:#b48ead;">for</span><span> file </span><span style="color:#b48ead;">in </span><span>*.md -</span><span> </span><span style="color:#b48ead;">do -</span><span> </span><span style="color:#65737e;"># convert each file to html and place it in the html directory -</span><span> </span><span style="color:#65737e;"># --gfm == use github flavoured markdown -</span><span> </span><span style="color:#bf616a;">marked -o</span><span> html/$</span><span style="color:#bf616a;">file</span><span>.html $</span><span style="color:#bf616a;">file --gfm -</span><span style="color:#b48ead;">done -</span></code></pre> -<p>Now if you look in the <code>html</code> directory, you will see all the markdown files have been converted and are in that folder.</p> -<p>In the next week or so, I will write a new post about how to use this method and the <a href="https://github.com/assemble/assemble" title="Grunt Assemble Project">grunt assemble</a> plugin to make simple pages.</p> - - - - - Phile CMS - 2013-11-04T00:00:00+00:00 - 2013-11-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/phile-cms/ - - <p>After being a upset at the progress with Pico, myself and a developer from Germany(<a href="https://twitter.com/neoblack" title="Frank Twitter">Frank</a>) have developed a fork project.</p> -<div class="center"> - <a href="http://philecms.github.io/Phile/" title="PhileCMS Homepage"> - <img src="/images/phile-logo.png" alt="PhileCMS Logo"> - </a> -</div> -<p>The project is <a href="http://philecms.github.io/Phile/" title="PhileCMS Homepage">PhileCMS</a>. It maintains the philosophy of Pico, being fast and small, but it makes a lot of improvements on the core. Most the project is now OOP based with classes and models.</p> -<p>Also the parser and the template engine have been pushed into services. Which means they can be overloaded and replaced with different ones. Don't like Markdown? Use a plugin for TextTile instead. Don't like <a href="https://ohdoylerules.com/personal-project/phile-cms/twig.sensiolabs.org">Twig</a>? Replace it with <a href="https://github.com/pyrocms/lex">Lex</a>!</p> -<p>The hooks system was completely replaced with an Evented system. The plugins have also changed. They now have a config.php file that is used instead of having to write your own file reader for each plugin.</p> -<h3 id="so-why-use-this-over-pico">So why use this over Pico?</h3> -<p>Here is a small list of differences in design from Pico:</p> -<ul> -<li>OOP based (Classes)</li> -<li>Events system</li> -<li>Parser Overloading</li> -<li>Template Engine Overloading</li> -<li>Performance Improvements (<em>33% to 65% speed increase</em>)</li> -</ul> -<p>The main increase in speed is when there are multiple pages. Once you get to 20 pages you see a minumum of a 50% increase in load times.</p> -<p>I have actually converted this site to run on Phile. It is probably the first site in production to be using it. I also use the <a href="https://github.com/PhileCMS/Sundown-Parser-Plugin">Sundown Plugin</a> I wrote since I have <a href="https://github.com/chobie/php-sundown">PHP-Sundown</a> installed on my server.</p> -<p>Anyway, check out the project. It is pretty cool and I am very happy with the work of Frank.</p> -<p><a href="https://github.com/PhileCMS/Phile">Github Repo</a></p> -<p><a href="http://philecms.github.io/Phile/" title="PhileCMS Homepage">Homepage</a></p> - - - - - Google Drive Flat SVG Logo - 2013-11-01T00:00:00+00:00 - 2013-11-01T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/google-drive-svg-logo/ - - <p>Recently we needed a bunch of vector logos for a blog post. So again, in typical fashion, I had to craft a vector logo from scratch. Luckily the Google Drive logo is pretty simple. Just 3 shapes. All trapezoids (?).</p> -<div class="center"> - <a href="/images/google-drive.svg" target="_blank" title="google drive svg vector"><img width="300" alt="google drive svg vector" src="/images/google-drive.svg" ></a> -</div> - - - - - Docracy SVG logo - 2013-10-31T00:00:00+00:00 - 2013-10-31T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/docracy-logo-svg/ - - <p>The other founder of my company (<a href="http://warpaintmedia.ca" title="WARPAINT Media Homepage">WARPAINT Media</a>) was working on a blog post where we list all the PAAS/SAAS tools that we use. One of them is <a href="https://www.docracy.com/" title="Docracy Homepage">Docracy</a>. It is a site that shares free legal documents. You can modify and fork them to your own account, they have signing features as well.</p> -<p>Well again, I was unable to find the vector logo of their icon. So in my typical fashion, I recreated it. You can see it below.</p> -<div class="center"> - <a href="/images/docracy.svg" title="docracy svg vector" target="_blank"><img alt="docracy svg vector" src="/images/docracy.svg" ></a> -</div> - - - - - Assemble Starter - 2013-10-17T00:00:00+00:00 - 2013-10-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/assemble-starter/ - - <p>Assemble starter is my starting point for any of my static <a href="http://assemble.io/" title="Grunt Assemble Homepage">assemble sites</a>.</p> -<p>You can find the <a href="https://github.com/james2doyle/assemble-starter" title="Assemble Starter Repo">project on Github</a>.</p> -<p>I often use assemble when I am building themes. The reason being you can do quick templating (thanks to <a href="http://handlebarsjs.com/" title="Handlebars Homepage">Handlebars</a>), it compiles fast, requires no server, and I am using <a href="http://gruntjs.com/" title="Grunt Homepage">Grunt</a> anyway.</p> -<p>Extra grunt tasks:</p> -<ul> -<li>grunt-contrib-watch -- <em>live reload and compiles on save</em></li> -<li>grunt-sass -- <em>C lib SASS action</em></li> -<li>grunt-contrib-concat -- <em>combine things</em></li> -<li>grunt-autoprefixer -- <em>prefix that ugliness</em></li> -</ul> -<p>Modernizr Checks:</p> -<ul> -<li>cssanimations</li> -<li>csstransforms</li> -<li>csstransforms3d</li> -<li>svg</li> -<li>touch</li> -<li>shiv</li> -<li>cssclasses</li> -<li>teststyles</li> -<li>testprop</li> -<li>testallprops</li> -<li>prefixes</li> -<li>domprefixes</li> -<li>css_filters</li> -</ul> -<p>Other Libs:</p> -<ul> -<li><a href="http://swipejs.com/" title="Swipe.js Homepage">swipe.js</a> -- <em>awesome slider lib</em></li> -<li><a href="http://github.com/james2doyle/saltjs">salt.js</a> -- <em>my micro selector lib</em></li> -</ul> -<p>Javascript Goodies:</p> -<ul> -<li>prefix() -- <em>detect the js/css prefixes for different browsers</em></li> -<li>xhr() -- <em>function for no-jQuery AJAX</em></li> -</ul> -<p>CSS Goodies:</p> -<ul> -<li>normalize.scss</li> -</ul> -<p>You can find the <a href="https://github.com/james2doyle/assemble-starter" title="Assemble Starter Repo">project on Github</a>.</p> - - - - - pico-download plugin - 2013-09-18T00:00:00+00:00 - 2013-09-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pico-download-plugin/ - - <p>I created a plugin to force files to download in <a href="http://pico.dev7studios.com">PicoCMS</a>.</p> -<p>I needed this because I wanted to PDFs to download and not just render in the browser.</p> -<h3 id="usage">Usage</h3> -<p>Place your files in the content folder. Then replace the word <code>content/</code> in the url with the word <code>download/</code>.</p> -<p><em>The download folder can be controlled in the plugin file. Default for downloading is <code>content/</code>.</em></p> -<h3 id="example">Example</h3> -<p>If you wanted to render the file in the browser:</p> -<p><strong>http://localhost:8888/Pico/content/sub/page.md</strong></p> -<p>Now with this plugin installed, you can force a download:</p> -<p><strong>http://localhost:8888/Pico/download/sub/page.md</strong></p> -<h3 id="more-info">More info</h3> -<p>I have added quite a few comments in the plugin so just take a look. It's nothing new, just bringing different snippets together.</p> -<p>You can find the project <a href="https://github.com/james2doyle/pico_download" title="james2doyle/pico_download">here on Github</a>.</p> - - - - - Pico-Useragent Plugin - 2013-09-15T00:00:00+00:00 - 2013-09-15T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pico-useragent/ - - <p>I created another plugin for <a href="http://pico.dev7studios.com/">Pico CMS</a>. It is esentially a clone of my <a href="https://ohdoylerules.com/personal-project/pyrocms-ua-sniffer-plugin">pyro-sniffer-plugin</a> for <a href="http://pyrocms.com" title="Pyro CMS Homepage">PyroCMS</a>.</p> -<p>Here is the <a href="https://github.com/james2doyle/pico_useragent">Github project</a>.</p> -<p>This plugin allows you to parse the user agent of the current visitor and then expose that information in an easy to use variable in your twig templates.</p> -<p>Hopefully that makese sense.</p> -<h3 id="output">Output</h3> -<p>When using the plugin, you get a new variable called <code>browser</code>. The browser variable has the following properties in it when dumped from my computer:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>$browser = array ( -</span><span> &#39;useragent&#39; =&gt; &#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.37 Safari/537.36&#39; // full ua string -</span><span> &#39;name&#39; =&gt; &#39;Google Chrome&#39; // name of the browser -</span><span> &#39;browser&#39; =&gt; &#39;google-chrome&#39; // CSS safe browser name -</span><span> &#39;version&#39; =&gt; &#39;30.0.1599.37&#39; // bowser version -</span><span> &#39;type&#39; =&gt; &#39;desktop&#39; // device form factor -</span><span> &#39;platform&#39; =&gt; &#39;mac&#39; // OS platform -</span><span> &#39;pattern&#39; =&gt; &#39;#(?Version|Chrome|other)[/ ]+(?[0-9.|a-zA-Z.]*)#&#39; // match pattern -</span><span>); -</span></code></pre> -<h3 id="example">Example</h3> -<p>I use this example when I want to make small modifications to my CSS. Not unlike how Modernizr is supposed to work. Except modernizr doesn't give you browser information.</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">html </span><span style="color:#d08770;">lang</span><span>=&quot;</span><span style="color:#a3be8c;">en</span><span>&quot; </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">\{\{ browser.browser \}\} \{\{ browser.platform \}\} \{\{ browser.type \}\}</span><span>&quot;&gt; -</span></code></pre> -<p>Here is the output for that html tag:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">html </span><span style="color:#d08770;">lang</span><span>=&quot;</span><span style="color:#a3be8c;">en</span><span>&quot; </span><span style="color:#d08770;">class</span><span>=&quot;</span><span style="color:#a3be8c;">google-chrome mac desktop</span><span>&quot;&gt; -</span></code></pre> -<p>I usually use it to normalize issues across different browsers. Like something looking weird in Firefox, so I know I can modify some CSS by using a <code>.firefox</code> parent.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">0.25em 1em</span><span>; -</span><span>} -</span><span style="color:#65737e;">/* fix padding in FF */ -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">firefox </span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">button </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">0.28em 1em</span><span>; -</span><span>} -</span></code></pre> -<h3 id="use-cases">Use Cases</h3> -<ul> -<li>conditional content</li> -<li>conditional styles/scripts</li> -<li>layout modifications</li> -<li>serving specific images</li> -<li>Modernizr-esque CSS classes</li> -</ul> -<p>Here is the <a href="https://github.com/james2doyle/pico_useragent">Github project</a> again.</p> - - - - - compare multiple md5 hashes - 2013-09-15T00:00:00+00:00 - 2013-09-15T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/compare-multiple-md5-hashes/ - - <p>Sometimes you need to check a file against a <code>md5</code> hash. This can be annoying. Just look at this output:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># command -</span><span style="color:#bf616a;">md5</span><span> file.xml </span><span style="color:#bf616a;">~</span><span>/Downloads/file.xml file2.xml -</span><span> -</span><span style="color:#65737e;"># output -</span><span style="color:#bf616a;">MD5</span><span> (file.xml) = </span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span><span style="color:#bf616a;">MD5</span><span> (/Users/james2doyle/Downloads/file.xml) = </span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span><span style="color:#bf616a;">MD5</span><span> (file.xml) = </span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span></code></pre> -<p>Well it would be nice to not see all that junk in front of the hash. If they were lined up then it would be easier to compare them. Like so:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># command -</span><span style="color:#bf616a;">md5-check</span><span> file.xml </span><span style="color:#bf616a;">~</span><span>/Downloads/file.xml file2.xml -</span><span> -</span><span style="color:#65737e;"># output -</span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span><span style="color:#bf616a;">389a537b7443108f610038b4e4dd549a -</span></code></pre> -<p>Better. Here is the <code>md5-check</code> function I wrote to take an array of arguments and then trim out all the garabage.</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">md5-check</span><span>() { -</span><span> </span><span style="color:#b48ead;">for</span><span> ARG </span><span style="color:#b48ead;">in </span><span>&quot;$</span><span style="color:#bf616a;">@</span><span>&quot; -</span><span> </span><span style="color:#b48ead;">do -</span><span> </span><span style="color:#bf616a;">temp</span><span>=$</span><span style="color:#a3be8c;">(</span><span style="color:#bf616a;">md5 </span><span>$</span><span style="color:#bf616a;">ARG </span><span>| </span><span style="color:#bf616a;">cut -d</span><span>&#39;</span><span style="color:#a3be8c;">=</span><span>&#39;</span><span style="color:#bf616a;"> -f2</span><span style="color:#a3be8c;">) -</span><span> </span><span style="color:#96b5b4;">echo </span><span>$</span><span style="color:#bf616a;">temp </span><span>| </span><span style="color:#bf616a;">tr -d </span><span>&#39; &#39; -</span><span> </span><span style="color:#b48ead;">done -</span><span>} -</span></code></pre> -<p>It is also good for saving the output of a md5 hash:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#65737e;"># output into file -</span><span style="color:#bf616a;">md5-check</span><span> Downloads/logo.jpg | &gt; check.md5 -</span><span> -</span><span style="color:#65737e;"># check the contents of the check.md5 file -</span><span style="color:#bf616a;">cat</span><span> check.md5 -</span><span style="color:#bf616a;">6ed200ea7afa42e3bd90010fb14b06fd -</span></code></pre> -<p>Or you can read the file contents and compare it to the file's md5 hash:</p> -<pre data-lang="sh" style="background-color:#2b303b;color:#c0c5ce;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#bf616a;">md5-check</span><span> Downloads/logo.jpg &amp;&amp; </span><span style="color:#bf616a;">cat</span><span> check.md5 -</span></code></pre> -<p>this would output like so:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>6ed200ea7afa42e3bd90010fb14b06fd -</span><span>6ed200ea7afa42e3bd90010fb14b06fd -</span></code></pre> -<p>Seems a bit easier to compare the results of the hashes. Although I never really use them, I think it makes sense when you are transfering large files or you are downloading files in chunks.</p> - - - - - jQuery-doodal-js - 2013-09-09T00:00:00+00:00 - 2013-09-09T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/jquery-doodal-js/ - - <p>jQuery.doodal.js is a very simplistic modal plugin for jQuery. It has custom events, allows stacking, and is powered by CSS transitions</p> -<p><a href="http://james2doyle.github.io/jquery.doodal.js/">See the demo</a></p> -<h3 id="usage">Usage</h3> -<p>Instatiate a new doodal.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$(&#39;.doodal&#39;).doodal({ -</span><span> type: &#39;modal&#39;, -</span><span> closeclass: &#39;.doodal-close&#39;, -</span><span> trueclass: &#39;.doodal-true&#39;, -</span><span> falseclass: &#39;.doodal-false&#39;, -</span><span> showclass: &#39;showing&#39; -</span><span>}); -</span></code></pre> -<p>Those are all the default options so in this specific example I am not actually overwriting anything.</p> -<p>Now trigger an <code>open</code> to see it:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$(&#39;#doodal1&#39;).trigger(&#39;open&#39;); -</span></code></pre> -<h3 id="custom-events">Custom Events</h3> -<ul> -<li><em>open</em>: - when the modal starts to open</li> -<li><em>afteropen</em>: - after the animation is over and it is open</li> -<li><em>ontrue</em>: - for confirms yes button</li> -<li><em>onfalse</em>: - for confirms no button</li> -<li><em>close</em>: - when the close is clicked</li> -<li><em>afterclose</em>: - after the animation is over and it is hidden</li> -</ul> -<p>You can also view the <a href="https://github.com/james2doyle/jquery.doodal.js">project on Github</a>.</p> - - - - - Grunt Sundown - 2013-09-08T00:00:00+00:00 - 2013-09-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/grunt-sundown/ - - <p><strong>grunt-sundown</strong> is a wrapper for <a href="https://github.com/benmills/robotskirt">robotskirt</a>(<a href="https://github.com/vmg/sundown">Sundown</a>) - a C implementation of <a href="http://daringfireball.net/projects/markdown/">Markdown</a></p> -<h3 id="getting-started">Getting Started</h3> -<p>This plugin requires Grunt <code>~0.4.1</code></p> -<p>If you haven't used <a href="http://gruntjs.com/">Grunt</a> before, be sure to check out the <a href="http://gruntjs.com/getting-started">Getting Started</a> guide, as it explains how to create a <a href="http://gruntjs.com/sample-gruntfile">Gruntfile</a> as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>npm install grunt-sundown --save-dev -</span></code></pre> -<p>Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>grunt.loadNpmTasks(&#39;grunt-sundown&#39;); -</span></code></pre> -<p>You can find the project <a href="https://github.com/james2doyle/grunt-sundown" title="grunt-sundown on Github">on Github</a>.</p> -<h3 id="the-sundown-task">The &quot;sundown&quot; task</h3> -<h4 id="overview">Overview</h4> -<p>In your project's Gruntfile, add a section named <code>sundown</code> to the data object passed into <code>grunt.initConfig()</code>.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>grunt.initConfig({ -</span><span> sundown: { -</span><span> target: { -</span><span> options: { -</span><span> extensions: { -</span><span> fenced_code: true -</span><span> }, -</span><span> render_flags: { -</span><span> skip_html: true -</span><span> } -</span><span> }, -</span><span> files: { -</span><span> &#39;output.html&#39;: [&#39;input1.md&#39;, &#39;input2.md&#39;] -</span><span> } -</span><span> } -</span><span> } -</span><span>}); -</span></code></pre> -<h4 id="options">Options</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>options: { -</span><span> extensions: { -</span><span> autolink: false, -</span><span> fenced_code: false, -</span><span> lax_html_blocks: false, -</span><span> no_intra_emphasis: false, -</span><span> space_headers: false, -</span><span> strikethrough: false, -</span><span> tables: false -</span><span> }, -</span><span> render_flags: { -</span><span> skip_html: false, -</span><span> skip_style: false, -</span><span> skip_images: false, -</span><span> skip_links: false, -</span><span> expand_tabs: false, -</span><span> safelink: false, -</span><span> toc: false, -</span><span> hard_wrap: false, -</span><span> use_xhtml: false, -</span><span> escape: false -</span><span> }, -</span><span> separator: &#39;\n\n&#39; // concat option for multiple files -</span><span>} -</span></code></pre> -<h4 id="more-information">More Information</h4> -<p>You can try your luck on the <a href="https://github.com/vmg/sundown">Sundown</a> homepage. Or check out some of the <a href="https://github.com/vmg/sundown#bindings">other wrappers</a>.</p> - - - - - Render PHP File With Data - 2013-08-29T00:00:00+00:00 - 2013-08-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/render-php-with-data/ - - <p>I am modifying an open source CMS to use the <a href="https://ohdoylerules.com/snippets/render-php-with-data/phalconphp.com/en/" title="Phalcon PHP Framework">Phalcon PHP framework</a>, as well as the <a href="https://github.com/chobie/php-sundown" title="PHP-Sundown">PHP-Sundown</a> C implementation of Markdown.</p> -<p>It is a very simple CMS which previously would just echo out compiled HTML. But I am using the Volt template engine in Phalcon. It renders <code>.volt</code> files to native PHP. This means that I cannot just spit out raw HTML. I need to create a render function that passes an array of data to my PHP file.</p> -<p>Here is that function:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>function renderPhpFile($filename, $vars = null) { -</span><span> if (is_array($vars) &amp;&amp; !empty($vars)) { -</span><span> extract($vars); -</span><span> } -</span><span> ob_start(); -</span><span> include $filename; -</span><span> return ob_get_clean(); -</span><span>} -</span><span>// usage -</span><span>echo renderPhpFile(&#39;views/templates/index.php&#39;, $view_data); -</span></code></pre> -<p>This works! It is a handy little function for passing data into a PHP file.</p> -<p>If you wanted to use an object, you would need to cast it to an array first.</p> - - - - - Simple HTML5 Notifications - 2013-08-29T00:00:00+00:00 - 2013-08-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/simple-html5-notification/ - - <p>I was playing around with HTML5 Notifications the other day. They are pretty slick! It allows you to essentially send growl notifications to your desktop from the browser.</p> -<p>This little function would be used during an event to request permission for notifications and then display it with a simple abstraction of the native API.</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">notify</span><span>(</span><span style="color:#bf616a;">title</span><span>, </span><span style="color:#bf616a;">body</span><span>, </span><span style="color:#bf616a;">timeout</span><span>) { -</span><span> </span><span style="color:#bf616a;">timeout </span><span>= (</span><span style="color:#bf616a;">timeout</span><span>) ? </span><span style="color:#bf616a;">timeout </span><span>: </span><span style="color:#d08770;">3000</span><span>; -</span><span> </span><span style="color:#ebcb8b;">Notification</span><span>.</span><span style="color:#8fa1b3;">requestPermission</span><span>(</span><span style="color:#b48ead;">function </span><span>() { -</span><span> </span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">nf </span><span>= new Notification(</span><span style="color:#bf616a;">title</span><span>, { -</span><span> body: </span><span style="color:#bf616a;">body</span><span>, -</span><span> iconUrl: &quot;</span><span style="color:#a3be8c;">test.png</span><span>&quot; -</span><span> }); -</span><span> </span><span style="color:#bf616a;">nf</span><span>.</span><span style="color:#8fa1b3;">onshow </span><span>= </span><span style="color:#b48ead;">function </span><span>() { -</span><span> </span><span style="color:#96b5b4;">setTimeout</span><span>(</span><span style="color:#b48ead;">function </span><span>() { -</span><span> </span><span style="color:#bf616a;">nf</span><span>.</span><span style="color:#96b5b4;">close</span><span>() -</span><span> }, </span><span style="color:#bf616a;">timeout</span><span>) -</span><span> }; -</span><span> }); -</span><span>} -</span><span style="color:#65737e;">// usage -</span><span style="color:#8fa1b3;">notify</span><span>(&#39;</span><span style="color:#a3be8c;">My Title</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">My hot body with a bunch of lorem in it</span><span>&#39;); -</span></code></pre> -<p>It will then ask for permission, if your page doesn't have it already, then show the notification. Right now it just shows a small grey box for the test image.</p> -<p>The last parameter is for a custom timeout. I like the default of 3 seconds but if you need to you can override it without modifying the function.</p> - - - - - Pyro Twitter Widget - 2013-08-08T00:00:00+00:00 - 2013-08-08T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-twitter-widget/ - - <p>I created another widget for PyroCMS. This one is for Twitter. I didn't find one that I liked or thought was very good, so I created my own. This widget actually uses a 3rd party sub-module, for the Twitter authentication, called <a href="https://github.com/J7mbo/twitter-api-php" title="J7mbo/twitter-api-php">twitter-api-php</a>.</p> -<p>Here are the current supported (basically just tested) API endpoints:</p> -<ul> -<li>statuses/mentions_timeline</li> -<li>statuses/user_timeline</li> -<li>statuses/home_timeline</li> -<li>statuses/retweetsofme</li> -<li>favorites/list</li> -</ul> -<p>Here is the <a href="https://github.com/james2doyle/pyro-twitter-widget" title="james2doyle/pyro-twitter-widget">widget on Github</a>.</p> - - - - - Pico Get By Filename Plugin - 2013-07-27T00:00:00+00:00 - 2013-07-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pico-get-by-filename-plugin/ - - <p>I wrote another small plugin for PicoCMS that actually lets you load files based on the filename. I called it <a href="https://github.com/james2doyle/pico_get_by_filename" title="james2doyle/pico_get_by_filename">pico_get_by_filename</a>.</p> - - - - - Markdown Logo Vector SVG - 2013-07-26T00:00:00+00:00 - 2013-07-26T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/markdown-logo-vector-svg/ - - <p>The markdown logo in SVG vector format. I found this was not that easy to find. So I whipped one up of my own.</p> -<div class="center"> - <a href="/images/markdown.svg" target="_blank" title="Markdown Vector SVG logo"><img alt="Markdown Vector SVG logo" src="/images/markdown.svg" ></a> -</div> - - - - - CMS Watch List - 2013-07-26T00:00:00+00:00 - 2013-07-26T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/cms-watch-list/ - - <p>I created a new post on WARPAINT about some of the upcoming CMS platforms you may not have heard of.</p> -<p><a href="http://warpaintmedia.ca/blog/2013/07/cms-watch-list" title="CMS Watch List">Here is the article</a>.</p> - - - - - The $100 Website - 2013-07-23T00:00:00+00:00 - 2013-07-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/the-100-dollar-website/ - - <p>I wrote a post on WARPAINT Media about people who ask about getting a website for $100. It isn't an angry rant, although there is some frustration. It is more about courtesy.</p> -<p>I wouldn't ask you to take $100 for an entire weeks worth of work. So please, don't ask me. <a href="http://warpaintmedia.ca/blog/2013/07/the-100-dollar-website" title="The $100 Website">Here is the article</a>.</p> - - - - - Kube in Styl/Rework - 2013-07-19T00:00:00+00:00 - 2013-07-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/kube-in-stylrework/ - - <p>I finally finished the conversion of the <a href="http://imperavi.com/kube/">Kube CSS Framework</a>.</p> -<p>The original is written in <a href="http://lesscss.org/" title="LESSCSS Homepage">LESS</a> and I have rewrote it to work with <a href="https://github.com/visionmedia/styl" title="visionmedia/styl">Styl</a> and <a href="https://github.com/visionmedia/rework" title="visionmedia/rework">Rework</a>.</p> -<p><a href="https://github.com/james2doyle/kube-styl" title="james2doyle/kube-styl">Here is the github repo</a></p> - - - - - Pico Slider Plugin - 2013-07-17T00:00:00+00:00 - 2013-07-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pico-slider-plugin/ - - <p>I wrote a small plugin for the Pico CMS. I recently discovered a very cool PHP-based CMS called, <a href="http://pico.dev7studios.com/" title="Pico CMS Homepage">Pico CMS</a>. This CMS is a no-database flat-file CMS. It is really fast and very easy. There was no real way to handle images in the base version. So I developed a plugin that can list images in a folder. I named it pico_slider but it could probably be named pico_image_list because all it really does is expose an image array to the front-end variables. <a href="https://github.com/james2doyle/pico_slider" title="james2doyle/pico_slider">Here it is on Github</a>.</p> - - - - - NPM logo SVG - 2013-07-07T00:00:00+00:00 - 2013-07-07T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/npm-logo-svg/ - - <p>I made a vector version of the logo for <a href="http://npmjs.org" title="NPM Homepage">NPM</a>. Here is the vector version as a SVG.</p> -<p><a href="/images/npm-logo.svg"><img src="/images/npm-logo.svg" alt="npm vector svg logo" /></a></p> - - - - - Apax apache theme in htdocs - 2013-07-02T00:00:00+00:00 - 2013-07-02T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/apax-in-htdocs/ - - <p>I was tired of looking at the ugly default no-style of the htdocs file listing. I had seen <a href="http://adamwhitcroft.com/apaxy/" title="Apaxy Homepage">Apaxy theme</a> before and thought it was really nice. But I couldn't figure out how to get it to work with the default htdocs MAMP folder. I tried again tonight, and I got it working without much hassle.</p> -<ol> -<li><a href="https://github.com/AdamWhitcroft/Apaxy/archive/master.zip" title="Apaxy Download Link">Download Apaxy</a> and move everything from the apaxy folder into your MAMP htdocs folder.</li> -<li>open &quot;htaccess.txt&quot; and replace &quot;/{FOLDERNAME}/theme&quot; with &quot;/.theme/&quot;</li> -<li>rename the &quot;htaccess.txt&quot; file to &quot;.htaccess&quot; which will hide the file</li> -<li>rename the &quot;theme&quot; folder to &quot;.theme&quot; which will hide the directory</li> -<li>go to your localhost url and refresh</li> -<li>enjoy a not-ugly page</li> -</ol> -<p>Now you can edit the files in the &quot;.theme&quot; folder and style your page. I changed the &quot;.wrapper&quot; to have no max-width or margin, this way it was full screen.</p> -<p><a href="/images/Screen-Shot-2013-07-02-at-12.38.08-AM.png"><img src="/images/Screen-Shot-2013-07-02-at-12.38.08-AM.png" alt="apaxy theme applied to htdocs" /></a></p> -<p>Above is a screenshot of what my htdocs/localhost:8888 now looks like.</p> -<h4 id="optional">OPTIONAL</h4> -<p>You can also hide the &quot;.theme&quot; folder. You will see a section that looks -like this, in your .htaccess file:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># HIDE /theme DIRECTORY -</span><span>IndexIgnore .htaccess /.theme -</span></code></pre> -<p>The old version should read &quot;/theme&quot; and not &quot;/.theme&quot;. If change this -line, it will NOT show the .theme folder in the localhost listing.</p> - - - - - Disqus Comments - 2013-06-27T00:00:00+00:00 - 2013-06-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/disqus-comments/ - - <p>I have finally added a decent comment system to this site. I installed Disqus. It is pretty awesome. I have used it before and it is a great system. I have enabled comments on <em>every</em> post on the site.</p> -<p>I may go back and turn some off but for the most part they are enabled by default. You could be really meta and comment on this post about the new comments! <em>wink wink!</em></p> - - - - - Pyro Github Markdown Field Type - 2013-06-23T00:00:00+00:00 - 2013-06-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-github-markdown/ - - <p>Github flavoured markdown field type for PyroCMS.</p> -<p>Here is the link to the <a href="https://github.com/james2doyle/pyro-github-markdown" title="pyro-github-markdown field type">Github -Repo</a>.</p> -<p>Most of the work for this repo is taken from <a href="https://github.com/evansolomon/wp-github-flavored-markdown-comments">GitHub-Flavored Markdown Comments</a> plugin for Wordpress. That repository is also based on <a href="https://github.com/michelf/php-markdown/">Michel Fortin's PHP markdown library</a> with added features from <a href="https://github.com/github/github-flavored-markdown">GitHub-flavored Markdown</a>.</p> -<p><em>All I did was just bring it all together and make it play nice with -Pyro.</em></p> -<h3 id="usage"><a href="https://ohdoylerules.com/personal-project/pyro-github-markdown/#usage"></a>Usage</h3> -<ul> -<li>Install the field type as normal.</li> -<li>Add the field type to a page type or stream</li> -<li>Enter in your sexy Github Markdown</li> -<li>Just use &quot;the_field_slug&quot; to render the HTML</li> -</ul> -<h3 id="examples"><a href="https://ohdoylerules.com/personal-project/pyro-github-markdown/#examples"></a>Examples</h3> -<p>Input:</p> -<pre data-lang="markdown" style="background-color:#2b303b;color:#c0c5ce;" class="language-markdown "><code class="language-markdown" data-lang="markdown"><span>GitHub-Flavored Markdown Comments -</span><span style="color:#8fa1b3;">============================= -</span><span> -</span><span>Based on </span><span style="color:#d08770;">[Michel Fortin&#39;s PHP markdown library](https://github.com/michelf/php-markdown/)</span><span> with added features from </span><span style="color:#d08770;">[GitHub-flavored Markdown](https://github.com/github/github-flavored-markdown)</span><span>. -</span><span> -</span><span style="color:#bf616a;">* Single linebreaks are treated as new paragraphs -</span><span style="color:#bf616a;">* Code &quot;fencing&quot; with three backticks (</span><span style="color:#a3be8c;">```) -</span><span style="background-color:#bf616a;color:#2b303b;"> -</span><span style="color:#bf616a;">### Heading 3 -</span><span style="color:#bf616a;"> -</span><span>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -</span></code></pre> -<p>Output:</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>&lt;</span><span style="color:#bf616a;">h1</span><span>&gt;GitHub-Flavored Markdown Comments&lt;/</span><span style="color:#bf616a;">h1</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">p</span><span>&gt;Based on &lt;</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://github.com/michelf/php-markdown/</span><span>&quot;&gt;Michel Fortin&#39;s PHP markdown library&lt;/</span><span style="color:#bf616a;">a</span><span>&gt; with added features from &lt;</span><span style="color:#bf616a;">a </span><span style="color:#d08770;">href</span><span>=&quot;</span><span style="color:#a3be8c;">https://github.com/github/github-flavored-markdown</span><span>&quot;&gt;GitHub-flavored Markdown&lt;/</span><span style="color:#bf616a;">a</span><span>&gt;.&lt;/</span><span style="color:#bf616a;">p</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span>&lt;</span><span style="color:#bf616a;">li</span><span>&gt;Single linebreaks are treated as new paragraphs&lt;/</span><span style="color:#bf616a;">li</span><span>&gt; -</span><span>&lt;</span><span style="color:#bf616a;">li</span><span>&gt;Code &quot;fencing&quot; with three backticks (```)&lt;/</span><span style="color:#bf616a;">li</span><span>&gt; -</span><span>&lt;/</span><span style="color:#bf616a;">ul</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">h3</span><span>&gt;Heading 3&lt;/</span><span style="color:#bf616a;">h3</span><span>&gt; -</span><span> -</span><span>&lt;</span><span style="color:#bf616a;">p</span><span>&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.&lt;/</span><span style="color:#bf616a;">p</span><span>&gt; -</span></code></pre> -<h3 id="more-info"><a href="https://ohdoylerules.com/personal-project/pyro-github-markdown/#more-info"></a>More Info!</h3> -<p>If you need to know more about the caveats of this plugin, please <a href="https://github.com/evansolomon/wp-github-flavored-markdown-comments/blob/master/README.md">see -the -README</a> -for the original lib.</p> -<h3 id="update">UPDATE</h3> -<p>I added the ability to preview your results too!</p> -<p><a href="https://github.com/james2doyle/pyro-github-markdown"><img src="/images/write.png" alt="pyro github markdown write preview" /></a> -<a href="https://github.com/james2doyle/pyro-github-markdown"><img src="/images/preview.png" alt="pyro github markdown preview" /></a> -Go check out this badboy!</p> - - - - - PyroCMS UA Sniffer Plugin - 2013-06-23T00:00:00+00:00 - 2013-06-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyrocms-ua-sniffer-plugin/ - - <p>This plugin lets you sniff information from the user agent for use in the frontend. I use it for adding classes or conditional loading of partials and templates.</p> -<p>You can see the <a href="https://github.com/james2doyle/pyro-sniffer-plugin" title="pyro-sniffer-plugin github">github repository here</a>.</p> -<p>This plugin is <strong>not</strong> built on the <a href="http://ellislab.com/codeigniter/user-guide/libraries/user_agent.html">CodeIgniter User Agent Library</a>.</p> -<p>The reason I did not use the built in CodeIgniter lib, was because Pyro is only going to have CodeIgniter for a few more months(right?!?!), and I also want to have the information returned in a different way. This plugin is pretty small and only really gets information that is helpful to be used in CSS and Javascript (CSS custom classes and js feature detection/fallbacks).</p> -<p>If you are looking for a plugin that uses the user agent library, check out <a href="https://www.pyrocms.com/store/details/agent_plugin">this plugin called Agent</a>.</p> -<h3 id="usage"><a href="https://ohdoylerules.com/personal-project/pyrocms-ua-sniffer-plugin/#usage"></a>Usage</h3> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;body class=&quot;\{\{ sniffer:get key=&quot;browser|platform|type&quot; \}\}&quot;&gt; -</span></code></pre> -<p>On my Mac running Google Chrome, this would return:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;body class=&quot; google-chrome mac desktop&quot;&gt; -</span></code></pre> -<p>On my iPhone, this would return:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;body class=&quot; apple-mobile-safari ios mobile&quot;&gt; -</span></code></pre> -<h4 id="conditional-content"><a href="https://ohdoylerules.com/personal-project/pyrocms-ua-sniffer-plugin/#conditional-content"></a>conditional content</h4> -<p>This works in 2.2/develop. Not sure about 2.3 or 2.1.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>\{\{ if \{ sniffer:get key=&quot;type&quot; \} == &#39;desktop&#39; \}\} -</span><span>&lt;div class=&quot;huge-slider&quot;&gt; -</span><span> &lt;div class=&quot;slide&quot;&gt; -</span><span> &lt;img src=&quot;img/kitten1.jpg&quot; width=&quot;1400&quot; height=&quot;500&quot;&gt; -</span><span> &lt;/div&gt; -</span><span> &lt;div class=&quot;slide&quot;&gt; -</span><span> &lt;img src=&quot;img/puppy1.jpg&quot; width=&quot;1400&quot; height=&quot;500&quot;&gt; -</span><span> &lt;/div&gt; -</span><span> &lt;div class=&quot;slide&quot;&gt; -</span><span> &lt;img src=&quot;img/snake1.jpg&quot; width=&quot;1400&quot; height=&quot;500&quot;&gt; -</span><span> &lt;/div&gt; -</span><span>&lt;/div&gt; -</span><span>\{\{ else \}\} -</span><span>&lt;div class=&quot;mobile-logo&quot;&gt; -</span><span> &lt;img src=&quot;img/mobile-logo.png&quot; width=&quot;200&quot; height=&quot;200&quot;&gt; -</span><span>&lt;/div&gt; -</span><span>\{\{ endif \}\} -</span></code></pre> -<p>here is the full dump of the <code>$results</code> object for my machine:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$results = array ( -</span><span> [&#39;useragent&#39;] =&gt; &#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.44 Safari/537.36&#39; // full ua string -</span><span> [&#39;name&#39;] =&gt; &#39;Google Chrome&#39; // name of the browser -</span><span> [&#39;browser&#39;] =&gt; &#39;google-chrome&#39; // CSS safe browser name -</span><span> [&#39;version&#39;] =&gt; &#39;28.0.1500.44&#39; // bowser version -</span><span> [&#39;type&#39;] =&gt; &#39;desktop&#39; // device form factor -</span><span> [&#39;platform&#39;] =&gt; &#39;mac&#39; // OS platform -</span><span> [&#39;pattern&#39;] =&gt; &#39;#(?Version|Chrome|other)[/ ]+(?[0-9.|a-zA-Z.]*)#&#39; // match pattern -</span><span>); -</span></code></pre> - - - - - rework-math - 2013-06-23T00:00:00+00:00 - 2013-06-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/rework-math/ - - <p>I created a plugin for <a href="https://github.com/visionmedia/rework" title="rework">Rework</a> CSS preprocessor to do math. Here is the <a href="https://github.com/james2doyle/rework-math" title="rework-math">github repo</a>. It is also my first ever NPM package and it can be found on the website <a href="https://npmjs.org/package/rework-math" title="rework-math on NPM">here</a>.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/* input */ -</span><span style="color:#bf616a;">div </span><span>{ -</span><span> padding: math(</span><span style="color:#d08770;">5+5px</span><span>); -</span><span>} -</span><span style="color:#65737e;">/* output */ -</span><span style="color:#bf616a;">div </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">10px</span><span>; -</span><span>} -</span></code></pre> -<p>It also works with the rework-vars plugin.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/* input */ -</span><span style="color:#8fa1b3;">:</span><span style="color:#b48ead;">root </span><span>{ -</span><span> var-fontSize: </span><span style="color:#d08770;">10px</span><span>; -</span><span>} -</span><span> -</span><span style="color:#bf616a;">div </span><span>{ -</span><span> padding: math((</span><span style="color:#96b5b4;">var</span><span>(fontSize) * </span><span style="color:#d08770;">2</span><span>) + 10px); -</span><span>} -</span><span> -</span><span style="color:#65737e;">/* output */ -</span><span style="color:#8fa1b3;">:</span><span style="color:#b48ead;">root </span><span>{ -</span><span> var-fontSize: </span><span style="color:#d08770;">10px</span><span>; -</span><span>} -</span><span> -</span><span style="color:#bf616a;">div </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">30px</span><span>; -</span><span>} -</span></code></pre> - - - - - rework-shade - 2013-06-23T00:00:00+00:00 - 2013-06-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/rework-shade/ - - <p>I created another plugin for Rework that makes it easy to do lighten and darken functions. I called it <a href="https://github.com/james2doyle/rework-shade" title="rework-shade github">rework-shade</a>. This package is also available <a href="https://npmjs.org/package/rework-shade" title="rework-shade on NPM">on NPM here</a>.</p> -<p>Here is the basic usage.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/* input */ -</span><span style="color:#bf616a;">body </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">10px</span><span>; -</span><span> background: </span><span style="color:#96b5b4;">shade</span><span>(rgba(0, 0, 0, 0.5), </span><span style="color:#d08770;">5</span><span>); -</span><span>} -</span><span> -</span><span style="color:#65737e;">/* using points */ -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">stuff </span><span>{ -</span><span> color: </span><span style="color:#96b5b4;">shade</span><span>(rgb(0, 200, 50), </span><span style="color:#d08770;">1.3</span><span>); -</span><span>} -</span><span> -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">bright </span><span>{ -</span><span> background: </span><span style="color:#96b5b4;">shade</span><span>(#004080, 30); -</span><span>} -</span><span> -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">dark </span><span>{ -</span><span> background: </span><span style="color:#96b5b4;">shade</span><span>(#fff, -50); -</span><span>} -</span><span> -</span><span style="color:#65737e;">/* output */ -</span><span style="color:#bf616a;">body </span><span>{ -</span><span> padding: </span><span style="color:#d08770;">10px</span><span>; -</span><span> background: </span><span style="color:#96b5b4;">rgb</span><span>(</span><span style="color:#d08770;">13</span><span>, </span><span style="color:#d08770;">13</span><span>, </span><span style="color:#d08770;">13</span><span>, </span><span style="color:#d08770;">0.5</span><span>); -</span><span>} -</span><span> -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">stuff </span><span>{ -</span><span> color: </span><span style="color:#96b5b4;">rgb</span><span>(</span><span style="color:#d08770;">3</span><span>, </span><span style="color:#d08770;">203</span><span>, </span><span style="color:#d08770;">53</span><span>, </span><span style="color:#d08770;">1</span><span>); -</span><span>} -</span><span> -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">bright </span><span>{ -</span><span> background: </span><span style="color:#96b5b4;">rgb</span><span>(</span><span style="color:#d08770;">77</span><span>, </span><span style="color:#d08770;">141</span><span>, </span><span style="color:#d08770;">205</span><span>, </span><span style="color:#d08770;">1</span><span>); -</span><span>} -</span><span> -</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">dark </span><span>{ -</span><span> background: </span><span style="color:#96b5b4;">rgb</span><span>(</span><span style="color:#d08770;">128</span><span>, </span><span style="color:#d08770;">128</span><span>, </span><span style="color:#d08770;">128</span><span>, </span><span style="color:#d08770;">1</span><span>); -</span><span>} -</span></code></pre> - - - - - grunt terminal-notifier setup - 2013-06-07T00:00:00+00:00 - 2013-06-07T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/grunt-terminal-notifier-setup/ - - <p>I just downloaded the new Mountain Lion, finally. One of the biggest new things is the cool little native notifications akin to growl. I thought it would be cool to get a nice notification when my &quot;grunt watch&quot; task finished. First things first. You need to install <a href="https://github.com/alloy/terminal-notifier" title="alloy/terminal notifier">terminal-notifier</a>. This allows you to interact with the native OSX notifications system.</p> -<p>There is a ruby gem and a standalone &quot;.app&quot;. Once this is installed, you will need to grab the <a href="https://github.com/alextucker/grunt-growl" title="alextucker/grunt-growl">grunt-growl</a> plugin. There are more instructions there for the terminal-notifier app. Now you will need to setup a new task in your gruntfile:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>growl: { -</span><span> css: { -</span><span> title: &#39;</span><span style="color:#a3be8c;">STYLUS BUILT</span><span>&#39;, -</span><span> message: &#39;</span><span style="color:#a3be8c;">css/style.css has been created</span><span>&#39; -</span><span> }, -</span><span> js: { -</span><span> title: &#39;</span><span style="color:#a3be8c;">JAVASCRIPT BUILT</span><span>&#39;, -</span><span> message: &#39;</span><span style="color:#a3be8c;">dist/js/scripts.js has been created</span><span>&#39; -</span><span> } -</span><span>} -</span></code></pre> -<p>Now my watch task looks like this:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span>watch: { -</span><span> scripts: { -</span><span> files: [&#39;</span><span style="color:#a3be8c;">&lt;%= concat.dist.src %&gt;</span><span>&#39;], -</span><span> tasks: [&#39;</span><span style="color:#a3be8c;">jshint</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">concat</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">growl:js</span><span>&#39;], -</span><span> options: {} -</span><span> }, -</span><span> styles: { -</span><span> files: [&#39;</span><span style="color:#a3be8c;">css/*.styl</span><span>&#39;], -</span><span> tasks: [&#39;</span><span style="color:#a3be8c;">stylus</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">growl:css</span><span>&#39;], -</span><span> options: {} -</span><span> } -</span><span>} -</span></code></pre> -<p>You can see that the growl task runs after the initial stylus and javascript watch tasks.</p> - - - - - NudeProject - a starting point for simple websites - 2013-06-04T00:00:00+00:00 - 2013-06-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/nudeproject/ - - <p>NudeProject is meant to be a starting point for new landing pages, single-page sites, or even just basic mockups. The point is to get me off the ground quickly. It only provides the most basic things that I need most of the time. These include <a href="http://gruntjs.com/" title="gruntjs homepage">grunt tasks</a>, <a href="http://necolas.github.io/normalize.css/" title="normalize css">normalized CSS</a>, <a href="http://modernizr.com" title="modernizr">modernizr</a>, and a <a href="https://gist.github.com/james2doyle/5659710" title="modernizr-svg-replace.js">SVG fallback snippet</a> in javascript. Check out the project <a href="https://github.com/james2doyle/nudeproject" title="james2doyle/nudeproject">on github</a>.</p> -<h4 id="grunt">grunt</h4> -<p>included grunt taks</p> -<ul> -<li>clean -- removing files before tasks run</li> -<li>cssmin -- minify and concat css files</li> -<li>imagemin -- compress images and jpeg</li> -<li>svgmin -- compress svg files</li> -<li>uglify -- minify and concat js</li> -</ul> -<h4 id="stylesheets">stylesheets</h4> -<ul> -<li>normalize.css</li> -</ul> -<h4 id="javascript">javascript</h4> -<p>modernizr custom build</p> -<ul> -<li>cssanimations</li> -<li>csstransforms</li> -<li>csstransforms3d</li> -<li>csstransitions</li> -<li>canvas</li> -<li>audio</li> -<li>video</li> -<li>localstorage</li> -<li>svg</li> -<li>touch</li> -<li>webgl</li> -<li>shiv</li> -<li>cssclasses</li> -<li>teststyles</li> -<li>testprop</li> -<li>testallprops</li> -<li>prefixes</li> -<li>domprefixes</li> -<li>css_boxsizing</li> -<li>script_async</li> -<li>script_defer</li> -</ul> - - - - - Detect Animation Events in Javascript - 2013-05-31T00:00:00+00:00 - 2013-05-31T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/animation-events-in-javascript/ - - <p>Whenever I am doing animations that have javascript and CSS, most of the time, I want an callback to fire in javascript when the animations are complete. I have used this event for modals and little UI plugins. Normally, I would have a start event(click or touch) that just adds a class that has a CSS animation attached to it. Lets say we have a class called 'on'.</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">modal</span><span style="color:#8fa1b3;">.</span><span style="color:#d08770;">on </span><span>{ -</span><span> animation: showModal </span><span style="color:#d08770;">1s </span><span>ease; -</span><span>} -</span><span style="color:#b48ead;">@keyframes </span><span>showModal { -</span><span> </span><span style="color:#d08770;">0% </span><span>{ -</span><span> opacity: </span><span style="color:#d08770;">0</span><span>; -</span><span> } -</span><span> </span><span style="color:#d08770;">100% </span><span>{ -</span><span> opacity: </span><span style="color:#d08770;">1</span><span>; -</span><span> } -</span><span>} -</span></code></pre> -<p>So now in javascript I might have a button click that adds 'on' to my modal. In my javascript I would have a function to detect different animation events(start, iterate and end). Here is the code that I modified from a <a href="http://www.sitepoint.com/css3-animation-javascript-event-handlers/" title="Sitepoint">SitePoint Article</a> that was posting about the same topic.</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">var </span><span style="color:#bf616a;">pfx </span><span>= [&quot;</span><span style="color:#a3be8c;">webkit</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">moz</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">MS</span><span>&quot;, &quot;</span><span style="color:#a3be8c;">o</span><span>&quot;, &quot;&quot;]; -</span><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">doAnim</span><span>(</span><span style="color:#bf616a;">element</span><span>, </span><span style="color:#bf616a;">animClass</span><span>, </span><span style="color:#bf616a;">type</span><span>, </span><span style="color:#bf616a;">callback</span><span>) { -</span><span> </span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">p </span><span>= </span><span style="color:#d08770;">0</span><span>, </span><span style="color:#bf616a;">l </span><span>= </span><span style="color:#bf616a;">pfx</span><span>.length; -</span><span> </span><span style="color:#b48ead;">function </span><span style="color:#8fa1b3;">removeAndCall</span><span>(){ -</span><span> </span><span style="color:#bf616a;">this</span><span>.</span><span style="color:#96b5b4;">removeEventListener</span><span>(</span><span style="color:#bf616a;">pfx</span><span>[</span><span style="color:#bf616a;">p</span><span>]+</span><span style="color:#bf616a;">type</span><span>, </span><span style="color:#bf616a;">arguments</span><span>.callee,</span><span style="color:#d08770;">false</span><span>); -</span><span> </span><span style="color:#8fa1b3;">callback</span><span>(); -</span><span> } -</span><span> </span><span style="color:#b48ead;">for </span><span>(; </span><span style="color:#bf616a;">p </span><span>&lt; </span><span style="color:#bf616a;">l</span><span>; </span><span style="color:#bf616a;">p</span><span>++) { -</span><span> </span><span style="color:#b48ead;">if </span><span>(!</span><span style="color:#bf616a;">pfx</span><span>[</span><span style="color:#bf616a;">p</span><span>]) { -</span><span> </span><span style="color:#bf616a;">type </span><span>= </span><span style="color:#bf616a;">type</span><span>.</span><span style="color:#96b5b4;">toLowerCase</span><span>(); -</span><span> } -</span><span> </span><span style="color:#bf616a;">element</span><span>.</span><span style="color:#bf616a;">classList</span><span>.</span><span style="color:#96b5b4;">add</span><span>(</span><span style="color:#bf616a;">animClass</span><span>); -</span><span> </span><span style="color:#bf616a;">element</span><span>.</span><span style="color:#96b5b4;">addEventListener</span><span>(</span><span style="color:#bf616a;">pfx</span><span>[</span><span style="color:#bf616a;">p</span><span>]+</span><span style="color:#bf616a;">type</span><span>, </span><span style="color:#bf616a;">removeAndCall</span><span>, </span><span style="color:#d08770;">false</span><span>); -</span><span> } -</span><span>} -</span><span style="color:#65737e;">// doAnim(elem, &#39;show&#39;, &#39;AnimationEnd&#39;, function(){ -</span><span style="color:#65737e;">// this function will fire when the animation is finished -</span><span style="color:#65737e;">// elem.classList.remove(&#39;show&#39;); -</span><span style="color:#65737e;">// }); -</span></code></pre> -<p>In this particular example I would do:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#8fa1b3;">doAnim</span><span>(</span><span style="color:#bf616a;">myModalElement</span><span>, &#39;</span><span style="color:#a3be8c;">on</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">AnimationEnd</span><span>&#39;, </span><span style="color:#b48ead;">function</span><span>(){ -</span><span> </span><span style="color:#65737e;">// this function will fire when the animation is finished -</span><span> </span><span style="color:#bf616a;">myModalElement</span><span>.</span><span style="color:#bf616a;">classList</span><span>.</span><span style="color:#96b5b4;">remove</span><span>(&#39;</span><span style="color:#a3be8c;">on</span><span>&#39;); -</span><span>}); -</span></code></pre> -<p>So now I have a function that will add a class to an element and then fire my callback when the animation is complete.</p> - - - - - CrunchBase SVG logo - 2013-05-29T00:00:00+00:00 - 2013-05-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/crunchbase-svg/ - - <p>I needed the CrunchBase logo in a vector so I had to create it from -scratch.</p> -<div class="center"> - <a href="/images/crunchbase.svg" target="_blank"><img alt="crunchbase svg vector" src="/images/crunchbase.svg" ></a> -</div> - - - - - Startup Canada SVG logos - 2013-05-29T00:00:00+00:00 - 2013-05-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/startup-canada-svg/ - - <p>I had to create these SVGs for the new <a href="http://spotlander.com" title="Spotlander">Spotlander</a> website. They are 2 of them, one for the parent company; Startup Canada. The last one is for the divisions, in this case: Startup London. I figured I would share.</p> -<div class="center"> - <a href="/images/startupcanada.svg" target="_blank"><img alt="startupcanada svg vector" src="/images/startupcanada.svg" ></a> -</div> -<div class="center"> - <a href="/images/startuplondon.svg" target="_blank"><img alt="startuplondon svg vector" src="/images/startuplondon.svg" ></a> -</div> - - - - - Modernizr SVG Fallback to PNG - 2013-05-27T00:00:00+00:00 - 2013-05-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/modernizr-svg-fallback-to-png/ - - <div class="center"> - <img src="/images/githubgistlogo.png" alt="Github Gists Logo"> -</div> -<p>I have been building a small project boilerplate for when I am starting new projects. I wrote this small snippet based on <a href="http://toddmotto.com/mastering-svg-use-for-a-retina-web-fallbacks-with-png-script/" title="Todd Motto - mastering-svg-use-for-a-retina-web-fallbacks-with-png-script">this article</a>.</p> -<p>The only changes I made were wrapping it in a closure and combining all the vars to make it smaller. Of course your minifier would do this anyway unless you are using it inline after including Modernizr.</p> -<p><a href="https://gist.github.com/james2doyle/5659710" title="modernizr-svg-replace.js">Here is the gist</a>.</p> -<p>Or you can copy the current version from right here.</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#b48ead;">if </span><span>(!</span><span style="color:#bf616a;">Modernizr</span><span>.</span><span style="color:#bf616a;">svg</span><span>) { -</span><span> </span><span style="color:#65737e;">// wrap this in a closure to not expose any conflicts -</span><span> (</span><span style="color:#b48ead;">function</span><span>() { -</span><span> </span><span style="color:#65737e;">// grab all images. getElementsByTagName works with IE5.5 and up -</span><span> </span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">imgs </span><span>= document.</span><span style="color:#96b5b4;">getElementsByTagName</span><span>(&#39;</span><span style="color:#a3be8c;">img</span><span>&#39;),</span><span style="color:#bf616a;">endsWithDotSvg </span><span>= /</span><span style="color:#d08770;">.</span><span>*</span><span style="color:#96b5b4;">\.svg</span><span style="color:#b48ead;">$</span><span>/,</span><span style="color:#bf616a;">i </span><span>= </span><span style="color:#d08770;">0</span><span>,</span><span style="color:#bf616a;">l </span><span>= </span><span style="color:#bf616a;">imgs</span><span>.length; -</span><span> </span><span style="color:#65737e;">// quick for loop -</span><span> </span><span style="color:#b48ead;">for</span><span>(; </span><span style="color:#bf616a;">i </span><span>&lt; </span><span style="color:#bf616a;">l</span><span>; ++</span><span style="color:#bf616a;">i</span><span>) { -</span><span> </span><span style="color:#b48ead;">if</span><span>(</span><span style="color:#bf616a;">imgs</span><span>[</span><span style="color:#bf616a;">i</span><span>].</span><span style="color:#bf616a;">src</span><span>.</span><span style="color:#96b5b4;">match</span><span>(</span><span style="color:#bf616a;">endsWithDotSvg</span><span>)) { -</span><span> </span><span style="color:#65737e;">// replace the png suffix with the svg one -</span><span> </span><span style="color:#bf616a;">imgs</span><span>[</span><span style="color:#bf616a;">i</span><span>].</span><span style="color:#bf616a;">src </span><span>= </span><span style="color:#bf616a;">imgs</span><span>[</span><span style="color:#bf616a;">i</span><span>].</span><span style="color:#bf616a;">src</span><span>.</span><span style="color:#96b5b4;">slice</span><span>(</span><span style="color:#d08770;">0</span><span>, -</span><span style="color:#d08770;">3</span><span>) + &#39;</span><span style="color:#a3be8c;">png</span><span>&#39;; -</span><span> } -</span><span> } -</span><span> })(); -</span><span>} -</span></code></pre> - - - - - Custom Google Forms - 2013-05-22T00:00:00+00:00 - 2014-09-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/custom-google-forms/ - - <div class="center"> - <img src="/images/googleforms.png" alt="" align="middle"> -</div> -<p><strong>Update: Google has added the tools to customize a form with logos, colours, fonts, and backgrounds. Use this guide if you want even more custom styles, or if you want to embed the form within another page.</strong></p> -<p>I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something. After a bit of research I have found a way to create custom forms rather easily.</p> -<h3 id="how-to-create-a-custom-form">How to create a custom form</h3> -<p>This is just the normal way to make a new google form. If you have made one before then just skip this.</p> -<ul> -<li>Create a form as normal</li> -<li>Click the view live form</li> -<li>Copy everything inside the form tag including the form tag itself</li> -<li>Create a new blank HTML file</li> -<li>Create an empty div with a container class</li> -<li>Paste all the form markup inside there</li> -<li>Link the style.css stylsheet</li> -<li>Test the form and check the response in Google drive</li> -</ul> -<h3 id="hosting-the-form">Hosting the form</h3> -<p><strong>Edit: This feature is <a href="https://support.google.com/drive/answer/2881970?hl=en">no longer avaliable</a> to the new google drive.</strong></p> -<p>Something relatively new to Google drive is the ability to host static HTML pages.</p> -<ul> -<li>Create a public shared folder</li> -<li>Upload all your static html files</li> -<li>Open the index.html file in drive and click the preview button</li> -<li>Copy the link to the page it sends you too</li> -<li>Share that link with whoever because you are done!</li> -<li>This is how the <a href="https://googledrive.com/host/0B3SHb_huRFdyNENfQjVzSGpIOFU/index.html" title="Hosted Demo of custom Google Form">demo form</a> is hosted.</li> -</ul> -<p>As a starting point I basically just copied the stylesheet from the default Google form page. Then I took all the colors and placed them into variables. The stylesheet needs to be stripped of things not necessary.</p> -<h3 id="moving-forward">Moving Forward</h3> -<p>I do not want to add any extra markup to the pages. The idea hear is to just copy the form mark that Google gives you and then just add a stylesheet that will make it themed.</p> -<p><strong>Think themes for bootstrap. Markup stays, stylesheets change.</strong></p> -<p>But right now I am just going to try and normalize the current stylsheet into something as default as possible so that I can then create a my-theme-name.css file that contains all the variables to do the styling. I am currently only using the variables in LESS but eventually I will use more of the feaures to get everything nice and themeable.</p> -<p>You can check out the <a href="https://googledrive.com/host/0B3SHb_huRFdyNENfQjVzSGpIOFU/index.html" title="Hosted Demo of custom Google Form">Demo form in action</a> or just jump right to the <a href="https://github.com/james2doyle/google-form-styling" title="james2doyle/google-form-styling">Github Repo</a>.</p> - - - - - Salt.js micro selector library - 2013-05-12T00:00:00+00:00 - 2013-05-12T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/salt-js-mirco-selector-library/ - - <div class="center"> - <img src="/images/saltjs.png" alt="Slat.js Logo"> -</div> -<p>I made a tiny dom selector library called <a href="https://github.com/james2doyle/saltjs" title="james2doyle/saltjs">Salt.js</a>.</p> -<p>It uses a regular expression to map different queries you pass through it to their native get functions. The reason I don’t just use <code>querySelectorAll</code> for everything is because it is slower than the native get commands. <a href="http://jsperf.com/getelementbyid-vs-queryselector/11">See this jsperf test</a>.</p> -<p>Yes, I see that the mapping is slower for newer versions of Chrome. But, almost every other browser and device is slower using <code>querySelectorAll</code> over the mapping method. Also keep in mind the regex used in that example is much more complicated than mine.</p> -<p>Here are some examples of how you would use the library:</p> -<h4 id="salt-js-examples">Salt.js Examples</h4> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#65737e;">// get by id -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">#iddiv</span><span>&#39;); -</span><span style="color:#65737e;">// get by class name -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">.classdiv</span><span>&#39;); -</span><span style="color:#65737e;">// get by element name -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">@namediv</span><span>&#39;); -</span><span style="color:#65737e;">// get by element tag name -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">=div</span><span>&#39;); -</span><span style="color:#65737e;">// get element using querySelectorAll -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">*div div.inside</span><span>&#39;); -</span><span style="color:#65737e;">// getAttribute of name -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">#iddiv</span><span>&#39;).</span><span style="color:#96b5b4;">getAttribute</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;); -</span><span style="color:#65737e;">// getAttribute of name from nodelist -</span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">.classdiv</span><span>&#39;)[</span><span style="color:#d08770;">0</span><span>].</span><span style="color:#96b5b4;">getAttribute</span><span>(&#39;</span><span style="color:#a3be8c;">name</span><span>&#39;); -</span></code></pre> -<p><a href="https://github.com/james2doyle/saltjs" title="james2doyle/saltjs">Check out the library on Github</a>.</p> -<h4 id="update">Update</h4> -<p>Looks like there are a bunch of better ways to make this smaller! I’ve updated the github to reflect the new libraries. I have also added a <a href="http://jsperf.com/micro-selector-libraries">jsPerf test</a>.</p> - - - - - CSS3 badge logo in SVG - 2013-04-21T00:00:00+00:00 - 2013-04-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/css3-badge-logo-in-svg/ - - <p>I have been trying to find the CSS3 badge in a SVG format but it wasn’t that easy. The HTML5 one was the first result on Google. Now finally I found one. I am posting it here because now I will never lose it!</p> -<div class="center"> - <img src="/images/html5css3badges.png" alt="HTML5 and CSS3 badges"> -</div> -<p>Here is the CSS3 logo:</p> -<div class="center"> - <a href="/images/css3.svg" target="_blank" title="Download CSS3.svg"><img src="/images/css3.svg" alt="CSS3 Scalable vector graphic"></a> -</div> -<p>Here is the HTML5 logo in-case you need it:</p> -<div class="center"> - <a href="/images/html5.svg" target="_blank" title="Download HTML5.svg"><img src="/images/html5.svg" alt="HTML5 Scalable vector graphic"></a> -</div> -<h3 id="references">References</h3> -<p>I found the logos on <a href="http://www.bobbyberberyan.com/2012/03/html-5-css-3-logos/" title="bobby berberyan - html-5-css-3-logos">this website</a>. They were in one single SVG file and I split them up.</p> - - - - - April 2013 Redesign! - 2013-04-17T00:00:00+00:00 - 2013-04-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/april-2013-redesign/ - - <p>Another redesign. This one is completely by me, with a little help from the <a href="http://html5blank.com" title="html5blank wordpress theme">html5blank</a> Wordpress template. I am using SVGs exclusively. Although I only have 2 images for the entire site, the logo and the mobile nav hamburger/menu button. I think the best part is the new code highlighter. It has some cool features.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>// here is some javascript -</span><span>var item = document.getElementById(&#39;#item&#39;); -</span><span>item.style.background = &#39;red&#39;; -</span><span>item.setAttribute(&#39;data-index&#39;, 1); -</span></code></pre> -<p>There it is. If you inspect you will see something, strange. I used the new CSS3 generated content. It allows you to use element attributes as css content attributes. Here is the special CSS for prettyprint, highlighted with prettyprint. How meta.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>pre[title]:before { -</span><span> font-family: &#39;Source Code Pro&#39;,monospace; -</span><span> font-weight: 700; -</span><span> display: block; -</span><span> position: relative; -</span><span> top: -10px; -</span><span> left: -25px; -</span><span> content: attr(title); -</span><span> width: auto; -</span><span> height: 20px; -</span><span> color: #FFF; -</span><span> padding: .1em 1.5em .3em; -</span><span> background: #91B6C7; -</span><span> text-shadow: 0 1px 0 rgba(0, 0, 0, .3); -</span><span> -webkit-box-shadow: 2px 2px 4px rgba(0, 0, 0, .2); -</span><span> box-shadow: 2px 2px 4px rgba(0, 0, 0, .2); -</span><span> overflow: hidden; -</span><span> text-overflow: ellipsis; -</span><span> white-space: nowrap; -</span><span>} -</span></code></pre> -<p>The special part of this style is the <code>content: attr(title);</code>. This grabs the title attribute, and its value, and sets it as the content. This is pretty cool. Also the support is high, IE7 and down. Let me know what you think in the newly enabled comments section!</p> - - - - - AngularJS Hangout - Promises Promises - 2013-04-17T00:00:00+00:00 - 2013-04-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/angularjs-hangout-promises-promises/ - - <p>I somehow managed to find my way into an <a href="https://plus.google.com/u/0/events/cljavmi7kpup1fso43k3fkpk2eg" title="AngularJS Promises">AngularJS hangout</a>.</p> -<p>The hangout is about promises and deffereds. On the google plus <a href="https://plus.google.com/u/0/events/cljavmi7kpup1fso43k3fkpk2eg" title="Angularjs Promises Event Page">event page</a>, there are links to all the plunkrs in the video. Here is the direct <a href="https://www.youtube.com/watch?v=XLaYaaq2Miw" title="Angularjs Promises Youtube">youtube link</a>.</p> - - - - - Switching to SVG on Grey Nimbus - 2013-04-14T00:00:00+00:00 - 2013-04-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/switching-to-svg-on-grey-nimbus/ - - <p>So I recently bought the <a href="http://www.bohemiancoding.com/sketch/" title="Sketch Website">Sketch app</a> for Mac. I am using it because I don't have illustrator. But to be honest, it is much better at doing small things. It's been about 2 hours switching the whole thing over and I have to say it is worth it.</p> -<p>I also found a <a href="https://github.com/svg/svgo" title="svg/svgo">SVG minifier</a> that I will definitely be using in the future. It managed to save about 50-60% on each image. Which, for compression, is very good. After all is said and done, I saved about 10kb. Now this is not a lot but I also eliminated the use of retina.js. Which cause a second request for each image that has an @2x version. So on mobile I have made the site much faster.</p> -<p>Also, because of the way that FuelPHP does it's caching, I was not able to cache the images. Because it would append a query at the end for the cache and retina.js would not be able to find the retina version. That means that any retina device would take double(approximately) requests to get the full page.</p> -<p>I did something I think is rather clever. What I did was, since SVG support is high, I sniffed the user agent to see if it is one of the browsers that doesn't support SVG. Then I set a global and used that to define the extension I was going to use.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;a href=&quot;#welcome&quot;&gt; -</span><span> &lt;?php echo Asset::img(&#39;nimbus&#39;.$ext, array(&quot;width&quot;=&gt;&quot;275&quot;, &quot;height&quot;=&gt;&quot;57&quot;, &quot;alt&quot;=&gt;&quot;Grey Nimbus Logo&quot;)); ?&gt; -</span><span>&lt;/a&gt; -</span></code></pre> -<p>Now $ext would be equal to &quot;.png&quot; or &quot;.svg&quot; depending on the browser you were in. Now the changes are live so you can see that everything is all SVG! It should also look quite pretty on retina screens. <a href="http://greynimbus.com" title="Grey Nimbus Website">Have a look</a>. The site still loads in under 1 second. Which, according to Google, is a good thing.</p> - - - - - PyroCMS Module Generator - 2013-04-13T00:00:00+00:00 - 2013-04-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-module-generator/ - - <div class="center"> - <img src="/images/pmgh.png" alt="PyroCMS Module generator header image"> -</div> -<h3 id="update">UPDATE</h3> -<p>I created a <a href="http://pyromg.aws.af.cm/" title="Hosted Pyro Module Generator">hosted version</a> of the module generator.</p> -<p>FINALLY!! I finished my module generator. It lets you create modules by just filling in a simple form. The module it generates can be used with 2.2. There is no support for any other version at this time.</p> -<p><a href="http://www.youtube.com/watch?v=g7moZUqIwHU" title="Pyro Module generator video">See the Video</a> or go to the <a href="https://github.com/james2doyle/pyro-module-generator" title="james2doyle/pyro-module-generator">github repo</a>.</p> -<p>The “app” is just installed to your localhost. It uses no database and relies strictly on writing files and reading files.</p> -<p>It is built using <a href="http://laravel.com/" title="Laravel Homepage">Laravel</a> because Pyro is eventually going to move to Laravel. It is kind of funny that a Laravel based app is building a CodeIgniter based CMS! Hehehee.</p> -<p>There is a lot of extra junk in there now just because I may create a dedicated site for it. I also want to make it more dynamic for when you are creating dropdown/multiselect and radio/checkbox inputs. But that depends. Right now it can just run locally and be used/customized that way.</p> -<p>This is actually my fourth iteration of the generator. I created one with no framework, just straight PHP. That was rough. Then I made one with just CodeIgniter. It was a little better. Next, I went kind of crazy and made a PHP command line tool. It can actually make plugins and widgets quite nicely.</p> -<p>In the end I chose Laravel because I might as well start learning it and it was pretty easy to use. Sp please <a href="https://github.com/james2doyle/pyro-module-generator" title="james2doyle/pyro-module-generator">check it out</a> and help clean it up if you can.</p> -<p>Some little screenshots:</p> -<div class="center"> - <img src="/images/pmg1.png" alt="PyroCMS Module generator input form"> - <img src="/images/pmg2.png" alt="PyroCMS Module generator field input"> -</div> - - - - - Pyro Swipe.js Module - 2013-04-13T00:00:00+00:00 - 2013-04-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-swipe-js-module/ - - <p>Yep, another module. This one is for the awesome <a href="https://github.com/bradbirdsall/Swipe" title="bradbirdsall/Swipe">Swipe.js library</a>. Swipe is amazing because it is touch-capable, lightweight and has no dependencies(no jQuery). This module allows users to create multiple slideshows just by choosing a folder they want to pull the images from.</p> -<p>Then using the modules plugin, they can call it anywhere on a page.</p> -<p>Here is is <a href="https://github.com/james2doyle/pyro-swipe-module" title="james2doyle/pyro-swipe-module">on github</a>.</p> -<p>It should also make it to the PyroCMS store.</p> - - - - - Grey Nimbus website - 2013-04-11T00:00:00+00:00 - 2013-04-11T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/portfolio/grey-nimbus-website/ - - <p>Finally took the time and initiative to launch <a href="http://greynimbus.com" title="Grey Nimbus Website">Grey Nimbus</a>, my business. The website itself is built using <a href="http://fuelphp.com" title="FuelPHP">FuelPHP</a>. The reason I chose it was because I was curious and it was pretty light weight.</p> -<p>The website has no database so that was pretty much ignored. But I wanted easy validation for forms and a good email library. This is handled nicely by Fuel. The docs are mostly good. I had to do a little google searching when I was trying to tie things in together.</p> -<p>I knew I wanted parallax and responsive. I found <a href="https://github.com/spencerbaynton/cool-kitten" title="spencerbaynton/cool kitten">a fork</a> of the <a href="http://www.jalxob.com/cool-kitten/" title="cool-kitten homepage">cool-kitten</a> framework that I liked and created <a href="https://github.com/james2doyle/cool-kitten" title="james2doyle/cool kitten">my own fork</a>.</p> -<p>In my fork, the first thing I did was add <a href="http://retinajs.com/" title="Retina.js Homepage">retina.js</a>. I know some people knock retina.js because it uses <a href="http://mir.aculo.us/2012/09/22/dont-use-javascript-for-retinafying/" title="Thomas Fuchs - Don’t use JavaScript for Retinafying">javascript</a> to replace images with an @2x version.</p> -<blockquote> -<p>Maybe in the next update I will switch to SVG images, but I digress.</p> -</blockquote> -<p>The second thing I did was create a <a href="https://github.com/james2doyle/cool-kitten/blob/master/compile.sh" title="james2doyle/cool-kitten build script">build script</a> to concatenate and minify the javascript and css. It uses <a href="https://github.com/GoalSmashers/clean-css" title="GoalSmashers/clean-css">clean-css</a> and <a href="https://github.com/mishoo/UglifyJS" title="mishoo/UglifyJS">uglifyjs</a>.</p> -<p>The reason I didn't use something like grunt.js is because I only had 2 tasks to run and it was only when I was ready to push to the server. I added a condition in the head and footer to check to see if the site was in production and load the minified versions or the normal big list of separate files. The rest was just testing and building. Creating content is always tough for me. I want to sound casual which some people don't like. But it reflects me better so, whatever.</p> -<p>I ended up adding <a href="http://daneden.me/animate/" title="daneden.me/animate/">animate.css</a> too. I didn't create any fallbacks for no css animation support because they just won't show if you don't have support. I've used the library before and it is pretty great. I created a custom build because I am trying to keep my footprint small. In the end, even with all the images and javascript libs, I managed to get the page to load in <a href="http://blog.kissmetrics.com/loading-time/?wide=1" title="How Loading Time Affects Your Bottom Line">under 1 second</a> (at least on desktop...).</p> -<p>In conclusion, I am very happy with it. I have gotten a lot of positive feedback and suggestions. Everyday I make a few minor tweaks. Please <a href="http://greynimbus.com/" title="Grey Nimbus Website">check it out</a>.</p> - - - - - Pyro Image Select Field - 2013-03-28T00:00:00+00:00 - 2013-03-28T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-image-select-field/ - - <p>Another <a href="http://pyrocms.com" title="PyroCMS Website">PyroCMS</a> field type. This one is called Image Select. Why? Because it does exactly that. It lets you select images. But wait! There is already an image field type. Yes, but that is for uploading images. Sometimes you just want to choose and image you already have. An image drop down is good for this.</p> -<div class="center"> - <img alt="pyro image select screen3" src="/images/screen3.png" > -</div> -<p>I think a nice grid with thumbnail previews is even better!! I have the project <a href="https://github.com/james2doyle/pyro-image-select" title="pyro image select github page">hosted on github</a>. It will also be submitted to the PyroCMS store.</p> - - - - - jQuery Plugin Snippets for Sublime Text 2 - 2013-03-28T00:00:00+00:00 - 2013-03-28T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/jquery-plugin-snippets/ - - <div class="center"> - <img src="/images/jquery.png" alt="jquery logo"> -</div> -<p>I created a <a href="https://github.com/james2doyle/jquery-plugin-snippets" title="james2doyle/jquery-plugin-snippets">bunch of snippets</a> out of the patterns from <a href="https://github.com/shichuan/javascript-patterns/tree/master/jquery-plugin-patterns" title="shichuan javascript patterns">shichuans javascript patterns repo</a>.</p> -<p>These are for <a href="http://www.sublimetext.com/" title="sublime text site">Sublime Text 2</a> which everyone knows. You just clone the repo into the packages directory and they magically appear. If you don’t use sublime text…</p> -<div class="center"> - <img src="/images/yunost2.jpg" alt="y u no use sublime text 2"> -</div> -<p>I had to create that image because it didn’t exists for some reason.</p> -<p><a href="https://github.com/james2doyle/jquery-plugin-snippets" title="james2doyle/jquery-plugin-snippets">Github Linky</a>.</p> - - - - - PHP Variables in strings - 2013-03-21T00:00:00+00:00 - 2013-03-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/php-variables-in-strings/ - - <p>I have been getting quite annoyed lately when escaping a string to output a PHP variable. So I decided to make a little test so I could see what the best way to tackle this was.</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>// tested on PHP 5.4.4 -</span><span>$var = &quot;variable value&quot;; -</span><span>echo &quot;tell me the &quot;.$var.&quot; please&quot;; // variable value -</span><span>echo &quot;tell me the &quot;,$var,&quot; please&quot;; // variable value -</span><span>echo &quot;tell me the $var please&quot;; // variable value -</span><span>echo &quot;tell me the {$var} please&quot;; // variable value -</span><span>echo &#39;tell me the &#39;,$var,&#39; please&#39;; // variable value -</span><span>echo &#39;tell me the $var please&#39;; // $var -</span><span>echo &#39;tell me the {$var} please&#39;; // {$var} -</span></code></pre> -<p>Maybe I am a little dumb for not knowing this. But I would always escape my strings like:</p> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>echo &quot;my little &quot;.$var.&quot; went to the market.&quot;; -</span></code></pre> -<p>when I could have just used &quot;$var&quot; with double quotes... Everything in single quotes is treated as a plain string and anything with double is interpreted. I saw something saying that some of these will not output properly in older versions of PHP.</p> - - - - - Pyro Blurb Field - 2013-03-19T00:00:00+00:00 - 2013-03-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-blurb-field/ - - <p>I created another new field type for <a href="http://pyrocms.com" title="PyroCMS Website">PyroCMS</a>.</p> -<div class="center"> - <img alt="pyro-blurb-field" src="/images/Screen-Shot-2013-03-13-at-3.38.39-PM.png" > -</div> -<p>This one is for doing little &quot;blurb&quot; sections. Essentially I keep seeing little title+image+body components. Like testimonials, user profiles, portfolio snippets. These all follow a title+image+body format. So I built a little field type to provide an easy way to manage these in your page types.</p> -<div class="center"> - <img alt="pyro-blurb-field" src="/images/Screen-Shot-2013-03-13-at-3.38.21-PM.png" > -</div> -<p>I also put the <a href="https://github.com/james2doyle/pyro-blurb-field" title="pyro-blurb field github">project on github</a> so that you can see it. It will also be on the PyroCMS Store.</p> - - - - - Canadian Provinces Field - 2013-03-15T00:00:00+00:00 - 2013-03-15T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/canadian-provinces-field/ - - <p>Created another <a href="http://pyrocms.com" title="PyroCMS website">PyroCMS</a> field. -This one is for Canadian Provinces and Territories. Here is <a href="https://github.com/james2doyle/canadian-provinces-field" title="canadian-provinces-field">the github -page</a>.</p> - - - - - Git Website Workflow - 2013-03-13T00:00:00+00:00 - 2013-03-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/git-website-workflow/ - - <div class="center"> - <img alt="Amazon Web Services Logo" src="/images/awslogo.png"> -</div> -<p>I recently switched to using Amazon Web Services for my hosting. It is awesome. Because it(EC2) is just a cloud computer, I can install anything I want and set up any workflow I desire. I recently found <a href="http://goo.gl/0L3E6" title="A web-focused Git workflow">an article</a> that was about a website git workflow.</p> -<p>I finally got the flow down, but it was taking a little too long to start a project. So I decided to <a href="https://github.com/james2doyle/git-website-workflow" title="git-websit-workflow bash script">make it into a script</a>.This works with any cloud computer workflow. It doesn't have to be just Amazon.</p> -<p>With a little help from <a href="https://plus.google.com/109231487156400680487/postsWzueZxHuP7b" title="google plus bash community post">some people on google+</a> I finally got it down. Check out the <a href="http://goo.gl/0L3E6" title="A web-focused Git workflow">original article</a> on how itworks and the philosophy of why.</p> - - - - - PyroCMS PageWidgets Field Type - 2013-03-05T00:00:00+00:00 - 2013-03-05T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyrocms-pagewidgets-field-type/ - - <p>I created another field type for <a href="https://www.pyrocms.com/" title="PyroCMS Website">PyroCMS</a>. This one is so you can add widgets on a page-by-page basis instead of area-by-area.</p> -<p>I kept getting asked by clients for this feature, and with the advent of the new page type and field types this is a lot easier. The source is on <a href="https://github.com/james2doyle/pyro-pagewidgets-field" title="pyro-pagewidgets-field">github</a></p> - - - - - Clean CSS Updated - 2013-02-24T00:00:00+00:00 - 2013-02-24T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/clean-css-updated/ - - <p>I updated my Chrome packaged app, Clean CSS. Here is <a href="http://james2doyle.github.com/clean-css-chrome-app/" title="Clean CSS Chrome App">the website</a>.</p> - - - - - Pyro List Field - 2013-02-23T00:00:00+00:00 - 2013-02-23T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-list-field/ - - <p>I created a list field type for PyroCMS. It allows users to easily add and manage list content. The source code is up <a href="https://github.com/james2doyle/pyro-list-field" title="Pyro List Field">on github</a> and avaliable on the <a href="https://www.pyrocms.com/store/details/list_field_type" title="PyroCMS Store List Field Type">PyroCMS store</a>.</p> - - - - - Pyro Image Widget - 2013-02-21T00:00:00+00:00 - 2013-02-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/pyro-image-widget/ - - <p>I created a small image widget for PyroCMS. It allows a user to choose any image in the files as a widget. It also allows you to add a link and a target so it can be opened in a new tab. Here is <a href="https://github.com/james2doyle/pyro-image-widget" title="Pyro Image Widget Github">the github link</a> and the <a href="https://www.pyrocms.com/store/details/pyro_image_widget" title="Pyro Image Widget">PyroCMS store link</a>.</p> - - - - - Zepto Drag & Swap - 2013-02-21T00:00:00+00:00 - 2013-02-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/zepto-drag-swap/ - - <p>I created a little plugin for zepto.js called Drag &amp; Swap. Here is the <a href="http://james2doyle.github.com/zepto-dragswap/" title="Zepto Dragswap">github link</a>.</p> - - - - - Making a socket.io app and how socket.io works - 2013-01-13T00:00:00+00:00 - 2013-01-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/socketio-works-howto/ - - <p>I made my first video about web development. It is a quick video about socket.io and how it works with node and such.</p> -<p><a href="http://youtu.be/JfYRGTvMbBA"><img src="/images/vlcsnap-2013-01-12-16h52m23s239.jpg" alt="Socket.io demo app" /></a></p> -<p>It also includes a little demo of a tiny app I built. I may release the source later on depending on the videos popularity.</p> -<p><a href="http://youtu.be/JfYRGTvMbBA">Making a socket.io app</a></p> - - - - - Clean CSS in Chrome Apps Office Hours - 2012-12-19T00:00:00+00:00 - 2012-12-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/clean-css-in-office-hours/ - - <h3 id="clean-css-made-it-into-a-chrome-apps-office-hours">Clean CSS made it into a Chrome Apps Office hours!!</h3> -<p>So my chrome app <a href="http://james2doyle.github.com/clean-css-chrome-app/" title="Clean CSS Chrome App">Clean CSS</a> made it onto one of the Chrome Apps Office Hours.</p> -<p>The Office Hours is a series of videos by Google where developers talk to the community about Google technology, software and programming. The way I got on was I saw Paul Kinlan, a Google Chrome developer advocate, make a post about the live office hours being held that day.</p> -<p>I managed to get a link to my app and he then reviewed it on the show!</p> -<p>The app <a href="https://github.com/james2doyle/clean-css-chrome-app/" title="Clean CSS on Github">is also on github</a> so anyone can submit some changes and improvements. You can download the app on the <a href="http://goo.gl/D9F7u" title="Clean CSS on Chrome Web Store">Chrome Web Store</a></p> - - - - - View Folder Tree in MacOSX Terminal - 2012-11-19T00:00:00+00:00 - 2012-11-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/view-folder-tree-in-macosx-terminal/ - - <p><a href="https://coderwall.com/p/owb6eg" title="terminal tree command">View Folder Tree in MacOSX Terminal</a></p> -<p><a href="/images/Screen-Shot-2012-11-19-at-1.05.22-PM11.png"><img src="/images/Screen-Shot-2012-11-19-at-1.05.22-PM11.png" alt="zsh tree alias" title="zsh tree alias" /></a></p> - - - - - New business cards - 2012-11-15T00:00:00+00:00 - 2012-11-15T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/portfolio/new-business-cards/ - - <p>Picked up my new cards right before <a href="http://www.diglondon.ca/">Dig London</a>. They have my new favourite emoticon on the back too.</p> -<p><a href="/images/20121114-14232711.jpg"><img src="/images/20121114-14232711.jpg" alt="20121114-142327.jpg" /></a></p> - - - - - Flexbox Demo - 2012-11-14T00:00:00+00:00 - 2012-11-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/flexbox-demo/ - - <p>An old flexbox demo I made a while back. It may not be the latest syntax so beware!!</p> -<p><a href="http://codepen.io/james2doyle/pen/svEek"><img src="/images/Screen-Shot-2012-11-13-at-4.23.41-PM-e135284190554411.png" alt="Flexbox Demo" title="Flexbox Demo" /></a></p> -<p><a href="http://codepen.io/james2doyle/pen/svEek" title="Codepen Link">Check out the pen</a>.</p> - - - - - WYSIWYG in PyroCMS Widgets - 2012-11-10T00:00:00+00:00 - 2012-11-10T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/wysiwyg-in-pyrocms-widgets/ - - <p>I have been working on a site that uses <a href="https://www.pyrocms.com/" title="PyroCMS Homepage">PyroCMS</a>. I needed to build a custom widget that had a WYSIWYG textarea. This is what worked for me.</p> -<p>Add this to template in the constructors function.</p> -<h4 id="pyrocms-system-cms-modules-widgets-controllers-admin-php">pyrocms/system/cms/modules/widgets/controllers/admin.php</h4> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>// my new template -</span><span>$this-&gt;template -</span><span>-&gt;set_partial(&#39;shortcuts&#39;, &#39;admin/partials/shortcuts&#39;) -</span><span>-&gt;append_js(&#39;module::widgets.js&#39;) -</span><span>-&gt;append_css(&#39;module::widgets.css&#39;) -</span><span>-&gt;append_metadata($this-&gt;load-&gt;view(&#39;fragments/wysiwyg&#39;, $this-&gt;data, TRUE)); -</span></code></pre> -<p>The following javascript needs to be added to the top of your view/form.php file.</p> -<h4 id="widgets-mywysiwygwidget-views-form-php">/widgets/mywysiwygwidget/views/form.php</h4> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#65737e;">// my self special jquery -</span><span>(</span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">$</span><span>){ -</span><span> </span><span style="color:#8fa1b3;">$</span><span>(&#39;</span><span style="color:#a3be8c;">textarea.wysiwyg-simple</span><span>&#39;).</span><span style="color:#8fa1b3;">ckeditor</span><span>({ -</span><span> </span><span style="color:#65737e;">// this is the config for the simple wysiwyg -</span><span> toolbar: [ -</span><span> [&#39;</span><span style="color:#a3be8c;">Bold</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">Italic</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">-</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">NumberedList</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">BulletedList</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">-</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">Link</span><span>&#39;, &#39;</span><span style="color:#a3be8c;">Unlink</span><span>&#39;] -</span><span> ], -</span><span> width: &#39;</span><span style="color:#a3be8c;">99%</span><span>&#39;, -</span><span> height: </span><span style="color:#d08770;">100</span><span>, -</span><span> dialog_backgroundCoverColor: &#39;</span><span style="color:#a3be8c;">#000</span><span>&#39;, -</span><span> defaultLanguage: &#39;&#39;, -</span><span> language: &#39;&#39; -</span><span> }); -</span><span>})(</span><span style="color:#bf616a;">jQuery</span><span>); -</span></code></pre> -<p>Also, you need to have the wysiwyg-simple class on your textarea.</p> -<h4 id="widgets-mywysiwygwidget-views-form-php-1">/widgets/mywysiwygwidget/views/form.php</h4> -<pre data-lang="php" style="background-color:#2b303b;color:#c0c5ce;" class="language-php "><code class="language-php" data-lang="php"><span>echo form_textarea(array(&#39;name&#39;=&gt; &#39;html&#39;, &#39;value&#39; =&gt; $options[&#39;html&#39;], &#39;class&#39; =&gt; &#39;wysiwyg-simple&#39;)); -</span></code></pre> - - - - - Letterpress loader in CSS - 2012-11-04T00:00:00+00:00 - 2012-11-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/letterpress-loader-in-css/ - - <p>I recently bought the amazing Letterpress app for iPhone. It truly is gorgeous, and has an amazing UI. It inspired me to make this little doodle.</p> -<div class="center"> - <img src="/images/Screen-Shot-2012-11-04-at-1.34.39-AM11.png" alt="Letterpress Loader In CSS"> -</div> -<p>This is a little tribute. It's an animated css loader in the style of Letterpress. It's a little different because it uses the custom cubic-bezier curve called 'ease-in-out-back', which is essentially elastic. I got the values from the great site <a href="http://matthewlein.com/ceaser/" title="Ceaser">Ceaser</a>.</p> -<p>As always, <a href="http://codepen.io/james2doyle/pen/rDEzp" title="Letterpress CSS Loader">check out the pen</a></p> - - - - - Kube-Node-Express - 2012-10-21T00:00:00+00:00 - 2012-10-21T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/personal-project/kube-node-express/ - - <p>I have released my first node.js-based public repo! It is called <a href="https://github.com/james2doyle/kube-node-express" title="Kube-Node-Express">Kube-Node-Express</a>.</p> -<img src="/images/Screen-Shot-2012-10-20-at-3.46.33-PM11.png" alt="Kube CSS Framework" > -<p>This is based off of the <a href="http://imperavi.com/kube/" title="Kube CSS Framework">Kube CSS Framework by Imperavi</a>. It uses the <a href="http://expressjs.com/" title="expressjs">express</a> framework for node. This was inspired by a similar project I saw that was <a href="https://github.com/robrighter/node-boilerplate" title="node boilerplate">based on the HTML5 Boilerplate</a>. I will probably end up stealing more ideas from that project too.</p> -<h4 id="disclaimer">disclaimer</h4> -<p>I did not create any of these frameworks, I just combined them all together.</p> - - - - - Source Code Pro on Sublime Text - 2012-10-19T00:00:00+00:00 - 2012-10-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/source-code-pro-sublime/ - - <div class="center"> - <img src="/images/githubgistlogo.png" alt="Github Gists Logo"> -</div> -<p>I recently switched my font in sublime text to the new Adobe font, <a href="http://blogs.adobe.com/typblography/2012/09/source-code-pro.html" title="Adobe Blog - Source Code Pro">Source Code Pro</a>.</p> -<div class="center"> - <img src="/images/sourcecodepro.png" alt="Source Code Pro sublime text 2 screenshot"> -</div> -<p>It looks fantastic! It is so smooth and crisp. To switch to it as your main font, first download the font. Then add the line “font_face”: “Source Code Pro” in your Preferences-&gt;User Settings(keyboard shortcut command+comma to open the settings) and that’s it! Pretty fly.</p> - - - - - Generated Content in CSS - 2012-10-18T00:00:00+00:00 - 2012-10-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/generated-content-title/ - - <p>I like <a href="http://jsfiddle.net/" title="jsFiddle">jsFiddle</a>. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my <a href="https://ohdoylerules.com/work/" title="Work">work</a> section.</p> -<p>Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the PHP <code>echo</code>s. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/*! styles for printing */ -</span><span style="color:#b48ead;">@media </span><span>print{ -</span><span> </span><span style="color:#65737e;">/*! all a tags with an href attribute */ -</span><span> </span><span style="color:#bf616a;">a</span><span style="color:#8fa1b3;">[</span><span style="color:#d08770;">href</span><span style="color:#8fa1b3;">]:</span><span style="color:#b48ead;">after</span><span>{ -</span><span> </span><span style="color:#65737e;">/*! display that href after the value */ -</span><span> content: &quot;</span><span style="color:#a3be8c;"> (</span><span>&quot; </span><span style="color:#96b5b4;">attr</span><span>(</span><span style="color:#d08770;">href</span><span>) &quot;</span><span style="color:#a3be8c;">)</span><span>&quot;; -</span><span> } -</span><span>} -</span></code></pre> -<h3 id="what-does-this-look-like">What does this look like?</h3> -<p>Would render as <a href="https://ohdoylerules.com/demo/generated-content-title/www.example.com/">Website (www.example.com/)</a></p> -<p>So with that in mind, I thought it would be cool to have a button that would render a count. So in practical applications it might be used for something like an inbox button. In the case I built it is a notifications button.</p> -<div class="center"> - <img src="/images/54368011.png" alt="notifications button" > -</div> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>button { -</span><span> color: #333; -</span><span> padding: 4px 10px; -</span><span> border: 1px solid #aaa; -</span><span> outline: none; -</span><span> font-weight: bold; -</span><span> font-size: 12px; -</span><span> font-family: &#39;Corbin&#39;; -</span><span> background: #eee; -</span><span> border-radius: 3px; -</span><span> box-shadow: 0 2px 0 rgba(0,0,0,0.5); -</span><span> text-shadow: 0 1px 0 rgba(255,255,255,0.8); -</span><span>} -</span><span>button:after { -</span><span> content: attr(data-count); -</span><span> border-radius: 10px; -</span><span> display: inline-block; -</span><span> background: #777; -</span><span> padding: 2px 5px; -</span><span> width: 15px; -</span><span> margin: 0 0 0 8px; -</span><span> color: white; -</span><span> font-weight: normal; -</span><span> border: 1px solid #555; -</span><span> box-shadow: inset 0 1px 2px rgba(255,255,255,0.5), -</span><span> inset 0 -1px 2px rgba(0,0,0,0.5); -</span><span> text-shadow: 0 1px 0 rgba(0,0,0,0.6); -</span><span>}​ -</span></code></pre> -<p>If you would like to see that bad boy in action check out <a href="http://jsfiddle.net/james2doyle/LjgzD/" title="jsFiddle css content">this fiddle</a>.</p> - - - - - Array.prototype.range() - 2012-10-18T00:00:00+00:00 - 2012-10-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/array-prototype-range/ - - <p>I wrote this little prototype after seeing the range function in ruby.</p> -<pre data-lang="js" style="background-color:#2b303b;color:#c0c5ce;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#ebcb8b;">Array</span><span>.prototype.</span><span style="color:#8fa1b3;">range </span><span>= </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">a</span><span>, </span><span style="color:#bf616a;">b</span><span>, </span><span style="color:#bf616a;">step</span><span>) { -</span><span> </span><span style="color:#bf616a;">step </span><span>= !</span><span style="color:#bf616a;">step </span><span>? </span><span style="color:#d08770;">1 </span><span>: </span><span style="color:#bf616a;">step</span><span>; -</span><span> </span><span style="color:#bf616a;">b </span><span>= </span><span style="color:#bf616a;">b </span><span>/ </span><span style="color:#bf616a;">step</span><span>; -</span><span> </span><span style="color:#b48ead;">for</span><span>(</span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">i </span><span>= </span><span style="color:#bf616a;">a</span><span>; </span><span style="color:#bf616a;">i </span><span>&lt;= </span><span style="color:#bf616a;">b</span><span>; </span><span style="color:#bf616a;">i</span><span>++) { -</span><span> </span><span style="color:#bf616a;">this</span><span>.</span><span style="color:#96b5b4;">push</span><span>(</span><span style="color:#bf616a;">i </span><span>* </span><span style="color:#bf616a;">step</span><span>); -</span><span> } -</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#bf616a;">this</span><span>; -</span><span>}; -</span><span> -</span><span style="color:#65737e;">// usage -</span><span> -</span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">myarray </span><span>= []; -</span><span style="color:#bf616a;">myarray</span><span>.</span><span style="color:#8fa1b3;">range</span><span>(</span><span style="color:#d08770;">0</span><span>, </span><span style="color:#d08770;">10</span><span>, </span><span style="color:#d08770;">2</span><span>); </span><span style="color:#65737e;">// myarray now returns [0, 2, 4, 6, 8, 10] -</span></code></pre> - - - - - No Javascript CSS Accordion - 2012-10-17T00:00:00+00:00 - 2012-10-17T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/no-javascript-accordion/ - - <p>I made this today. It's a little CSS-only accordion. It uses the <code>:target</code> selector. The <code>:target</code> selector is rather new. It also has some quirks. It is almost like an onclick without the javascript.</p> -<div class="center"> - <img src="/images/Screen-Shot-2012-10-17-at-1.54.00-PM11.png" alt="no-js accordion" > -</div> -<p>The thing I find is that the name is being passed through the URL when using this method. This is nice when you want to refresh the page and keep the same item active. But that is also a downside because maybe when you refresh you want everything to reset. In that case, you would need to delete the variable/id out of the URL and then refresh.</p> -<p>But just <a href="http://codepen.io/james2doyle/pen/tgxDr" title="no-js accordion">check it out</a> already!</p> - - - - - Dancing Music Bars - 2012-10-16T00:00:00+00:00 - 2012-10-16T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/dancing-music-bars/ - - <p data-height="265" data-theme-id="0" data-slug-hash="bwbJgW" data-default-tab="result" data-user="james2doyle" data-embed-version="2" data-pen-title="bwbJgW" class="codepen">See the Pen <a href="http://codepen.io/james2doyle/pen/bwbJgW/">bwbJgW</a> by James Doyle (<a href="http://codepen.io/james2doyle">@james2doyle</a>) on <a href="http://codepen.io">CodePen</a>.</p> -<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> - - - - - zsh new file && open file function - 2012-10-13T00:00:00+00:00 - 2012-10-13T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/zsh-new-open-function/ - - <p>Here is a little function that I made for <a href="https://github.com/robbyrussell/oh-my-zsh" title="oh-my-zsh">oh-my-zsh</a>. I found myself constantly doing <code>sudo touch app.js &amp;&amp; open app.js</code>.</p> -<p>What this little command does is create an empty file called <code>app.js</code> and then opens it with whatever your default editor is.</p> -<h4 id="here-is-the-function">Here is the function:</h4> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span># create a new file in the current directory and then open it -</span><span>new () { -</span><span> sudo touch $1 &amp;&amp; open $1 -</span><span>} -</span></code></pre> -<h4 id="how-to-use">How to use:</h4> -<p>Open your <code>.zshrc</code> file and add this function at the bottom. If you haven't yet, uncomment the line that adds the zshconfig alias. If you are looking for example aliases, well, there are a couple in this section. When you want to create and then open a file just type <code>new myfile.xxx</code>. This will create the new file in the directory and then open it with whatever editor you want.</p> - - - - - Target Mozilla-only in CSS - 2012-10-11T00:00:00+00:00 - 2012-10-11T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/target-mozilla-only-in-css/ - - <p>I had some issues in Firefox recently. I was building a complicated “item” in CSS and it looked great in Chrome. I got an email later saying that the sizing was all off for a bunch of things. I thought this was really strange. I went back to the CSS and Chrome and I could not see any issues.</p> -<p>I then fired up Firefox and, yikes! There was a bunch of weird issues. This is strange because normally Chrome to Firefox translates pretty well. I was using the <code>::first-letter</code> element and a few <code>::before</code> elements. But somehow, someway they got messed up. Anyway, I discovered this little snippet:</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span>@-</span><span style="color:#bf616a;">moz-document url-prefix</span><span style="color:#b48ead;">(</span><span>) { -</span><span> </span><span style="color:#65737e;">/* firefox only styles */ -</span><span>} -</span></code></pre> -<p>It works. But what does it mean? The url-prefix() is a way to serve specific styles to a specific URL. In this case, I just want to target a -moz- device. <a href="https://developer.mozilla.org/en-US/docs/CSS/@document?redirectlocale=en-US&amp;redirectslug=CSS%2F%40-moz-document" title="MDN @Document">Here is a more in depth definition</a>. This worked nicely, and so it will stay into production.</p> - - - - - My Old Website - 2012-10-04T00:00:00+00:00 - 2012-10-04T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/portfolio/my-old-website/ - - <p>I decided to host my old website because I wanted to show some work with Modernizr.</p> -<p>Looking back it is actually pretty cool! I kind of miss it haha. It seems to run much better than I remember. I think that is because it was the days of chrome 18 and Firefox 10 which had spotty 3d transform support.</p> -<p><a href="https://ohdoylerules.com/test/old/"><img src="/images/Screen-Shot-2012-10-04-at-11.26.23-AM-e134936451049011.png" alt="My Old Site" title="My Old Site" /></a></p> -<p>Maybe someday I can make a sub-site or a project site based off of that design. This was also one of my first sites to use AJAX. It was pretty frustrating and hard at the time. It is funny looking at my code and the way I handled things back then. I read it and feel like I am looking at a strangers code.</p> - - - - - CSS Date Card - 2012-09-18T00:00:00+00:00 - 2012-09-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/css-date-card/ - - <p>I am trying to make up for not posting in the last few days. Here is another thing I have been working on. It is a replication of <a href="http://dribbble.com/shots/713807-Extended" title="Event Card">another dribbble post</a>.</p> -<p><a href="http://dabblet.com/gist/3743024"><img src="/images/css-event-card2111.jpg" alt="css-event-card" title="Event card replicated in CSS" /></a></p> -<p>There are a couple of things missing but most of them would be replicated with a webfont. There was also the issue of not having a choice in the anti-aliasing of the font. The image was made using photoshop and probably used crisp or smooth which makes the font look <em>real nice</em>. I don't even know if I got the font right. I used Open Sans. It looked close enough so whatever. I've been working on this on and off for about 2 weeks so it is time to give it away!</p> -<p>Click the image for a dabblet. <em>BTW, I always misspell dabblet because of the triple B in dribbble!</em></p> - - - - - CSS3 Pagebend - 2012-09-18T00:00:00+00:00 - 2012-09-18T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/css3-pagebend/ - - <p>I crafted <a href="http://codepen.io/james2doyle/pen/HpbrL" title="css3-pagebend">this lil' beauty</a> in class today. It turns out that it is really hard to make transforming elements intersect. At least, I couldn't figure it out.</p> -<p><a href="http://codepen.io/james2doyle/pen/HpbrL"><img src="/images/css3-pagebend11.jpg" alt="css3-pagebend" title="css3-pagebend" /></a></p> -<p>I ended up having to essentially split the image in 2 and then rotateY one of the sides. It didn't look quite right when it was bending at the middle, so I made the split like 60-40. As always, click the image for the live action.</p> - - - - - nodelist.each - 2012-09-06T00:00:00+00:00 - 2012-09-06T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/snippets/nodelist-each/ - - <p>I was doing a project in vanilla javascript that used querySelectorAll, which returns a nodelist object. I wanted the jQuery each function so that I could add an event listener to each element. It was a school project and no jQuery allowed so I did some research and came up with this little prototype.</p> -<p>Here is the result:</p> -<pre data-lang="javascript" style="background-color:#2b303b;color:#c0c5ce;" class="language-javascript "><code class="language-javascript" data-lang="javascript"><span style="color:#ebcb8b;">Object</span><span>.prototype.</span><span style="color:#8fa1b3;">each </span><span>= </span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">callback</span><span>) { -</span><span> </span><span style="color:#65737e;">// new empty array -</span><span> </span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">a </span><span>= []; -</span><span> </span><span style="color:#65737e;">// iterate through the nodelist -</span><span> </span><span style="color:#b48ead;">for </span><span>(</span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">i </span><span>= </span><span style="color:#d08770;">0</span><span>; </span><span style="color:#bf616a;">i </span><span>&lt; </span><span style="color:#bf616a;">this</span><span>.length; </span><span style="color:#bf616a;">i</span><span>++) { -</span><span> </span><span style="color:#65737e;">// put the objects into the array -</span><span> </span><span style="color:#bf616a;">a</span><span>[</span><span style="color:#bf616a;">i</span><span>] = </span><span style="color:#bf616a;">this</span><span>[</span><span style="color:#bf616a;">i</span><span>]; -</span><span> </span><span style="color:#65737e;">// callback the new array -</span><span> </span><span style="color:#8fa1b3;">callback</span><span>(</span><span style="color:#bf616a;">a</span><span>[</span><span style="color:#bf616a;">i</span><span>]); -</span><span> } -</span><span>} -</span><span style="color:#65737e;">// USAGE -</span><span style="color:#b48ead;">var </span><span style="color:#bf616a;">x </span><span>= document.</span><span style="color:#96b5b4;">querySelectorAll</span><span>(&#39;</span><span style="color:#a3be8c;">li</span><span>&#39;); -</span><span style="color:#bf616a;">x</span><span>.</span><span style="color:#8fa1b3;">each</span><span>(</span><span style="color:#b48ead;">function</span><span>(</span><span style="color:#bf616a;">elem</span><span>) { -</span><span> </span><span style="color:#bf616a;">elem</span><span>.style.background = &#39;</span><span style="color:#a3be8c;">red</span><span>&#39;; -</span><span>}); -</span></code></pre> -<p>Pretty cool. <a href="http://jsfiddle.net/james2doyle/nrhgr/" title="each prototype">Here is the fiddle</a></p> - - - - - Mozilla Dev Derby - 2012-08-29T00:00:00+00:00 - 2012-08-29T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/demo/mozilla-dev-derby/ - - <p>I have just submitted my demo for the <a href="https://developer.mozilla.org/en-US/demos/detail/photo-shake" title="Photo Shake Page">Mozzila Dev Derby</a>. If you like it, please vote. Thank you!</p> -<div class="center"> - <img src="/images/james2doyle-Camera-Api-Demo2111.png" alt="Camera Api Demo" > -</div> -<p>It uses the HTML5 File Api and also uses the device orientation API. Firstly, you upload or take a picture, then that picture fades up below in a polaroid-style div. You can then move you device and see the picture move.</p> -<p>I also added something I called dynamic shine. This is just a diagonal white-to-transparent gradient. Tilting the device causes the photo to look like it is interacting with the light.</p> -<p>You can also see the project <a href="https://ohdoylerules.com/test/cameraapi/" title="photo shake">self-hosted here</a>.</p> -<h4 id="update">UPDATE:</h4> -<p>I got runner up! Here are the <a href="https://hacks.mozilla.org/2012/09/announcing-the-august-dev-derby-winners/" title="Mozilla Dev Derby August">results</a></p> - - - - - No-Js Image Preview - 2012-08-27T00:00:00+00:00 - 2012-08-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/no-js-image-preview/ - - <p>Using CSS transforms, I was able to build a little image previewer. There is only one image for both the thumbnail and full size view. Scale does wonders.</p> -<div class="center"> - <img src="/images/No-Js-Image-Preview-CodePen.png" alt="No Js Image Preview · CodePen" > -</div> -<p>I created this guy in <a href="http://codepen.io/james2doyle/pen/fChbD" title="Codepen.io">Codepen.io</a>. It seems to be a lot better than just showing all the html/css/js in a post. I would appreciate any comments or possible improvements. I might try to build some sort of template/plugin so that anyone can just plop in their images and BAM it works nicely.</p> - - - - - Styling Radios and Checkboxes. Redux. - 2012-08-19T00:00:00+00:00 - 2012-08-19T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/styling-input-redux/ - - <p>A few weeks ago, or something like that, I made a post about <a href="https://ohdoylerules.com/web/radio-checkboxes/" title="Styling radio and checkbox inputs">styling your labels to act as checkboxes/radios</a>. I recently saw <a href="http://dribbble.com/shots/693245-Untitled-iPhone-synth-app-waveforms" title="iPhone-synth-app-waveforms">a post on Dribbble by Mikael Eidenberg</a> which inspired me to make some nicely styled examples of that method in action.</p> -<div class="center"> - <img src="/images/synthappbuttons.png" alt="Synth App Buttons in pure CSS"> -</div> -<p><a href="http://codepen.io/james2doyle/pen/AKblD" title="Synth App Buttons">Here is the pen on Codepen.io</a>. I usually use jsFidddle but I decided to change it up this time. Maybe my next post will be hosted on tinker.io or dabblet… although tinker does not have accounts like the others.</p> -<p>This is the exact same method as my <a href="https://ohdoylerules.com/web/radio-checkboxes/" title="Styling radio and checkbox inputs">previous article</a>, only there is a lot more css.</p> - - - - - WordPress Plugin Swipe.js - 2012-08-14T00:00:00+00:00 - 2012-08-14T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/wordpress-plugin-swipe-js/ - - <p>I have made my first WordPress plugin and github project: <a href="https://github.com/james2doyle/Swipe.js-WordPress-Plugin" title="WordPress Plugin: SwipeJS">A WordPress plugin for Swipe.js</a>.</p> -<p>This is my first attempt at a WordPress plugin and a Github project.</p> - - - - - Making Checkboxes in WordPress options pages - 2012-08-11T00:00:00+00:00 - 2012-08-11T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/checkboxes-options/ - - <p>I am in the process of building my first WordPress plugin. Of course I am wildly researching how to do things. One thing that was particularly hard to find was how to use checkboxes in options pages. Here is the solution I used.</p> -<p>WordPress has a function called <a href="http://codex.wordpress.org/Function_Reference/checked" title="WordPress Codex For Checked Function">checked()</a>. This basically returns a true checked attribute if the conditions it is passed are met. Here is how I used it:</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;input type=&quot;checkbox&quot; name=&quot;my_options&quot; &lt;?php checked( get_option(&#39;my_option&#39;) == &#39;on&#39;,true); ?&gt; /&gt; -</span></code></pre> -<p>What I found is that when the option was getting updated it was being stored as 'on'. So this little PHP snippet says: &quot;If the option named 'my_option' is equal to 'on' then add a checked=&quot;checked&quot; attribute to this input tag.&quot; Anyway, I found it quite hard to get a straight up answer to this problem. Since the reason I made this blog is to share my discoveries; here you go.</p> - - - - - Styling radio and checkbox inputs - 2012-08-07T00:00:00+00:00 - 2012-08-07T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/web/radio-checkboxes/ - - <p>Styling inputs can be pretty annoying. I don’t think I really have any consistent way of making custom inputs. Especially when it comes to radio and checkboxes. I will always prefer using CSS instead of images for obvious reasons. But for radios and checkboxes I normally use images. I usually just make a png sprite and use the :checked selector to move its position.</p> -<p>But now, I have discovered a new way! I took the idea from <a href="http://css-tricks.com/snippets/css/custom-checkboxes-and-radio-buttons/">CSS Tricks</a>/<a href="https://examples.wufoo.com/forms/custom-checkboxes-and-radio-buttons/" title="WuFoo Forms Example">Wufoo Forms</a>. It basically uses the label as the element. You hide the input tag and then style the label with checked and unchecked states.</p> -<div class="center"> - <img src="/images/radio-checkboxes.png" alt="End Results"> -</div> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>/* style this span element so we can display nicely, this styling is not necessary */ -</span><span>span { -</span><span> margin: 10px 0; -</span><span> display: block; -</span><span> width: 100%; -</span><span> float: left; -</span><span>} -</span><span> -</span><span>input[type=&quot;radio&quot;], -</span><span>input[type=&quot;checkbox&quot;] { -</span><span> /* hide the inputs */ -</span><span> opacity: 0; -</span><span>} -</span><span> -</span><span>/* style your lables/button */ -</span><span>input[type=&quot;radio&quot;] + label, -</span><span>input[type=&quot;checkbox&quot;] + label { -</span><span> /* keep pointer so that you get the little hand showing when you are on a button */ -</span><span> cursor: pointer; -</span><span> /* the following are the styles */ -</span><span> padding: 4px 10px; -</span><span> border: 1px solid #ccc; -</span><span> background: #efefef; -</span><span> color: #aaa; -</span><span> border-radius: 3px; -</span><span> text-shadow: 1px 1px 0 rgba(0,0,0,0); -</span><span>} -</span><span> -</span><span>input[type=&quot;radio&quot;]:checked + label, -</span><span>input[type=&quot;checkbox&quot;]:checked + label{ -</span><span> /* style for the checked/selected state */ -</span><span> background: #777; -</span><span> border: 1px solid #444; -</span><span> text-shadow: 1px 1px 0 rgba(0,0,0,0.4); -</span><span> color: white; -</span><span>}​ -</span></code></pre> -<p>I have removed all the unnecessary styles and only put in the relevant stylings. Of course you could always use a background image that moves/changes but for this demo I just styled the labels to resemble buttons.</p> -<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>&lt;span&gt; -</span><span> &lt;input id=&quot;Radio1&quot; name=&quot;Radios&quot; type=&quot;radio&quot; value=&quot;Option 1&quot;&gt; -</span><span> &lt;label for=&quot;Radio1&quot;&gt;Option 1&lt;/label&gt; -</span><span>&lt;/span&gt; -</span><span>&lt;span&gt; -</span><span> &lt;input id=&quot;Check1&quot; name=&quot;Checks&quot; type=&quot;checkbox&quot; value=&quot;Item 1&quot;&gt; -</span><span> &lt;label for=&quot;Check1&quot;&gt;Item 1&lt;/label&gt; -</span><span>&lt;/span&gt; -</span></code></pre> -<p>The text inside the label will be the text displayed as your button. You must have the ID and FOR attributes on the input and labels. Removing those will break the functionality. As always I have added a little <a href="http://jsfiddle.net/james2doyle/YB5c3/" title="Styling radios and checkboxes jsFiddle demo">jsFiddle demo</a> into the mix.</p> - - - - - Generated content in CSS - 2012-07-27T00:00:00+00:00 - 2012-07-27T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/fiddle/generated-content-in-css/ - - <p>I like <a href="http://jsfiddle.net/" title="jsFiddle">jsFiddle</a>. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my <a href="https://ohdoylerules.com/work/" title="Work">work</a> section. Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the php echos. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:</p> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#65737e;">/* styles for printing */ -</span><span style="color:#b48ead;">@media </span><span>print{ -</span><span> </span><span style="color:#65737e;">/* all a tags with an href attribute */ -</span><span> </span><span style="color:#bf616a;">a</span><span style="color:#8fa1b3;">[</span><span style="color:#d08770;">href</span><span style="color:#8fa1b3;">]:</span><span style="color:#b48ead;">after</span><span>{ -</span><span> </span><span style="color:#65737e;">/* display that href after the value */ -</span><span> content: &quot;</span><span style="color:#a3be8c;"> (</span><span>&quot; </span><span style="color:#96b5b4;">attr</span><span>(</span><span style="color:#d08770;">href</span><span>) &quot;</span><span style="color:#a3be8c;">)</span><span>&quot;; -</span><span> } -</span><span>} -</span></code></pre> -<p>What does this look like?</p> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>Website -</span></code></pre> -<p>Would render as <a href="https://ohdoylerules.com/fiddle/generated-content-in-css/www.example.com/">Website (www.example.com/)</a></p> -<p>So with that in mind, I thought it would be cool to have a button that -would render a count. So in practical applications it might be used for -something like an inbox button. In the case I built it is a -notifications button.</p> -<div class="center"> - <a href="http://jsfiddle.net/james2doyle/LjgzD" target="_blank" title="notifications button"><img src="/images/54368011.png" alt="notifications button"></a> -</div> -<pre data-lang="css" style="background-color:#2b303b;color:#c0c5ce;" class="language-css "><code class="language-css" data-lang="css"><span style="color:#bf616a;">button </span><span>{ -</span><span> color: </span><span style="color:#96b5b4;">#333</span><span>; -</span><span> padding: </span><span style="color:#d08770;">4px 10px</span><span>; -</span><span> border: </span><span style="color:#d08770;">1px </span><span>solid </span><span style="color:#96b5b4;">#aaa</span><span>; -</span><span> outline: none; -</span><span> font-weight: bold; -</span><span> font-size: </span><span style="color:#d08770;">12px</span><span>; -</span><span> font-family: &#39;</span><span style="color:#a3be8c;">Corbin</span><span>&#39;; -</span><span> background: </span><span style="color:#96b5b4;">#eee</span><span>; -</span><span> border-radius: </span><span style="color:#d08770;">3px</span><span>; -</span><span> box-shadow: </span><span style="color:#d08770;">0 2px 0 </span><span style="color:#96b5b4;">rgba</span><span>(</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0.5</span><span>); -</span><span> text-shadow: </span><span style="color:#d08770;">0 1px 0 </span><span style="color:#96b5b4;">rgba</span><span>(</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">0.8</span><span>); -</span><span>} -</span><span> -</span><span style="color:#bf616a;">button</span><span style="color:#8fa1b3;">:</span><span style="color:#b48ead;">after </span><span>{ -</span><span> content: </span><span style="color:#96b5b4;">attr</span><span>(</span><span style="color:#d08770;">data-count</span><span>); -</span><span> border-radius: </span><span style="color:#d08770;">10px</span><span>; -</span><span> display: inline-block; -</span><span> background: </span><span style="color:#96b5b4;">#777</span><span>; -</span><span> padding: </span><span style="color:#d08770;">2px 5px</span><span>; -</span><span> width: </span><span style="color:#d08770;">15px</span><span>; -</span><span> margin: </span><span style="color:#d08770;">0 0 0 8px</span><span>; -</span><span> color: white; -</span><span> font-weight: normal; -</span><span> border: </span><span style="color:#d08770;">1px </span><span>solid </span><span style="color:#96b5b4;">#555</span><span>; -</span><span> box-shadow: inset </span><span style="color:#d08770;">0 1px 2px </span><span style="color:#96b5b4;">rgba</span><span>(</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">255</span><span>,</span><span style="color:#d08770;">0.5</span><span>), -</span><span> inset </span><span style="color:#d08770;">0 -1px 2px </span><span style="color:#96b5b4;">rgba</span><span>(</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0.5</span><span>); -</span><span> text-shadow: </span><span style="color:#d08770;">0 1px 0 </span><span style="color:#96b5b4;">rgba</span><span>(</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0</span><span>,</span><span style="color:#d08770;">0.6</span><span>); -</span><span>}​ -</span></code></pre> -<pre data-lang="html" style="background-color:#2b303b;color:#c0c5ce;" class="language-html "><code class="language-html" data-lang="html"><span>Notifications​ -</span></code></pre> -<p>If you would like to see that bad boy in action check out <a href="http://jsfiddle.net/james2doyle/LjgzD/" title="jsFiddle css content">this -fiddle</a>.</p> - - - - - Installing Android 4.1.1 Jelly Bean for Nexus S - 2012-07-20T00:00:00+00:00 - 2012-07-20T00:00:00+00:00 - - - - - james2doyle@gmail.com (James Doyle) - - - - - - https://ohdoylerules.com/android/jellybean-nexuss/ - - <p>So I have been flashing ROMs for a while now. I recently installed Jelly Bean 4.1. I used the OTA(Over The Air) version from <a href="http://forum.xda-developers.com/showthread.php?t=1784497" title="here">here</a>. Just make sure you also flash the Simple-Root.zip file. I did not. So I had to root my phone again and then install it. Which wasn't too bad but still annoying.</p> -<p>As with flashing any kind of ROM there is always the thought and reality that you could break something. I had a custom ROM on before I switched to a Jelly Bean based ROM. It had the nice notification power controls which was awesome. But Jelly Bean (or at least any of the ROMs so far) do not. So with a bit of searching I found <a href="https://play.google.com/store/apps/details?id=com.painless.pc" title="Power Controls">Power Controls</a>.</p> -<p>It works well and has a lot of customizable features. It is a recommend.</p> - - - - diff --git a/docs/demo/flexbox-demo/index.html b/docs/demo/flexbox-demo/index.html index 53c2ba6..687b6ef 100644 --- a/docs/demo/flexbox-demo/index.html +++ b/docs/demo/flexbox-demo/index.html @@ -1 +1 @@ -Flexbox Demo | OhDoyleRules

Go home

An old flexbox demo I made a while back. It may not be the latest syntax so beware!!

Flexbox Demo

Check out the pen.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Flexbox Demo | OhDoyleRules

Go home

An old flexbox demo I made a while back. It may not be the latest syntax so beware!!

Flexbox Demo

Check out the pen.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/generated-content-title/index.html b/docs/demo/generated-content-title/index.html index 112e581..aee4c41 100644 --- a/docs/demo/generated-content-title/index.html +++ b/docs/demo/generated-content-title/index.html @@ -1,4 +1,4 @@ -Generated Content in CSS | OhDoyleRules

Go home

I like jsFiddle. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my work section.

Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the PHP echos. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:

/*! styles for printing */
+Generated Content in CSS | OhDoyleRules

Go home

I like jsFiddle. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my work section.

Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the PHP echos. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:

/*! styles for printing */
 @media print{
   /*! all a tags with an href attribute */
   a[href]:after{
@@ -34,4 +34,4 @@
       inset 0 -1px 2px rgba(0,0,0,0.5);
   text-shadow: 0 1px 0 rgba(0,0,0,0.6);
 }​
-

If you would like to see that bad boy in action check out this fiddle.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

If you would like to see that bad boy in action check out this fiddle.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/godaddy-digital-ocean/index.html b/docs/demo/godaddy-digital-ocean/index.html index 643bf46..e009564 100644 --- a/docs/demo/godaddy-digital-ocean/index.html +++ b/docs/demo/godaddy-digital-ocean/index.html @@ -1 +1 @@ -GoDaddy Email on Digital Ocean | OhDoyleRules

Go home

I was recently trying to send an email to a domain I had purchased on GoDaddy but had hosting on Digital Ocean.

I sent the email and a couple hours later it bounced. This wasn't good. My email was going to GoDadddy but I want the site hosted on Digital Ocean.

So I had to find how to keep the domain hosted on Digital Ocean but the email needs to stay on GoDaddy servers.

Here are the DNS settings I used:

GoDaddy Digital Ocean DNS Records

Your settings may differ, so please follow these instructions in order to check if your settings are correct:

  • Log into GoDaddy
  • Launch the domain you are looking to check
  • Go to the email tab
  • Hover over tools and click "Server Settings"
  • You will see a popup showing all the settings

There you can see that there are a bunch of records listed. These are the ones for your specific domain.

GoDaddy Default MX and DNS Records

Hopefully this was helpful because it took a long time to figure out! It is even more painful because the records take a while to propogate. Boo!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +GoDaddy Email on Digital Ocean | OhDoyleRules

Go home

I was recently trying to send an email to a domain I had purchased on GoDaddy but had hosting on Digital Ocean.

I sent the email and a couple hours later it bounced. This wasn't good. My email was going to GoDadddy but I want the site hosted on Digital Ocean.

So I had to find how to keep the domain hosted on Digital Ocean but the email needs to stay on GoDaddy servers.

Here are the DNS settings I used:

GoDaddy Digital Ocean DNS Records

Your settings may differ, so please follow these instructions in order to check if your settings are correct:

  • Log into GoDaddy
  • Launch the domain you are looking to check
  • Go to the email tab
  • Hover over tools and click "Server Settings"
  • You will see a popup showing all the settings

There you can see that there are a bunch of records listed. These are the ones for your specific domain.

GoDaddy Default MX and DNS Records

Hopefully this was helpful because it took a long time to figure out! It is even more painful because the records take a while to propogate. Boo!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/index.html b/docs/demo/index.html index a071074..f491fab 100644 --- a/docs/demo/index.html +++ b/docs/demo/index.html @@ -1 +1 @@ -Demo | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file +Demo | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file diff --git a/docs/demo/letterpress-loader-in-css/index.html b/docs/demo/letterpress-loader-in-css/index.html index b738e2d..232212b 100644 --- a/docs/demo/letterpress-loader-in-css/index.html +++ b/docs/demo/letterpress-loader-in-css/index.html @@ -1 +1 @@ -Letterpress loader in CSS | OhDoyleRules

Go home

I recently bought the amazing Letterpress app for iPhone. It truly is gorgeous, and has an amazing UI. It inspired me to make this little doodle.

Letterpress Loader In CSS

This is a little tribute. It's an animated css loader in the style of Letterpress. It's a little different because it uses the custom cubic-bezier curve called 'ease-in-out-back', which is essentially elastic. I got the values from the great site Ceaser.

As always, check out the pen

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Letterpress loader in CSS | OhDoyleRules

Go home

I recently bought the amazing Letterpress app for iPhone. It truly is gorgeous, and has an amazing UI. It inspired me to make this little doodle.

Letterpress Loader In CSS

This is a little tribute. It's an animated css loader in the style of Letterpress. It's a little different because it uses the custom cubic-bezier curve called 'ease-in-out-back', which is essentially elastic. I got the values from the great site Ceaser.

As always, check out the pen

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/mozilla-dev-derby/index.html b/docs/demo/mozilla-dev-derby/index.html index bb35c6c..78297a5 100644 --- a/docs/demo/mozilla-dev-derby/index.html +++ b/docs/demo/mozilla-dev-derby/index.html @@ -1 +1 @@ -Mozilla Dev Derby | OhDoyleRules

Go home

I have just submitted my demo for the Mozzila Dev Derby. If you like it, please vote. Thank you!

Camera Api Demo

It uses the HTML5 File Api and also uses the device orientation API. Firstly, you upload or take a picture, then that picture fades up below in a polaroid-style div. You can then move you device and see the picture move.

I also added something I called dynamic shine. This is just a diagonal white-to-transparent gradient. Tilting the device causes the photo to look like it is interacting with the light.

You can also see the project self-hosted here.

UPDATE:

I got runner up! Here are the results

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Mozilla Dev Derby | OhDoyleRules

Go home

I have just submitted my demo for the Mozzila Dev Derby. If you like it, please vote. Thank you!

Camera Api Demo

It uses the HTML5 File Api and also uses the device orientation API. Firstly, you upload or take a picture, then that picture fades up below in a polaroid-style div. You can then move you device and see the picture move.

I also added something I called dynamic shine. This is just a diagonal white-to-transparent gradient. Tilting the device causes the photo to look like it is interacting with the light.

You can also see the project self-hosted here.

UPDATE:

I got runner up! Here are the results

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/no-javascript-accordion/index.html b/docs/demo/no-javascript-accordion/index.html index 8d0839b..9f8ad9d 100644 --- a/docs/demo/no-javascript-accordion/index.html +++ b/docs/demo/no-javascript-accordion/index.html @@ -1 +1 @@ -No Javascript CSS Accordion | OhDoyleRules

Go home

I made this today. It's a little CSS-only accordion. It uses the :target selector. The :target selector is rather new. It also has some quirks. It is almost like an onclick without the javascript.

no-js accordion

The thing I find is that the name is being passed through the URL when using this method. This is nice when you want to refresh the page and keep the same item active. But that is also a downside because maybe when you refresh you want everything to reset. In that case, you would need to delete the variable/id out of the URL and then refresh.

But just check it out already!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +No Javascript CSS Accordion | OhDoyleRules

Go home

I made this today. It's a little CSS-only accordion. It uses the :target selector. The :target selector is rather new. It also has some quirks. It is almost like an onclick without the javascript.

no-js accordion

The thing I find is that the name is being passed through the URL when using this method. This is nice when you want to refresh the page and keep the same item active. But that is also a downside because maybe when you refresh you want everything to reset. In that case, you would need to delete the variable/id out of the URL and then refresh.

But just check it out already!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/demo/socketio-works-howto/index.html b/docs/demo/socketio-works-howto/index.html index fbff411..0060baf 100644 --- a/docs/demo/socketio-works-howto/index.html +++ b/docs/demo/socketio-works-howto/index.html @@ -1 +1 @@ -Making a socket.io app and how socket.io works | OhDoyleRules

Go home

I made my first video about web development. It is a quick video about socket.io and how it works with node and such.

Socket.io demo app

It also includes a little demo of a tiny app I built. I may release the source later on depending on the videos popularity.

Making a socket.io app

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Making a socket.io app and how socket.io works | OhDoyleRules

Go home

I made my first video about web development. It is a quick video about socket.io and how it works with node and such.

Socket.io demo app

It also includes a little demo of a tiny app I built. I may release the source later on depending on the videos popularity.

Making a socket.io app

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/fiddle/css-date-card/index.html b/docs/fiddle/css-date-card/index.html index 6589c1d..a16089b 100644 --- a/docs/fiddle/css-date-card/index.html +++ b/docs/fiddle/css-date-card/index.html @@ -1 +1 @@ -CSS Date Card | OhDoyleRules

Go home

I am trying to make up for not posting in the last few days. Here is another thing I have been working on. It is a replication of another dribbble post.

css-event-card

There are a couple of things missing but most of them would be replicated with a webfont. There was also the issue of not having a choice in the anti-aliasing of the font. The image was made using photoshop and probably used crisp or smooth which makes the font look real nice. I don't even know if I got the font right. I used Open Sans. It looked close enough so whatever. I've been working on this on and off for about 2 weeks so it is time to give it away!

Click the image for a dabblet. BTW, I always misspell dabblet because of the triple B in dribbble!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +CSS Date Card | OhDoyleRules

Go home

I am trying to make up for not posting in the last few days. Here is another thing I have been working on. It is a replication of another dribbble post.

css-event-card

There are a couple of things missing but most of them would be replicated with a webfont. There was also the issue of not having a choice in the anti-aliasing of the font. The image was made using photoshop and probably used crisp or smooth which makes the font look real nice. I don't even know if I got the font right. I used Open Sans. It looked close enough so whatever. I've been working on this on and off for about 2 weeks so it is time to give it away!

Click the image for a dabblet. BTW, I always misspell dabblet because of the triple B in dribbble!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/fiddle/css3-pagebend/index.html b/docs/fiddle/css3-pagebend/index.html index e95f9b7..1775ccf 100644 --- a/docs/fiddle/css3-pagebend/index.html +++ b/docs/fiddle/css3-pagebend/index.html @@ -1 +1 @@ -CSS3 Pagebend | OhDoyleRules

Go home

I crafted this lil' beauty in class today. It turns out that it is really hard to make transforming elements intersect. At least, I couldn't figure it out.

css3-pagebend

I ended up having to essentially split the image in 2 and then rotateY one of the sides. It didn't look quite right when it was bending at the middle, so I made the split like 60-40. As always, click the image for the live action.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +CSS3 Pagebend | OhDoyleRules

Go home

I crafted this lil' beauty in class today. It turns out that it is really hard to make transforming elements intersect. At least, I couldn't figure it out.

css3-pagebend

I ended up having to essentially split the image in 2 and then rotateY one of the sides. It didn't look quite right when it was bending at the middle, so I made the split like 60-40. As always, click the image for the live action.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/fiddle/generated-content-in-css/index.html b/docs/fiddle/generated-content-in-css/index.html index 814557b..46e46ac 100644 --- a/docs/fiddle/generated-content-in-css/index.html +++ b/docs/fiddle/generated-content-in-css/index.html @@ -1,4 +1,4 @@ -Generated content in CSS | OhDoyleRules

Go home

I like jsFiddle. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my work section. Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the php echos. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:

/* styles for printing */
+Generated content in CSS | OhDoyleRules

Go home

I like jsFiddle. I often use it for prototyping. I might want to see what I can make in css or maybe I want to build a little template. A perfect example, I used it to mockup my work section. Since it is just a repeating template, I built the classes and styles in jsFiddle and then just dropped in the php echos. Anyway, here is something I made. It uses generated content. You can use HTML attributes in CSS. This is a classic example:

/* styles for printing */
 @media print{
     /* all a tags with an href attribute */
     a[href]:after{
@@ -37,4 +37,4 @@
     text-shadow: 0 1px 0 rgba(0,0,0,0.6);
 }​
 
Notifications​
-

If you would like to see that bad boy in action check out this fiddle.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

If you would like to see that bad boy in action check out this fiddle.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/fiddle/index.html b/docs/fiddle/index.html index 80dc6f5..bf0ac65 100644 --- a/docs/fiddle/index.html +++ b/docs/fiddle/index.html @@ -1 +1 @@ -Fiddle | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

CSS Date Card

Create a Date Card using CSS transitions, animations, and gradients

Read More

\ No newline at end of file +Fiddle | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

CSS Date Card

Create a Date Card using CSS transitions, animations, and gradients

Read More

\ No newline at end of file diff --git a/docs/fiddle/no-js-image-preview/index.html b/docs/fiddle/no-js-image-preview/index.html index 74ca3b8..dfce428 100644 --- a/docs/fiddle/no-js-image-preview/index.html +++ b/docs/fiddle/no-js-image-preview/index.html @@ -1 +1 @@ -No-Js Image Preview | OhDoyleRules

Go home

Using CSS transforms, I was able to build a little image previewer. There is only one image for both the thumbnail and full size view. Scale does wonders.

No Js Image Preview · CodePen

I created this guy in Codepen.io. It seems to be a lot better than just showing all the html/css/js in a post. I would appreciate any comments or possible improvements. I might try to build some sort of template/plugin so that anyone can just plop in their images and BAM it works nicely.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +No-Js Image Preview | OhDoyleRules

Go home

Using CSS transforms, I was able to build a little image previewer. There is only one image for both the thumbnail and full size view. Scale does wonders.

No Js Image Preview · CodePen

I created this guy in Codepen.io. It seems to be a lot better than just showing all the html/css/js in a post. I would appreciate any comments or possible improvements. I might try to build some sort of template/plugin so that anyone can just plop in their images and BAM it works nicely.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/index.html b/docs/index.html index b846709..690723f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1 +1 @@ -James Doyle | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

Vue Omnibar Component

A Vue component that is used to create modal popups that emulate omnibar, command palette, open anywhere, or other search functions/features

Read More

Using slots in Vue js

If you are working with server-rendered apps, using Vue slots can help you create more reusable and flexible components

Read More

SQL As An API

An example of how to use SQL as an API instead of reaching for something like GraphQL

Read More

Nuxt Firebase Starter

An example project that uses nuxt.js and Firebase for simple auth (social or email/pass) and account profiles

Read More

Backup MySQL And Email It

Don't bother paying for a SaaS that creates MySQL backups and emails them to you on a schedule, you can do this with CRON and a small script

Read More

Phalcon Micro App Starter

A showcase of a Phalcon Micro App starter template that makes it easy to get started with the Phalcon micro app class

Read More

Lico

Lico is a re-creation of the PicoCMS for PHP, but written for Luvit for Lua

Read More

Typeform Vector Logo

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

Read More

Tips For Using SVGs

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Read More

Vim Vector Logo

The Vim logo in a vector SVG format. Also includes a ICNS file for OSX.

Read More

The $100 Website

I wrote a post on WARPAINT Media about people who ask about getting a website for $100.

Read More

rework-math

I created a plugin for rework that allows you to do simple math

Read More

Custom Google Forms

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something

Read More

April 2013 Redesign!

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively.

Read More

CSS Date Card

Create a Date Card using CSS transitions, animations, and gradients

Read More

\ No newline at end of file +James Doyle | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

Vue Omnibar Component

A Vue component that is used to create modal popups that emulate omnibar, command palette, open anywhere, or other search functions/features

Read More

Using slots in Vue js

If you are working with server-rendered apps, using Vue slots can help you create more reusable and flexible components

Read More

SQL As An API

An example of how to use SQL as an API instead of reaching for something like GraphQL

Read More

Nuxt Firebase Starter

An example project that uses nuxt.js and Firebase for simple auth (social or email/pass) and account profiles

Read More

Backup MySQL And Email It

Don't bother paying for a SaaS that creates MySQL backups and emails them to you on a schedule, you can do this with CRON and a small script

Read More

Phalcon Micro App Starter

A showcase of a Phalcon Micro App starter template that makes it easy to get started with the Phalcon micro app class

Read More

Lico

Lico is a re-creation of the PicoCMS for PHP, but written for Luvit for Lua

Read More

Typeform Vector Logo

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

Read More

Tips For Using SVGs

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Read More

Vim Vector Logo

The Vim logo in a vector SVG format. Also includes a ICNS file for OSX.

Read More

The $100 Website

I wrote a post on WARPAINT Media about people who ask about getting a website for $100.

Read More

rework-math

I created a plugin for rework that allows you to do simple math

Read More

Custom Google Forms

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something

Read More

April 2013 Redesign!

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively.

Read More

CSS Date Card

Create a Date Card using CSS transitions, animations, and gradients

Read More

\ No newline at end of file diff --git a/docs/personal-project/april-2013-redesign/index.html b/docs/personal-project/april-2013-redesign/index.html index 644bffe..6972f7f 100644 --- a/docs/personal-project/april-2013-redesign/index.html +++ b/docs/personal-project/april-2013-redesign/index.html @@ -1,4 +1,4 @@ -April 2013 Redesign! | OhDoyleRules

Go home

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively. Although I only have 2 images for the entire site, the logo and the mobile nav hamburger/menu button. I think the best part is the new code highlighter. It has some cool features.

// here is some javascript
+April 2013 Redesign! | OhDoyleRules

Go home

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively. Although I only have 2 images for the entire site, the logo and the mobile nav hamburger/menu button. I think the best part is the new code highlighter. It has some cool features.

// here is some javascript
 var item = document.getElementById('#item');
 item.style.background = 'red';
 item.setAttribute('data-index', 1);
diff --git a/docs/personal-project/assemble-starter/index.html b/docs/personal-project/assemble-starter/index.html
index 54f0e94..870ba03 100644
--- a/docs/personal-project/assemble-starter/index.html
+++ b/docs/personal-project/assemble-starter/index.html
@@ -1 +1 @@
-Assemble Starter | OhDoyleRules

Go home

Assemble starter is my starting point for any of my static assemble sites.

You can find the project on Github.

I often use assemble when I am building themes. The reason being you can do quick templating (thanks to Handlebars), it compiles fast, requires no server, and I am using Grunt anyway.

Extra grunt tasks:

  • grunt-contrib-watch -- live reload and compiles on save
  • grunt-sass -- C lib SASS action
  • grunt-contrib-concat -- combine things
  • grunt-autoprefixer -- prefix that ugliness

Modernizr Checks:

  • cssanimations
  • csstransforms
  • csstransforms3d
  • svg
  • touch
  • shiv
  • cssclasses
  • teststyles
  • testprop
  • testallprops
  • prefixes
  • domprefixes
  • css_filters

Other Libs:

Javascript Goodies:

  • prefix() -- detect the js/css prefixes for different browsers
  • xhr() -- function for no-jQuery AJAX

CSS Goodies:

  • normalize.scss

You can find the project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Assemble Starter | OhDoyleRules

Go home

Assemble starter is my starting point for any of my static assemble sites.

You can find the project on Github.

I often use assemble when I am building themes. The reason being you can do quick templating (thanks to Handlebars), it compiles fast, requires no server, and I am using Grunt anyway.

Extra grunt tasks:

  • grunt-contrib-watch -- live reload and compiles on save
  • grunt-sass -- C lib SASS action
  • grunt-contrib-concat -- combine things
  • grunt-autoprefixer -- prefix that ugliness

Modernizr Checks:

  • cssanimations
  • csstransforms
  • csstransforms3d
  • svg
  • touch
  • shiv
  • cssclasses
  • teststyles
  • testprop
  • testallprops
  • prefixes
  • domprefixes
  • css_filters

Other Libs:

Javascript Goodies:

  • prefix() -- detect the js/css prefixes for different browsers
  • xhr() -- function for no-jQuery AJAX

CSS Goodies:

  • normalize.scss

You can find the project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/atom-monokai-dark/index.html b/docs/personal-project/atom-monokai-dark/index.html index 001f01f..643d90a 100644 --- a/docs/personal-project/atom-monokai-dark/index.html +++ b/docs/personal-project/atom-monokai-dark/index.html @@ -1,4 +1,4 @@ -Atom Monokai Dark | OhDoyleRules

Go home

I made a dark monokai syntax theme for Atom.

Originally converted from monokai which in turn came from the TextMate theme using the TextMate bundle converter.

atom monokai dark screenshot

I would also suggest editing your main stylesheet and adding the following CSS:

/* really nice smooth fonts */
+Atom Monokai Dark | OhDoyleRules

Go home

I made a dark monokai syntax theme for Atom.

Originally converted from monokai which in turn came from the TextMate theme using the TextMate bundle converter.

atom monokai dark screenshot

I would also suggest editing your main stylesheet and adding the following CSS:

/* really nice smooth fonts */
 body {
   -webkit-font-smoothing: antialiased;
   text-rendering: optimizeLegibility;
@@ -28,4 +28,4 @@
     overflow: auto;
   }
 }
-

This adds some nicer smoothing and also adds some custom scrollbars to both panes. This gets rid of the ugly strange white ones. I would also suggest checking out Source Code Pro for your font!

You can download the theme on Atom.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

This adds some nicer smoothing and also adds some custom scrollbars to both panes. This gets rid of the ugly strange white ones. I would also suggest checking out Source Code Pro for your font!

You can download the theme on Atom.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/canadian-provinces-field/index.html b/docs/personal-project/canadian-provinces-field/index.html index 7ddbdc2..e715838 100644 --- a/docs/personal-project/canadian-provinces-field/index.html +++ b/docs/personal-project/canadian-provinces-field/index.html @@ -1 +1 @@ -Canadian Provinces Field | OhDoyleRules

Go home

Created another PyroCMS field. This one is for Canadian Provinces and Territories. Here is the github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Canadian Provinces Field | OhDoyleRules

Go home

Created another PyroCMS field. This one is for Canadian Provinces and Territories. Here is the github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/chrome-reverse-geocode/index.html b/docs/personal-project/chrome-reverse-geocode/index.html index 3eb9453..86f980d 100644 --- a/docs/personal-project/chrome-reverse-geocode/index.html +++ b/docs/personal-project/chrome-reverse-geocode/index.html @@ -1 +1 @@ -Chrome Reverse Geocode App | OhDoyleRules

Go home

I have created a new app (with the help of Beatrice Law) called Reverse Geocode.

In the process of building the site for Textbooks For Change, a WARPAINT Media client, I realized I needed an easy way for them to reverse geocode an address for their map listings.

The site is built on PhileCMS so it is very fast, but requires a little more savvy-ness than normal. I added the static Google Map that is based on the list of coordinates they enter in.

chrome reverse geocode app screenshot

It is very easy to update, but not quite that easy to get the coordinates. There is a Google tool to do this, but it is actually not as nice (but I have an obvious bias).

The app allows you to enter in an address, then they app goes and gets the latitude and longitude. Then, because this is a Chrome Packaged App, you can copy it right to your clipboard with a single click!

You can download the app on the Chrome Web Store.

The app is also on github so anyone can submit some changes and improvements.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Chrome Reverse Geocode App | OhDoyleRules

Go home

I have created a new app (with the help of Beatrice Law) called Reverse Geocode.

In the process of building the site for Textbooks For Change, a WARPAINT Media client, I realized I needed an easy way for them to reverse geocode an address for their map listings.

The site is built on PhileCMS so it is very fast, but requires a little more savvy-ness than normal. I added the static Google Map that is based on the list of coordinates they enter in.

chrome reverse geocode app screenshot

It is very easy to update, but not quite that easy to get the coordinates. There is a Google tool to do this, but it is actually not as nice (but I have an obvious bias).

The app allows you to enter in an address, then they app goes and gets the latitude and longitude. Then, because this is a Chrome Packaged App, you can copy it right to your clipboard with a single click!

You can download the app on the Chrome Web Store.

The app is also on github so anyone can submit some changes and improvements.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/clean-css-in-office-hours/index.html b/docs/personal-project/clean-css-in-office-hours/index.html index 6886117..a3819ed 100644 --- a/docs/personal-project/clean-css-in-office-hours/index.html +++ b/docs/personal-project/clean-css-in-office-hours/index.html @@ -1 +1 @@ -Clean CSS in Chrome Apps Office Hours | OhDoyleRules

Go home

Clean CSS made it into a Chrome Apps Office hours!!

So my chrome app Clean CSS made it onto one of the Chrome Apps Office Hours.

The Office Hours is a series of videos by Google where developers talk to the community about Google technology, software and programming. The way I got on was I saw Paul Kinlan, a Google Chrome developer advocate, make a post about the live office hours being held that day.

I managed to get a link to my app and he then reviewed it on the show!

The app is also on github so anyone can submit some changes and improvements. You can download the app on the Chrome Web Store

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Clean CSS in Chrome Apps Office Hours | OhDoyleRules

Go home

Clean CSS made it into a Chrome Apps Office hours!!

So my chrome app Clean CSS made it onto one of the Chrome Apps Office Hours.

The Office Hours is a series of videos by Google where developers talk to the community about Google technology, software and programming. The way I got on was I saw Paul Kinlan, a Google Chrome developer advocate, make a post about the live office hours being held that day.

I managed to get a link to my app and he then reviewed it on the show!

The app is also on github so anyone can submit some changes and improvements. You can download the app on the Chrome Web Store

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/clean-css-updated/index.html b/docs/personal-project/clean-css-updated/index.html index 9471094..cb16e34 100644 --- a/docs/personal-project/clean-css-updated/index.html +++ b/docs/personal-project/clean-css-updated/index.html @@ -1 +1 @@ -Clean CSS Updated | OhDoyleRules

Go home

I updated my Chrome packaged app, Clean CSS. Here is the website.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Clean CSS Updated | OhDoyleRules

Go home

I updated my Chrome packaged app, Clean CSS. Here is the website.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/docracy-logo-svg/index.html b/docs/personal-project/docracy-logo-svg/index.html index bdcf92a..15a6b69 100644 --- a/docs/personal-project/docracy-logo-svg/index.html +++ b/docs/personal-project/docracy-logo-svg/index.html @@ -1 +1 @@ -Docracy SVG logo | OhDoyleRules

Go home

The other founder of my company (WARPAINT Media) was working on a blog post where we list all the PAAS/SAAS tools that we use. One of them is Docracy. It is a site that shares free legal documents. You can modify and fork them to your own account, they have signing features as well.

Well again, I was unable to find the vector logo of their icon. So in my typical fashion, I recreated it. You can see it below.

docracy svg vector

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Docracy SVG logo | OhDoyleRules

Go home

The other founder of my company (WARPAINT Media) was working on a blog post where we list all the PAAS/SAAS tools that we use. One of them is Docracy. It is a site that shares free legal documents. You can modify and fork them to your own account, they have signing features as well.

Well again, I was unable to find the vector logo of their icon. So in my typical fashion, I recreated it. You can see it below.

docracy svg vector

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/git-website-workflow/index.html b/docs/personal-project/git-website-workflow/index.html index 29d7c8c..6a311d7 100644 --- a/docs/personal-project/git-website-workflow/index.html +++ b/docs/personal-project/git-website-workflow/index.html @@ -1 +1 @@ -Git Website Workflow | OhDoyleRules

Go home

Amazon Web Services Logo

I recently switched to using Amazon Web Services for my hosting. It is awesome. Because it(EC2) is just a cloud computer, I can install anything I want and set up any workflow I desire. I recently found an article that was about a website git workflow.

I finally got the flow down, but it was taking a little too long to start a project. So I decided to make it into a script.This works with any cloud computer workflow. It doesn't have to be just Amazon.

With a little help from some people on google+ I finally got it down. Check out the original article on how itworks and the philosophy of why.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Git Website Workflow | OhDoyleRules

Go home

Amazon Web Services Logo

I recently switched to using Amazon Web Services for my hosting. It is awesome. Because it(EC2) is just a cloud computer, I can install anything I want and set up any workflow I desire. I recently found an article that was about a website git workflow.

I finally got the flow down, but it was taking a little too long to start a project. So I decided to make it into a script.This works with any cloud computer workflow. It doesn't have to be just Amazon.

With a little help from some people on google+ I finally got it down. Check out the original article on how itworks and the philosophy of why.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/github-wiki-to-html/index.html b/docs/personal-project/github-wiki-to-html/index.html index 4601df3..e9f879b 100644 --- a/docs/personal-project/github-wiki-to-html/index.html +++ b/docs/personal-project/github-wiki-to-html/index.html @@ -1,4 +1,4 @@ -Github Wiki To HTML | OhDoyleRules

Go home

Have you ever wanted to convert a Github wiki to a set of HTML pages? This can be an easy way to generate new gh-pages (github web pages) based on the projects Wiki.

As of August 2010, you can actually clone a repositories wiki to your local machine just by adding .wiki at the end.

This pulls down all the wiki pages in their current format, by default this is .md files.

Now what can you do with these files? Well how about converting them to HTML so that you can use them in your gh-pages repo?

After cloning the .wiki repo to your local, you can create a script to convert all the files to HTML.

  • first install marked globally via NPM
  • make a directory called html in the root of the repo
  • create a file called convert.sh
  • run chmod +x convert.sh on that file to allow execution
  • paste the following into the file:
#!/bin/bash
+Github Wiki To HTML | OhDoyleRules

Go home

Have you ever wanted to convert a Github wiki to a set of HTML pages? This can be an easy way to generate new gh-pages (github web pages) based on the projects Wiki.

As of August 2010, you can actually clone a repositories wiki to your local machine just by adding .wiki at the end.

This pulls down all the wiki pages in their current format, by default this is .md files.

Now what can you do with these files? Well how about converting them to HTML so that you can use them in your gh-pages repo?

After cloning the .wiki repo to your local, you can create a script to convert all the files to HTML.

  • first install marked globally via NPM
  • make a directory called html in the root of the repo
  • create a file called convert.sh
  • run chmod +x convert.sh on that file to allow execution
  • paste the following into the file:
#!/bin/bash
 
 # for each md file in the directory
 for file in *.md
@@ -7,4 +7,4 @@
     # --gfm == use github flavoured markdown
     marked -o html/$file.html $file --gfm
 done
-

Now if you look in the html directory, you will see all the markdown files have been converted and are in that folder.

In the next week or so, I will write a new post about how to use this method and the grunt assemble plugin to make simple pages.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Now if you look in the html directory, you will see all the markdown files have been converted and are in that folder.

In the next week or so, I will write a new post about how to use this method and the grunt assemble plugin to make simple pages.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/grunt-highlight/index.html b/docs/personal-project/grunt-highlight/index.html index 1534e07..a4bb248 100644 --- a/docs/personal-project/grunt-highlight/index.html +++ b/docs/personal-project/grunt-highlight/index.html @@ -1,4 +1,4 @@ -Grunt Highlight Plugin | OhDoyleRules

Go home

Over the weekend, in a couple hours, I wrote this grunt plugin for Highlight.js. I know that marked does an excellent job of parsing markdown, and can also use highlight, but I wanted something I could use in assemble for HTML parsing or full css/js files.

This was made much easier thanks to the yeoman-gruntplugin project.

Getting Started

This plugin requires Grunt.

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-highlight --save-dev
+Grunt Highlight Plugin | OhDoyleRules

Go home

Over the weekend, in a couple hours, I wrote this grunt plugin for Highlight.js. I know that marked does an excellent job of parsing markdown, and can also use highlight, but I wanted something I could use in assemble for HTML parsing or full css/js files.

This was made much easier thanks to the yeoman-gruntplugin project.

Getting Started

This plugin requires Grunt.

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-highlight --save-dev
 

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-highlight');
 

The "highlight" task

Overview

In your project's Gruntfile, add a section named highlight to the data object passed into grunt.initConfig().

grunt.initConfig({
   highlight: {
@@ -57,4 +57,4 @@
     }
   }
 });
-

Check out the project on github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Check out the project on github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/grunt-sundown/index.html b/docs/personal-project/grunt-sundown/index.html index 7d63886..f00e794 100644 --- a/docs/personal-project/grunt-sundown/index.html +++ b/docs/personal-project/grunt-sundown/index.html @@ -1,6 +1,6 @@ -Grunt Sundown | OhDoyleRules

Go home

grunt-sundown is a wrapper for robotskirt(Sundown) - a C implementation of Markdown

Getting Started

This plugin requires Grunt ~0.4.1

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-sundown --save-dev
+Grunt Sundown | OhDoyleRules

Go home

grunt-sundown is a wrapper for robotskirt(Sundown) - a C implementation of Markdown

Getting Started

This plugin requires Grunt ~0.4.1

If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:

npm install grunt-sundown --save-dev
 

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-sundown');
-

You can find the project on Github.

The "sundown" task

Overview

In your project's Gruntfile, add a section named sundown to the data object passed into grunt.initConfig().

grunt.initConfig({
+

You can find the project on Github.

The "sundown" task

Overview

In your project's Gruntfile, add a section named sundown to the data object passed into grunt.initConfig().

grunt.initConfig({
   sundown: {
     target: {
       options: {
@@ -41,4 +41,4 @@
   },
   separator: '\n\n' // concat option for multiple files
 }
-

More Information

You can try your luck on the Sundown homepage. Or check out some of the other wrappers.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

More Information

You can try your luck on the Sundown homepage. Or check out some of the other wrappers.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/index.html b/docs/personal-project/index.html index 3643ad1..b612526 100644 --- a/docs/personal-project/index.html +++ b/docs/personal-project/index.html @@ -1 +1 @@ -Personal Project | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

April 2013 Redesign!

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively.

Read More

Lico

Lico is a re-creation of the PicoCMS for PHP, but written for Luvit for Lua

Read More

rework-math

I created a plugin for rework that allows you to do simple math

Read More

Phalcon Micro App Starter

A showcase of a Phalcon Micro App starter template that makes it easy to get started with the Phalcon micro app class

Read More

Vim Vector Logo

The Vim logo in a vector SVG format. Also includes a ICNS file for OSX.

Read More

\ No newline at end of file +Personal Project | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

April 2013 Redesign!

Another redesign. This one is completely by me, with a little help from the html5blank Wordpress template. I am using SVGs exclusively.

Read More

Lico

Lico is a re-creation of the PicoCMS for PHP, but written for Luvit for Lua

Read More

Phalcon Micro App Starter

A showcase of a Phalcon Micro App starter template that makes it easy to get started with the Phalcon micro app class

Read More

rework-math

I created a plugin for rework that allows you to do simple math

Read More

Vim Vector Logo

The Vim logo in a vector SVG format. Also includes a ICNS file for OSX.

Read More

\ No newline at end of file diff --git a/docs/personal-project/jquery-doodal-js/index.html b/docs/personal-project/jquery-doodal-js/index.html index 49dd558..d8a2589 100644 --- a/docs/personal-project/jquery-doodal-js/index.html +++ b/docs/personal-project/jquery-doodal-js/index.html @@ -1,4 +1,4 @@ -jQuery-doodal-js | OhDoyleRules

Go home

jQuery.doodal.js is a very simplistic modal plugin for jQuery. It has custom events, allows stacking, and is powered by CSS transitions

See the demo

Usage

Instatiate a new doodal.

$('.doodal').doodal({
+jQuery-doodal-js | OhDoyleRules

Go home

jQuery.doodal.js is a very simplistic modal plugin for jQuery. It has custom events, allows stacking, and is powered by CSS transitions

See the demo

Usage

Instatiate a new doodal.

$('.doodal').doodal({
   type: 'modal',
   closeclass: '.doodal-close',
   trueclass: '.doodal-true',
@@ -6,4 +6,4 @@
   showclass: 'showing'
 });
 

Those are all the default options so in this specific example I am not actually overwriting anything.

Now trigger an open to see it:

$('#doodal1').trigger('open');
-

Custom Events

  • open: - when the modal starts to open
  • afteropen: - after the animation is over and it is open
  • ontrue: - for confirms yes button
  • onfalse: - for confirms no button
  • close: - when the close is clicked
  • afterclose: - after the animation is over and it is hidden

You can also view the project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Custom Events

  • open: - when the modal starts to open
  • afteropen: - after the animation is over and it is open
  • ontrue: - for confirms yes button
  • onfalse: - for confirms no button
  • close: - when the close is clicked
  • afterclose: - after the animation is over and it is hidden

You can also view the project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/koding-interview/index.html b/docs/personal-project/koding-interview/index.html index 055774e..9b5de3a 100644 --- a/docs/personal-project/koding-interview/index.html +++ b/docs/personal-project/koding-interview/index.html @@ -1 +1 @@ -Koding Interview | OhDoyleRules

Go home

A few months back I was interviewed by the developer socila network Koding.

The initiative was for them to showcase some of the people using their network to build their skill and circle of friends. It is a really cool social network that has been gaining a lot of traction lately.

Here is the link to the interview.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Koding Interview | OhDoyleRules

Go home

A few months back I was interviewed by the developer socila network Koding.

The initiative was for them to showcase some of the people using their network to build their skill and circle of friends. It is a really cool social network that has been gaining a lot of traction lately.

Here is the link to the interview.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/kube-in-stylrework/index.html b/docs/personal-project/kube-in-stylrework/index.html index 8870fc0..116e28b 100644 --- a/docs/personal-project/kube-in-stylrework/index.html +++ b/docs/personal-project/kube-in-stylrework/index.html @@ -1 +1 @@ -Kube in Styl/Rework | OhDoyleRules

Go home

I finally finished the conversion of the Kube CSS Framework.

The original is written in LESS and I have rewrote it to work with Styl and Rework.

Here is the github repo

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Kube in Styl/Rework | OhDoyleRules

Go home

I finally finished the conversion of the Kube CSS Framework.

The original is written in LESS and I have rewrote it to work with Styl and Rework.

Here is the github repo

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/kube-node-express/index.html b/docs/personal-project/kube-node-express/index.html index 7c9e871..5644f9d 100644 --- a/docs/personal-project/kube-node-express/index.html +++ b/docs/personal-project/kube-node-express/index.html @@ -1 +1 @@ -Kube-Node-Express | OhDoyleRules

Go home

I have released my first node.js-based public repo! It is called Kube-Node-Express.

Kube CSS Framework

This is based off of the Kube CSS Framework by Imperavi. It uses the express framework for node. This was inspired by a similar project I saw that was based on the HTML5 Boilerplate. I will probably end up stealing more ideas from that project too.

disclaimer

I did not create any of these frameworks, I just combined them all together.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Kube-Node-Express | OhDoyleRules

Go home

I have released my first node.js-based public repo! It is called Kube-Node-Express.

Kube CSS Framework

This is based off of the Kube CSS Framework by Imperavi. It uses the express framework for node. This was inspired by a similar project I saw that was based on the HTML5 Boilerplate. I will probably end up stealing more ideas from that project too.

disclaimer

I did not create any of these frameworks, I just combined them all together.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/lico-luvit-cms/index.html b/docs/personal-project/lico-luvit-cms/index.html index e76cd3d..0b85572 100644 --- a/docs/personal-project/lico-luvit-cms/index.html +++ b/docs/personal-project/lico-luvit-cms/index.html @@ -1 +1 @@ -Lico | OhDoyleRules

Go home

I have re-created Pico (github or homepage) using the awesome Luvit framework. Luvit is a LuaJIT wrapper for libuv, of node.js fame. It is called Lico!

What is Lico?

Pico says, "Pico is a stupidly simple, blazing fast, flat file CMS". Lico aims for the same thing. There is a very close parity with Pico even though this is very early.

I used the static server from the Luvit examples as a base and went from there.

How to create content?

You can understand the basics by looking at the included content directory and just running the server.lua file and hitting the index page.

If you need more information you can see the Pico docs and understand what is happening and how it works.

What features are implemented?

  • Markdown Parsing using luvit-markdown -- looking to switch to Hoedown
  • HTML Templating (using my own modified version of SLT2
  • Flexible Meta schema (Uses HTML comments instead of PHP style)

Whats missing?

Plugins. Although with the native of the Event Emitter inside Luvit, this should be rather easy to re-create.

You can use SLTLuv to add new functions and features to your templates. You can see the modules/slt-extensions.lua on how to add extensions to the templates. I also added in some examples in the default/themes/index.html, if you want to see how they work.

Check out the slt2 examples to see how to write proper templates.

The markdown engine is rather simple. There is no fenced code blocks, and sometimes it will wrap uncommon HTML tags with <p> tags (I tried using a figure element and it was wrapped in p tags). I want switch to Hoedown soon.

Performance

Well, this is very interesting. Running the default setup for Pico and Lico, reveals Lico is twice as fast in at the browser level.

Using the Chrome Devtools Network Panel, I measured the index page of each system. I consistently got around 120ms for each request. For Pico, the results were varied quite a bit. They ranged from 200ms to as high as 500ms, but never going under 200ms.

There are a lot of factors here, but the default Pico has 3 pages and my Lico testing suite (same one as this repo) had 6 pages.

I did some other testing against my other project, PhileCMS. You can see that Pico doesn't handle large amounts of pages very well.

There are issues!

Yeah I bet. I am not a Lua developer. I made this over a week-long period trying to learn Lua. If you notice some funky stuff or clean n00b issues, please create issues or pull requests.

Repo?

Here is the link to the Github repository.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Lico | OhDoyleRules

Go home

I have re-created Pico (github or homepage) using the awesome Luvit framework. Luvit is a LuaJIT wrapper for libuv, of node.js fame. It is called Lico!

What is Lico?

Pico says, "Pico is a stupidly simple, blazing fast, flat file CMS". Lico aims for the same thing. There is a very close parity with Pico even though this is very early.

I used the static server from the Luvit examples as a base and went from there.

How to create content?

You can understand the basics by looking at the included content directory and just running the server.lua file and hitting the index page.

If you need more information you can see the Pico docs and understand what is happening and how it works.

What features are implemented?

  • Markdown Parsing using luvit-markdown -- looking to switch to Hoedown
  • HTML Templating (using my own modified version of SLT2
  • Flexible Meta schema (Uses HTML comments instead of PHP style)

Whats missing?

Plugins. Although with the native of the Event Emitter inside Luvit, this should be rather easy to re-create.

You can use SLTLuv to add new functions and features to your templates. You can see the modules/slt-extensions.lua on how to add extensions to the templates. I also added in some examples in the default/themes/index.html, if you want to see how they work.

Check out the slt2 examples to see how to write proper templates.

The markdown engine is rather simple. There is no fenced code blocks, and sometimes it will wrap uncommon HTML tags with <p> tags (I tried using a figure element and it was wrapped in p tags). I want switch to Hoedown soon.

Performance

Well, this is very interesting. Running the default setup for Pico and Lico, reveals Lico is twice as fast in at the browser level.

Using the Chrome Devtools Network Panel, I measured the index page of each system. I consistently got around 120ms for each request. For Pico, the results were varied quite a bit. They ranged from 200ms to as high as 500ms, but never going under 200ms.

There are a lot of factors here, but the default Pico has 3 pages and my Lico testing suite (same one as this repo) had 6 pages.

I did some other testing against my other project, PhileCMS. You can see that Pico doesn't handle large amounts of pages very well.

There are issues!

Yeah I bet. I am not a Lua developer. I made this over a week-long period trying to learn Lua. If you notice some funky stuff or clean n00b issues, please create issues or pull requests.

Repo?

Here is the link to the Github repository.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/minimal-raspberry-pi-os/index.html b/docs/personal-project/minimal-raspberry-pi-os/index.html index b3ee644..ae74e51 100644 --- a/docs/personal-project/minimal-raspberry-pi-os/index.html +++ b/docs/personal-project/minimal-raspberry-pi-os/index.html @@ -1,5 +1,5 @@ -Minimal Raspberry Pi OS | OhDoyleRules

Go home

Introduction

I have had a Raspberry Pi B+ for a while now, and I was looking to setup a very minimal Linux OS. I am sure this would work fine with the Pi 2.

Although the other Raspberry Pi OSs are great, a lot of them are too feature-full (read bloated) and have a packaged GUI that I would never use. Therefore, I wanted to install something much more naked than the ones on the Raspberry Pi website.

Enter, Moebius. A few words from the Moebius site:

[Moebius] is a Raspberry Pi armhf Debian based distribution targeted to have a minimal footprint. Size, speed and optimizations are main goals for this distro...

With that said, the unzipped img file is about 110Mb. That is pretty small!

The other thing that Moebius does is remove the default Raspbian sources from apt-get. This means you can't just download all the Rasbian packages you want.

Moebius introduces the idea of containers. This isn't the same container technology like Docker. The "containers" are more like groups of packages. When installing a Moebius container, everything is installed in /usr/bin as if it came with the system.

I am going to provide a little walkthrough to get started with Moebius as a light-weight development environment, as well as how to install some other tools.

Initial Setup

First, visit the Moebius Sourceforge page and follow the instructions to download Moebius. The basic instructions tell you how to copy the img to the SD Card. Once everything is setup and the Raspberry Pi has booted, complete the following:

  • Either connect a screen and keyboard to the pi, or SSH to the pi
  • The default user is root and password is moebius
  • When logged in, run moebius
Moebius command line app

Moebius command line app

Moebius is the name of the OS, but also the name of a sweet little built-in command line tool to setup the rest of the Pi.

SSH Niceness

Optional: Add your public key to Moebius in order to ssh without a password. This is not required, but it does make popping in and out of the Pi nice and quick. Plus, no password to remember!

Moebius does not come with nano! If you are not familiar with the vi tool, you should use this site to learn some basics.

Just create ~/.ssh and then use vi ~/.ssh/authorized_keys to create a new file, then paste in your public key.

Dev Environment Setup

  • Run moebius and select Basic/Initial Setup
  • Choose Autoresize SD partition, follow the instructions
  • Run apt-get update
  • Run apt-get install tzdata
  • Run moebius and select Basic/Initial Setup, choose System timezone setup and follow instructions
  • In moebius, select Software Management and select Update containers list from repository
  • Do the same as above except choose Install a container from the Software Management menu
  • Select the lang.gcc container
Moebius container command line app

Moebius container command line app

You may get an error telling you to run dpkg --configure -a. If this happens, press any key to close the container installed and then run that command. When that completes, try to install the lang.gcc container again.

You may have to repeat the process above multiple times. I did it twice.

Once the container is installed, you should have make and gcc, and a bunch of other tools on your system.


Install git

You must install the lang.gcc container first. That container provides the necessary compilers we need in order to build git.

  • Get the required development files with apt-get install openssl-dev curl-dev libexpat-dev dropbear-dev coreutils coreutils-dev
  • Download the latest zip archive wget https://github.com/git/git/archive/v2.3.3.zip
  • Run unzip v2.3.3.zip and then cd v2.3.3/
  • Allow the scripts to run with chmod +x *.sh && chmod +x check_bindir

Following the official INSTALL in the git source code repository, we want to leave out some of the features in order to build without some of the required libraries.

To do this, we need to run:

make NO_PERL=YesPlease NO_TCLTK=YesPlease NO_GETTEXT=YesPlease prefix=/usr install
-

This will take a while to build, so grab a coffee or a beer.

Note: This command does not build the docs, so if you want those, you will have to consult the INSTALL file in the git repo.

Samba setup

Samba lets us access the Pi like a hard drive on our local network. Samba works well with Windows and OSX, and of course Linux as well.

If you are not familiar with the vi tool, you should use this site to learn some basics.

Complete the following to setup Samba:

  • run apt-get install samba
  • open the config with vi /etc/samba/smb.conf
  • then complete the following:
# find in the top part of the file
+Minimal Raspberry Pi OS | OhDoyleRules

Go home

Introduction

I have had a Raspberry Pi B+ for a while now, and I was looking to setup a very minimal Linux OS. I am sure this would work fine with the Pi 2.

Although the other Raspberry Pi OSs are great, a lot of them are too feature-full (read bloated) and have a packaged GUI that I would never use. Therefore, I wanted to install something much more naked than the ones on the Raspberry Pi website.

Enter, Moebius. A few words from the Moebius site:

[Moebius] is a Raspberry Pi armhf Debian based distribution targeted to have a minimal footprint. Size, speed and optimizations are main goals for this distro...

With that said, the unzipped img file is about 110Mb. That is pretty small!

The other thing that Moebius does is remove the default Raspbian sources from apt-get. This means you can't just download all the Rasbian packages you want.

Moebius introduces the idea of containers. This isn't the same container technology like Docker. The "containers" are more like groups of packages. When installing a Moebius container, everything is installed in /usr/bin as if it came with the system.

I am going to provide a little walkthrough to get started with Moebius as a light-weight development environment, as well as how to install some other tools.

Initial Setup

First, visit the Moebius Sourceforge page and follow the instructions to download Moebius. The basic instructions tell you how to copy the img to the SD Card. Once everything is setup and the Raspberry Pi has booted, complete the following:

  • Either connect a screen and keyboard to the pi, or SSH to the pi
  • The default user is root and password is moebius
  • When logged in, run moebius
Moebius command line app

Moebius command line app

Moebius is the name of the OS, but also the name of a sweet little built-in command line tool to setup the rest of the Pi.

SSH Niceness

Optional: Add your public key to Moebius in order to ssh without a password. This is not required, but it does make popping in and out of the Pi nice and quick. Plus, no password to remember!

Moebius does not come with nano! If you are not familiar with the vi tool, you should use this site to learn some basics.

Just create ~/.ssh and then use vi ~/.ssh/authorized_keys to create a new file, then paste in your public key.

Dev Environment Setup

  • Run moebius and select Basic/Initial Setup
  • Choose Autoresize SD partition, follow the instructions
  • Run apt-get update
  • Run apt-get install tzdata
  • Run moebius and select Basic/Initial Setup, choose System timezone setup and follow instructions
  • In moebius, select Software Management and select Update containers list from repository
  • Do the same as above except choose Install a container from the Software Management menu
  • Select the lang.gcc container
Moebius container command line app

Moebius container command line app

You may get an error telling you to run dpkg --configure -a. If this happens, press any key to close the container installed and then run that command. When that completes, try to install the lang.gcc container again.

You may have to repeat the process above multiple times. I did it twice.

Once the container is installed, you should have make and gcc, and a bunch of other tools on your system.


Install git

You must install the lang.gcc container first. That container provides the necessary compilers we need in order to build git.

  • Get the required development files with apt-get install openssl-dev curl-dev libexpat-dev dropbear-dev coreutils coreutils-dev
  • Download the latest zip archive wget https://github.com/git/git/archive/v2.3.3.zip
  • Run unzip v2.3.3.zip and then cd v2.3.3/
  • Allow the scripts to run with chmod +x *.sh && chmod +x check_bindir

Following the official INSTALL in the git source code repository, we want to leave out some of the features in order to build without some of the required libraries.

To do this, we need to run:

make NO_PERL=YesPlease NO_TCLTK=YesPlease NO_GETTEXT=YesPlease prefix=/usr install
+

This will take a while to build, so grab a coffee or a beer.

Note: This command does not build the docs, so if you want those, you will have to consult the INSTALL file in the git repo.

Samba setup

Samba lets us access the Pi like a hard drive on our local network. Samba works well with Windows and OSX, and of course Linux as well.

If you are not familiar with the vi tool, you should use this site to learn some basics.

Complete the following to setup Samba:

  • run apt-get install samba
  • open the config with vi /etc/samba/smb.conf
  • then complete the following:
# find in the top part of the file
 workgroup = your_workgroup_name
 # find and uncomment this line
 wins support = yes
@@ -13,7 +13,7 @@
   create mask=0777
   directory mask=0777
   public=no
-
  • set the samba password with smbpasswd -a root

Install lit and luvit

Again, have lang.gcc installed before continuing.

I have been playing with Lit and Luvit. They are like lightweight versions of Node.js, but written with Lua. So let's install them with a series of commands:

apt-get install curl
+
  • set the samba password with smbpasswd -a root

Install lit and luvit

Again, have lang.gcc installed before continuing.

I have been playing with Lit and Luvit. They are like lightweight versions of Node.js, but written with Lua. So let's install them with a series of commands:

apt-get install curl
 curl -L https://github.com/luvit/lit/raw/master/get-lit.sh | sh
 mkdir -p /usr/local/bin
 mv lit /usr/local/bin/lit
diff --git a/docs/personal-project/npm-logo-svg/index.html b/docs/personal-project/npm-logo-svg/index.html
index 524edce..15855dc 100644
--- a/docs/personal-project/npm-logo-svg/index.html
+++ b/docs/personal-project/npm-logo-svg/index.html
@@ -1 +1 @@
-NPM logo SVG | OhDoyleRules

Go home

I made a vector version of the logo for NPM. Here is the vector version as a SVG.

npm vector svg logo

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +NPM logo SVG | OhDoyleRules

Go home

I made a vector version of the logo for NPM. Here is the vector version as a SVG.

npm vector svg logo

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/nudeproject/index.html b/docs/personal-project/nudeproject/index.html index 02464d4..50d5830 100644 --- a/docs/personal-project/nudeproject/index.html +++ b/docs/personal-project/nudeproject/index.html @@ -1 +1 @@ -NudeProject - a starting point for simple websites | OhDoyleRules

Go home

NudeProject is meant to be a starting point for new landing pages, single-page sites, or even just basic mockups. The point is to get me off the ground quickly. It only provides the most basic things that I need most of the time. These include grunt tasks, normalized CSS, modernizr, and a SVG fallback snippet in javascript. Check out the project on github.

grunt

included grunt taks

  • clean -- removing files before tasks run
  • cssmin -- minify and concat css files
  • imagemin -- compress images and jpeg
  • svgmin -- compress svg files
  • uglify -- minify and concat js

stylesheets

  • normalize.css

javascript

modernizr custom build

  • cssanimations
  • csstransforms
  • csstransforms3d
  • csstransitions
  • canvas
  • audio
  • video
  • localstorage
  • svg
  • touch
  • webgl
  • shiv
  • cssclasses
  • teststyles
  • testprop
  • testallprops
  • prefixes
  • domprefixes
  • css_boxsizing
  • script_async
  • script_defer

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +NudeProject - a starting point for simple websites | OhDoyleRules

Go home

NudeProject is meant to be a starting point for new landing pages, single-page sites, or even just basic mockups. The point is to get me off the ground quickly. It only provides the most basic things that I need most of the time. These include grunt tasks, normalized CSS, modernizr, and a SVG fallback snippet in javascript. Check out the project on github.

grunt

included grunt taks

  • clean -- removing files before tasks run
  • cssmin -- minify and concat css files
  • imagemin -- compress images and jpeg
  • svgmin -- compress svg files
  • uglify -- minify and concat js

stylesheets

  • normalize.css

javascript

modernizr custom build

  • cssanimations
  • csstransforms
  • csstransforms3d
  • csstransitions
  • canvas
  • audio
  • video
  • localstorage
  • svg
  • touch
  • webgl
  • shiv
  • cssclasses
  • teststyles
  • testprop
  • testallprops
  • prefixes
  • domprefixes
  • css_boxsizing
  • script_async
  • script_defer

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/phalcon-micro-starter/index.html b/docs/personal-project/phalcon-micro-starter/index.html index 4764906..a0f88d1 100644 --- a/docs/personal-project/phalcon-micro-starter/index.html +++ b/docs/personal-project/phalcon-micro-starter/index.html @@ -1 +1 @@ -Phalcon Micro App Starter | OhDoyleRules

Go home

I created a simple application template that helps people get started with Phalcon PHP using a more practical example of the Micro application.

There is already a sample application created by the Phalcon team that uses the Micro class, but I found it to be a little more specific than I would like. It uses things like the Volt template engine, models, Database connections, and some other glossed over Bootstrapping.

My example application contains very little. It has enough to get you started creating a simple JSON-based application, or just serving a static site with a few cached views.

What's Included?

Basic page example

Just shows a simple GET route and serves a single view.

Partial views (Simple view engine)

The templates use partials for the header and footer of the site.

URL with params

You can pass parameters into the URL, and they will be rendered on the page.

JSON return

An example of how to return JSON via a POST request. There is also a comment that tells you the jQuery test function to try.

Cached view

This shows how you can serve a cached view, with an expiry. Good for those complicated pages that need to be refreshed every other day.

Redirect URL

This one is really simple. It just shows how you can redirect one URL request to another.

Other Niceness

I also included a simple grunt task that uses livereload. This will refresh the browser when view files, or the app.php file, changes.

You can find the repositry on Github. I will be updating and tweaking this project as I move along. It may become more feature-rich in the next few months. I would like to build a nice solid base for myself when using the Micro app.

There may be some need to add in some simple search examples, models, forms, validation, or even Database connections. But we will see if that is where it moved organically.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Phalcon Micro App Starter | OhDoyleRules

Go home

I created a simple application template that helps people get started with Phalcon PHP using a more practical example of the Micro application.

There is already a sample application created by the Phalcon team that uses the Micro class, but I found it to be a little more specific than I would like. It uses things like the Volt template engine, models, Database connections, and some other glossed over Bootstrapping.

My example application contains very little. It has enough to get you started creating a simple JSON-based application, or just serving a static site with a few cached views.

What's Included?

Basic page example

Just shows a simple GET route and serves a single view.

Partial views (Simple view engine)

The templates use partials for the header and footer of the site.

URL with params

You can pass parameters into the URL, and they will be rendered on the page.

JSON return

An example of how to return JSON via a POST request. There is also a comment that tells you the jQuery test function to try.

Cached view

This shows how you can serve a cached view, with an expiry. Good for those complicated pages that need to be refreshed every other day.

Redirect URL

This one is really simple. It just shows how you can redirect one URL request to another.

Other Niceness

I also included a simple grunt task that uses livereload. This will refresh the browser when view files, or the app.php file, changes.

You can find the repositry on Github. I will be updating and tweaking this project as I move along. It may become more feature-rich in the next few months. I would like to build a nice solid base for myself when using the Micro app.

There may be some need to add in some simple search examples, models, forms, validation, or even Database connections. But we will see if that is where it moved organically.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/phile-cms/index.html b/docs/personal-project/phile-cms/index.html index 1187b3c..9567ad6 100644 --- a/docs/personal-project/phile-cms/index.html +++ b/docs/personal-project/phile-cms/index.html @@ -1 +1 @@ -Phile CMS | OhDoyleRules

Go home

After being a upset at the progress with Pico, myself and a developer from Germany(Frank) have developed a fork project.

The project is PhileCMS. It maintains the philosophy of Pico, being fast and small, but it makes a lot of improvements on the core. Most the project is now OOP based with classes and models.

Also the parser and the template engine have been pushed into services. Which means they can be overloaded and replaced with different ones. Don't like Markdown? Use a plugin for TextTile instead. Don't like Twig? Replace it with Lex!

The hooks system was completely replaced with an Evented system. The plugins have also changed. They now have a config.php file that is used instead of having to write your own file reader for each plugin.

So why use this over Pico?

Here is a small list of differences in design from Pico:

  • OOP based (Classes)
  • Events system
  • Parser Overloading
  • Template Engine Overloading
  • Performance Improvements (33% to 65% speed increase)

The main increase in speed is when there are multiple pages. Once you get to 20 pages you see a minumum of a 50% increase in load times.

I have actually converted this site to run on Phile. It is probably the first site in production to be using it. I also use the Sundown Plugin I wrote since I have PHP-Sundown installed on my server.

Anyway, check out the project. It is pretty cool and I am very happy with the work of Frank.

Github Repo

Homepage

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Phile CMS | OhDoyleRules

Go home

After being a upset at the progress with Pico, myself and a developer from Germany(Frank) have developed a fork project.

The project is PhileCMS. It maintains the philosophy of Pico, being fast and small, but it makes a lot of improvements on the core. Most the project is now OOP based with classes and models.

Also the parser and the template engine have been pushed into services. Which means they can be overloaded and replaced with different ones. Don't like Markdown? Use a plugin for TextTile instead. Don't like Twig? Replace it with Lex!

The hooks system was completely replaced with an Evented system. The plugins have also changed. They now have a config.php file that is used instead of having to write your own file reader for each plugin.

So why use this over Pico?

Here is a small list of differences in design from Pico:

  • OOP based (Classes)
  • Events system
  • Parser Overloading
  • Template Engine Overloading
  • Performance Improvements (33% to 65% speed increase)

The main increase in speed is when there are multiple pages. Once you get to 20 pages you see a minumum of a 50% increase in load times.

I have actually converted this site to run on Phile. It is probably the first site in production to be using it. I also use the Sundown Plugin I wrote since I have PHP-Sundown installed on my server.

Anyway, check out the project. It is pretty cool and I am very happy with the work of Frank.

Github Repo

Homepage

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/phile-intro-video/index.html b/docs/personal-project/phile-intro-video/index.html index a32c914..007b739 100644 --- a/docs/personal-project/phile-intro-video/index.html +++ b/docs/personal-project/phile-intro-video/index.html @@ -1 +1 @@ -All About PhileCMS Video | OhDoyleRules

Go home

PhileCMS now has a new instructional video.

Here are some of the points I hit on in the video:

  • Installation
  • Differences To Pico
  • About Twig
  • Creating Content
  • Using Meta Data
  • Themes
  • Conditional Navigations
  • Plugins
  • Tricks

The video is about 35 minutes. So there is quite a lot of stuff in there. I plan on making a few more videos. One about making themes and using the phileGruntThemeing project, and creating plugins with the events system.

Watch it on YouTube.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +All About PhileCMS Video | OhDoyleRules

Go home

PhileCMS now has a new instructional video.

Here are some of the points I hit on in the video:

  • Installation
  • Differences To Pico
  • About Twig
  • Creating Content
  • Using Meta Data
  • Themes
  • Conditional Navigations
  • Plugins
  • Tricks

The video is about 35 minutes. So there is quite a lot of stuff in there. I plan on making a few more videos. One about making themes and using the phileGruntThemeing project, and creating plugins with the events system.

Watch it on YouTube.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/php-websocket-chat/index.html b/docs/personal-project/php-websocket-chat/index.html index 1232a24..dbb2f26 100644 --- a/docs/personal-project/php-websocket-chat/index.html +++ b/docs/personal-project/php-websocket-chat/index.html @@ -1 +1 @@ -PHP WebSocket Chat | OhDoyleRules

Go home

About 6 months ago, I made a little socket.io chat app. At the time, this was really only possible with Node.js because the HTML5 WebSocket support was too low.

But now, months later, the support for WebSockets is actually very good.Looking at caniuse.com right now, there is better support for WebSocket than there is WebGL. I would argue that WebGL support is actually more important than the WebSocket support, but I digress. Here is a non-jargon-laden explanation from HTML5Rocks:

The WebSocket specification defines an API establishing "socket" connections between a web browser and a server. In plain words: There is an persistent connection between the client and the server and both parties can start sending data at any time.

Here is a little more technical explanation.

A WebSocket creates a TCP connection to server, and keeps it as long as needed. The Server or client can easily close it. It uses Bidirectional communication - so server and client can exchange data both directions at any time. It is very efficient if the application requires frequent messages. WebSockets have data framing that includes masking for each message sent from client to server so data is simply encrypted.

If you want a technically in-depth overview, checkout websocket.org.

Anyway, I made a little chat app with Ratchet. People knock PHP for all the bad things it does. But getting the WebSocket example running, actually wasn't that bad. Apparently Apache doesn't play nice with Ratchet (not sure about pure WebSockets) so you have to use the built-in PHP server which comes with PHP 5.4.

php ratchet socket server form example

The app I made is pretty much a copy paste from the Rachet Hello World Example but tried to make the simplest chat app I could. The server is actually pretty close the Hello World code, just with a bunch of extra client-side javascript.

Once you download the app, if you have PHP properly installed and in your path, you can use php bin/chat-server.php in the root folder to start the server. You can then hit the index page and see the green connection message. You will also see some information in your terminal.

You can then open a new browser (or incognito/private window) and "create" another user to chat with.

You can see your messages going back and forth. Pretty slick. With the way I develop things at WARPAINT Media, I really can't wait to create some sites and apps that use the WebSocket server.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +PHP WebSocket Chat | OhDoyleRules

Go home

About 6 months ago, I made a little socket.io chat app. At the time, this was really only possible with Node.js because the HTML5 WebSocket support was too low.

But now, months later, the support for WebSockets is actually very good.Looking at caniuse.com right now, there is better support for WebSocket than there is WebGL. I would argue that WebGL support is actually more important than the WebSocket support, but I digress. Here is a non-jargon-laden explanation from HTML5Rocks:

The WebSocket specification defines an API establishing "socket" connections between a web browser and a server. In plain words: There is an persistent connection between the client and the server and both parties can start sending data at any time.

Here is a little more technical explanation.

A WebSocket creates a TCP connection to server, and keeps it as long as needed. The Server or client can easily close it. It uses Bidirectional communication - so server and client can exchange data both directions at any time. It is very efficient if the application requires frequent messages. WebSockets have data framing that includes masking for each message sent from client to server so data is simply encrypted.

If you want a technically in-depth overview, checkout websocket.org.

Anyway, I made a little chat app with Ratchet. People knock PHP for all the bad things it does. But getting the WebSocket example running, actually wasn't that bad. Apparently Apache doesn't play nice with Ratchet (not sure about pure WebSockets) so you have to use the built-in PHP server which comes with PHP 5.4.

php ratchet socket server form example

The app I made is pretty much a copy paste from the Rachet Hello World Example but tried to make the simplest chat app I could. The server is actually pretty close the Hello World code, just with a bunch of extra client-side javascript.

Once you download the app, if you have PHP properly installed and in your path, you can use php bin/chat-server.php in the root folder to start the server. You can then hit the index page and see the green connection message. You will also see some information in your terminal.

You can then open a new browser (or incognito/private window) and "create" another user to chat with.

You can see your messages going back and forth. Pretty slick. With the way I develop things at WARPAINT Media, I really can't wait to create some sites and apps that use the WebSocket server.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pico-download-plugin/index.html b/docs/personal-project/pico-download-plugin/index.html index 4c3dc34..0d6525a 100644 --- a/docs/personal-project/pico-download-plugin/index.html +++ b/docs/personal-project/pico-download-plugin/index.html @@ -1 +1 @@ -pico-download plugin | OhDoyleRules

Go home

I created a plugin to force files to download in PicoCMS.

I needed this because I wanted to PDFs to download and not just render in the browser.

Usage

Place your files in the content folder. Then replace the word content/ in the url with the word download/.

The download folder can be controlled in the plugin file. Default for downloading is content/.

Example

If you wanted to render the file in the browser:

http://localhost:8888/Pico/content/sub/page.md

Now with this plugin installed, you can force a download:

http://localhost:8888/Pico/download/sub/page.md

More info

I have added quite a few comments in the plugin so just take a look. It's nothing new, just bringing different snippets together.

You can find the project here on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +pico-download plugin | OhDoyleRules

Go home

I created a plugin to force files to download in PicoCMS.

I needed this because I wanted to PDFs to download and not just render in the browser.

Usage

Place your files in the content folder. Then replace the word content/ in the url with the word download/.

The download folder can be controlled in the plugin file. Default for downloading is content/.

Example

If you wanted to render the file in the browser:

http://localhost:8888/Pico/content/sub/page.md

Now with this plugin installed, you can force a download:

http://localhost:8888/Pico/download/sub/page.md

More info

I have added quite a few comments in the plugin so just take a look. It's nothing new, just bringing different snippets together.

You can find the project here on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pico-get-by-filename-plugin/index.html b/docs/personal-project/pico-get-by-filename-plugin/index.html index afa744c..d260b09 100644 --- a/docs/personal-project/pico-get-by-filename-plugin/index.html +++ b/docs/personal-project/pico-get-by-filename-plugin/index.html @@ -1 +1 @@ -Pico Get By Filename Plugin | OhDoyleRules

Go home

I wrote another small plugin for PicoCMS that actually lets you load files based on the filename. I called it pico_get_by_filename.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pico Get By Filename Plugin | OhDoyleRules

Go home

I wrote another small plugin for PicoCMS that actually lets you load files based on the filename. I called it pico_get_by_filename.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pico-slider-plugin/index.html b/docs/personal-project/pico-slider-plugin/index.html index fa378d2..7408a95 100644 --- a/docs/personal-project/pico-slider-plugin/index.html +++ b/docs/personal-project/pico-slider-plugin/index.html @@ -1 +1 @@ -Pico Slider Plugin | OhDoyleRules

Go home

I wrote a small plugin for the Pico CMS. I recently discovered a very cool PHP-based CMS called, Pico CMS. This CMS is a no-database flat-file CMS. It is really fast and very easy. There was no real way to handle images in the base version. So I developed a plugin that can list images in a folder. I named it pico_slider but it could probably be named pico_image_list because all it really does is expose an image array to the front-end variables. Here it is on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pico Slider Plugin | OhDoyleRules

Go home

I wrote a small plugin for the Pico CMS. I recently discovered a very cool PHP-based CMS called, Pico CMS. This CMS is a no-database flat-file CMS. It is really fast and very easy. There was no real way to handle images in the base version. So I developed a plugin that can list images in a folder. I named it pico_slider but it could probably be named pico_image_list because all it really does is expose an image array to the front-end variables. Here it is on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pico-useragent/index.html b/docs/personal-project/pico-useragent/index.html index 5b16ab2..e9d7119 100644 --- a/docs/personal-project/pico-useragent/index.html +++ b/docs/personal-project/pico-useragent/index.html @@ -1,4 +1,4 @@ -Pico-Useragent Plugin | OhDoyleRules

Go home

I created another plugin for Pico CMS. It is esentially a clone of my pyro-sniffer-plugin for PyroCMS.

Here is the Github project.

This plugin allows you to parse the user agent of the current visitor and then expose that information in an easy to use variable in your twig templates.

Hopefully that makese sense.

Output

When using the plugin, you get a new variable called browser. The browser variable has the following properties in it when dumped from my computer:

$browser = array (
+Pico-Useragent Plugin | OhDoyleRules

Go home

I created another plugin for Pico CMS. It is esentially a clone of my pyro-sniffer-plugin for PyroCMS.

Here is the Github project.

This plugin allows you to parse the user agent of the current visitor and then expose that information in an easy to use variable in your twig templates.

Hopefully that makese sense.

Output

When using the plugin, you get a new variable called browser. The browser variable has the following properties in it when dumped from my computer:

$browser = array (
   'useragent'   => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.37 Safari/537.36' // full ua string
   'name'        => 'Google Chrome' // name of the browser
   'browser'     => 'google-chrome' // CSS safe browser name
@@ -16,4 +16,4 @@
 .firefox .button {
   padding: 0.28em 1em;
 }
-

Use Cases

  • conditional content
  • conditional styles/scripts
  • layout modifications
  • serving specific images
  • Modernizr-esque CSS classes

Here is the Github project again.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Use Cases

  • conditional content
  • conditional styles/scripts
  • layout modifications
  • serving specific images
  • Modernizr-esque CSS classes

Here is the Github project again.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-blurb-field/index.html b/docs/personal-project/pyro-blurb-field/index.html index 982f57c..b1c93fd 100644 --- a/docs/personal-project/pyro-blurb-field/index.html +++ b/docs/personal-project/pyro-blurb-field/index.html @@ -1 +1 @@ -Pyro Blurb Field | OhDoyleRules

Go home

I created another new field type for PyroCMS.

pyro-blurb-field

This one is for doing little "blurb" sections. Essentially I keep seeing little title+image+body components. Like testimonials, user profiles, portfolio snippets. These all follow a title+image+body format. So I built a little field type to provide an easy way to manage these in your page types.

pyro-blurb-field

I also put the project on github so that you can see it. It will also be on the PyroCMS Store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro Blurb Field | OhDoyleRules

Go home

I created another new field type for PyroCMS.

pyro-blurb-field

This one is for doing little "blurb" sections. Essentially I keep seeing little title+image+body components. Like testimonials, user profiles, portfolio snippets. These all follow a title+image+body format. So I built a little field type to provide an easy way to manage these in your page types.

pyro-blurb-field

I also put the project on github so that you can see it. It will also be on the PyroCMS Store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-github-markdown/index.html b/docs/personal-project/pyro-github-markdown/index.html index a682bc6..59239d7 100644 --- a/docs/personal-project/pyro-github-markdown/index.html +++ b/docs/personal-project/pyro-github-markdown/index.html @@ -1,4 +1,4 @@ -Pyro Github Markdown Field Type | OhDoyleRules

Go home

Github flavoured markdown field type for PyroCMS.

Here is the link to the Github Repo.

Most of the work for this repo is taken from GitHub-Flavored Markdown Comments plugin for Wordpress. That repository is also based on Michel Fortin's PHP markdown library with added features from GitHub-flavored Markdown.

All I did was just bring it all together and make it play nice with Pyro.

Usage

  • Install the field type as normal.
  • Add the field type to a page type or stream
  • Enter in your sexy Github Markdown
  • Just use "the_field_slug" to render the HTML

Examples

Input:

GitHub-Flavored Markdown Comments
+Pyro Github Markdown Field Type | OhDoyleRules

Go home

Github flavoured markdown field type for PyroCMS.

Here is the link to the Github Repo.

Most of the work for this repo is taken from GitHub-Flavored Markdown Comments plugin for Wordpress. That repository is also based on Michel Fortin's PHP markdown library with added features from GitHub-flavored Markdown.

All I did was just bring it all together and make it play nice with Pyro.

Usage

  • Install the field type as normal.
  • Add the field type to a page type or stream
  • Enter in your sexy Github Markdown
  • Just use "the_field_slug" to render the HTML

Examples

Input:

GitHub-Flavored Markdown Comments
 =============================
 
 Based on [Michel Fortin's PHP markdown library](https://github.com/michelf/php-markdown/) with added features from [GitHub-flavored Markdown](https://github.com/github/github-flavored-markdown).
@@ -21,4 +21,6 @@
 <h3>Heading 3</h3>
 
 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
-

More Info!

If you need to know more about the caveats of this plugin, please see the README for the original lib.

UPDATE

I added the ability to preview your results too!

pyro github markdown write preview pyro github markdown preview Go check out this badboy!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

More Info!

If you need to know more about the caveats of this plugin, please see the README for the original lib.

UPDATE

I added the ability to preview your results too!

pyro github markdown write
+preview pyro github markdown
+preview Go check out this badboy!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-image-select-field/index.html b/docs/personal-project/pyro-image-select-field/index.html index b53641d..135608a 100644 --- a/docs/personal-project/pyro-image-select-field/index.html +++ b/docs/personal-project/pyro-image-select-field/index.html @@ -1 +1 @@ -Pyro Image Select Field | OhDoyleRules

Go home

Another PyroCMS field type. This one is called Image Select. Why? Because it does exactly that. It lets you select images. But wait! There is already an image field type. Yes, but that is for uploading images. Sometimes you just want to choose and image you already have. An image drop down is good for this.

pyro image select screen3

I think a nice grid with thumbnail previews is even better!! I have the project hosted on github. It will also be submitted to the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro Image Select Field | OhDoyleRules

Go home

Another PyroCMS field type. This one is called Image Select. Why? Because it does exactly that. It lets you select images. But wait! There is already an image field type. Yes, but that is for uploading images. Sometimes you just want to choose and image you already have. An image drop down is good for this.

pyro image select screen3

I think a nice grid with thumbnail previews is even better!! I have the project hosted on github. It will also be submitted to the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-image-widget/index.html b/docs/personal-project/pyro-image-widget/index.html index 22ee253..5612962 100644 --- a/docs/personal-project/pyro-image-widget/index.html +++ b/docs/personal-project/pyro-image-widget/index.html @@ -1 +1 @@ -Pyro Image Widget | OhDoyleRules

Go home

I created a small image widget for PyroCMS. It allows a user to choose any image in the files as a widget. It also allows you to add a link and a target so it can be opened in a new tab. Here is the github link and the PyroCMS store link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro Image Widget | OhDoyleRules

Go home

I created a small image widget for PyroCMS. It allows a user to choose any image in the files as a widget. It also allows you to add a link and a target so it can be opened in a new tab. Here is the github link and the PyroCMS store link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-list-field/index.html b/docs/personal-project/pyro-list-field/index.html index 2fd4ce2..9883890 100644 --- a/docs/personal-project/pyro-list-field/index.html +++ b/docs/personal-project/pyro-list-field/index.html @@ -1 +1 @@ -Pyro List Field | OhDoyleRules

Go home

I created a list field type for PyroCMS. It allows users to easily add and manage list content. The source code is up on github and avaliable on the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro List Field | OhDoyleRules

Go home

I created a list field type for PyroCMS. It allows users to easily add and manage list content. The source code is up on github and avaliable on the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-module-generator-2/index.html b/docs/personal-project/pyro-module-generator-2/index.html index babc61a..0f3c496 100644 --- a/docs/personal-project/pyro-module-generator-2/index.html +++ b/docs/personal-project/pyro-module-generator-2/index.html @@ -1 +1 @@ -PyroCMS Module Generator 2.0 | OhDoyleRules

Go home

Finally, I found a good excuse to re-write my old Pyro Module Generator.

This tool was originally made when I was freelancing. I built it off the Sample Module project on Github. I wanted to be able to build modules quickly, since I wasn't using streams. In fact, streams wasn't even a thing when I made the first version of the module generator.

Again, I have made a live hosted version of the generator which you can use without having to have anything setup locally. The generated module is zipped and then ready for download.

The source is also on Github for people who want to patch issues or fork.

About Version 2.0

This new version is built with PhalconPHP because phalcon is crazy fast and easy to make small apps with. I managed to get the whole thing re-written in a day. Much of the code was a copy paste for the build process. But now the Add Field button is actually an AJAX call to generate a new partial for the new field. This is much nicer than the pervious version.

That being said, YOU MUST HAVE PHALCONPHP INSTALLED TO USE THIS APP!!.

There are a few little things I need to refactor, so that when 3.0 comes out, it will be easy to switch between the different versions of Pyro.

Usage

Just throw it in your localhost root and point your browser to it. There is no database, since it just writes and renames files for you.

If you have used a custom folder name (and didn't just clone as pyro-module-generator), then open the config/config.php and change the baseUri to match that folder name.

Writeable Folders

We need to run chmod -R 777 cache/volt and chmod -R 777 public/generated if you have write errors.

  • cache/volt/
  • public/generated/

Genrated Modules

Included in all generated modules is the following setup:

  • ID field by default
  • order field by default
  • functionality for drag and drop table order (add ui-sortable-container to tbody in admin index view)
  • basic function for files included but commented out
  • _form_data function for passing data to form views
  • settings table included, but commented out

The generated module gets put in the public/generated/ folder. As well as the Zip file.

Screenshot of the current version of the app

PyroCMS Module Generator Screenshot

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +PyroCMS Module Generator 2.0 | OhDoyleRules

Go home

Finally, I found a good excuse to re-write my old Pyro Module Generator.

This tool was originally made when I was freelancing. I built it off the Sample Module project on Github. I wanted to be able to build modules quickly, since I wasn't using streams. In fact, streams wasn't even a thing when I made the first version of the module generator.

Again, I have made a live hosted version of the generator which you can use without having to have anything setup locally. The generated module is zipped and then ready for download.

The source is also on Github for people who want to patch issues or fork.

About Version 2.0

This new version is built with PhalconPHP because phalcon is crazy fast and easy to make small apps with. I managed to get the whole thing re-written in a day. Much of the code was a copy paste for the build process. But now the Add Field button is actually an AJAX call to generate a new partial for the new field. This is much nicer than the pervious version.

That being said, YOU MUST HAVE PHALCONPHP INSTALLED TO USE THIS APP!!.

There are a few little things I need to refactor, so that when 3.0 comes out, it will be easy to switch between the different versions of Pyro.

Usage

Just throw it in your localhost root and point your browser to it. There is no database, since it just writes and renames files for you.

If you have used a custom folder name (and didn't just clone as pyro-module-generator), then open the config/config.php and change the baseUri to match that folder name.

Writeable Folders

We need to run chmod -R 777 cache/volt and chmod -R 777 public/generated if you have write errors.

  • cache/volt/
  • public/generated/

Genrated Modules

Included in all generated modules is the following setup:

  • ID field by default
  • order field by default
  • functionality for drag and drop table order (add ui-sortable-container to tbody in admin index view)
  • basic function for files included but commented out
  • _form_data function for passing data to form views
  • settings table included, but commented out

The generated module gets put in the public/generated/ folder. As well as the Zip file.

Screenshot of the current version of the app

PyroCMS Module Generator Screenshot

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-module-generator/index.html b/docs/personal-project/pyro-module-generator/index.html index b2c4045..e2e23de 100644 --- a/docs/personal-project/pyro-module-generator/index.html +++ b/docs/personal-project/pyro-module-generator/index.html @@ -1 +1 @@ -PyroCMS Module Generator | OhDoyleRules

Go home

PyroCMS Module generator header image

UPDATE

I created a hosted version of the module generator.

FINALLY!! I finished my module generator. It lets you create modules by just filling in a simple form. The module it generates can be used with 2.2. There is no support for any other version at this time.

See the Video or go to the github repo.

The “app” is just installed to your localhost. It uses no database and relies strictly on writing files and reading files.

It is built using Laravel because Pyro is eventually going to move to Laravel. It is kind of funny that a Laravel based app is building a CodeIgniter based CMS! Hehehee.

There is a lot of extra junk in there now just because I may create a dedicated site for it. I also want to make it more dynamic for when you are creating dropdown/multiselect and radio/checkbox inputs. But that depends. Right now it can just run locally and be used/customized that way.

This is actually my fourth iteration of the generator. I created one with no framework, just straight PHP. That was rough. Then I made one with just CodeIgniter. It was a little better. Next, I went kind of crazy and made a PHP command line tool. It can actually make plugins and widgets quite nicely.

In the end I chose Laravel because I might as well start learning it and it was pretty easy to use. Sp please check it out and help clean it up if you can.

Some little screenshots:

PyroCMS Module generator input formPyroCMS Module generator field input

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +PyroCMS Module Generator | OhDoyleRules

Go home

PyroCMS Module generator header image

UPDATE

I created a hosted version of the module generator.

FINALLY!! I finished my module generator. It lets you create modules by just filling in a simple form. The module it generates can be used with 2.2. There is no support for any other version at this time.

See the Video or go to the github repo.

The “app” is just installed to your localhost. It uses no database and relies strictly on writing files and reading files.

It is built using Laravel because Pyro is eventually going to move to Laravel. It is kind of funny that a Laravel based app is building a CodeIgniter based CMS! Hehehee.

There is a lot of extra junk in there now just because I may create a dedicated site for it. I also want to make it more dynamic for when you are creating dropdown/multiselect and radio/checkbox inputs. But that depends. Right now it can just run locally and be used/customized that way.

This is actually my fourth iteration of the generator. I created one with no framework, just straight PHP. That was rough. Then I made one with just CodeIgniter. It was a little better. Next, I went kind of crazy and made a PHP command line tool. It can actually make plugins and widgets quite nicely.

In the end I chose Laravel because I might as well start learning it and it was pretty easy to use. Sp please check it out and help clean it up if you can.

Some little screenshots:

PyroCMS Module generator input formPyroCMS Module generator field input

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-swipe-js-module/index.html b/docs/personal-project/pyro-swipe-js-module/index.html index edeaa73..9e61e5e 100644 --- a/docs/personal-project/pyro-swipe-js-module/index.html +++ b/docs/personal-project/pyro-swipe-js-module/index.html @@ -1 +1 @@ -Pyro Swipe.js Module | OhDoyleRules

Go home

Yep, another module. This one is for the awesome Swipe.js library. Swipe is amazing because it is touch-capable, lightweight and has no dependencies(no jQuery). This module allows users to create multiple slideshows just by choosing a folder they want to pull the images from.

Then using the modules plugin, they can call it anywhere on a page.

Here is is on github.

It should also make it to the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro Swipe.js Module | OhDoyleRules

Go home

Yep, another module. This one is for the awesome Swipe.js library. Swipe is amazing because it is touch-capable, lightweight and has no dependencies(no jQuery). This module allows users to create multiple slideshows just by choosing a folder they want to pull the images from.

Then using the modules plugin, they can call it anywhere on a page.

Here is is on github.

It should also make it to the PyroCMS store.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyro-twitter-widget/index.html b/docs/personal-project/pyro-twitter-widget/index.html index d3229b7..f2452c4 100644 --- a/docs/personal-project/pyro-twitter-widget/index.html +++ b/docs/personal-project/pyro-twitter-widget/index.html @@ -1 +1 @@ -Pyro Twitter Widget | OhDoyleRules

Go home

I created another widget for PyroCMS. This one is for Twitter. I didn't find one that I liked or thought was very good, so I created my own. This widget actually uses a 3rd party sub-module, for the Twitter authentication, called twitter-api-php.

Here are the current supported (basically just tested) API endpoints:

  • statuses/mentions_timeline
  • statuses/user_timeline
  • statuses/home_timeline
  • statuses/retweetsofme
  • favorites/list

Here is the widget on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Pyro Twitter Widget | OhDoyleRules

Go home

I created another widget for PyroCMS. This one is for Twitter. I didn't find one that I liked or thought was very good, so I created my own. This widget actually uses a 3rd party sub-module, for the Twitter authentication, called twitter-api-php.

Here are the current supported (basically just tested) API endpoints:

  • statuses/mentions_timeline
  • statuses/user_timeline
  • statuses/home_timeline
  • statuses/retweetsofme
  • favorites/list

Here is the widget on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyrocms-pagewidgets-field-type/index.html b/docs/personal-project/pyrocms-pagewidgets-field-type/index.html index 7cec90b..26bed7e 100644 --- a/docs/personal-project/pyrocms-pagewidgets-field-type/index.html +++ b/docs/personal-project/pyrocms-pagewidgets-field-type/index.html @@ -1 +1 @@ -PyroCMS PageWidgets Field Type | OhDoyleRules

Go home

I created another field type for PyroCMS. This one is so you can add widgets on a page-by-page basis instead of area-by-area.

I kept getting asked by clients for this feature, and with the advent of the new page type and field types this is a lot easier. The source is on github

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +PyroCMS PageWidgets Field Type | OhDoyleRules

Go home

I created another field type for PyroCMS. This one is so you can add widgets on a page-by-page basis instead of area-by-area.

I kept getting asked by clients for this feature, and with the advent of the new page type and field types this is a lot easier. The source is on github

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/pyrocms-ua-sniffer-plugin/index.html b/docs/personal-project/pyrocms-ua-sniffer-plugin/index.html index 392ca4d..b6e2d57 100644 --- a/docs/personal-project/pyrocms-ua-sniffer-plugin/index.html +++ b/docs/personal-project/pyrocms-ua-sniffer-plugin/index.html @@ -1,4 +1,4 @@ -PyroCMS UA Sniffer Plugin | OhDoyleRules

Go home

This plugin lets you sniff information from the user agent for use in the frontend. I use it for adding classes or conditional loading of partials and templates.

You can see the github repository here.

This plugin is not built on the CodeIgniter User Agent Library.

The reason I did not use the built in CodeIgniter lib, was because Pyro is only going to have CodeIgniter for a few more months(right?!?!), and I also want to have the information returned in a different way. This plugin is pretty small and only really gets information that is helpful to be used in CSS and Javascript (CSS custom classes and js feature detection/fallbacks).

If you are looking for a plugin that uses the user agent library, check out this plugin called Agent.

Usage

<body class="\{\{ sniffer:get key="browser|platform|type" \}\}">
+PyroCMS UA Sniffer Plugin | OhDoyleRules

Go home

This plugin lets you sniff information from the user agent for use in the frontend. I use it for adding classes or conditional loading of partials and templates.

You can see the github repository here.

This plugin is not built on the CodeIgniter User Agent Library.

The reason I did not use the built in CodeIgniter lib, was because Pyro is only going to have CodeIgniter for a few more months(right?!?!), and I also want to have the information returned in a different way. This plugin is pretty small and only really gets information that is helpful to be used in CSS and Javascript (CSS custom classes and js feature detection/fallbacks).

If you are looking for a plugin that uses the user agent library, check out this plugin called Agent.

Usage

<body class="\{\{ sniffer:get key="browser|platform|type" \}\}">
 

On my Mac running Google Chrome, this would return:

<body class=" google-chrome mac desktop">
 

On my iPhone, this would return:

<body class=" apple-mobile-safari ios mobile">
 

conditional content

This works in 2.2/develop. Not sure about 2.3 or 2.1.

\{\{ if \{ sniffer:get key="type" \} == 'desktop' \}\}
diff --git a/docs/personal-project/rework-math/index.html b/docs/personal-project/rework-math/index.html
index 21641da..55d26c2 100644
--- a/docs/personal-project/rework-math/index.html
+++ b/docs/personal-project/rework-math/index.html
@@ -1,4 +1,4 @@
-rework-math | OhDoyleRules

Go home

I created a plugin for Rework CSS preprocessor to do math. Here is the github repo. It is also my first ever NPM package and it can be found on the website here.

/* input */
+rework-math | OhDoyleRules

Go home

I created a plugin for Rework CSS preprocessor to do math. Here is the github repo. It is also my first ever NPM package and it can be found on the website here.

/* input */
 div {
   padding: math(5+5px);
 }
diff --git a/docs/personal-project/rework-shade/index.html b/docs/personal-project/rework-shade/index.html
index e290654..64976aa 100644
--- a/docs/personal-project/rework-shade/index.html
+++ b/docs/personal-project/rework-shade/index.html
@@ -1,4 +1,4 @@
-rework-shade | OhDoyleRules

Go home

I created another plugin for Rework that makes it easy to do lighten and darken functions. I called it rework-shade. This package is also available on NPM here.

Here is the basic usage.

/* input */
+rework-shade | OhDoyleRules

Go home

I created another plugin for Rework that makes it easy to do lighten and darken functions. I called it rework-shade. This package is also available on NPM here.

Here is the basic usage.

/* input */
 body {
   padding: 10px;
   background: shade(rgba(0, 0, 0, 0.5), 5);
diff --git a/docs/personal-project/simple-binder/index.html b/docs/personal-project/simple-binder/index.html
index 6c35da7..51ef669 100644
--- a/docs/personal-project/simple-binder/index.html
+++ b/docs/personal-project/simple-binder/index.html
@@ -1,4 +1,4 @@
-Simple Binder | OhDoyleRules

Go home

The other day I was working on a custom form that had a lot of javascript interaction. It got a little too far before I realized I should have been using something like Angular.js. I was looking for a simple one-way databinding library, but I couldn't find anything that wasn't overkill.

So I created Simple Binder. Simple Binder is a zero dependency one-way databinder for javascript. The great thing about it is that, not only is it very simple, but it is super small as well. No dependencies is also nice.

Using the lib is pretty straightforward. Here is the markup required for a simplebinder element:

<p data-model="number">number</p>
+Simple Binder | OhDoyleRules

Go home

The other day I was working on a custom form that had a lot of javascript interaction. It got a little too far before I realized I should have been using something like Angular.js. I was looking for a simple one-way databinding library, but I couldn't find anything that wasn't overkill.

So I created Simple Binder. Simple Binder is a zero dependency one-way databinder for javascript. The great thing about it is that, not only is it very simple, but it is super small as well. No dependencies is also nice.

Using the lib is pretty straightforward. Here is the markup required for a simplebinder element:

<p data-model="number">number</p>
 <input type="number" data-controller="number" />
 

As you can see, you must have a data-model and a data-controller set on your items. Models are like the destination for the data-controllers value.

This would be the javascript for this element:

var sb = SimpleBinder('number', function(input, model) {
   console.log(input.value);
@@ -14,4 +14,4 @@
 }, function(input, model) {
   console.log(input.value);
 });
-

That's it. I will be adding the ability to remove a Model or Controller in the future. I tested this on a variety of devices. This library uses querySelectorAll, so if you don't have that... well you're fucked.

Check out the source on github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

That's it. I will be adding the ability to remove a Model or Controller in the future. I tested this on a variety of devices. This library uses querySelectorAll, so if you don't have that... well you're fucked.

Check out the source on github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/startup-canada-svg/index.html b/docs/personal-project/startup-canada-svg/index.html index d1582ef..1c69c95 100644 --- a/docs/personal-project/startup-canada-svg/index.html +++ b/docs/personal-project/startup-canada-svg/index.html @@ -1 +1 @@ -Startup Canada SVG logos | OhDoyleRules

Go home

I had to create these SVGs for the new Spotlander website. They are 2 of them, one for the parent company; Startup Canada. The last one is for the divisions, in this case: Startup London. I figured I would share.

startupcanada svg vector
startuplondon svg vector

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Startup Canada SVG logos | OhDoyleRules

Go home

I had to create these SVGs for the new Spotlander website. They are 2 of them, one for the parent company; Startup Canada. The last one is for the divisions, in this case: Startup London. I figured I would share.

startupcanada svg vector
startuplondon svg vector

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/sublime-node-snippets/index.html b/docs/personal-project/sublime-node-snippets/index.html index 0a394a2..a7e8120 100644 --- a/docs/personal-project/sublime-node-snippets/index.html +++ b/docs/personal-project/sublime-node-snippets/index.html @@ -1,10 +1,10 @@ -Sublime Node Snippets | OhDoyleRules

Go home

I created a huge snippet library based on the docs for node 10.26. There are 783 total right now (2014-03-25).

The way that I quickly made this big repository, was I wrote a script that would generate new sublime snippets based on a text file.

The converter just reads the text file line by line and then generates a .sublime-completions file.

There is a template that is sort of setup. So you can actually just clone the repo, drop in a new sources file, and then generate a new snippets library with the converter.

Here is an excerpt from the github repo:

Installing

Package Control

Just look for sublime-node-snippets on Package Control. It is called "Node Completions" on the site, but comes up as "sublime-node-snippets".

Manual Install

  • Open the Commands Palette (command+shift+p)
  • Package Control: Add Repository
  • Past in this repos URL
  • Press Enter
  • Open the palette again
  • press enter on "sublime-node-snippets"
  • watch it install

Using

Pressing . (period) will end the snippet lookup.

You will have better results if you pretend the period isn't needed. So if you are looking for fs.readdir, you would type fsread and you would see the results coming up.

Snippet Categories

Node Populars

  • async
  • underscore
  • lodash

Node Core

  • Assert
  • Buffer
  • Child
  • Console
  • Cluster
  • Crypto
  • Decoder
  • Domain
  • Dns
  • Event
  • Http
  • Https
  • Fs
  • Global
  • Module
  • Net
  • Path
  • Punnycode
  • Process
  • Querystring
  • Readline
  • Repl
  • Timers
  • Tls Ssl
  • Tty
  • Udp
  • Util
  • Url
  • Os
  • Vm
  • Zlib

Adding New Snippets

Here is how I quickly got all these snippets.

I will use Express as an example since it isn't in here.

First I went to the docs for the framework, and I looked to see what the code examples were wrapped in.

For the express docs site, the codes are shown in section h3 tags. So to quickly get the list, I ran the following code:

Array.prototype.slice.call(document.querySelectorAll("section h3"), 0).map(function(item){
+Sublime Node Snippets | OhDoyleRules

Go home

I created a huge snippet library based on the docs for node 10.26. There are 783 total right now (2014-03-25).

The way that I quickly made this big repository, was I wrote a script that would generate new sublime snippets based on a text file.

The converter just reads the text file line by line and then generates a .sublime-completions file.

There is a template that is sort of setup. So you can actually just clone the repo, drop in a new sources file, and then generate a new snippets library with the converter.

Here is an excerpt from the github repo:

Installing

Package Control

Just look for sublime-node-snippets on Package Control. It is called "Node Completions" on the site, but comes up as "sublime-node-snippets".

Manual Install

  • Open the Commands Palette (command+shift+p)
  • Package Control: Add Repository
  • Past in this repos URL
  • Press Enter
  • Open the palette again
  • press enter on "sublime-node-snippets"
  • watch it install

Using

Pressing . (period) will end the snippet lookup.

You will have better results if you pretend the period isn't needed. So if you are looking for fs.readdir, you would type fsread and you would see the results coming up.

Snippet Categories

Node Populars

  • async
  • underscore
  • lodash

Node Core

  • Assert
  • Buffer
  • Child
  • Console
  • Cluster
  • Crypto
  • Decoder
  • Domain
  • Dns
  • Event
  • Http
  • Https
  • Fs
  • Global
  • Module
  • Net
  • Path
  • Punnycode
  • Process
  • Querystring
  • Readline
  • Repl
  • Timers
  • Tls Ssl
  • Tty
  • Udp
  • Util
  • Url
  • Os
  • Vm
  • Zlib

Adding New Snippets

Here is how I quickly got all these snippets.

I will use Express as an example since it isn't in here.

First I went to the docs for the framework, and I looked to see what the code examples were wrapped in.

For the express docs site, the codes are shown in section h3 tags. So to quickly get the list, I ran the following code:

Array.prototype.slice.call(document.querySelectorAll("section h3"), 0).map(function(item){
   return item.textContent.trim();
 }).join("\n");
-

Then copied the output and pasted it in the sources.txt file. Done!

Cool Feature

The word callback will automagically be converted into a function.

Building

I went to each page of the node docs, and copied the functions. Then I wrote a converter to take each function and convert it to a snippet.

For Example, this line:

setTimeout(fun, delay)
+

Then copied the output and pasted it in the sources.txt file. Done!

Cool Feature

The word callback will automagically be converted into a function.

Building

I went to each page of the node docs, and copied the functions. Then I wrote a converter to take each function and convert it to a snippet.

For Example, this line:

setTimeout(fun, delay)
 

Is going to get converted to:

setTimeout(${1:fun}, ${2:delay})${0}
 

When the word callback appears, it will convert it to the standard fun snippet.

fs.readdir(path, callback)
 

will become

fs.readdir(${1:path}, function(${2:args}){
   ${3:// body}
 })${0}
-

sources.txt

This file is cool.

It is just a line-by-line output of the node docs functions. This is the file that is raked over to generate the snippets.

Running The Build

Just run php convert.php and it will rake the sources.txt file and then write the new snippet in the snippets folder.

Everything before the first ( will be used as the filename and tab snippet.

Contributing

Just add (or edit) a line in the source file. Then run php convert.php to generate the new snippets.

Why PHP?!

Well, PHP is actually pretty good at manipulating strings and writing files. Maybe at some point I will convert the converter and release it as a separate tool.

Source

You can find the source code on github. You can also install via package control by looking for sublime-node-snippets.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

sources.txt

This file is cool.

It is just a line-by-line output of the node docs functions. This is the file that is raked over to generate the snippets.

Running The Build

Just run php convert.php and it will rake the sources.txt file and then write the new snippet in the snippets folder.

Everything before the first ( will be used as the filename and tab snippet.

Contributing

Just add (or edit) a line in the source file. Then run php convert.php to generate the new snippets.

Why PHP?!

Well, PHP is actually pretty good at manipulating strings and writing files. Maybe at some point I will convert the converter and release it as a separate tool.

Source

You can find the source code on github. You can also install via package control by looking for sublime-node-snippets.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/switching-to-svg-on-grey-nimbus/index.html b/docs/personal-project/switching-to-svg-on-grey-nimbus/index.html index 6396c51..869d986 100644 --- a/docs/personal-project/switching-to-svg-on-grey-nimbus/index.html +++ b/docs/personal-project/switching-to-svg-on-grey-nimbus/index.html @@ -1,4 +1,4 @@ -Switching to SVG on Grey Nimbus | OhDoyleRules

Go home

So I recently bought the Sketch app for Mac. I am using it because I don't have illustrator. But to be honest, it is much better at doing small things. It's been about 2 hours switching the whole thing over and I have to say it is worth it.

I also found a SVG minifier that I will definitely be using in the future. It managed to save about 50-60% on each image. Which, for compression, is very good. After all is said and done, I saved about 10kb. Now this is not a lot but I also eliminated the use of retina.js. Which cause a second request for each image that has an @2x version. So on mobile I have made the site much faster.

Also, because of the way that FuelPHP does it's caching, I was not able to cache the images. Because it would append a query at the end for the cache and retina.js would not be able to find the retina version. That means that any retina device would take double(approximately) requests to get the full page.

I did something I think is rather clever. What I did was, since SVG support is high, I sniffed the user agent to see if it is one of the browsers that doesn't support SVG. Then I set a global and used that to define the extension I was going to use.

<a href="#welcome">
+Switching to SVG on Grey Nimbus | OhDoyleRules

Go home

So I recently bought the Sketch app for Mac. I am using it because I don't have illustrator. But to be honest, it is much better at doing small things. It's been about 2 hours switching the whole thing over and I have to say it is worth it.

I also found a SVG minifier that I will definitely be using in the future. It managed to save about 50-60% on each image. Which, for compression, is very good. After all is said and done, I saved about 10kb. Now this is not a lot but I also eliminated the use of retina.js. Which cause a second request for each image that has an @2x version. So on mobile I have made the site much faster.

Also, because of the way that FuelPHP does it's caching, I was not able to cache the images. Because it would append a query at the end for the cache and retina.js would not be able to find the retina version. That means that any retina device would take double(approximately) requests to get the full page.

I did something I think is rather clever. What I did was, since SVG support is high, I sniffed the user agent to see if it is one of the browsers that doesn't support SVG. Then I set a global and used that to define the extension I was going to use.

<a href="#welcome">
   <?php echo Asset::img('nimbus'.$ext, array("width"=>"275", "height"=>"57", "alt"=>"Grey Nimbus Logo")); ?>
 </a>
-

Now $ext would be equal to ".png" or ".svg" depending on the browser you were in. Now the changes are live so you can see that everything is all SVG! It should also look quite pretty on retina screens. Have a look. The site still loads in under 1 second. Which, according to Google, is a good thing.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Now $ext would be equal to ".png" or ".svg" depending on the browser you were in. Now the changes are live so you can see that everything is all SVG! It should also look quite pretty on retina screens. Have a look. The site still loads in under 1 second. Which, according to Google, is a good thing.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/vim-svg/index.html b/docs/personal-project/vim-svg/index.html index 3ede13d..7fb027a 100644 --- a/docs/personal-project/vim-svg/index.html +++ b/docs/personal-project/vim-svg/index.html @@ -1 +1 @@ -Vim Vector Logo | OhDoyleRules

Go home

Here is a beauty. People have been looking for this Vim logo in a vector format for quite some time. There is of course the old logo, but it looks pretty strange. It reminds me of Tron for some reason.

Anyway this was a pain to make because of all the layers and custom shaping of the V. Also I had to make a bunch of changes to everything once I tested it in the browser because it was all busted.

I usually test my SVGs in the browser as a final OK point. I know if it renders there, then everything should be fine. Also people want it for using as a retina icon now, since the advent of responsive design and development.

vim svg vector

If you happen to use this for anything, it would be nice to recieve some credit for it. I actually couldn't find who made the original Vim logo. It might just be lost in time.

Update

I made a icns version for OSX. You can download it here.

You can change the MacVim icon if you want. I happen to think this one is a little nicer.

Update 2

I have found another person who wanted a new vim icon. They added it to the fork of MacVim. Here is the commit from his repo with the new icon.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Vim Vector Logo | OhDoyleRules

Go home

Here is a beauty. People have been looking for this Vim logo in a vector format for quite some time. There is of course the old logo, but it looks pretty strange. It reminds me of Tron for some reason.

Anyway this was a pain to make because of all the layers and custom shaping of the V. Also I had to make a bunch of changes to everything once I tested it in the browser because it was all busted.

I usually test my SVGs in the browser as a final OK point. I know if it renders there, then everything should be fine. Also people want it for using as a retina icon now, since the advent of responsive design and development.

vim svg vector

If you happen to use this for anything, it would be nice to recieve some credit for it. I actually couldn't find who made the original Vim logo. It might just be lost in time.

Update

I made a icns version for OSX. You can download it here.

You can change the MacVim icon if you want. I happen to think this one is a little nicer.

Update 2

I have found another person who wanted a new vim icon. They added it to the fork of MacVim. Here is the commit from his repo with the new icon.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/personal-project/zepto-drag-swap/index.html b/docs/personal-project/zepto-drag-swap/index.html index 4125571..e8a0991 100644 --- a/docs/personal-project/zepto-drag-swap/index.html +++ b/docs/personal-project/zepto-drag-swap/index.html @@ -1 +1 @@ -Zepto Drag & Swap | OhDoyleRules

Go home

I created a little plugin for zepto.js called Drag & Swap. Here is the github link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Zepto Drag & Swap | OhDoyleRules

Go home

I created a little plugin for zepto.js called Drag & Swap. Here is the github link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/portfolio/grey-nimbus-website/index.html b/docs/portfolio/grey-nimbus-website/index.html index 7ed71a8..500637c 100644 --- a/docs/portfolio/grey-nimbus-website/index.html +++ b/docs/portfolio/grey-nimbus-website/index.html @@ -1 +1 @@ -Grey Nimbus website | OhDoyleRules

Go home

Finally took the time and initiative to launch Grey Nimbus, my business. The website itself is built using FuelPHP. The reason I chose it was because I was curious and it was pretty light weight.

The website has no database so that was pretty much ignored. But I wanted easy validation for forms and a good email library. This is handled nicely by Fuel. The docs are mostly good. I had to do a little google searching when I was trying to tie things in together.

I knew I wanted parallax and responsive. I found a fork of the cool-kitten framework that I liked and created my own fork.

In my fork, the first thing I did was add retina.js. I know some people knock retina.js because it uses javascript to replace images with an @2x version.

Maybe in the next update I will switch to SVG images, but I digress.

The second thing I did was create a build script to concatenate and minify the javascript and css. It uses clean-css and uglifyjs.

The reason I didn't use something like grunt.js is because I only had 2 tasks to run and it was only when I was ready to push to the server. I added a condition in the head and footer to check to see if the site was in production and load the minified versions or the normal big list of separate files. The rest was just testing and building. Creating content is always tough for me. I want to sound casual which some people don't like. But it reflects me better so, whatever.

I ended up adding animate.css too. I didn't create any fallbacks for no css animation support because they just won't show if you don't have support. I've used the library before and it is pretty great. I created a custom build because I am trying to keep my footprint small. In the end, even with all the images and javascript libs, I managed to get the page to load in under 1 second (at least on desktop...).

In conclusion, I am very happy with it. I have gotten a lot of positive feedback and suggestions. Everyday I make a few minor tweaks. Please check it out.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Grey Nimbus website | OhDoyleRules

Go home

Finally took the time and initiative to launch Grey Nimbus, my business. The website itself is built using FuelPHP. The reason I chose it was because I was curious and it was pretty light weight.

The website has no database so that was pretty much ignored. But I wanted easy validation for forms and a good email library. This is handled nicely by Fuel. The docs are mostly good. I had to do a little google searching when I was trying to tie things in together.

I knew I wanted parallax and responsive. I found a fork of the cool-kitten framework that I liked and created my own fork.

In my fork, the first thing I did was add retina.js. I know some people knock retina.js because it uses javascript to replace images with an @2x version.

Maybe in the next update I will switch to SVG images, but I digress.

The second thing I did was create a build script to concatenate and minify the javascript and css. It uses clean-css and uglifyjs.

The reason I didn't use something like grunt.js is because I only had 2 tasks to run and it was only when I was ready to push to the server. I added a condition in the head and footer to check to see if the site was in production and load the minified versions or the normal big list of separate files. The rest was just testing and building. Creating content is always tough for me. I want to sound casual which some people don't like. But it reflects me better so, whatever.

I ended up adding animate.css too. I didn't create any fallbacks for no css animation support because they just won't show if you don't have support. I've used the library before and it is pretty great. I created a custom build because I am trying to keep my footprint small. In the end, even with all the images and javascript libs, I managed to get the page to load in under 1 second (at least on desktop...).

In conclusion, I am very happy with it. I have gotten a lot of positive feedback and suggestions. Everyday I make a few minor tweaks. Please check it out.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/portfolio/index.html b/docs/portfolio/index.html index af9452b..6b7ab98 100644 --- a/docs/portfolio/index.html +++ b/docs/portfolio/index.html @@ -1 +1 @@ -Portfolio | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file +Portfolio | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file diff --git a/docs/portfolio/my-old-website/index.html b/docs/portfolio/my-old-website/index.html index 84d8319..5a42e5c 100644 --- a/docs/portfolio/my-old-website/index.html +++ b/docs/portfolio/my-old-website/index.html @@ -1 +1 @@ -My Old Website | OhDoyleRules

Go home

I decided to host my old website because I wanted to show some work with Modernizr.

Looking back it is actually pretty cool! I kind of miss it haha. It seems to run much better than I remember. I think that is because it was the days of chrome 18 and Firefox 10 which had spotty 3d transform support.

My Old Site

Maybe someday I can make a sub-site or a project site based off of that design. This was also one of my first sites to use AJAX. It was pretty frustrating and hard at the time. It is funny looking at my code and the way I handled things back then. I read it and feel like I am looking at a strangers code.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +My Old Website | OhDoyleRules

Go home

I decided to host my old website because I wanted to show some work with Modernizr.

Looking back it is actually pretty cool! I kind of miss it haha. It seems to run much better than I remember. I think that is because it was the days of chrome 18 and Firefox 10 which had spotty 3d transform support.

My Old Site

Maybe someday I can make a sub-site or a project site based off of that design. This was also one of my first sites to use AJAX. It was pretty frustrating and hard at the time. It is funny looking at my code and the way I handled things back then. I read it and feel like I am looking at a strangers code.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/portfolio/new-business-cards/index.html b/docs/portfolio/new-business-cards/index.html index 7ade696..3990ea7 100644 --- a/docs/portfolio/new-business-cards/index.html +++ b/docs/portfolio/new-business-cards/index.html @@ -1 +1 @@ -New business cards | OhDoyleRules

Go home

Picked up my new cards right before Dig London. They have my new favourite emoticon on the back too.

20121114-142327.jpg

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +New business cards | OhDoyleRules

Go home

Picked up my new cards right before Dig London. They have my new favourite emoticon on the back too.

20121114-142327.jpg

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 6a0e9eb..d0f2953 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -10,6 +10,10 @@ https://ohdoylerules.com/android/jellybean-nexuss/ 2012-07-20 + + https://ohdoylerules.com/apps/light-weight/privacy-policy/ + 2024-12-01 + https://ohdoylerules.com/demo/ diff --git a/docs/snippets/animation-events-in-javascript/index.html b/docs/snippets/animation-events-in-javascript/index.html index b6b9923..4d0a2f7 100644 --- a/docs/snippets/animation-events-in-javascript/index.html +++ b/docs/snippets/animation-events-in-javascript/index.html @@ -9,7 +9,7 @@
opacity: 1; } } -

So now in javascript I might have a button click that adds 'on' to my modal. In my javascript I would have a function to detect different animation events(start, iterate and end). Here is the code that I modified from a SitePoint Article that was posting about the same topic.

var pfx = ["webkit", "moz", "MS", "o", ""];
+

So now in javascript I might have a button click that adds 'on' to my modal. In my javascript I would have a function to detect different animation events(start, iterate and end). Here is the code that I modified from a SitePoint Article that was posting about the same topic.

var pfx = ["webkit", "moz", "MS", "o", ""];
 function doAnim(element, animClass, type, callback) {
   var p = 0, l = pfx.length;
   function removeAndCall(){
diff --git a/docs/snippets/apax-in-htdocs/index.html b/docs/snippets/apax-in-htdocs/index.html
index fa5b10b..8650371 100644
--- a/docs/snippets/apax-in-htdocs/index.html
+++ b/docs/snippets/apax-in-htdocs/index.html
@@ -1,3 +1,3 @@
-Apax apache theme in htdocs | OhDoyleRules

Go home

I was tired of looking at the ugly default no-style of the htdocs file listing. I had seen Apaxy theme before and thought it was really nice. But I couldn't figure out how to get it to work with the default htdocs MAMP folder. I tried again tonight, and I got it working without much hassle.

  1. Download Apaxy and move everything from the apaxy folder into your MAMP htdocs folder.
  2. open "htaccess.txt" and replace "/{FOLDERNAME}/theme" with "/.theme/"
  3. rename the "htaccess.txt" file to ".htaccess" which will hide the file
  4. rename the "theme" folder to ".theme" which will hide the directory
  5. go to your localhost url and refresh
  6. enjoy a not-ugly page

Now you can edit the files in the ".theme" folder and style your page. I changed the ".wrapper" to have no max-width or margin, this way it was full screen.

apaxy theme applied to htdocs

Above is a screenshot of what my htdocs/localhost:8888 now looks like.

OPTIONAL

You can also hide the ".theme" folder. You will see a section that looks like this, in your .htaccess file:

# HIDE /theme DIRECTORY
+Apax apache theme in htdocs | OhDoyleRules

Go home

I was tired of looking at the ugly default no-style of the htdocs file listing. I had seen Apaxy theme before and thought it was really nice. But I couldn't figure out how to get it to work with the default htdocs MAMP folder. I tried again tonight, and I got it working without much hassle.

  1. Download Apaxy and move everything from the apaxy folder into your MAMP htdocs folder.
  2. open "htaccess.txt" and replace "/{FOLDERNAME}/theme" with "/.theme/"
  3. rename the "htaccess.txt" file to ".htaccess" which will hide the file
  4. rename the "theme" folder to ".theme" which will hide the directory
  5. go to your localhost url and refresh
  6. enjoy a not-ugly page

Now you can edit the files in the ".theme" folder and style your page. I changed the ".wrapper" to have no max-width or margin, this way it was full screen.

apaxy theme applied to htdocs

Above is a screenshot of what my htdocs/localhost:8888 now looks like.

OPTIONAL

You can also hide the ".theme" folder. You will see a section that looks like this, in your .htaccess file:

# HIDE /theme DIRECTORY
 IndexIgnore .htaccess /.theme
 

The old version should read "/theme" and not "/.theme". If change this line, it will NOT show the .theme folder in the localhost listing.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/autocomplete-tailwind-classes/index.html b/docs/snippets/autocomplete-tailwind-classes/index.html index 4998bc1..79a6f18 100644 --- a/docs/snippets/autocomplete-tailwind-classes/index.html +++ b/docs/snippets/autocomplete-tailwind-classes/index.html @@ -1,4 +1,4 @@ -Autocomplete TailwindCSS In Custom Attributes/Strings | OhDoyleRules

Go home

If you are using TailwindCSS along with their extension for completing tailwind classes but you are using styled components, custom attributes/props for class names, or packages like twin.macro, then autocomplete for class names might not work properly for you.

There is a setting inside the language server for tailwind that let's you provide a custom regex for when/where you want the tailwind autocomplete to work. By default it works inside class and className. But what if we want to change that? For, say, a tagged template literal?

You can use the following config for various types/styles of solutions for writing tailwind classes:

"tailwindCSS.experimental.classRegex": [
+Autocomplete TailwindCSS In Custom Attributes/Strings | OhDoyleRules

Go home

If you are using TailwindCSS along with their extension for completing tailwind classes but you are using styled components, custom attributes/props for class names, or packages like twin.macro, then autocomplete for class names might not work properly for you.

There is a setting inside the language server for tailwind that let's you provide a custom regex for when/where you want the tailwind autocomplete to work. By default it works inside class and className. But what if we want to change that? For, say, a tagged template literal?

You can use the following config for various types/styles of solutions for writing tailwind classes:

"tailwindCSS.experimental.classRegex": [
   ["classnames\\(([^)]*)\\)", "'([^']*)'"],
   "class=\"([^\"]*)", // <div class="..." />
   "tw`([^`]*)", // tw`...`
@@ -7,4 +7,4 @@
   "tw\\.\\w+`([^`]*)", // tw.xxx`...`
   "tw\\(.*?\\)`([^`]*)" // tw(Component)`...`
 ],
-

The above rules are what I am using with React and the twin.macro package. I can complete under various tw props or tagged templates which is exactly what I need for the project I'm on.

If you would like to see some of the other use cases for this setting, you can browse the issues on Github. As you can see, it is a common feature that is reached for when you need to customize the location of the autocomplete trigger.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

The above rules are what I am using with React and the twin.macro package. I can complete under various tw props or tagged templates which is exactly what I need for the project I'm on.

If you would like to see some of the other use cases for this setting, you can browse the issues on Github. As you can see, it is a common feature that is reached for when you need to customize the location of the autocomplete trigger.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/backup-mysql-and-email-it/index.html b/docs/snippets/backup-mysql-and-email-it/index.html index a48a639..ba87cd6 100644 --- a/docs/snippets/backup-mysql-and-email-it/index.html +++ b/docs/snippets/backup-mysql-and-email-it/index.html @@ -1 +1 @@ -Backup MySQL And Email It | OhDoyleRules

Go home

Recently, a friend of mine asked me what we use for managing the backups for our clients. I mentioned that we use mysqldump running on a CRON schedule. He said that he used a paid service for managing all the servers and their backups. He mentioned it sends to an Amazon S3 bucket, and also sends a notification.

With my setup, he noted that I could be in trouble if the hard drive failed, or the site gets wiped because my backups are stored locally right beside the site itself.

I thought to myself, "Hmmm. What would help me back these files up, let me know that it is done, access them quickly, and also cost no money?". Email + CRON. Email is a pretty reasonable solution for this.

With Email, you get the following:

  • A notification of when the backup is done
  • An "offsite" backup of the file
  • A searchable history of the files/backups
  • Forwarding of the backup to someone else
  • CC multiple accounts and have the backup available to multiple people
  • Send a copy to the client, so they can have one too

So I whipped up a script with mysqldump, curl, and CRON.

Although you can send email sendmail or mail, I opted for Mailgun. It is something we are already using, so hooking it up took no time at all. They also have an excellent API, which is faster than SMTP.

On top of the regular email features I listed above, with Mailgun, I also get:

  • read receipts
  • delivery reports
  • logging
  • web hooks (you can catch emails with individual subjects or recipients)

So, without spending any money, I can get a SaaS experience using some built-in tools.

You can see the script here:

I included some rules for the crontab settings. I used 0 0 * * 1 * which is once a week on Monday at midnight.

You can still run the script above without using CRON. Just put it somewhere on your server and run it like you normally would: ./backup-mysql-db.sh.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Backup MySQL And Email It | OhDoyleRules

Go home

Recently, a friend of mine asked me what we use for managing the backups for our clients. I mentioned that we use mysqldump running on a CRON schedule. He said that he used a paid service for managing all the servers and their backups. He mentioned it sends to an Amazon S3 bucket, and also sends a notification.

With my setup, he noted that I could be in trouble if the hard drive failed, or the site gets wiped because my backups are stored locally right beside the site itself.

I thought to myself, "Hmmm. What would help me back these files up, let me know that it is done, access them quickly, and also cost no money?". Email + CRON. Email is a pretty reasonable solution for this.

With Email, you get the following:

  • A notification of when the backup is done
  • An "offsite" backup of the file
  • A searchable history of the files/backups
  • Forwarding of the backup to someone else
  • CC multiple accounts and have the backup available to multiple people
  • Send a copy to the client, so they can have one too

So I whipped up a script with mysqldump, curl, and CRON.

Although you can send email sendmail or mail, I opted for Mailgun. It is something we are already using, so hooking it up took no time at all. They also have an excellent API, which is faster than SMTP.

On top of the regular email features I listed above, with Mailgun, I also get:

  • read receipts
  • delivery reports
  • logging
  • web hooks (you can catch emails with individual subjects or recipients)

So, without spending any money, I can get a SaaS experience using some built-in tools.

You can see the script here:

I included some rules for the crontab settings. I used 0 0 * * 1 * which is once a week on Monday at midnight.

You can still run the script above without using CRON. Just put it somewhere on your server and run it like you normally would: ./backup-mysql-db.sh.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/bash-random-password-generation/index.html b/docs/snippets/bash-random-password-generation/index.html index 5cfb56e..4147f71 100644 --- a/docs/snippets/bash-random-password-generation/index.html +++ b/docs/snippets/bash-random-password-generation/index.html @@ -1,4 +1,4 @@ -Randomly Generate A Password In Bash | OhDoyleRules

Go home

When installing or setting up frameworks, in this case I was playing around with Laravel, you usually need to set a session/secret/encryption key.

I know why this is, but I always end up looking around for some random password generator so I can get a random string that is exactly 32 characters. Isn't there an easier way?!?!

Yes there is. If you have the magical OpenSSL installed, which most do, you can use it to generate a random string.

I found a article online that uses base64 to generate a string of a certain length. The only thing is that base64 is padded with 8 bits. Which means that if you want 32 then you need to use 24. This goes up exponentially as the number gets bigger. So there is a trim part of the function that clips off the extra characters.

Here is the function broken down into steps:

  • pass a number to the function
  • cut the resulting string
  • generate a base64 string using that number
  • echo out the result
  • copy the output to the clipboard with a newline
  • echo out a success

I put a check in there if the argument is not a number. This is just for the dummies out there.

pbcopy is not defined

You are probably on Linux. I found this little snippet for the lazy. This way you can forget about translating it each time.

alias pbcopy="xsel --clipboard --input"
+Randomly Generate A Password In Bash | OhDoyleRules

Go home

When installing or setting up frameworks, in this case I was playing around with Laravel, you usually need to set a session/secret/encryption key.

I know why this is, but I always end up looking around for some random password generator so I can get a random string that is exactly 32 characters. Isn't there an easier way?!?!

Yes there is. If you have the magical OpenSSL installed, which most do, you can use it to generate a random string.

I found a article online that uses base64 to generate a string of a certain length. The only thing is that base64 is padded with 8 bits. Which means that if you want 32 then you need to use 24. This goes up exponentially as the number gets bigger. So there is a trim part of the function that clips off the extra characters.

Here is the function broken down into steps:

  • pass a number to the function
  • cut the resulting string
  • generate a base64 string using that number
  • echo out the result
  • copy the output to the clipboard with a newline
  • echo out a success

I put a check in there if the argument is not a number. This is just for the dummies out there.

pbcopy is not defined

You are probably on Linux. I found this little snippet for the lazy. This way you can forget about translating it each time.

alias pbcopy="xsel --clipboard --input"
 alias pbpaste="xsel --clipboard --output"
 

Now for the actual shell function:

Code

# if the argument is a number
 # cut the string so that there is no base64 padding
diff --git a/docs/snippets/bash-select-example/index.html b/docs/snippets/bash-select-example/index.html
index 016a702..59c67f6 100644
--- a/docs/snippets/bash-select-example/index.html
+++ b/docs/snippets/bash-select-example/index.html
@@ -1,4 +1,4 @@
-Bash select example | OhDoyleRules

Go home

I recently bought 2 raspberry pi computers. One is for home, and one is for the office.

Since we have dynamic IPs setup in the office, and I have the same at my house, I needed to be able to connect using the MAC address of the pi. I ended up writing a little script to get the IP based on the MAC Address, and then ssh into the computer. Pretty slick.

To make my life easier I used the select command in bash. The documentation for select leaves a lot to be desired. So I had to fiddle with it until I got it right. Here is a simple boilerplate for a bash script using select:

Function

#!/usr/bin/env bash
+Bash select example | OhDoyleRules

Go home

I recently bought 2 raspberry pi computers. One is for home, and one is for the office.

Since we have dynamic IPs setup in the office, and I have the same at my house, I needed to be able to connect using the MAC address of the pi. I ended up writing a little script to get the IP based on the MAC Address, and then ssh into the computer. Pretty slick.

To make my life easier I used the select command in bash. The documentation for select leaves a lot to be desired. So I had to fiddle with it until I got it right. Here is a simple boilerplate for a bash script using select:

Function

#!/usr/bin/env bash
 
 speak() {
   echo "$1 $2"
diff --git a/docs/snippets/bitbucket-weekly-reports-using-integromat-make/index.html b/docs/snippets/bitbucket-weekly-reports-using-integromat-make/index.html
index b82718b..af61a1e 100644
--- a/docs/snippets/bitbucket-weekly-reports-using-integromat-make/index.html
+++ b/docs/snippets/bitbucket-weekly-reports-using-integromat-make/index.html
@@ -1,4 +1,4 @@
-Bitbucket Weekly Reports Using Make (Integromat) | OhDoyleRules

Go home

At my job, we like to keep our team updated with all the dev work we do each week. This means we have meetings every Monday to review the work that has happened since the previous week.

I often like to review the work done over that time frame by looking at the projects git log for all the pull-requests merged during that previous week.

Now, I could open up the develop branch and run a git shortlog --since "1 week ago" every Monday, but I would much rather automate this task so I don't have to remember to do anything and it stays consistent. I can just wake up on Monday and see a pretty little report printed in the project Slack channel.

The way I accomplished this is using Make.com (formerly Integromat). This is a "no-code" tool that allows you to use visual programming to build tools and apps.

Let's break down the solution I came up with:

With a final output something like this:

+Bitbucket Weekly Reports Using Make (Integromat) | OhDoyleRules

Go home

At my job, we like to keep our team updated with all the dev work we do each week. This means we have meetings every Monday to review the work that has happened since the previous week.

I often like to review the work done over that time frame by looking at the projects git log for all the pull-requests merged during that previous week.

Now, I could open up the develop branch and run a git shortlog --since "1 week ago" every Monday, but I would much rather automate this task so I don't have to remember to do anything and it stays consistent. I can just wake up on Monday and see a pretty little report printed in the project Slack channel.

The way I accomplished this is using Make.com (formerly Integromat). This is a "no-code" tool that allows you to use visual programming to build tools and apps.

Let's break down the solution I came up with:

With a final output something like this:

 
BitBucket Reporter [APP] 9:01 AM
List of BitBucket PRs since 2022-07-25 [1 of 2] #824 - Feature/events [MERGED] by James Doyle @@ -18,4 +18,4 @@
BitBucket Reporter [APP] 9:01 AM
#837 - Bugfix/user tickets [OPEN] by Jr. Developer

Here is the overall solution laid out. As you can see, it wasn't as simple as it might seem.

I will go through the solution step-by-step and explain each node. I will assume you already have Slack and BitBucket connected.

Step 1

First we need set the schedule to run each Monday. Nothing fancy here. I set it to 9:01 just because I am usually on the computer by then so I will see the channel notification.

Step 2

Here is the reason this is so complicated: the BitBucket API is paginated and there is no way to easily page through the results. So we need to loop through each page from the results and append them to a variable we eventually send to Slack.

In this request, we ask BitBucket for the "merged" and "open" pull-requests that happened last week. There was no nice way to calculate the previous weeks date, so we have this fun code to get the date of the previous Monday.

You can copy the text below for the query:

(state="merged" OR state="open") AND created_on>="{{formatDate(setDay(parseDate(timestamp - 604800; "X"); "monday"); "YYYY-MM-DD")}}"
 

The only reason we run this first request just so we can get the page count that we will then use to create a loop to make the real requests.

Step 3

Here is the loop that we use to iterate over the pages in the results. We check the page size and make sure we repeat the right amount of times.

Our first route (the top one) is the condition that the loop continues to run in. You can see the condition is a basic for loop that repeats for each page in the loop.

Step 4

Here is the request that happens within the loop. It is identical to the first one but we have a page parameter so that we can get each page of the results.

Step 5

All we are doing in this node is sorting the pull requests by their title. We do this because the PR number is in the title and not sorting it would look off.

Step 6

Here we are defining the string template for the PR titles that will go into our bulleted list. The output of this will be a single variable that contains all the row strings.

Step 7

Nothing fancy here. Just getting the variable the we will be pushing the results of our text aggregation to.

Step 8

Here we are updating our actual results that will be sent to Slack. We break up the list with a nice header that is chunked by page.

Here is the code in the box for setting the variable:

{{21.results}}{{newline}}*List of Project PRs since {{formatDate(setDay(parseDate(timestamp - 604800; "X"); "monday"); "YYYY-MM-DD")}} [{{13.i}} of {{ceil(1.body.size / 1.body.pagelen)}}]*{{newline}}{{11.text}}
-

Step 9

Here is the other condition in the router that will run at the end of the loop. It checks that the loop variable is larger that the page count.

Our first step in the end case is to get the variable. We are passing this to the Slack body.

Step 10

Our last step is to just shoot off that text to Slack. We have already formatted the text we are sending so we just slap it in.

All Done!

That is it! Hopefully this helps anyone that is trying to use the BitBucket in Make.com (Integromat).

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Step 9

Here is the other condition in the router that will run at the end of the loop. It checks that the loop variable is larger that the page count.

Our first step in the end case is to get the variable. We are passing this to the Slack body.

Step 10

Our last step is to just shoot off that text to Slack. We have already formatted the text we are sending so we just slap it in.

All Done!

That is it! Hopefully this helps anyone that is trying to use the BitBucket in Make.com (Integromat).

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/copy-file-path-clipboard-osx/index.html b/docs/snippets/copy-file-path-clipboard-osx/index.html index e9fab1a..620b084 100644 --- a/docs/snippets/copy-file-path-clipboard-osx/index.html +++ b/docs/snippets/copy-file-path-clipboard-osx/index.html @@ -1,5 +1,5 @@ -Copy filepath to clipboard in OSX | OhDoyleRules

Go home

At WARPAINT, we use Dropbox for collaborating on our files. This is awesome, but a lot of the times you get some pretty nasty file paths. Especially when you are trying to guide someone to a place where you saved a file.

I wanted to solve this problem by creating an AppleScript service that would allow everyone to Copy the selected file's path to the clipboard. Here is how I did it.

+++

We are going to be using Automator to create a new service. Here is the description of Automator in case you don't know what it is:

Automator is an application developed by Apple Inc. for OS X that implements point-and-click (or drag and drop) creation of workflows for automating repetitive tasks into batches for quicker alteration, thus saving time and effort over human intervention to manually change each file separately.

So the first thing is to open Automator and create a new service. Like so:

Then you need to select files or folders for "Service receives selected" and choose Finder.app for the second option. The do a search for applescript and drag the Run AppleScript choice into the window on the right.

You will need to paste the following code into the AppleScript window:

tell application "Finder"
+Copy filepath to clipboard in OSX | OhDoyleRules

Go home

At WARPAINT, we use Dropbox for collaborating on our files. This is awesome, but a lot of the times you get some pretty nasty file paths. Especially when you are trying to guide someone to a place where you saved a file.

I wanted to solve this problem by creating an AppleScript service that would allow everyone to Copy the selected file's path to the clipboard. Here is how I did it.

+++

We are going to be using Automator to create a new service. Here is the description of Automator in case you don't know what it is:

Automator is an application developed by Apple Inc. for OS X that implements point-and-click (or drag and drop) creation of workflows for automating repetitive tasks into batches for quicker alteration, thus saving time and effort over human intervention to manually change each file separately.

So the first thing is to open Automator and create a new service. Like so:

Then you need to select files or folders for "Service receives selected" and choose Finder.app for the second option. The do a search for applescript and drag the Run AppleScript choice into the window on the right.

You will need to paste the following code into the AppleScript window:

tell application "Finder"
   set sel to the selection as text
   set the clipboard to POSIX path of sel
 end tell
-

When that is all done, it should look something like this.

Go to File > Save or press ⌘S. Do not Save-As. Enter in Copy Path To Clipboard as the name. It shouldn't ask for a location, it will just show an input field. This is perfectly fine.

Now open a new finder window and go to Finder > Services > Services Preferences... or System Preferences > Keyboard > Shortcuts. Select services on the left menu if it isn't already and scroll down to find Copy Path To Clipboard. This will open a window like this:

Click on that item and make sure it is checked off, it should be by default. Then add a shortcut by clicking on the right side where it says "add shortcut". I made mine ⌃⌘\. But if you have Alfred.app that might conflict with it's copy feature. So you choose.

You can use these steps to run any AppleScript on a file you choose. Pretty slick!

Now when you have a file of folder selected in the Finder, you can right-click, go to Services, and select Copy Path To Clipboard!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

When that is all done, it should look something like this.

Go to File > Save or press ⌘S. Do not Save-As. Enter in Copy Path To Clipboard as the name. It shouldn't ask for a location, it will just show an input field. This is perfectly fine.

Now open a new finder window and go to Finder > Services > Services Preferences... or System Preferences > Keyboard > Shortcuts. Select services on the left menu if it isn't already and scroll down to find Copy Path To Clipboard. This will open a window like this:

Click on that item and make sure it is checked off, it should be by default. Then add a shortcut by clicking on the right side where it says "add shortcut". I made mine ⌃⌘\. But if you have Alfred.app that might conflict with it's copy feature. So you choose.

You can use these steps to run any AppleScript on a file you choose. Pretty slick!

Now when you have a file of folder selected in the Finder, you can right-click, go to Services, and select Copy Path To Clipboard!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/easy-command-line-reverse-geocoding/index.html b/docs/snippets/easy-command-line-reverse-geocoding/index.html index 447553f..1c30165 100644 --- a/docs/snippets/easy-command-line-reverse-geocoding/index.html +++ b/docs/snippets/easy-command-line-reverse-geocoding/index.html @@ -1,4 +1,4 @@ -Easy Command Line Reverse Geocoding | OhDoyleRules

Go home

Using this function you can easily reverse geocode an address into a lat and lang position. This uses the jq executable and the Google Maps API.

Requirements

This little snippet requires jq to be installed. It is very easy to install.

From the site:

jq is a lightweight and flexible command-line JSON processor

It is multi-platform, so no worries for Windows users.

Here is the meat:

Function

function reverse-geocode() {
+Easy Command Line Reverse Geocoding | OhDoyleRules

Go home

Using this function you can easily reverse geocode an address into a lat and lang position. This uses the jq executable and the Google Maps API.

Requirements

This little snippet requires jq to be installed. It is very easy to install.

From the site:

jq is a lightweight and flexible command-line JSON processor

It is multi-platform, so no worries for Windows users.

Here is the meat:

Function

function reverse-geocode() {
   # replace spaces with + signs
   STRING=$(echo $1 | tr ' ' '+')
   # save results
diff --git a/docs/snippets/easy-ffmpeg-video-posters/index.html b/docs/snippets/easy-ffmpeg-video-posters/index.html
index 7c9fd83..3c9fd44 100644
--- a/docs/snippets/easy-ffmpeg-video-posters/index.html
+++ b/docs/snippets/easy-ffmpeg-video-posters/index.html
@@ -1,4 +1,4 @@
-Easy FFmpeg Video Posters | OhDoyleRules

Go home

A week ago I was tasked with uploading about 20 different videos to a CMS. Normally for the HTML5 Video element to look nice, you should upload a poster image so that there can be something showing before the video starts to play.

In my case, I had to generate a poster for each of these 20 videos. This would have taken a long time, so I scripted it using FFmpeg!

Here is the script:

#!/usr/bin/env bash
+Easy FFmpeg Video Posters | OhDoyleRules

Go home

A week ago I was tasked with uploading about 20 different videos to a CMS. Normally for the HTML5 Video element to look nice, you should upload a poster image so that there can be something showing before the video starts to play.

In my case, I had to generate a poster for each of these 20 videos. This would have taken a long time, so I scripted it using FFmpeg!

Here is the script:

#!/usr/bin/env bash
 
 # take in mp4, take screenshot at 5 seconds
 # output same filename, but with jpg extension
diff --git a/docs/snippets/grunt-terminal-notifier-setup/index.html b/docs/snippets/grunt-terminal-notifier-setup/index.html
index 1fe3da9..1db03d5 100644
--- a/docs/snippets/grunt-terminal-notifier-setup/index.html
+++ b/docs/snippets/grunt-terminal-notifier-setup/index.html
@@ -1,4 +1,4 @@
-grunt terminal-notifier setup | OhDoyleRules

Go home

I just downloaded the new Mountain Lion, finally. One of the biggest new things is the cool little native notifications akin to growl. I thought it would be cool to get a nice notification when my "grunt watch" task finished. First things first. You need to install terminal-notifier. This allows you to interact with the native OSX notifications system.

There is a ruby gem and a standalone ".app". Once this is installed, you will need to grab the grunt-growl plugin. There are more instructions there for the terminal-notifier app. Now you will need to setup a new task in your gruntfile:

growl: {
+grunt terminal-notifier setup | OhDoyleRules

Go home

I just downloaded the new Mountain Lion, finally. One of the biggest new things is the cool little native notifications akin to growl. I thought it would be cool to get a nice notification when my "grunt watch" task finished. First things first. You need to install terminal-notifier. This allows you to interact with the native OSX notifications system.

There is a ruby gem and a standalone ".app". Once this is installed, you will need to grab the grunt-growl plugin. There are more instructions there for the terminal-notifier app. Now you will need to setup a new task in your gruntfile:

growl: {
   css: {
     title: 'STYLUS BUILT',
     message: 'css/style.css has been created'
diff --git a/docs/snippets/hostmonster-phpmailer/index.html b/docs/snippets/hostmonster-phpmailer/index.html
index b532f59..9ad93d4 100644
--- a/docs/snippets/hostmonster-phpmailer/index.html
+++ b/docs/snippets/hostmonster-phpmailer/index.html
@@ -4,4 +4,4 @@
 $mail->SMTPAuth = true; // yes to auth please
 $mail->Port = 26; // nope not port 25, 26!!
 $mail->Host = 'host286.hostmonster.com';
-

Now your host may differ. I used this tool to check the MX records for the domain. After the check is complete, you will see a small table showing the hostname, IP address, TTL, and some links. Click "SMTP Test".

Once that text completes, you will see another table. The first result is the "SMTP Reverse Banner Check". Copy the hostname, which is the domain in that value field.

Hopefully this works for you. I had a hell of a time getting the correct settings. My pain is your gain.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Now your host may differ. I used this tool to check the MX records for the domain. After the check is complete, you will see a small table showing the hostname, IP address, TTL, and some links. Click "SMTP Test".

Once that text completes, you will see another table. The first result is the "SMTP Reverse Banner Check". Copy the hostname, which is the domain in that value field.

Hopefully this works for you. I had a hell of a time getting the correct settings. My pain is your gain.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/index.html b/docs/snippets/index.html index b64efaf..af5929c 100644 --- a/docs/snippets/index.html +++ b/docs/snippets/index.html @@ -1 +1 @@ -Snippets | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

Backup MySQL And Email It

Don't bother paying for a SaaS that creates MySQL backups and emails them to you on a schedule, you can do this with CRON and a small script

Read More

\ No newline at end of file +Snippets | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

Backup MySQL And Email It

Don't bother paying for a SaaS that creates MySQL backups and emails them to you on a schedule, you can do this with CRON and a small script

Read More

\ No newline at end of file diff --git a/docs/snippets/jquery-plugin-snippets/index.html b/docs/snippets/jquery-plugin-snippets/index.html index e423d92..4b2989c 100644 --- a/docs/snippets/jquery-plugin-snippets/index.html +++ b/docs/snippets/jquery-plugin-snippets/index.html @@ -1 +1 @@ -jQuery Plugin Snippets for Sublime Text 2 | OhDoyleRules

Go home

jquery logo

I created a bunch of snippets out of the patterns from shichuans javascript patterns repo.

These are for Sublime Text 2 which everyone knows. You just clone the repo into the packages directory and they magically appear. If you don’t use sublime text…

y u no use sublime text 2

I had to create that image because it didn’t exists for some reason.

Github Linky.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +jQuery Plugin Snippets for Sublime Text 2 | OhDoyleRules

Go home

jquery logo

I created a bunch of snippets out of the patterns from shichuans javascript patterns repo.

These are for Sublime Text 2 which everyone knows. You just clone the repo into the packages directory and they magically appear. If you don’t use sublime text…

y u no use sublime text 2

I had to create that image because it didn’t exists for some reason.

Github Linky.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/list-file-permission-numbers/index.html b/docs/snippets/list-file-permission-numbers/index.html index edc1aa9..6f22f05 100644 --- a/docs/snippets/list-file-permission-numbers/index.html +++ b/docs/snippets/list-file-permission-numbers/index.html @@ -7,4 +7,4 @@
644 -rw-r--r-- 1 james2doyle LICENSE

greping the output

show-permissions | grep README.md
 644 -rw-r--r--   1 james2doyle   README.md
-

Here is the stackoverflow question where I stole this from.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Here is the stackoverflow question where I stole this from.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/lodash-memo-with-timeout/index.html b/docs/snippets/lodash-memo-with-timeout/index.html index 50e19ce..1462434 100644 --- a/docs/snippets/lodash-memo-with-timeout/index.html +++ b/docs/snippets/lodash-memo-with-timeout/index.html @@ -1 +1 @@ -Lodash memoize with a timeout | OhDoyleRules

Go home

If you are familiar with lodash you may also be familiar with one of the very handy functions called [memoize](https://lodash.com/docs/4.17.15#memoize).

The definition of memoize on the lodash site is quite verbose. So I will use the definition on wikipedia:

In computing, "memoization" or "memoisation" is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

The default memo function in lodash uses a local Map to cache the results of each call. This means the results of the calls to memo will be cached for the entire browser session. The way you would clear the cache is to refresh or trigger a full navigation.

A function that returns the same result given the same input, is called a pure function. So if you have a "pure function" that gets called a lot with the same arguments, and should have the same output given the same arguments, then this is a perfect candidate for memoization. Of course, it is perfectly fine to reach for memoization in order to keep things like expensive HTTP requests from being repeated.

But is a "session" based cache really the best cache for memo? Personally, I think that a time-based caching mechanism is better. What I mean is that the cache will only live for a specific amount of time and then expire.

Imagine a case where you just fetched a users account from your API. If for some reason your code calls that endpoint again just a few seconds later, is it worth redoing the request or should you return cached results given you just called that endpoint earlier?

If you are using the default cache, the request will never be run again unless you refresh. But there may have been changes to the results of the call but you can't rerun it. This is where a TTL cache comes in.

TTL (time to live) is the amount of time that needs to elapse before the cache needs to be refreshed. If you are familiar with the HTTP protocol, you may have come across this term when learning about cache headers.

So how do you implement a custom cache backend for lodash memoize? Easy! The memoize function allows you to write your own resolver function that lets you decide when to fetch from the cache or run the function again.

I've done the work for you and made a simple version in TypeScript that uses the current minute as a tracker for when the last call was:

You can see in this function that we take in the arguments and add a time to the end. We then serialize that object as JSON and use that as our cache key. When lodash runs our new memo function, it will first compare the cached keys and see if they are different. If they are, then the function will actually run and the cached results will not be used and instead our original function will run, and the result of that run, will be cached. Subsequent calls repeat the whole process.

In this case, we only cache the results for one minute. So any calls to our new memo function that are over a minute old will run. This will allow some inefficient code that calls an endpoint too often to only make those calls if at least a minute has passed.

In this case, our new memo is almost like the throttle function in lodash except we get the results back when we call our memo. But if you need to control the mechanism of caching (maybe you want to use localstorage, the URL, or global state) then you can write your own memoize.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Lodash memoize with a timeout | OhDoyleRules

Go home

If you are familiar with lodash you may also be familiar with one of the very handy functions called [memoize](https://lodash.com/docs/4.17.15#memoize).

The definition of memoize on the lodash site is quite verbose. So I will use the definition on wikipedia:

In computing, "memoization" or "memoisation" is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

The default memo function in lodash uses a local Map to cache the results of each call. This means the results of the calls to memo will be cached for the entire browser session. The way you would clear the cache is to refresh or trigger a full navigation.

A function that returns the same result given the same input, is called a pure function. So if you have a "pure function" that gets called a lot with the same arguments, and should have the same output given the same arguments, then this is a perfect candidate for memoization. Of course, it is perfectly fine to reach for memoization in order to keep things like expensive HTTP requests from being repeated.

But is a "session" based cache really the best cache for memo? Personally, I think that a time-based caching mechanism is better. What I mean is that the cache will only live for a specific amount of time and then expire.

Imagine a case where you just fetched a users account from your API. If for some reason your code calls that endpoint again just a few seconds later, is it worth redoing the request or should you return cached results given you just called that endpoint earlier?

If you are using the default cache, the request will never be run again unless you refresh. But there may have been changes to the results of the call but you can't rerun it. This is where a TTL cache comes in.

TTL (time to live) is the amount of time that needs to elapse before the cache needs to be refreshed. If you are familiar with the HTTP protocol, you may have come across this term when learning about cache headers.

So how do you implement a custom cache backend for lodash memoize? Easy! The memoize function allows you to write your own resolver function that lets you decide when to fetch from the cache or run the function again.

I've done the work for you and made a simple version in TypeScript that uses the current minute as a tracker for when the last call was:

You can see in this function that we take in the arguments and add a time to the end. We then serialize that object as JSON and use that as our cache key. When lodash runs our new memo function, it will first compare the cached keys and see if they are different. If they are, then the function will actually run and the cached results will not be used and instead our original function will run, and the result of that run, will be cached. Subsequent calls repeat the whole process.

In this case, we only cache the results for one minute. So any calls to our new memo function that are over a minute old will run. This will allow some inefficient code that calls an endpoint too often to only make those calls if at least a minute has passed.

In this case, our new memo is almost like the throttle function in lodash except we get the results back when we call our memo. But if you need to control the mechanism of caching (maybe you want to use localstorage, the URL, or global state) then you can write your own memoize.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/lodash-translation-function/index.html b/docs/snippets/lodash-translation-function/index.html index 77e430b..9596390 100644 --- a/docs/snippets/lodash-translation-function/index.html +++ b/docs/snippets/lodash-translation-function/index.html @@ -1 +1 @@ -Lodash i18n (translation) function | OhDoyleRules

Go home

One of the great things about lodash is that it gives you all the building blocks to create some really powerful functions.

I was working on a site that needed to use dynamic translations. The way this is done today is usually through a function that is loaded in your components and called with a key that then gets mapped to whatever language you are using.

You can pass in variables to the function in order to translate sentences that include placeholders. For example, you might want to have a welcome message like "Hello {name_of_user}!". Pretty common use case as you can imagine.

I managed to get a simple version of an intl function working by using a combination of get, template, and memoize functions in lodash.

What we are doing here is using first getting a value from our translation file. We use the template function to parse the value we find from our translation function using a pattern that looks for single words wrapped with curly braces. Like {this}. We can then pass in any variables we wanted to replace in that string. Finally, we use memoize to avoid recompiling the template on each additional call. This will just return the cached results of any translations instead of grabbing the value and parsing the string again.

If you find yourself needing a simple translation function, this could be a good option if you already have lodash in your project.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Lodash i18n (translation) function | OhDoyleRules

Go home

One of the great things about lodash is that it gives you all the building blocks to create some really powerful functions.

I was working on a site that needed to use dynamic translations. The way this is done today is usually through a function that is loaded in your components and called with a key that then gets mapped to whatever language you are using.

You can pass in variables to the function in order to translate sentences that include placeholders. For example, you might want to have a welcome message like "Hello {name_of_user}!". Pretty common use case as you can imagine.

I managed to get a simple version of an intl function working by using a combination of get, template, and memoize functions in lodash.

What we are doing here is using first getting a value from our translation file. We use the template function to parse the value we find from our translation function using a pattern that looks for single words wrapped with curly braces. Like {this}. We can then pass in any variables we wanted to replace in that string. Finally, we use memoize to avoid recompiling the template on each additional call. This will just return the cached results of any translations instead of grabbing the value and parsing the string again.

If you find yourself needing a simple translation function, this could be a good option if you already have lodash in your project.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/modernizr-svg-fallback-to-png/index.html b/docs/snippets/modernizr-svg-fallback-to-png/index.html index 4c0ffa2..c8b6384 100644 --- a/docs/snippets/modernizr-svg-fallback-to-png/index.html +++ b/docs/snippets/modernizr-svg-fallback-to-png/index.html @@ -1,4 +1,4 @@ -Modernizr SVG Fallback to PNG | OhDoyleRules

Go home

Github Gists Logo

I have been building a small project boilerplate for when I am starting new projects. I wrote this small snippet based on this article.

The only changes I made were wrapping it in a closure and combining all the vars to make it smaller. Of course your minifier would do this anyway unless you are using it inline after including Modernizr.

Here is the gist.

Or you can copy the current version from right here.

if (!Modernizr.svg) {
+Modernizr SVG Fallback to PNG | OhDoyleRules

Go home

Github Gists Logo

I have been building a small project boilerplate for when I am starting new projects. I wrote this small snippet based on this article.

The only changes I made were wrapping it in a closure and combining all the vars to make it smaller. Of course your minifier would do this anyway unless you are using it inline after including Modernizr.

Here is the gist.

Or you can copy the current version from right here.

if (!Modernizr.svg) {
   // wrap this in a closure to not expose any conflicts
   (function() {
     // grab all images. getElementsByTagName works with IE5.5 and up
diff --git a/docs/snippets/nodelist-each/index.html b/docs/snippets/nodelist-each/index.html
index ed82068..561f210 100644
--- a/docs/snippets/nodelist-each/index.html
+++ b/docs/snippets/nodelist-each/index.html
@@ -14,4 +14,4 @@
 x.each(function(elem) {
     elem.style.background = 'red';
 });
-

Pretty cool. Here is the fiddle

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Pretty cool. Here is the fiddle

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/openssl-encrypt-decrypt-functions/index.html b/docs/snippets/openssl-encrypt-decrypt-functions/index.html index 23996af..db05726 100644 --- a/docs/snippets/openssl-encrypt-decrypt-functions/index.html +++ b/docs/snippets/openssl-encrypt-decrypt-functions/index.html @@ -1,4 +1,4 @@ -Decrypt-Encrypt Functions From Command Line | OhDoyleRules

Go home

Preamble

I have been reading about encryption and security since the whole NSA/Edward Snowden thing. It is pretty intense stuff. Most of the security comes from the philosophy of "security through obfuscation". What this means, is that you are making it extremely difficult (expensive, time-consuming) to try and look at your "stuff".

I would suggest reading this article on "Key Size". This is probably my favorite quote from the article:

With a key of length n bits, there are 2n possible keys. This number grows very rapidly as n increases. Moore's law suggests that computing power doubles roughly every 18 to 24 months, but even this doubling effect leaves the larger symmetric key lengths currently considered acceptable well out of reach.

The best thing you can do for this type of encryption is pick a good password.

Code

# take in a file and output an encrypted one
+Decrypt-Encrypt Functions From Command Line | OhDoyleRules

Go home

Preamble

I have been reading about encryption and security since the whole NSA/Edward Snowden thing. It is pretty intense stuff. Most of the security comes from the philosophy of "security through obfuscation". What this means, is that you are making it extremely difficult (expensive, time-consuming) to try and look at your "stuff".

I would suggest reading this article on "Key Size". This is probably my favorite quote from the article:

With a key of length n bits, there are 2n possible keys. This number grows very rapidly as n increases. Moore's law suggests that computing power doubles roughly every 18 to 24 months, but even this doubling effect leaves the larger symmetric key lengths currently considered acceptable well out of reach.

The best thing you can do for this type of encryption is pick a good password.

Code

# take in a file and output an encrypted one
 function encrypt() {
   # take in a file and output a new one with a `.enc` extension
   openssl rc4 -in "$1" -out "$1".enc
@@ -14,4 +14,4 @@
 }
 

This still leave an "open" file when the file is encrypted. Remember to remove the file securely. You can use shred or gshred (for OSX). Here is the info from the --help output of gshred:

Overwrite the specified FILE(s) repeatedly, in order to make it harder for even very expensive hardware probing to recover the data.

Here is the function that I found to be pretty good:

# overwrite 'my-unsafe-file.txt' 3 times, with zeros (nulls) and then remove the file
 gshred --iterations=3 --zero --remove my-unsafe-file.txt
-

Note: I used RC4 instead of 3DES because it is faster (95% slower than RC4), but it is not as secure.

References:

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Note: I used RC4 instead of 3DES because it is faster (95% slower than RC4), but it is not as secure.

References:

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/phalconphp-crop-to-fit/index.html b/docs/snippets/phalconphp-crop-to-fit/index.html index 23102af..fd01412 100644 --- a/docs/snippets/phalconphp-crop-to-fit/index.html +++ b/docs/snippets/phalconphp-crop-to-fit/index.html @@ -1 +1 @@ -PhalconPHP Crop Image To Fit | OhDoyleRules

Go home

I was trying to find out how to crop-to-fit an image with GD. But there were no examples with Phalcon. There was one post that mentioned using Imagick. The only problem was that you needed to compiled Imagick with --lrf in order to use liquidRescale. This may not be an option on many hosting platforms. For that reason, I wanted to use GD instead.

I found this post after a quick Google search. It helped me create the base for my Phalcon version of the function. This may seem pretty easy for some people, but I found enough people asking, that I figured I would share the whole code.

If you really wanted to use Imagick, then you could just replace GD in the constructor (Phalcon\Image\Adapter\Imagick($source)) and it should work fine. This way you don't need to compile Imagick from source in order to get liquidRescale.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +PhalconPHP Crop Image To Fit | OhDoyleRules

Go home

I was trying to find out how to crop-to-fit an image with GD. But there were no examples with Phalcon. There was one post that mentioned using Imagick. The only problem was that you needed to compiled Imagick with --lrf in order to use liquidRescale. This may not be an option on many hosting platforms. For that reason, I wanted to use GD instead.

I found this post after a quick Google search. It helped me create the base for my Phalcon version of the function. This may seem pretty easy for some people, but I found enough people asking, that I figured I would share the whole code.

If you really wanted to use Imagick, then you could just replace GD in the constructor (Phalcon\Image\Adapter\Imagick($source)) and it should work fine. This way you don't need to compile Imagick from source in order to get liquidRescale.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/purge-file-from-github/index.html b/docs/snippets/purge-file-from-github/index.html index 40a7e1c..f8b6938 100644 --- a/docs/snippets/purge-file-from-github/index.html +++ b/docs/snippets/purge-file-from-github/index.html @@ -3,7 +3,7 @@
FN="git rm --cached --ignore-unmatch $1" git filter-branch --force --index-filter $FN --prune-empty --tag-name-filter cat -- --all } -

This was taken from the Github article about removing files. Here is what they said about the function:

Run git filter-branch, forcing (--force) Git to process—but not check out (--index-filter)—the entire history of every branch and tag (--tag-name-filter cat -- --all), removing the specified file ('git rm --cached --ignore-unmatch MYFILE') and any empty commits generated as a result (--prune-empty). Note that you need to specify the path to the file you want to remove, not just its filename. Be careful! This will overwrite your existing tags.

You should also add the file to your .gitignore:

echo "MYFILE" >> .gitignore
+

This was taken from the Github article about removing files. Here is what they said about the function:

Run git filter-branch, forcing (--force) Git to process—but not check out (--index-filter)—the entire history of every branch and tag (--tag-name-filter cat -- --all), removing the specified file ('git rm --cached --ignore-unmatch MYFILE') and any empty commits generated as a result (--prune-empty). Note that you need to specify the path to the file you want to remove, not just its filename. Be careful! This will overwrite your existing tags.

You should also add the file to your .gitignore:

echo "MYFILE" >> .gitignore
 git add .gitignore
 git commit -m "Add MYFILE to .gitignore"
-

Then to update the live repo, run git push origin master --force.

This process will remove the file from your repo, and from the history. This is in-case you committed a sensitive file. If you get in a real pickle, you can use the BFG Repo-Cleaner.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Then to update the live repo, run git push origin master --force.

This process will remove the file from your repo, and from the history. This is in-case you committed a sensitive file. If you get in a real pickle, you can use the BFG Repo-Cleaner.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/raspberry-pi-php-and-lighttpd/index.html b/docs/snippets/raspberry-pi-php-and-lighttpd/index.html index c1a2664..9f15eee 100644 --- a/docs/snippets/raspberry-pi-php-and-lighttpd/index.html +++ b/docs/snippets/raspberry-pi-php-and-lighttpd/index.html @@ -1,10 +1,10 @@ -Setup A Raspberry Pi with PHP And Lighttpd | OhDoyleRules

Go home

I recently got a Raspberry Pi model 4+. I'm using it to run a minidlna server that loads music and movies from an old external hard drive that I have. On top of that, I wanted to run a local web server so that I can write scripts and pages that I can access locally on my network without having to have my laptop open and running.

I wanted to setup lighttpd as it is much more efficient than the default installed apache2. Since I'm already running minidlna, I wanted a web server that was more performant and used less memory/resources.

Finally, I wanted the latest PHP installed given I use it a lot in my day job and it will be great for writing small websites that are only ever accessed through my local network. Unfortunately, the default installed PHP version is quite a bit behind and you need to setup new sources in order to install the latest version of PHP.

Here is a short list of all the steps you need to setup a newer version of PHP 8.1 as well as the lighttpd server.

Instructions

These instructions assume you're using the default debian-based raspberry pi operating system!

Update the local packages already installed:

sudo apt-get update
+Setup A Raspberry Pi with PHP And Lighttpd | OhDoyleRules

Go home

I recently got a Raspberry Pi model 4+. I'm using it to run a minidlna server that loads music and movies from an old external hard drive that I have. On top of that, I wanted to run a local web server so that I can write scripts and pages that I can access locally on my network without having to have my laptop open and running.

I wanted to setup lighttpd as it is much more efficient than the default installed apache2. Since I'm already running minidlna, I wanted a web server that was more performant and used less memory/resources.

Finally, I wanted the latest PHP installed given I use it a lot in my day job and it will be great for writing small websites that are only ever accessed through my local network. Unfortunately, the default installed PHP version is quite a bit behind and you need to setup new sources in order to install the latest version of PHP.

Here is a short list of all the steps you need to setup a newer version of PHP 8.1 as well as the lighttpd server.

Instructions

These instructions assume you're using the default debian-based raspberry pi operating system!

Update the local packages already installed:

sudo apt-get update
 

Install the dependencies needed to add the additional services:

sudo apt-get install lsb-release apt-transport-https ca-certificates
 

Add the repository for installing the latest version of PHP:

sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://origin.sury.org/php/apt.gpg
 echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list
 

Now we can install all the PHP packages we will need for basic PHP applications:

sudo apt-get update
 sudo apt-get install -y php8.1 php8.1-cli php8.1-cgi php8.1-intl php8.1-zip
-

If you need additional PHP extensions then the above command is where you would add those in.

Since we now have the latest PHP version, we can setup composer. The sha384 code may different for your installation based on whatever version is out when you are following these instructions. You can find the latest download/install instructions on the Composer download page.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+

If you need additional PHP extensions then the above command is where you would add those in.

Since we now have the latest PHP version, we can setup composer. The sha384 code may different for your installation based on whatever version is out when you are following these instructions. You can find the latest download/install instructions on the Composer download page.

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
 php -r "if (hash_file('sha384', 'composer-setup.php') === '55ce33d7678c5a611085589f1f3ddf8b3c52d662cd01d4ba75c0ee0459970c2200a51f492d557530c71c15d8dba01eae') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
 php composer-setup.php
 sudo mv composer.phar /usr/local/bin/composer
@@ -17,6 +17,6 @@
 sudo chown -R www-data:www-data /var/cache/lighttpd
 sudo chown -R www-data:www-data /var/www/html
 

To test out the server once we are finished, we can setup this simple project that emulates the apache directory listing module. This will just list everything in our /var/www/html folder in a nice display:

git clone https://github.com/halgatewood/file-directory-list /var/www/html/listing
-

If you are looking for a more efficient directory browser, or you don't want to use PHP for this, you can use the built-in dirlisting module that comes with lighttpd.

We can now make sure the lighttpd service is running so it will always start when we restart our pi:

sudo systemctl start lighttpd.service
+

If you are looking for a more efficient directory browser, or you don't want to use PHP for this, you can use the built-in dirlisting module that comes with lighttpd.

We can now make sure the lighttpd service is running so it will always start when we restart our pi:

sudo systemctl start lighttpd.service
 

If you want to test the server on the command line, you can just curl your localhost and see what happens. You should get the source code for the listing page we installed:

curl http://localhost/listing
 

Done! That should be all you need to have PHP 8.1 and a light weight web server setup!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/render-php-with-data/index.html b/docs/snippets/render-php-with-data/index.html index 4476b39..c5cef5d 100644 --- a/docs/snippets/render-php-with-data/index.html +++ b/docs/snippets/render-php-with-data/index.html @@ -1,4 +1,4 @@ -Render PHP File With Data | OhDoyleRules

Go home

I am modifying an open source CMS to use the Phalcon PHP framework, as well as the PHP-Sundown C implementation of Markdown.

It is a very simple CMS which previously would just echo out compiled HTML. But I am using the Volt template engine in Phalcon. It renders .volt files to native PHP. This means that I cannot just spit out raw HTML. I need to create a render function that passes an array of data to my PHP file.

Here is that function:

function renderPhpFile($filename, $vars = null) {
+Render PHP File With Data | OhDoyleRules

Go home

I am modifying an open source CMS to use the Phalcon PHP framework, as well as the PHP-Sundown C implementation of Markdown.

It is a very simple CMS which previously would just echo out compiled HTML. But I am using the Volt template engine in Phalcon. It renders .volt files to native PHP. This means that I cannot just spit out raw HTML. I need to create a render function that passes an array of data to my PHP file.

Here is that function:

function renderPhpFile($filename, $vars = null) {
   if (is_array($vars) && !empty($vars)) {
     extract($vars);
   }
diff --git a/docs/snippets/running-go-in-docker/index.html b/docs/snippets/running-go-in-docker/index.html
index 8d0374b..2f3410d 100644
--- a/docs/snippets/running-go-in-docker/index.html
+++ b/docs/snippets/running-go-in-docker/index.html
@@ -1 +1 @@
-Running Go (golang) in Docker | OhDoyleRules

Go home

Lately, I have been trying to learn golang. This means playing with a lot of tools and busting up my local environment.

In order to keep things simple, I have been using Docker container to run my applications when I am ready to deploy or build them.

I use now.sh to deploy the applications. Since now.sh support deploying Docker, using the Dockerfile approach makes it really simple to deploy.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Running Go (golang) in Docker | OhDoyleRules

Go home

Lately, I have been trying to learn golang. This means playing with a lot of tools and busting up my local environment.

In order to keep things simple, I have been using Docker container to run my applications when I am ready to deploy or build them.

I use now.sh to deploy the applications. Since now.sh support deploying Docker, using the Dockerfile approach makes it really simple to deploy.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/salt-js-mirco-selector-library/index.html b/docs/snippets/salt-js-mirco-selector-library/index.html index 9748ce7..95e0bd9 100644 --- a/docs/snippets/salt-js-mirco-selector-library/index.html +++ b/docs/snippets/salt-js-mirco-selector-library/index.html @@ -1,4 +1,4 @@ -Salt.js micro selector library | OhDoyleRules

Go home

Slat.js Logo

I made a tiny dom selector library called Salt.js.

It uses a regular expression to map different queries you pass through it to their native get functions. The reason I don’t just use querySelectorAll for everything is because it is slower than the native get commands. See this jsperf test.

Yes, I see that the mapping is slower for newer versions of Chrome. But, almost every other browser and device is slower using querySelectorAll over the mapping method. Also keep in mind the regex used in that example is much more complicated than mine.

Here are some examples of how you would use the library:

Salt.js Examples

// get by id
+Salt.js micro selector library | OhDoyleRules

Go home

Slat.js Logo

I made a tiny dom selector library called Salt.js.

It uses a regular expression to map different queries you pass through it to their native get functions. The reason I don’t just use querySelectorAll for everything is because it is slower than the native get commands. See this jsperf test.

Yes, I see that the mapping is slower for newer versions of Chrome. But, almost every other browser and device is slower using querySelectorAll over the mapping method. Also keep in mind the regex used in that example is much more complicated than mine.

Here are some examples of how you would use the library:

Salt.js Examples

// get by id
 $('#iddiv');
 // get by class name
 $('.classdiv');
@@ -12,4 +12,4 @@
 $('#iddiv').getAttribute('name');
 // getAttribute of name from nodelist
 $('.classdiv')[0].getAttribute('name');
-

Check out the library on Github.

Update

Looks like there are a bunch of better ways to make this smaller! I’ve updated the github to reflect the new libraries. I have also added a jsPerf test.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Check out the library on Github.

Update

Looks like there are a bunch of better ways to make this smaller! I’ve updated the github to reflect the new libraries. I have also added a jsPerf test.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/simple-php-json-response/index.html b/docs/snippets/simple-php-json-response/index.html index 9b8f71d..1847ecc 100644 --- a/docs/snippets/simple-php-json-response/index.html +++ b/docs/snippets/simple-php-json-response/index.html @@ -1,6 +1,6 @@ -Simple PHP JSON Response | OhDoyleRules

Go home

This is little snippet I use all the time when I am building simple flat sites that need a single route for an AJAX request.

There are a couple of things you need to do in order to create a proper JSON response.

Use json_encode

If you are really new to PHP, you may not have found json_encode. If that is the case, then look it up right now.

This function converts PHP arrays, strings, and objects, into a JSON safe string. This makes it simple for you to create safe responses that can be handled by your javascript.

Use Content-Type

This is usually the magic command that allows you to receive JSON from your script. If you don't set the header, PHP will simple return a string. Then in your javascript, you have to use JSON.parse in order to get the object that javascript can use.

header('Content-Type: application/json');
+Simple PHP JSON Response | OhDoyleRules

Go home

This is little snippet I use all the time when I am building simple flat sites that need a single route for an AJAX request.

There are a couple of things you need to do in order to create a proper JSON response.

Use json_encode

If you are really new to PHP, you may not have found json_encode. If that is the case, then look it up right now.

This function converts PHP arrays, strings, and objects, into a JSON safe string. This makes it simple for you to create safe responses that can be handled by your javascript.

Use Content-Type

This is usually the magic command that allows you to receive JSON from your script. If you don't set the header, PHP will simple return a string. Then in your javascript, you have to use JSON.parse in order to get the object that javascript can use.

header('Content-Type: application/json');
 

No more strings! The request is now treated accordingly and is parsed for you. I am sure everyone has encountered this in the beginning.

Use Status

Here is another header that you need to set in order for things to work smoothly. By setting the status, you can tell your javascript, as well as your browser, what the status of the request it. By default, the requests are treated as 200 OK. So although you may have sent a response with a failing message, with setting a failed status (usually 300 codes and above) the browser, and your javascript, think everything is fine.

header('Status: 400 Bad Request');
-

This becomes very apparent in jQuery when using $.get or $.post. If you are using a Promise style request (using $.get().then( ... )), your error handler never gets called.

If you want to find the perfect header for your fantastic error, check out the Wikipedia for the HTTP status codes.

Example

Here is an example of a simple script I might use when testing an AJAX response, or when building a flat-file site. I would place this somewhere in my websites directory, and then hit it directly with an AJAX request.

If my local path to my site was http://localhost:8888/website, I would save this script as json.php. Then I would then use the following jQuery to test the script:

$.get("http://localhost:8888/website/json.php")
+

This becomes very apparent in jQuery when using $.get or $.post. If you are using a Promise style request (using $.get().then( ... )), your error handler never gets called.

If you want to find the perfect header for your fantastic error, check out the Wikipedia for the HTTP status codes.

Example

Here is an example of a simple script I might use when testing an AJAX response, or when building a flat-file site. I would place this somewhere in my websites directory, and then hit it directly with an AJAX request.

If my local path to my site was http://localhost:8888/website, I would save this script as json.php. Then I would then use the following jQuery to test the script:

$.get("http://localhost:8888/website/json.php")
 .done(function(response){
   console.log(response);
 });
diff --git a/docs/snippets/tailwind-screens-in-js/index.html b/docs/snippets/tailwind-screens-in-js/index.html
index 9b1120a..a16c451 100644
--- a/docs/snippets/tailwind-screens-in-js/index.html
+++ b/docs/snippets/tailwind-screens-in-js/index.html
@@ -1,4 +1,4 @@
-Tailwind Screens In JS | OhDoyleRules

Go home

If you haven't heard of Tailwindcss before, what is going on? It is the hot new CSS framework for building custom designs.

From the site:

Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.

One of the things Tailwindcss gives you is a configuration file (named tailwind.config.js) for setting the configuration of the output CSS. This may be a little strange to people who have never used this before but there are some advantages. The output CSS can be configured based on the build environment or your own specifications.

One of the configuration settings is for what screen sizes and breakpoints are supported. One of the benefits of these values being defined in JS is that you can then use them in JS as well. You can read more about them in the docs.

So why might you want to use these values in JS? Well, if you are building components that have conditional content based on the screen size, having access to these values can be super handy. You can check the screen size and show/hide different content, conditional load different sizes elements, or even disable an entire swath of features/functionality. All while keep your JS code in sync with your CSS code. You can reuse the variable names.

Here is the code I used to accomplish this:

You can use the function like so:

// es6
+Tailwind Screens In JS | OhDoyleRules

Go home

If you haven't heard of Tailwindcss before, what is going on? It is the hot new CSS framework for building custom designs.

From the site:

Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.

One of the things Tailwindcss gives you is a configuration file (named tailwind.config.js) for setting the configuration of the output CSS. This may be a little strange to people who have never used this before but there are some advantages. The output CSS can be configured based on the build environment or your own specifications.

One of the configuration settings is for what screen sizes and breakpoints are supported. One of the benefits of these values being defined in JS is that you can then use them in JS as well. You can read more about them in the docs.

So why might you want to use these values in JS? Well, if you are building components that have conditional content based on the screen size, having access to these values can be super handy. You can check the screen size and show/hide different content, conditional load different sizes elements, or even disable an entire swath of features/functionality. All while keep your JS code in sync with your CSS code. You can reuse the variable names.

Here is the code I used to accomplish this:

You can use the function like so:

// es6
 // import screenIs from 'screenIs';
 // commonjs
 const screenIs = require('screenIs');
@@ -11,4 +11,4 @@
 if (md || lg) {
   // we are on an "md" or an "lg" screen...
 }
-

Basically, we load up the config, use window.matchMedia to test all the media queries in the config, then check if the key we pass in matches an existing key in our config and return the results.

Pretty simple but a nice way to share the config into your JS code.

If you are familiar with Tailwind, you might be asking, why not just use CSS with md:show and md:hide?

While that does work perfectly for visual content, it does not stop the browser from creating the DOM for the hidden content or stop and event listeners from being created or removed. If you are using the detection of the screen in JS, you can make sure to not render or add events for code that is not used. The opposite is true for when the screen changes and you need to cleanup any listeners or DOM elements you don't want to be there.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Basically, we load up the config, use window.matchMedia to test all the media queries in the config, then check if the key we pass in matches an existing key in our config and return the results.

Pretty simple but a nice way to share the config into your JS code.

If you are familiar with Tailwind, you might be asking, why not just use CSS with md:show and md:hide?

While that does work perfectly for visual content, it does not stop the browser from creating the DOM for the hidden content or stop and event listeners from being created or removed. If you are using the detection of the screen in JS, you can make sure to not render or add events for code that is not used. The opposite is true for when the screen changes and you need to cleanup any listeners or DOM elements you don't want to be there.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/target-mozilla-only-in-css/index.html b/docs/snippets/target-mozilla-only-in-css/index.html index 72eb403..8b95e81 100644 --- a/docs/snippets/target-mozilla-only-in-css/index.html +++ b/docs/snippets/target-mozilla-only-in-css/index.html @@ -1,4 +1,4 @@ Target Mozilla-only in CSS | OhDoyleRules

Go home

I had some issues in Firefox recently. I was building a complicated “item” in CSS and it looked great in Chrome. I got an email later saying that the sizing was all off for a bunch of things. I thought this was really strange. I went back to the CSS and Chrome and I could not see any issues.

I then fired up Firefox and, yikes! There was a bunch of weird issues. This is strange because normally Chrome to Firefox translates pretty well. I was using the ::first-letter element and a few ::before elements. But somehow, someway they got messed up. Anyway, I discovered this little snippet:

@-moz-document url-prefix() {
   /* firefox only styles */
 }
-

It works. But what does it mean? The url-prefix() is a way to serve specific styles to a specific URL. In this case, I just want to target a -moz- device. Here is a more in depth definition. This worked nicely, and so it will stay into production.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

It works. But what does it mean? The url-prefix() is a way to serve specific styles to a specific URL. In this case, I just want to target a -moz- device. Here is a more in depth definition. This worked nicely, and so it will stay into production.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/using-laravel-when-method-to-support-multiple-queries/index.html b/docs/snippets/using-laravel-when-method-to-support-multiple-queries/index.html index 4ae0c8b..d54d81c 100644 --- a/docs/snippets/using-laravel-when-method-to-support-multiple-queries/index.html +++ b/docs/snippets/using-laravel-when-method-to-support-multiple-queries/index.html @@ -1,4 +1,4 @@ -Using Laravel `when` Method To Support Multiple Queries | OhDoyleRules

Go home

There is a method on Laravel collections called when that allows you to create a condition on your code without using an if statement. This can be really handy given we often have conditions on queries to deal with missing or present data, the session-specific environment, or even information in the config.

Let's look at the common pattern I was using in the past to put conditions on my queries:

<?php
+Using Laravel `when` Method To Support Multiple Queries | OhDoyleRules

Go home

There is a method on Laravel collections called when that allows you to create a condition on your code without using an if statement. This can be really handy given we often have conditions on queries to deal with missing or present data, the session-specific environment, or even information in the config.

Let's look at the common pattern I was using in the past to put conditions on my queries:

<?php
 /** @var \Illuminate\Database\Eloquent\Builder|\App\Models\User */
 $query = User::query();
 
diff --git a/docs/snippets/using-node-in-applescript/index.html b/docs/snippets/using-node-in-applescript/index.html
index 79ed749..95a7ad4 100644
--- a/docs/snippets/using-node-in-applescript/index.html
+++ b/docs/snippets/using-node-in-applescript/index.html
@@ -1,4 +1,4 @@
-Using Node.js in an AppleScript | OhDoyleRules

Go home

A few days ago, I wrote an article about how to create a service in Automator to copy the selected file's path to the clipboard while in the Finder.app.

I was playing around some more and thought it would be cool to be able to right click and convert a markdown file to HTML. This can be useful for lazy people who don't want to open and app or terminal just to convert.

Here is the trick, you need absolute paths to node and the target module (or bin entry js file) file you are trying to run

Here is the code:

-- setup some valid extensions for markdown files
+Using Node.js in an AppleScript | OhDoyleRules

Go home

A few days ago, I wrote an article about how to create a service in Automator to copy the selected file's path to the clipboard while in the Finder.app.

I was playing around some more and thought it would be cool to be able to right click and convert a markdown file to HTML. This can be useful for lazy people who don't want to open and app or terminal just to convert.

Here is the trick, you need absolute paths to node and the target module (or bin entry js file) file you are trying to run

Here is the code:

-- setup some valid extensions for markdown files
 property validExtensions : {"md", "markdown", "mdown"}
 tell application "Finder"
   set theFile to item 1 of (get selection)
@@ -24,4 +24,4 @@
     delay 2
   end if
 end tell
-

Now this can be used in an Automator Service, which you can find out how to make in the previous article.

If you modify this for any other cool node tools, please let me know!

PS: Keep in mind that this doesn't iterate through multiple files. Only single files.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Now this can be used in an Automator Service, which you can find out how to make in the previous article.

If you modify this for any other cool node tools, please let me know!

PS: Keep in mind that this doesn't iterate through multiple files. Only single files.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/validate-email-with-lua/index.html b/docs/snippets/validate-email-with-lua/index.html index e050556..6baed35 100644 --- a/docs/snippets/validate-email-with-lua/index.html +++ b/docs/snippets/validate-email-with-lua/index.html @@ -1 +1 @@ -Validate Email With Lua | OhDoyleRules

Go home

Checking if an email is valid should be easy, right? WRONG.

This took about 3-4 hours to finally get. I scoured the web for a good email validation function. But, I was unable to find any that actually handled all the valid email variations.

If you didn't know, the spec for email is actually pretty complex. It allows a lot more than just a web-safe prefix and a domain sandwiched between an @ symbol. You can use quotes, brackets, escapes, and more @ symbols.

Looking at the results that Wikipedia gives me for emails that should fail or pass a validation, I knew this would require more than just a simple pattern match.

Here is the final product. I added some nice comments to explain some of the rules as well.

You can see there is a lot more logic than expected. I also made this function have multiple returns.

If the email passes the function returns true and nil, but if it fails, it will return nil and the reason that the validation failed. Pretty slick!

Anyway, this was a tedious task. So go forth and leverage my pain in your next form submission.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Validate Email With Lua | OhDoyleRules

Go home

Checking if an email is valid should be easy, right? WRONG.

This took about 3-4 hours to finally get. I scoured the web for a good email validation function. But, I was unable to find any that actually handled all the valid email variations.

If you didn't know, the spec for email is actually pretty complex. It allows a lot more than just a web-safe prefix and a domain sandwiched between an @ symbol. You can use quotes, brackets, escapes, and more @ symbols.

Looking at the results that Wikipedia gives me for emails that should fail or pass a validation, I knew this would require more than just a simple pattern match.

Here is the final product. I added some nice comments to explain some of the rules as well.

You can see there is a lot more logic than expected. I also made this function have multiple returns.

If the email passes the function returns true and nil, but if it fails, it will return nil and the reason that the validation failed. Pretty slick!

Anyway, this was a tedious task. So go forth and leverage my pain in your next form submission.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/versioning-service-workers-in-hugo/index.html b/docs/snippets/versioning-service-workers-in-hugo/index.html index 8140c7b..56caec5 100644 --- a/docs/snippets/versioning-service-workers-in-hugo/index.html +++ b/docs/snippets/versioning-service-workers-in-hugo/index.html @@ -1,4 +1,4 @@ -Versioning Service Workers In Hugo | OhDoyleRules

Go home

I have been running this blog on Hugo for quite some time. It is fast, well supported, and full of features. Until recently, one of the features I was not taking advantage of was the pipes feature.

For all intents and purposes, pipes are used for processing strings and templates. They are "assets" that are used on your site. They live outside your theme at the top level /assets folder in your Hugo project. But you can also pull in remote assets via a URL and Hugo will pull that in when you build your site.

From the day I switched to Hugo, I was always using a service worker to cache my sites static assets. The challenge I had was how to bust the cache. The old way I was doing this was to add a query string on the end of my sw.js URL. Something like this:

navigator.serviceWorker.register('sw.js?{{ now.Format "2006-01-02" }}');
+Versioning Service Workers In Hugo | OhDoyleRules

Go home

I have been running this blog on Hugo for quite some time. It is fast, well supported, and full of features. Until recently, one of the features I was not taking advantage of was the pipes feature.

For all intents and purposes, pipes are used for processing strings and templates. They are "assets" that are used on your site. They live outside your theme at the top level /assets folder in your Hugo project. But you can also pull in remote assets via a URL and Hugo will pull that in when you build your site.

From the day I switched to Hugo, I was always using a service worker to cache my sites static assets. The challenge I had was how to bust the cache. The old way I was doing this was to add a query string on the end of my sw.js URL. Something like this:

navigator.serviceWorker.register('sw.js?{{ now.Format "2006-01-02" }}');
 

This seemed to work just OK. I think maybe in the last couple years the browser's changed and they would still cache this file even when the URL was different. It would not reload the service worker and therefore update the cache with new articles making my site seem stale even though there was new content.

I can't say for sure but it used to work in the past and then one day it did not...

Now that this is a known issue in my site, I need a way to bust the cache in the service worker file itself instead of relying on the file's URL.

What I need to do is treat the sw.js file as a template so I can pass in a variable and tell the file it is new each time I deploy.

In this case, it is pipes to the rescue! Here is the code that runs in my footer.html file:

<!-- Get the file in "assets/service-worker.js" -->
 {{ $jsTemplate := resources.Get "service-worker.js" }}
 <!-- We are making a new file called "sw.js" -->
diff --git a/docs/snippets/vue-stateful-form-component/index.html b/docs/snippets/vue-stateful-form-component/index.html
index d84f681..2e6d2c6 100644
--- a/docs/snippets/vue-stateful-form-component/index.html
+++ b/docs/snippets/vue-stateful-form-component/index.html
@@ -1 +1 @@
-Vue stateful form component | OhDoyleRules

Go home

Recently, I needed to create a app that could recreate a form from static JSON and then fill it with values from another source. This was pretty difficult as storing a form in JSON is very hard. You can't add handlers or events given you only can store string, numbers, booleans, and arrays. No functions.

I ended up coming up with a component that uses a render function in order to recreate the form stored in JSON.

Features

I made a whole site on this and posted the code to NPM. It currently only works in Vue 2. So keep that in mind. You can find the source code and instructions here.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Vue stateful form component | OhDoyleRules

Go home

Recently, I needed to create a app that could recreate a form from static JSON and then fill it with values from another source. This was pretty difficult as storing a form in JSON is very hard. You can't add handlers or events given you only can store string, numbers, booleans, and arrays. No functions.

I ended up coming up with a component that uses a render function in order to recreate the form stored in JSON.

Features

I made a whole site on this and posted the code to NPM. It currently only works in Vue 2. So keep that in mind. You can find the source code and instructions here.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/vuex-crosstab/index.html b/docs/snippets/vuex-crosstab/index.html index 8a91858..5b1bf41 100644 --- a/docs/snippets/vuex-crosstab/index.html +++ b/docs/snippets/vuex-crosstab/index.html @@ -1,4 +1,4 @@ -Vuex Crosstab Plugin | OhDoyleRules

Go home

Did you know that you can use localstorage as an event bus between same-origin tabs?

Yep! Local storage is shared across same-origin domains. Which means you can share information between tabs on the same domain. Other cool thing is that the storage event that is fired on the window when localstorage changes, it only fired on non-focused tabs. This means you can write an event bus to sync the tabs you don't have focused with the one you do! Neat.

When browsing around GitHub, like I do, I found this project called storeon. It is a framework agnostic state manager. It's aim is to be small and flexible and provide additional functionality through plugins. One such plugin is called crosstab.

I am a Vue.js user who likes VueX. So I wanted to recreate this plugin in VueX. VueX also provides a plugin API so you can write plugins as well. So I wrote the plugin for VueX, published it to npm, and posted the source on GitHub.

Here is the demo of the plugin working:

laravel multi-lingual site demo

You can use the plugin like so:

// es6
+Vuex Crosstab Plugin | OhDoyleRules

Go home

Did you know that you can use localstorage as an event bus between same-origin tabs?

Yep! Local storage is shared across same-origin domains. Which means you can share information between tabs on the same domain. Other cool thing is that the storage event that is fired on the window when localstorage changes, it only fired on non-focused tabs. This means you can write an event bus to sync the tabs you don't have focused with the one you do! Neat.

When browsing around GitHub, like I do, I found this project called storeon. It is a framework agnostic state manager. It's aim is to be small and flexible and provide additional functionality through plugins. One such plugin is called crosstab.

I am a Vue.js user who likes VueX. So I wanted to recreate this plugin in VueX. VueX also provides a plugin API so you can write plugins as well. So I wrote the plugin for VueX, published it to npm, and posted the source on GitHub.

Here is the demo of the plugin working:

laravel multi-lingual site demo

You can use the plugin like so:

// es6
 // import CrossTab from 'vuex-crosstab';
 // commonjs
 const CrossTab = require('vuex-crosstab');
@@ -11,4 +11,4 @@
   ],
   // ...
 });
-

There is a simple config object you can pass to control some things like the "recovery" which will check the localstorage to see if there was state already in there. You can read more on the Github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

There is a simple config object you can pass to control some things like the "recovery" which will check the localstorage to see if there was state already in there. You can read more on the Github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/vuex-stateful-url/index.html b/docs/snippets/vuex-stateful-url/index.html index 058d6c7..f76af90 100644 --- a/docs/snippets/vuex-stateful-url/index.html +++ b/docs/snippets/vuex-stateful-url/index.html @@ -1,4 +1,4 @@ -Vuex Stateful URL Plugin | OhDoyleRules

Go home

Have you ever used permalinks on a site? These links store the state of the page in the URL. They can be super useful for simple single-page-apps (SPAs) for bringing users back to where they were when they left the page or just adding the ability to share that page state with someone else without requiring logins or anything like that.

I'm a Vue.js user who likes VueX. So I wanted to create a plugin in VueX that would send the state of the store into the URL.

VueX provides a plugin API so you can write plugins that can extend the basic functionality. So I wrote the plugin for VueX that would sync the store to the URL.

I used a great package called json-url which allows you to encode/compress JSON objects in a URL-safe way.

It is now published to npm and the source is on GitHub.

Here is the demo of the plugin working:

laravel multi-lingual site demo

You can use the plugin like so:

// es6
+Vuex Stateful URL Plugin | OhDoyleRules

Go home

Have you ever used permalinks on a site? These links store the state of the page in the URL. They can be super useful for simple single-page-apps (SPAs) for bringing users back to where they were when they left the page or just adding the ability to share that page state with someone else without requiring logins or anything like that.

I'm a Vue.js user who likes VueX. So I wanted to create a plugin in VueX that would send the state of the store into the URL.

VueX provides a plugin API so you can write plugins that can extend the basic functionality. So I wrote the plugin for VueX that would sync the store to the URL.

I used a great package called json-url which allows you to encode/compress JSON objects in a URL-safe way.

It is now published to npm and the source is on GitHub.

Here is the demo of the plugin working:

laravel multi-lingual site demo

You can use the plugin like so:

// es6
 // import StatefulURL from 'vuex-stateful-url';
 // commonjs
 const StatefulURL = require('vuex-stateful-url');
@@ -11,4 +11,4 @@
   ],
   // ...
 });
-

You can read more on the Github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

You can read more on the Github page.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/snippets/wysiwyg-in-pyrocms-widgets/index.html b/docs/snippets/wysiwyg-in-pyrocms-widgets/index.html index 749edb6..951cf7d 100644 --- a/docs/snippets/wysiwyg-in-pyrocms-widgets/index.html +++ b/docs/snippets/wysiwyg-in-pyrocms-widgets/index.html @@ -1,4 +1,4 @@ -WYSIWYG in PyroCMS Widgets | OhDoyleRules

Go home

I have been working on a site that uses PyroCMS. I needed to build a custom widget that had a WYSIWYG textarea. This is what worked for me.

Add this to template in the constructors function.

pyrocms/system/cms/modules/widgets/controllers/admin.php

// my new template
+WYSIWYG in PyroCMS Widgets | OhDoyleRules

Go home

I have been working on a site that uses PyroCMS. I needed to build a custom widget that had a WYSIWYG textarea. This is what worked for me.

Add this to template in the constructors function.

pyrocms/system/cms/modules/widgets/controllers/admin.php

// my new template
 $this->template
 ->set_partial('shortcuts', 'admin/partials/shortcuts')
 ->append_js('module::widgets.js')
diff --git a/docs/snippets/zsh-new-open-function/index.html b/docs/snippets/zsh-new-open-function/index.html
index b883505..895966b 100644
--- a/docs/snippets/zsh-new-open-function/index.html
+++ b/docs/snippets/zsh-new-open-function/index.html
@@ -1,4 +1,4 @@
-zsh new file && open file function | OhDoyleRules

Go home

Here is a little function that I made for oh-my-zsh. I found myself constantly doing sudo touch app.js && open app.js.

What this little command does is create an empty file called app.js and then opens it with whatever your default editor is.

Here is the function:

# create a new file in the current directory and then open it
+zsh new file && open file function | OhDoyleRules

Go home

Here is a little function that I made for oh-my-zsh. I found myself constantly doing sudo touch app.js && open app.js.

What this little command does is create an empty file called app.js and then opens it with whatever your default editor is.

Here is the function:

# create a new file in the current directory and then open it
 new () {
   sudo touch $1 && open $1
 }
diff --git a/docs/tricks/always-connect-to-starbucks-wifi/index.html b/docs/tricks/always-connect-to-starbucks-wifi/index.html
index 8b7a3ef..4e98e61 100644
--- a/docs/tricks/always-connect-to-starbucks-wifi/index.html
+++ b/docs/tricks/always-connect-to-starbucks-wifi/index.html
@@ -1 +1 @@
-Always Connect To Starbucks WiFi | OhDoyleRules

Go home

Lately, it has been quite difficult for me to connect to Starbucks WiFi. About a year ago, the network would connect pretty quickly, but now it seems like the network is powered by voodoo magic and the will of Satan.

Well, I have discovered the ultimate way to connect to the network every time. Now, this is still complete bullshit, considering the year is 2014 and we have a rover on Mars, but we still can't connect to a wireless network. Don't even get me started on wireless printers.

Here is the process I use to connect quickly:

1. Connect to the Starbucks network with your phone

Yes. Use your cell phone to connect to the Starbucks wireless network. I have never had a problem connecting any phone to the Starbucks wifi. It seems like phones have some sort of special powers.

2. Turn on tethering and connect to your computer to your phone

Once you connect your phone to the Starbucks network, turn on tethering or wifi hotspot. Then, on your computer, connect to your phones network.

Here are instructions for turning on wireless hotspot on iOS and android.

3. Load the Starbucks connection page

Now you will go to the Starbucks connection page and wait until it is finished loading.

If it doesn't load all the way, that is fine. Just hit the stop button when you think it has reached a point of not going any further.

DO NOT PRESS THE GREEN SUBMIT BUTTON!

Control yourself. We will do this later.

4. Switch your computer to the Starbucks network

While that connection page is still open, switch your computers wifi from the phones hotspot connection to the Starbucks network.

5. Click the big green button to connect

Now that you have the connection page loaded and your computer connected to the Starbucks wifi, you can press the big green submit button. This will complete the authentication and it should redirect you to the Starbucks homepage.

Why does this work?

I am not really sure. I think the reason might be that an unauthenticated connection to the network (a connection that has not made it past the connection page) has limited bandwidth.

This means that all pages are trying to load with a throttled speed. Typically, the browser will only try to connect for so long before it gives up and assumes the site is unreachable (a timeout occurs).

With my process, you are using a connection that is already authenticated, so you have the full speed you need in order to load the necessary page without it timing out on you.

Side Note

You can probably use your phones data network to load the initial Starbucks connection page. Then switch from the phone to Starbucks and press the green button.

Did this work?

This always works for me. It is a hell of a lot better than waiting a solid 30 minutes before the network finally connects. If this works for you, please leave a comment and let me know. If it doesn't work, let me know what you did in order to get it to work.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Always Connect To Starbucks WiFi | OhDoyleRules

Go home

Lately, it has been quite difficult for me to connect to Starbucks WiFi. About a year ago, the network would connect pretty quickly, but now it seems like the network is powered by voodoo magic and the will of Satan.

Well, I have discovered the ultimate way to connect to the network every time. Now, this is still complete bullshit, considering the year is 2014 and we have a rover on Mars, but we still can't connect to a wireless network. Don't even get me started on wireless printers.

Here is the process I use to connect quickly:

1. Connect to the Starbucks network with your phone

Yes. Use your cell phone to connect to the Starbucks wireless network. I have never had a problem connecting any phone to the Starbucks wifi. It seems like phones have some sort of special powers.

2. Turn on tethering and connect to your computer to your phone

Once you connect your phone to the Starbucks network, turn on tethering or wifi hotspot. Then, on your computer, connect to your phones network.

Here are instructions for turning on wireless hotspot on iOS and android.

3. Load the Starbucks connection page

Now you will go to the Starbucks connection page and wait until it is finished loading.

If it doesn't load all the way, that is fine. Just hit the stop button when you think it has reached a point of not going any further.

DO NOT PRESS THE GREEN SUBMIT BUTTON!

Control yourself. We will do this later.

4. Switch your computer to the Starbucks network

While that connection page is still open, switch your computers wifi from the phones hotspot connection to the Starbucks network.

5. Click the big green button to connect

Now that you have the connection page loaded and your computer connected to the Starbucks wifi, you can press the big green submit button. This will complete the authentication and it should redirect you to the Starbucks homepage.

Why does this work?

I am not really sure. I think the reason might be that an unauthenticated connection to the network (a connection that has not made it past the connection page) has limited bandwidth.

This means that all pages are trying to load with a throttled speed. Typically, the browser will only try to connect for so long before it gives up and assumes the site is unreachable (a timeout occurs).

With my process, you are using a connection that is already authenticated, so you have the full speed you need in order to load the necessary page without it timing out on you.

Side Note

You can probably use your phones data network to load the initial Starbucks connection page. Then switch from the phone to Starbucks and press the green button.

Did this work?

This always works for me. It is a hell of a lot better than waiting a solid 30 minutes before the network finally connects. If this works for you, please leave a comment and let me know. If it doesn't work, let me know what you did in order to get it to work.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/tricks/disallowed-characters-in-uri/index.html b/docs/tricks/disallowed-characters-in-uri/index.html index 8cd3b32..2c0d640 100644 --- a/docs/tricks/disallowed-characters-in-uri/index.html +++ b/docs/tricks/disallowed-characters-in-uri/index.html @@ -1,2 +1,2 @@ Disallowed Characters In URI | OhDoyleRules

Go home

How to fix "The URI you submitted has disallowed characters" error.

Does this screenshot look familiar?

codeigniter 400 error for disallowed characters

Well, it has been killing me for the last 2 hours. I have encountered this error before, but I never realized what caused it, or how it was fixed. I would just try random stuff, entering in different content, moving different functions. Eventually it would go away...

Symptoms

In this situation, whenever I would click on a particular link I got the error. I copied and pasted the URL from the browser into Sublime Text. It looked like this:

http://localhost:8080/website/page%E2%80%8B
-

What the heck? I never added those characters. They never showed in the browser URL, and trying to replace them in my code was not working.

I tried editing the entries in my database in order to remove the character. I deleted and edited each field. I even tried converting the character encoding and trimming the URL. Nothing worked!

I then read a post on the PyroCMS Forum that said there is probably a hidden character somewhere in the code. Well, I looked in my PHP and I didn't find anything, looking in my HTML there was nothing there either.

So what to do? When in doubt, use vim. I dusted off my vim and looked at the PHP file, nothing! Then I opened the HTML file...

HEY! There is a tiny little hidden character in my HTML!!

The Fix

I removed the character and everything worked perfectly. Here is the diff after I removed the character:

git diff for hidden character

You can see there is a little trailing character there. I don't know exactly how this got in there, but god damn was it driving me crazy.

In Summation

The real problem is, this didn't show in Sublime Text or in the Chrome elements panel. Even the commit on Github didn't show the hidden character I removed.

github diff for hidden character

The only way it would show up is when I opened the file in vim!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

What the heck? I never added those characters. They never showed in the browser URL, and trying to replace them in my code was not working.

I tried editing the entries in my database in order to remove the character. I deleted and edited each field. I even tried converting the character encoding and trimming the URL. Nothing worked!

I then read a post on the PyroCMS Forum that said there is probably a hidden character somewhere in the code. Well, I looked in my PHP and I didn't find anything, looking in my HTML there was nothing there either.

So what to do? When in doubt, use vim. I dusted off my vim and looked at the PHP file, nothing! Then I opened the HTML file...

HEY! There is a tiny little hidden character in my HTML!!

The Fix

I removed the character and everything worked perfectly. Here is the diff after I removed the character:

git diff for hidden character

You can see there is a little trailing character there. I don't know exactly how this got in there, but god damn was it driving me crazy.

In Summation

The real problem is, this didn't show in Sublime Text or in the Chrome elements panel. Even the commit on Github didn't show the hidden character I removed.

github diff for hidden character

The only way it would show up is when I opened the file in vim!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/tricks/fastmod-codemod-for-refactoring/index.html b/docs/tricks/fastmod-codemod-for-refactoring/index.html index 25ea6d3..f62ea45 100644 --- a/docs/tricks/fastmod-codemod-for-refactoring/index.html +++ b/docs/tricks/fastmod-codemod-for-refactoring/index.html @@ -1,4 +1,4 @@ -Fastmod Codemod For Refactoring | OhDoyleRules

Go home

If you have ever encounter a big refactor, you were probably dreading the steps it would take to get all the changes done. You have to find patterns, replace them, remove old code, rename variables, so much work! Well, like most things in the development world, there are tools to help you do this. Yes, you can use find-and-replace, but that approach is very naïve (as in simple) and doesn't take in some of the more nuanced cases that you will come across.

The tool that I have been using for the last couple of years is fastmod. fastmod is a flavour of codemod but written in Rust - so it has to be cool right? Here is a description from the codemod repo:

codemod is a tool/library to assist you with large-scale codebase refactors that can be partially automated but still require human oversight and occasional intervention.

What you do is write a "match pattern" and then a "replace pattern". When you run the tool you get a prompt for each match and you can decide what you want to do with the proposed changes. This is a lot like git add -p (which is also a great trick) where you only accept the changes you know are valid.

One of the best things about this tool is how it provides the interface to the changes you need to apply:

  • filter files by extension
  • use regex to create matches
  • named matches so they can be reordered
  • preview of the changes before you commit them
  • ability to accept or decline any single change
  • ability to edit the changes using $EDITOR (like git commit --verbose)
  • accept all changes without previews ("fast mode")

Help With Regex

If you are terrible at written regex patterns, you're not alone. There are lots of people who despise regex but it isn't going anyway. I actually made a conscious effort a few years ago to acquire a better understanding of how regex works and what the patterns and special characters do. I used this tool call regexr to help practice as well as learn new patterns. Check it out if you need help building and testing regex patterns.

Like most purpose-built tools, there is some learning curve to finding how to best use the interface provided. You really need to consider your match strategy and your replace strategy. Let's write some examples based on some real world examples I've come across.

CSS

Here we have a CSS example for a button component. This component can be grouped or have an icon inside it. The naming of the component in CSS is kinda redundant.

.button-group-component {
+Fastmod Codemod For Refactoring | OhDoyleRules

Go home

If you have ever encounter a big refactor, you were probably dreading the steps it would take to get all the changes done. You have to find patterns, replace them, remove old code, rename variables, so much work! Well, like most things in the development world, there are tools to help you do this. Yes, you can use find-and-replace, but that approach is very naïve (as in simple) and doesn't take in some of the more nuanced cases that you will come across.

The tool that I have been using for the last couple of years is fastmod. fastmod is a flavour of codemod but written in Rust - so it has to be cool right? Here is a description from the codemod repo:

codemod is a tool/library to assist you with large-scale codebase refactors that can be partially automated but still require human oversight and occasional intervention.

What you do is write a "match pattern" and then a "replace pattern". When you run the tool you get a prompt for each match and you can decide what you want to do with the proposed changes. This is a lot like git add -p (which is also a great trick) where you only accept the changes you know are valid.

One of the best things about this tool is how it provides the interface to the changes you need to apply:

  • filter files by extension
  • use regex to create matches
  • named matches so they can be reordered
  • preview of the changes before you commit them
  • ability to accept or decline any single change
  • ability to edit the changes using $EDITOR (like git commit --verbose)
  • accept all changes without previews ("fast mode")

Help With Regex

If you are terrible at written regex patterns, you're not alone. There are lots of people who despise regex but it isn't going anyway. I actually made a conscious effort a few years ago to acquire a better understanding of how regex works and what the patterns and special characters do. I used this tool call regexr to help practice as well as learn new patterns. Check it out if you need help building and testing regex patterns.

Like most purpose-built tools, there is some learning curve to finding how to best use the interface provided. You really need to consider your match strategy and your replace strategy. Let's write some examples based on some real world examples I've come across.

CSS

Here we have a CSS example for a button component. This component can be grouped or have an icon inside it. The naming of the component in CSS is kinda redundant.

.button-group-component {
   display: flex;
   align-items: center;
   justify-content: space-between;
@@ -19,11 +19,11 @@
     display: flex;
     align-items: center;
 Accept change (y = yes [default], n = no, e = edit, A = yes to all, E = yes+edit, q = quit)?
-

Here you can see all the options for the code that is going to be changed as well as a nice diff too. If we move from left to right in the options we get the following choices:

  • y or enter accepts the changes being shown
  • n will not change anything and will go to the next change
  • e will open your $EDITOR without any changes applied
  • A will accept this change and all of the future changes with reprompting you
  • E will open your $EDITOR with any changes applied
  • q will no accept the changes and stop the process

In this case we can hit enter for each change. We will be prompted 4 times: once for each occurrence of the string -component or we can skip all that and just hit A and everything will be changed.

Webpack Magic Comments

Recently I was working on a Vue project that had a lot of components in it. I wanted to start using webpack chunking in order to split the components up and have single chunks for each component instead of them being wrapped up together. In order to do that, I need to rewrite all my import statements to use "dynamic imports" with a special magic comment that webpack picks up.

You can see the example below:

# old code: `import MyComponent from 'Components/MyComponent.vue';`
+

Here you can see all the options for the code that is going to be changed as well as a nice diff too. If we move from left to right in the options we get the following choices:

  • y or enter accepts the changes being shown
  • n will not change anything and will go to the next change
  • e will open your $EDITOR without any changes applied
  • A will accept this change and all of the future changes with reprompting you
  • E will open your $EDITOR with any changes applied
  • q will no accept the changes and stop the process

In this case we can hit enter for each change. We will be prompted 4 times: once for each occurrence of the string -component or we can skip all that and just hit A and everything will be changed.

Webpack Magic Comments

Recently I was working on a Vue project that had a lot of components in it. I wanted to start using webpack chunking in order to split the components up and have single chunks for each component instead of them being wrapped up together. In order to do that, I need to rewrite all my import statements to use "dynamic imports" with a special magic comment that webpack picks up.

You can see the example below:

# old code: `import MyComponent from 'Components/MyComponent.vue';`
 # new code: `const MyComponent = () => import(/* webpackChunkName: "MyComponent" */ 'Components/MyComponent.vue');`
 # note: you may need to reorder your imports when using a dynamic import
 
 fastmod -m -d ./ --extensions vue \
   'import (.*?) from \'(.*?)\';' \
   'const ${1} = () => import(/* webpackChunkName: "${1}" */ \'${2}\');'
-

Here you can see the snippet I wrote to do the refactor. The great thing about using fastmod over find-and-replace, is that it allowed me to accept or deny the changes. I didn't want all the components updated this way. Especially imports that were not Vue components. So this approach was ideal.

More Use Cases

Those are two decent examples but I have used this tool quite a lot for refactoring code. Here are some more examples of what I used it for:

  • add/remove classes in HTML
  • rename bad variable names on multiple projects
  • quickly fix the misspelling of various words in multiple projects
  • ran a replace on an exported SQL database backup to change some values in the data (faster than running a query)
  • refactor isset into array_get on a Laravel (PHP) project
  • merge down multiple use statement in PHP to a single use line
  • rewrite {{ var }} interpolation to v-text="var" directives in a Vue project
  • replace imports with other versions
  • upgrade old code to new methods or APIs

As you can probably already tell, the possibilities are endless! I have used this on database exports and even CSV files. It is a much nicer alternative to find-and-replace. Best of all? It's editor agnostic. So you can use it with any other tools.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Here you can see the snippet I wrote to do the refactor. The great thing about using fastmod over find-and-replace, is that it allowed me to accept or deny the changes. I didn't want all the components updated this way. Especially imports that were not Vue components. So this approach was ideal.

More Use Cases

Those are two decent examples but I have used this tool quite a lot for refactoring code. Here are some more examples of what I used it for:

  • add/remove classes in HTML
  • rename bad variable names on multiple projects
  • quickly fix the misspelling of various words in multiple projects
  • ran a replace on an exported SQL database backup to change some values in the data (faster than running a query)
  • refactor isset into array_get on a Laravel (PHP) project
  • merge down multiple use statement in PHP to a single use line
  • rewrite {{ var }} interpolation to v-text="var" directives in a Vue project
  • replace imports with other versions
  • upgrade old code to new methods or APIs

As you can probably already tell, the possibilities are endless! I have used this on database exports and even CSV files. It is a much nicer alternative to find-and-replace. Best of all? It's editor agnostic. So you can use it with any other tools.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/tricks/hammerspoon-hyper-key/index.html b/docs/tricks/hammerspoon-hyper-key/index.html index 37f8555..f3a305d 100644 --- a/docs/tricks/hammerspoon-hyper-key/index.html +++ b/docs/tricks/hammerspoon-hyper-key/index.html @@ -1,4 +1,4 @@ -Hammerspoon hyper key | OhDoyleRules

Go home

If you aren't using Hammerspoon on OSX, you are missing out!

It has some great features that you can use to control your desktop, automate tasks, build small UIs and toolbar apps. It can also trigger key presses and modifiers like shift, alt, etc.

Here is the script I use to create "hyper" key shortcuts. If you aren't aware, the hyper key is a modifier key that combines the Shift, Control, Alt, and Command/Windows keys simultaneously. Then you can press a regular key and use that for shortcuts.

I wrote an article a while back to "Use Your Numberpad To Control Google Hangouts/Meet" which is pretty handy. It uses a similar trick to play out a combination of keys and trigger actions in Chrome.

The script below watches the keyboard for the hyper key + another key. In the script below, I just bind the hyper key + some letter keys to trigger apps to focus.

-- verbose logging
+Hammerspoon hyper key | OhDoyleRules

Go home

If you aren't using Hammerspoon on OSX, you are missing out!

It has some great features that you can use to control your desktop, automate tasks, build small UIs and toolbar apps. It can also trigger key presses and modifiers like shift, alt, etc.

Here is the script I use to create "hyper" key shortcuts. If you aren't aware, the hyper key is a modifier key that combines the Shift, Control, Alt, and Command/Windows keys simultaneously. Then you can press a regular key and use that for shortcuts.

I wrote an article a while back to "Use Your Numberpad To Control Google Hangouts/Meet" which is pretty handy. It uses a similar trick to play out a combination of keys and trigger actions in Chrome.

The script below watches the keyboard for the hyper key + another key. In the script below, I just bind the hyper key + some letter keys to trigger apps to focus.

-- verbose logging
 -- hs.logger.setGlobalLogLevel(5)
 local hyperKeyLogger = hs.logger.new('hyperkey','debug')
 
diff --git a/docs/tricks/hammerspoon-number-pad-shortcuts/index.html b/docs/tricks/hammerspoon-number-pad-shortcuts/index.html
index 37545e8..9134c78 100644
--- a/docs/tricks/hammerspoon-number-pad-shortcuts/index.html
+++ b/docs/tricks/hammerspoon-number-pad-shortcuts/index.html
@@ -1,4 +1,4 @@
-Use Your Numberpad To Control Google Hangouts/Meet | OhDoyleRules

Go home

The number pad

Number pads can be pretty handy. Not just for accountants or spreadsheet junkies. Did you know that the keys on a number pad register in their own way? If your number pad is setup right, pressing a 1 on your keyboard number row and pressing 1 on your number pad, will be different keys. I have a bunch of my number keys on my number pad set to control the window positions on my desktop.

I also use the 0 key and the . key to control Google Hangouts/Meet. Read more to find out how.

Hammerspoon

If you aren't using Hammerspoon on OSX, you are missing out! It has some great features! I will write some more articles on using Hammerspoon to control your Apple trackpad and also tricks with "hyper" keys to control your desktop.

Back on track. These are all the keys on the number pad that Hammerspoon will treat as "unique":

pad., pad*, pad+, pad/, pad-, pad=,
+Use Your Numberpad To Control Google Hangouts/Meet | OhDoyleRules

Go home

The number pad

Number pads can be pretty handy. Not just for accountants or spreadsheet junkies. Did you know that the keys on a number pad register in their own way? If your number pad is setup right, pressing a 1 on your keyboard number row and pressing 1 on your number pad, will be different keys. I have a bunch of my number keys on my number pad set to control the window positions on my desktop.

I also use the 0 key and the . key to control Google Hangouts/Meet. Read more to find out how.

Hammerspoon

If you aren't using Hammerspoon on OSX, you are missing out! It has some great features! I will write some more articles on using Hammerspoon to control your Apple trackpad and also tricks with "hyper" keys to control your desktop.

Back on track. These are all the keys on the number pad that Hammerspoon will treat as "unique":

pad., pad*, pad+, pad/, pad-, pad=,
 pad0, pad1, pad2,
 pad3, pad4, pad5,
 pad6, pad7, pad8, pad9,
diff --git a/docs/tricks/index.html b/docs/tricks/index.html
index 2b53958..e8eca9f 100644
--- a/docs/tricks/index.html
+++ b/docs/tricks/index.html
@@ -1 +1 @@
-Tricks | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file +Tricks | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

\ No newline at end of file diff --git a/docs/tricks/migrate-allpasswords-to-1password/index.html b/docs/tricks/migrate-allpasswords-to-1password/index.html index 6b6eac6..8a44d58 100644 --- a/docs/tricks/migrate-allpasswords-to-1password/index.html +++ b/docs/tricks/migrate-allpasswords-to-1password/index.html @@ -1 +1 @@ -Migrate AllPasswords To 1Password | OhDoyleRules

Go home

AllPasswords is an awesome, free, app for iPhone and OSX. It has a nice, simple interface, there is an awesome password generator, and it has iCloud sync.

The problem is, I recently bought an iPhone 6 and updated to iOS 8. It seems that the iCloud sync has busted for AllPasswords, at least on my device. With the advent of the 1Password app getting a free version I decided it might be time to switch.

I have about 130 logins in AllPasswords, so I wasn't about to manually enter in each account. Instead, I had to format the exported CSV from AllPasswords to be able to import into 1Password. Here is how I did it:

AllPasswords Export Format

Title, Username, Password, URL, Notes

The above line is the export format for AllPasswords.

Now this isn't going to work when you try to import it into 1Password. You will need to do some massaging of the CSV to get it to work properly.

Here is an export example:

ODR PW,super_cool_guy,ilovepuppies5000,http://ohdoylerules.com,"This is a fake entry"

Here is one that is less ideal, or maybe had some info missing:

ODR PW,super_cool_guy,ilovepuppies5000,,

1Password Import Expectations

"Title","Location (URL)","Username","Password","Notes"

Now we need to put our CSV in this format. We need to wrap the sections in quotes, and we need to make sure that the empty fields are just empty quotes.

Here is how we would arrange those 2 examples from before.

"ODR PW","http://ohdoylerules.com","super_cool_guy","ilovepuppies5000","This is a fake entry"

Here is the ugly one, and how to fix it:

"ODR PW","","super_cool_guy","ilovepuppies5000",""

Finding Issues

When you are trying to import, you will notice that 1Passwords gives no feedback on what is wrong with the CSV, it will just deny the import.

The trick is to go through the file and make sure there are 5 sets of quotes. That is what helped me.

So far I have been very happy with 1Password. It seems like a really solid app. I opted for Dropbox as the sync storage, and I downloaded the Chrome extension that will prompt me for pasting or saving of logins.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Migrate AllPasswords To 1Password | OhDoyleRules

Go home

AllPasswords is an awesome, free, app for iPhone and OSX. It has a nice, simple interface, there is an awesome password generator, and it has iCloud sync.

The problem is, I recently bought an iPhone 6 and updated to iOS 8. It seems that the iCloud sync has busted for AllPasswords, at least on my device. With the advent of the 1Password app getting a free version I decided it might be time to switch.

I have about 130 logins in AllPasswords, so I wasn't about to manually enter in each account. Instead, I had to format the exported CSV from AllPasswords to be able to import into 1Password. Here is how I did it:

AllPasswords Export Format

Title, Username, Password, URL, Notes

The above line is the export format for AllPasswords.

Now this isn't going to work when you try to import it into 1Password. You will need to do some massaging of the CSV to get it to work properly.

Here is an export example:

ODR PW,super_cool_guy,ilovepuppies5000,http://ohdoylerules.com,"This is a fake entry"

Here is one that is less ideal, or maybe had some info missing:

ODR PW,super_cool_guy,ilovepuppies5000,,

1Password Import Expectations

"Title","Location (URL)","Username","Password","Notes"

Now we need to put our CSV in this format. We need to wrap the sections in quotes, and we need to make sure that the empty fields are just empty quotes.

Here is how we would arrange those 2 examples from before.

"ODR PW","http://ohdoylerules.com","super_cool_guy","ilovepuppies5000","This is a fake entry"

Here is the ugly one, and how to fix it:

"ODR PW","","super_cool_guy","ilovepuppies5000",""

Finding Issues

When you are trying to import, you will notice that 1Passwords gives no feedback on what is wrong with the CSV, it will just deny the import.

The trick is to go through the file and make sure there are 5 sets of quotes. That is what helped me.

So far I have been very happy with 1Password. It seems like a really solid app. I opted for Dropbox as the sync storage, and I downloaded the Chrome extension that will prompt me for pasting or saving of logins.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/tricks/openssl-passwd-without-prompt/index.html b/docs/tricks/openssl-passwd-without-prompt/index.html index 8c1260d..7ac5ce5 100644 --- a/docs/tricks/openssl-passwd-without-prompt/index.html +++ b/docs/tricks/openssl-passwd-without-prompt/index.html @@ -1,3 +1,3 @@ -OpenSSL Passwd Without Prompt | OhDoyleRules

Go home

Have you ever wanted to generate a password using the openssl passwd command, but didn't want the prompt?

I encountered this problem when I was writing an Ansible role for setting up Nginx basic auth. I didn't want the prompt since I had no way for ansible to handle that gracefully. After some Googling, and some man page grepping, I found the answer.

You can generate a password without a prompt by piping text into openssl and passing a new flag. For example:

echo "password" | openssl passwd -apr1 -stdin
+OpenSSL Passwd Without Prompt | OhDoyleRules

Go home

Have you ever wanted to generate a password using the openssl passwd command, but didn't want the prompt?

I encountered this problem when I was writing an Ansible role for setting up Nginx basic auth. I didn't want the prompt since I had no way for ansible to handle that gracefully. After some Googling, and some man page grepping, I found the answer.

You can generate a password without a prompt by piping text into openssl and passing a new flag. For example:

echo "password" | openssl passwd -apr1 -stdin
 

This will echo to stdout. This way you can write a script or something instead of having to use the prompt to type in the password.

In my case of generating a basic auth password, I had to append the output to the /etc/nginx/.htpasswd file. That was done using the following command:

echo "password" | openssl passwd -apr1 -stdin >> /etc/nginx/.htpasswd
 

There ya go. It's that easy. I have actually used this trick a couple times for generating passwords and piping to pbcopy (the clipboard) on my mac.

Pretty useful stuff!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/tricks/using-browser-devtools-to-improve-your-bug-reports/index.html b/docs/tricks/using-browser-devtools-to-improve-your-bug-reports/index.html index c5e0286..258d104 100644 --- a/docs/tricks/using-browser-devtools-to-improve-your-bug-reports/index.html +++ b/docs/tricks/using-browser-devtools-to-improve-your-bug-reports/index.html @@ -1,4 +1,4 @@ -Using Browser Devtools To Improve Your Bug Reports | OhDoyleRules

Go home

Good bug reports

Reporting bugs can be very difficult when you are not a developer. How do you make sure the bug can be recreated? How do you avoid the "it works on my machine" rebuttal?

Well, we can greatly improve bug reports on web apps by using some simple tools built into the browsers we use every day.

Easy Screenshots

Did you know you can take screenshots of DOM nodes right from the browser?

Handy right? Use this to easy capture a smaller area of the screen and attach that to your bug report. Easy!

Saving "console output"

Have you ever seen a blast of red in the console that states an error? You can try to explain what happened and where the error occurs. Or, you can export your console output to a log file and send it along to a developer!

The output will look something like the following:

(index):233 Uncaught Error: Oh no! Something happened
+Using Browser Devtools To Improve Your Bug Reports | OhDoyleRules

Go home

Good bug reports

Reporting bugs can be very difficult when you are not a developer. How do you make sure the bug can be recreated? How do you avoid the "it works on my machine" rebuttal?

Well, we can greatly improve bug reports on web apps by using some simple tools built into the browsers we use every day.

Easy Screenshots

Did you know you can take screenshots of DOM nodes right from the browser?

Handy right? Use this to easy capture a smaller area of the screen and attach that to your bug report. Easy!

Saving "console output"

Have you ever seen a blast of red in the console that states an error? You can try to explain what happened and where the error occurs. Or, you can export your console output to a log file and send it along to a developer!

The output will look something like the following:

(index):233 Uncaught Error: Oh no! Something happened
     at (index):233:9
 (anonymous) @ (index):233
 (index):265 on localhost - not loading service worker. Query: http://localhost:1313/sw.js 2023-03-09
@@ -164,4 +164,4 @@
     ]
   }
 }
-

To read a HAR file that has already been exported, you can just drag it into the network panel of your devtools:

This will recreate the state of the world when the HAR file was captured.

You can also use a tool like the Google Admin Toolbox HAR Analyzer to view HAR files on a webpage:

If you want to read more about capturing HAR files, just check out this great list of steps on the IBM site - of all places.

In Summation

Hopefully these simple tips help improve your bug reports and make it easier for your team members to report them but also recreate them. Happy bug hunting!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

To read a HAR file that has already been exported, you can just drag it into the network panel of your devtools:

This will recreate the state of the world when the HAR file was captured.

You can also use a tool like the Google Admin Toolbox HAR Analyzer to view HAR files on a webpage:

If you want to read more about capturing HAR files, just check out this great list of steps on the IBM site - of all places.

In Summation

Hopefully these simple tips help improve your bug reports and make it easier for your team members to report them but also recreate them. Happy bug hunting!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/add-a-counter-for-duplicate-file-uploads/index.html b/docs/web/add-a-counter-for-duplicate-file-uploads/index.html index fb597e0..3d5822d 100644 --- a/docs/web/add-a-counter-for-duplicate-file-uploads/index.html +++ b/docs/web/add-a-counter-for-duplicate-file-uploads/index.html @@ -2,4 +2,4 @@
1-some-file.jpg 2-some-file.jpg 3-some-file.jpg -

Well, now you can! I have used this little script a couple times to remove any conflicting filenames when uploading files with the same name.

I use this in Laravel (with an instance of Symfony\Component\HttpFoundation\File\UploadedFile which extends SplFileInfo) but it can easily be modified for any other framework or system.

If you are OCD, and wanted to all files to start with a number, just remove the if statement for the $client_name, only leaving the string concatenate line, and it will always add a the counter when the file is new.

So give it a shot, and if you modify it for some other framework, post the link in the comments.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Well, now you can! I have used this little script a couple times to remove any conflicting filenames when uploading files with the same name.

I use this in Laravel (with an instance of Symfony\Component\HttpFoundation\File\UploadedFile which extends SplFileInfo) but it can easily be modified for any other framework or system.

If you are OCD, and wanted to all files to start with a number, just remove the if statement for the $client_name, only leaving the string concatenate line, and it will always add a the counter when the file is new.

So give it a shot, and if you modify it for some other framework, post the link in the comments.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/add-getstylesheet-to-jquery/index.html b/docs/web/add-getstylesheet-to-jquery/index.html index 5135f44..ec70423 100644 --- a/docs/web/add-getstylesheet-to-jquery/index.html +++ b/docs/web/add-getstylesheet-to-jquery/index.html @@ -4,4 +4,4 @@
}, function () { console.log('an error occurred somewhere'); }); -

Well, now you can! I have created an alternative to $.getScript that handles stylesheets. I called it $.getStylesheet for obvious reasons.

It implements the $.Deferred object, which allows for chaining and pseudo-promises-style code. Just like $.ajax, $.post, and $.get. This also means you would have access to all the other methods on that object too, this means: .then(), .always(), .done(), and .fail().

Here is the little function for $.getStylesheet. It is just hosted on Github gist, so I can update it if I need to:

You can see this is pretty simple. Just add the function after your jQuery script, or somewhere in your main script file.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Well, now you can! I have created an alternative to $.getScript that handles stylesheets. I called it $.getStylesheet for obvious reasons.

It implements the $.Deferred object, which allows for chaining and pseudo-promises-style code. Just like $.ajax, $.post, and $.get. This also means you would have access to all the other methods on that object too, this means: .then(), .always(), .done(), and .fail().

Here is the little function for $.getStylesheet. It is just hosted on Github gist, so I can update it if I need to:

You can see this is pretty simple. Just add the function after your jQuery script, or somewhere in your main script file.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/angular-through-iframe/index.html b/docs/web/angular-through-iframe/index.html index a83fdde..b7478ac 100644 --- a/docs/web/angular-through-iframe/index.html +++ b/docs/web/angular-through-iframe/index.html @@ -8,4 +8,4 @@
$scope = dataFromParent; }); }; -

As you can see, this is incredibly simple and allows for write-only control of an iframe.

What I did to integrate with Angular was use that window function inside the iframes controller to call $scope.$apply with the new data.

You can see a live demo of the technique or visit the demo project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

As you can see, this is incredibly simple and allows for write-only control of an iframe.

What I did to integrate with Angular was use that window function inside the iframes controller to call $scope.$apply with the new data.

You can see a live demo of the technique or visit the demo project on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/angularjs-hangout-promises-promises/index.html b/docs/web/angularjs-hangout-promises-promises/index.html index 6f40762..6a1592a 100644 --- a/docs/web/angularjs-hangout-promises-promises/index.html +++ b/docs/web/angularjs-hangout-promises-promises/index.html @@ -1 +1 @@ -AngularJS Hangout - Promises Promises | OhDoyleRules

Go home

I somehow managed to find my way into an AngularJS hangout.

The hangout is about promises and deffereds. On the google plus event page, there are links to all the plunkrs in the video. Here is the direct youtube link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +AngularJS Hangout - Promises Promises | OhDoyleRules

Go home

I somehow managed to find my way into an AngularJS hangout.

The hangout is about promises and deffereds. On the google plus event page, there are links to all the plunkrs in the video. Here is the direct youtube link.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/build-a-multi-lingual-laravel-site-with-subdomains/index.html b/docs/web/build-a-multi-lingual-laravel-site-with-subdomains/index.html index 4f759c1..fcb207d 100644 --- a/docs/web/build-a-multi-lingual-laravel-site-with-subdomains/index.html +++ b/docs/web/build-a-multi-lingual-laravel-site-with-subdomains/index.html @@ -1,4 +1,4 @@ -Build A Multi-lingual Laravel Site With Subdomains | OhDoyleRules

Go home

OK, so you want to make Laravel a multi-lingual (or just multi-functional) site based off the subdomain? Cool. Must be an interesting project.

In order to test this on local development, we are going to use Valet. Let's say our Laravel project folder is in ~/Sites/multisite. We can use that as our base path moving forward. With Valet, we can set up subdomains with valet link.

I'm going to pretend that we want to add support for a new subdomain for the French version of our site. I'm also going to assume that your valet TLD is .localhost and your project is called multisite.localhost. The domain we are adding in this case, it will be fr.multisite.localhost.

So in order to get that subdomain setup locally, we just go to our site folder and run valet link fr.multisite. This will allow fr.multisite.localhost to route to the same Laravel setup as multisite.localhost. Beauty!

Well, first things first. Let's make a helper to grab the subdomain from the request. This will come in handy for a lot of the future logic switching or setting application state on a per-request basis.

Let's use a macro to add a new method to the Request:

--- a/app/Providers/AppServiceProvider.php
+Build A Multi-lingual Laravel Site With Subdomains | OhDoyleRules

Go home

OK, so you want to make Laravel a multi-lingual (or just multi-functional) site based off the subdomain? Cool. Must be an interesting project.

In order to test this on local development, we are going to use Valet. Let's say our Laravel project folder is in ~/Sites/multisite. We can use that as our base path moving forward. With Valet, we can set up subdomains with valet link.

I'm going to pretend that we want to add support for a new subdomain for the French version of our site. I'm also going to assume that your valet TLD is .localhost and your project is called multisite.localhost. The domain we are adding in this case, it will be fr.multisite.localhost.

So in order to get that subdomain setup locally, we just go to our site folder and run valet link fr.multisite. This will allow fr.multisite.localhost to route to the same Laravel setup as multisite.localhost. Beauty!

Well, first things first. Let's make a helper to grab the subdomain from the request. This will come in handy for a lot of the future logic switching or setting application state on a per-request basis.

Let's use a macro to add a new method to the Request:

--- a/app/Providers/AppServiceProvider.php
 +++ b/app/Providers/AppServiceProvider.php
 @@ -3,6 +3,7 @@
  namespace App\Providers;
diff --git a/docs/web/checkboxes-options/index.html b/docs/web/checkboxes-options/index.html
index c89bd54..246db83 100644
--- a/docs/web/checkboxes-options/index.html
+++ b/docs/web/checkboxes-options/index.html
@@ -1,2 +1,2 @@
-Making Checkboxes in WordPress options pages | OhDoyleRules

Go home

I am in the process of building my first WordPress plugin. Of course I am wildly researching how to do things. One thing that was particularly hard to find was how to use checkboxes in options pages. Here is the solution I used.

WordPress has a function called checked(). This basically returns a true checked attribute if the conditions it is passed are met. Here is how I used it:

<input type="checkbox" name="my_options" <?php checked( get_option('my_option') == 'on',true); ?> />
+Making Checkboxes in WordPress options pages | OhDoyleRules

Go home

I am in the process of building my first WordPress plugin. Of course I am wildly researching how to do things. One thing that was particularly hard to find was how to use checkboxes in options pages. Here is the solution I used.

WordPress has a function called checked(). This basically returns a true checked attribute if the conditions it is passed are met. Here is how I used it:

<input type="checkbox" name="my_options" <?php checked( get_option('my_option') == 'on',true); ?> />
 

What I found is that when the option was getting updated it was being stored as 'on'. So this little PHP snippet says: "If the option named 'my_option' is equal to 'on' then add a checked="checked" attribute to this input tag." Anyway, I found it quite hard to get a straight up answer to this problem. Since the reason I made this blog is to share my discoveries; here you go.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/cms-watch-list/index.html b/docs/web/cms-watch-list/index.html index b1a64cd..67faa16 100644 --- a/docs/web/cms-watch-list/index.html +++ b/docs/web/cms-watch-list/index.html @@ -1 +1 @@ -CMS Watch List | OhDoyleRules

Go home

I created a new post on WARPAINT about some of the upcoming CMS platforms you may not have heard of.

Here is the article.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +CMS Watch List | OhDoyleRules

Go home

I created a new post on WARPAINT about some of the upcoming CMS platforms you may not have heard of.

Here is the article.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/create-json-sections-in-shopify/index.html b/docs/web/create-json-sections-in-shopify/index.html index cd066c5..3de3ab8 100644 --- a/docs/web/create-json-sections-in-shopify/index.html +++ b/docs/web/create-json-sections-in-shopify/index.html @@ -1,4 +1,4 @@ -Create JSON Sections In Shopify | OhDoyleRules

Go home

If you're like me, you're probably constantly running into issues with the flexibility of Shopify themes. There are a lot of issues that come up when trying to squeeze in features and functionality that are "shop-adjacent". Shopify does a good job at running a store but it can be difficult to reach beyond that and make some more interactive experiences.

Recently, I wanted to share the content from one section on one page to another section on another page. This isn't really possible using liquid because you can't pass variables to sections. So even including them both on the same page wouldn't work. You also can't include sections in other sections. So that also won't work.

So how can you pass data between sections? Well, I managed to get this done by using the Section Rendering API. Now, this is not something in liquid, but it can be used as a JS API. Specifically, you can render a section in isolation and without the surrounding page content.

Now, since you can call this API in JS, how would you use this content? You can just go ahead and use it as HTML, but I wanted to use it as JSON. How do we do that? How can we make sure the section works normally when rendered on a page but returns "JSON" when rendered through the section API?

Well, this is possible by taking advantage of the "page state" when being called via the API.

Here is an example of the if statement required:

{% comment %}https://help.shopify.com/en/themes/development/sections/section-rendering-api{% endcomment %}
+Create JSON Sections In Shopify | OhDoyleRules

Go home

If you're like me, you're probably constantly running into issues with the flexibility of Shopify themes. There are a lot of issues that come up when trying to squeeze in features and functionality that are "shop-adjacent". Shopify does a good job at running a store but it can be difficult to reach beyond that and make some more interactive experiences.

Recently, I wanted to share the content from one section on one page to another section on another page. This isn't really possible using liquid because you can't pass variables to sections. So even including them both on the same page wouldn't work. You also can't include sections in other sections. So that also won't work.

So how can you pass data between sections? Well, I managed to get this done by using the Section Rendering API. Now, this is not something in liquid, but it can be used as a JS API. Specifically, you can render a section in isolation and without the surrounding page content.

Now, since you can call this API in JS, how would you use this content? You can just go ahead and use it as HTML, but I wanted to use it as JSON. How do we do that? How can we make sure the section works normally when rendered on a page but returns "JSON" when rendered through the section API?

Well, this is possible by taking advantage of the "page state" when being called via the API.

Here is an example of the if statement required:

{% comment %}https://help.shopify.com/en/themes/development/sections/section-rendering-api{% endcomment %}
 {% comment %}request.page_type will be "index" when using the section rendering API{% endcomment %}
 {% comment %}content_for_header will contain the query string in the request{% endcomment %}
 {%- if request.page_type == 'index' and content_for_header contains 'section_id' -%}
@@ -20,7 +20,7 @@
   "second-block-title": {"title": "Second Block Title", "subtitle": "This is my second subtitle"}
 }
 </div>
-

As you can tell, you can't just JSON.parse this. It has text inside that will bust up our JSON parser. How can we confidently parse the content inside the div and get that sweet schema in the center?

Let me introduce the DOMParser API. This is a well-supported API that will allow you to parse different types of structured documents.

In this case we are going to treat this content as text/html (which it is) and then use other DOM APIs to extract the JSON from the wrapper div surrounding it.

Here is the little function I came up with to fetch the rendered section, parse the content as HTML, extract the JSON inside, and return the object it contains.

See below:

/**
+

As you can tell, you can't just JSON.parse this. It has text inside that will bust up our JSON parser. How can we confidently parse the content inside the div and get that sweet schema in the center?

Let me introduce the DOMParser API. This is a well-supported API that will allow you to parse different types of structured documents.

In this case we are going to treat this content as text/html (which it is) and then use other DOM APIs to extract the JSON from the wrapper div surrounding it.

Here is the little function I came up with to fetch the rendered section, parse the content as HTML, extract the JSON inside, and return the object it contains.

See below:

/**
  * Get section from the rendering API
  * @see https://help.shopify.com/en/themes/development/sections/section-rendering-api
  *
@@ -56,4 +56,4 @@
 .then((data) => {
   // do stuff with the object...
 });
-

That is how you use this function. Just pass in the filename of the section template (without the .liquid on the end) and you will get JSON back! The best part about this is that it still works with the normal HTML. So you can use this in both cases without any issues. This whole setup allows website admins to visually design JSON output for your more complex features.

Side Note

Shopify likes to change things up every once in a while. They recently deprecated the include tag in favour of a new render tag. So there is no promise this will work forever.

You have been warned!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

That is how you use this function. Just pass in the filename of the section template (without the .liquid on the end) and you will get JSON back! The best part about this is that it still works with the normal HTML. So you can use this in both cases without any issues. This whole setup allows website admins to visually design JSON output for your more complex features.

Side Note

Shopify likes to change things up every once in a while. They recently deprecated the include tag in favour of a new render tag. So there is no promise this will work forever.

You have been warned!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/css3-badge-logo-in-svg/index.html b/docs/web/css3-badge-logo-in-svg/index.html index 0ce3946..cbf5ac3 100644 --- a/docs/web/css3-badge-logo-in-svg/index.html +++ b/docs/web/css3-badge-logo-in-svg/index.html @@ -1 +1 @@ -CSS3 badge logo in SVG | OhDoyleRules

Go home

I have been trying to find the CSS3 badge in a SVG format but it wasn’t that easy. The HTML5 one was the first result on Google. Now finally I found one. I am posting it here because now I will never lose it!

HTML5 and CSS3 badges

Here is the CSS3 logo:

CSS3 Scalable vector graphic

Here is the HTML5 logo in-case you need it:

HTML5 Scalable vector graphic

References

I found the logos on this website. They were in one single SVG file and I split them up.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +CSS3 badge logo in SVG | OhDoyleRules

Go home

I have been trying to find the CSS3 badge in a SVG format but it wasn’t that easy. The HTML5 one was the first result on Google. Now finally I found one. I am posting it here because now I will never lose it!

HTML5 and CSS3 badges

Here is the CSS3 logo:

CSS3 Scalable vector graphic

Here is the HTML5 logo in-case you need it:

HTML5 Scalable vector graphic

References

I found the logos on this website. They were in one single SVG file and I split them up.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/custom-google-forms/index.html b/docs/web/custom-google-forms/index.html index d3ffaed..f17ab17 100644 --- a/docs/web/custom-google-forms/index.html +++ b/docs/web/custom-google-forms/index.html @@ -1 +1 @@ -Custom Google Forms | OhDoyleRules

Go home

Update: Google has added the tools to customize a form with logos, colours, fonts, and backgrounds. Use this guide if you want even more custom styles, or if you want to embed the form within another page.

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something. After a bit of research I have found a way to create custom forms rather easily.

How to create a custom form

This is just the normal way to make a new google form. If you have made one before then just skip this.

  • Create a form as normal
  • Click the view live form
  • Copy everything inside the form tag including the form tag itself
  • Create a new blank HTML file
  • Create an empty div with a container class
  • Paste all the form markup inside there
  • Link the style.css stylsheet
  • Test the form and check the response in Google drive

Hosting the form

Edit: This feature is no longer avaliable to the new google drive.

Something relatively new to Google drive is the ability to host static HTML pages.

  • Create a public shared folder
  • Upload all your static html files
  • Open the index.html file in drive and click the preview button
  • Copy the link to the page it sends you too
  • Share that link with whoever because you are done!
  • This is how the demo form is hosted.

As a starting point I basically just copied the stylesheet from the default Google form page. Then I took all the colors and placed them into variables. The stylesheet needs to be stripped of things not necessary.

Moving Forward

I do not want to add any extra markup to the pages. The idea hear is to just copy the form mark that Google gives you and then just add a stylesheet that will make it themed.

Think themes for bootstrap. Markup stays, stylesheets change.

But right now I am just going to try and normalize the current stylsheet into something as default as possible so that I can then create a my-theme-name.css file that contains all the variables to do the styling. I am currently only using the variables in LESS but eventually I will use more of the feaures to get everything nice and themeable.

You can check out the Demo form in action or just jump right to the Github Repo.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Custom Google Forms | OhDoyleRules

Go home

Update: Google has added the tools to customize a form with logos, colours, fonts, and backgrounds. Use this guide if you want even more custom styles, or if you want to embed the form within another page.

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something. After a bit of research I have found a way to create custom forms rather easily.

How to create a custom form

This is just the normal way to make a new google form. If you have made one before then just skip this.

  • Create a form as normal
  • Click the view live form
  • Copy everything inside the form tag including the form tag itself
  • Create a new blank HTML file
  • Create an empty div with a container class
  • Paste all the form markup inside there
  • Link the style.css stylsheet
  • Test the form and check the response in Google drive

Hosting the form

Edit: This feature is no longer avaliable to the new google drive.

Something relatively new to Google drive is the ability to host static HTML pages.

  • Create a public shared folder
  • Upload all your static html files
  • Open the index.html file in drive and click the preview button
  • Copy the link to the page it sends you too
  • Share that link with whoever because you are done!
  • This is how the demo form is hosted.

As a starting point I basically just copied the stylesheet from the default Google form page. Then I took all the colors and placed them into variables. The stylesheet needs to be stripped of things not necessary.

Moving Forward

I do not want to add any extra markup to the pages. The idea hear is to just copy the form mark that Google gives you and then just add a stylesheet that will make it themed.

Think themes for bootstrap. Markup stays, stylesheets change.

But right now I am just going to try and normalize the current stylsheet into something as default as possible so that I can then create a my-theme-name.css file that contains all the variables to do the styling. I am currently only using the variables in LESS but eventually I will use more of the feaures to get everything nice and themeable.

You can check out the Demo form in action or just jump right to the Github Repo.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/detect-theme-editor-in-shopify-liquid-templates/index.html b/docs/web/detect-theme-editor-in-shopify-liquid-templates/index.html index a80a985..a4d35df 100644 --- a/docs/web/detect-theme-editor-in-shopify-liquid-templates/index.html +++ b/docs/web/detect-theme-editor-in-shopify-liquid-templates/index.html @@ -1,4 +1,4 @@ -Detect Theme Editor In Shopify Liquid Templates | OhDoyleRules

Go home

So you may already know that you cannot detect if an liquid page is running in the theme editor. You can however, detect this in Javascript.

But what if you want conditional content? What if you want to use this "switch" in a liquid template?

There are a couple good reasons to do this. The first one being that sometimes you need to show the content the website admin is editing in a different way.

I needed this feature when I had a repeating section of blocks that only showed when certain filters where applied to a form. This made it really hard to edit the content. You couldn't see all the blocks at once while you were editing. I wanted to just show all the blocks, with no filtering applied, but only when the theme editor was being used to create and edit blocks.

Shopify says you can't do this though. I'm here to tell you they are incorrect.

Before I continue, you will need to understand this hack used to detect query strings in liquid. Once read up on that snippet, you'll see how we build on that to detect the theme editor.

{%- comment -%}
+Detect Theme Editor In Shopify Liquid Templates | OhDoyleRules

Go home

So you may already know that you cannot detect if an liquid page is running in the theme editor. You can however, detect this in Javascript.

But what if you want conditional content? What if you want to use this "switch" in a liquid template?

There are a couple good reasons to do this. The first one being that sometimes you need to show the content the website admin is editing in a different way.

I needed this feature when I had a repeating section of blocks that only showed when certain filters where applied to a form. This made it really hard to edit the content. You couldn't see all the blocks at once while you were editing. I wanted to just show all the blocks, with no filtering applied, but only when the theme editor was being used to create and edit blocks.

Shopify says you can't do this though. I'm here to tell you they are incorrect.

Before I continue, you will need to understand this hack used to detect query strings in liquid. Once read up on that snippet, you'll see how we build on that to detect the theme editor.

{%- comment -%}
   http://freakdesign.com.au/blogs/news/get-the-url-querystring-values-with-liquid-in-shopify
   Capture the content for header containing the tracking data
 {%- endcomment -%}
@@ -15,10 +15,10 @@
   | replace:'\u0026','&'
   | strip
 -%}
-

The code above is a snippet of the trick used to detect query strings in liquid. We only need this piece. So let's continue.

{% comment %}When in the theme editor, the pageUrl variable is malformed/empty{% endcomment %}
+

The code above is a snippet of the trick used to detect query strings in liquid. We only need this piece. So let's continue.

{% comment %}When in the theme editor, the pageUrl variable is malformed/empty{% endcomment %}
 {% if pageUrl contains page.handle %}
   We are in the frontend of the website
 {% else %}
   We are in the theme editor of the website
 {% endif %}
-

That's all we need now. I'll explain what is going on.

Basically, when the liquid template is being accessed in the theme editor, the pageUrl variable will not properly formed. Since that is the case, we just compare that value with the value that is in page.handle.

When the template is being loaded properly on the frontend of the site, the pageUrl will be equal to the page.handle value.

In summation, we can now detect if the theme is running in the section editor! Yay!

Side Note

Shopify likes to change things up every once in a while. They recently deprecated the include tag in favour of a new render tag. So there is no promise this will work forever.

You have been warned!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

That's all we need now. I'll explain what is going on.

Basically, when the liquid template is being accessed in the theme editor, the pageUrl variable will not properly formed. Since that is the case, we just compare that value with the value that is in page.handle.

When the template is being loaded properly on the frontend of the site, the pageUrl will be equal to the page.handle value.

In summation, we can now detect if the theme is running in the section editor! Yay!

Side Note

Shopify likes to change things up every once in a while. They recently deprecated the include tag in favour of a new render tag. So there is no promise this will work forever.

You have been warned!

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/flexible-svg-placeholders/index.html b/docs/web/flexible-svg-placeholders/index.html index a4b4114..70fab00 100644 --- a/docs/web/flexible-svg-placeholders/index.html +++ b/docs/web/flexible-svg-placeholders/index.html @@ -1,2 +1,2 @@ -Flexible SVG Placeholder Images | OhDoyleRules

Go home

Do you use placehold.it? It is a great service. The only thing is when you are offline, or you are testing a page that needs a lot of placeholders, it may not be the greatest solution.

Enter the SVG placeholder.

Here are the properties you can set:

  • width and height
  • background-fill
  • font-color
  • font-family
  • font-size

Here is the actual SVG file. As you can see it is a PHP file, but you are serving it as an SVG (see the Content-Type part?). Here we grab the URL arguments and assign them to the SVG.

If you saved the file as placeholder-svg.php then it would be used like so:

<img src="placeholder-svg.php?wh=400x400&fill=bada55&color=000000&font=Georgia&size=20" />
+Flexible SVG Placeholder Images | OhDoyleRules

Go home

Do you use placehold.it? It is a great service. The only thing is when you are offline, or you are testing a page that needs a lot of placeholders, it may not be the greatest solution.

Enter the SVG placeholder.

Here are the properties you can set:

  • width and height
  • background-fill
  • font-color
  • font-family
  • font-size

Here is the actual SVG file. As you can see it is a PHP file, but you are serving it as an SVG (see the Content-Type part?). Here we grab the URL arguments and assign them to the SVG.

If you saved the file as placeholder-svg.php then it would be used like so:

<img src="placeholder-svg.php?wh=400x400&fill=bada55&color=000000&font=Georgia&size=20" />
 

This would be the output:

Placeholder Example

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/font-awesome-svg-icons/index.html b/docs/web/font-awesome-svg-icons/index.html index a9524c0..9256272 100644 --- a/docs/web/font-awesome-svg-icons/index.html +++ b/docs/web/font-awesome-svg-icons/index.html @@ -1,4 +1,4 @@ -Font Awesome SVG Icons | OhDoyleRules

Go home

This one was kind of annoying. I was looking for all the font-awesome icons in a nice sheet so that WARPAINT could design some mockups for a client. Well, of course this sheet doesn't exists.

So I used the following code to grab the icons from the cheatsheet page.

var arr = "";
+Font Awesome SVG Icons | OhDoyleRules

Go home

This one was kind of annoying. I was looking for all the font-awesome icons in a nice sheet so that WARPAINT could design some mockups for a client. Well, of course this sheet doesn't exists.

So I used the following code to grab the icons from the cheatsheet page.

var arr = "";
 $('.container .col-md-4').each(function(i, e) {
   arr += e.innerText.slice(0,1) + ', ';
 });
diff --git a/docs/web/function-currying-to-make-reusable-code/index.html b/docs/web/function-currying-to-make-reusable-code/index.html
index 8092167..39d9763 100644
--- a/docs/web/function-currying-to-make-reusable-code/index.html
+++ b/docs/web/function-currying-to-make-reusable-code/index.html
@@ -113,4 +113,4 @@
 }
 
 ReactDOM.render(<Form />, document.body);
-

View this example on codesandbox.

This is where I think curried functions really shine. This is a very compact way to support a lot of different form items but they can all use the same function even though they may all be unique items. Obviously, you can use this for any type of event handlers. Hopefully you can see the flexibility here and the potential to make simpler code that is more usable.

In summation

So next time you see some of these patterns pop up in your code (or some smells with global variables and repeated code) maybe reach for a curried function instead. Delicious.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

View this example on codesandbox.

This is where I think curried functions really shine. This is a very compact way to support a lot of different form items but they can all use the same function even though they may all be unique items. Obviously, you can use this for any type of event handlers. Hopefully you can see the flexibility here and the potential to make simpler code that is more usable.

In summation

So next time you see some of these patterns pop up in your code (or some smells with global variables and repeated code) maybe reach for a curried function instead. Delicious.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/index.html b/docs/web/index.html index a1b2057..bc87290 100644 --- a/docs/web/index.html +++ b/docs/web/index.html @@ -1 +1 @@ -Web | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

SQL As An API

An example of how to use SQL as an API instead of reaching for something like GraphQL

Read More

The $100 Website

I wrote a post on WARPAINT Media about people who ask about getting a website for $100.

Read More

Typeform Vector Logo

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

Read More

Custom Google Forms

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something

Read More

Tips For Using SVGs

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Read More

Vue Omnibar Component

A Vue component that is used to create modal popups that emulate omnibar, command palette, open anywhere, or other search functions/features

Read More

Nuxt Firebase Starter

An example project that uses nuxt.js and Firebase for simple auth (social or email/pass) and account profiles

Read More

Using slots in Vue js

If you are working with server-rendered apps, using Vue slots can help you create more reusable and flexible components

Read More

\ No newline at end of file +Web | OhDoyleRules

The personal blog of James Doyle (james2doyle) Web Developer in Canada. Logo

SQL As An API

An example of how to use SQL as an API instead of reaching for something like GraphQL

Read More

Using slots in Vue js

If you are working with server-rendered apps, using Vue slots can help you create more reusable and flexible components

Read More

Vue Omnibar Component

A Vue component that is used to create modal popups that emulate omnibar, command palette, open anywhere, or other search functions/features

Read More

Tips For Using SVGs

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Read More

Typeform Vector Logo

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

Read More

Custom Google Forms

I have been complaining about the lack of themes for google forms for a while now. I finally decided to stop crying and do something

Read More

The $100 Website

I wrote a post on WARPAINT Media about people who ask about getting a website for $100.

Read More

Nuxt Firebase Starter

An example project that uses nuxt.js and Firebase for simple auth (social or email/pass) and account profiles

Read More

\ No newline at end of file diff --git a/docs/web/kijiji-vector-logo/index.html b/docs/web/kijiji-vector-logo/index.html index 039918d..0f80297 100644 --- a/docs/web/kijiji-vector-logo/index.html +++ b/docs/web/kijiji-vector-logo/index.html @@ -1 +1 @@ -Kijiji Vector Logo | OhDoyleRules

Go home

This logo was not only impossible to find, because searching for "kijiji vector logo" returns nothing but people wanting to sell vector logos on Kijiji, but it was difficult to trace as well.

kijiji svg vector logo

The reason I had to find this logo, was that I made a new Chrome app called, "Kijiji Enhanced". It allows you to browse with larger thumbnails, rotate images in the image viewer, and also view inline maps at the bottom of posts.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Kijiji Vector Logo | OhDoyleRules

Go home

This logo was not only impossible to find, because searching for "kijiji vector logo" returns nothing but people wanting to sell vector logos on Kijiji, but it was difficult to trace as well.

kijiji svg vector logo

The reason I had to find this logo, was that I made a new Chrome app called, "Kijiji Enhanced". It allows you to browse with larger thumbnails, rotate images in the image viewer, and also view inline maps at the bottom of posts.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/laravel-scout-sonic-driver/index.html b/docs/web/laravel-scout-sonic-driver/index.html index 517b9c5..0a14856 100644 --- a/docs/web/laravel-scout-sonic-driver/index.html +++ b/docs/web/laravel-scout-sonic-driver/index.html @@ -1,2 +1,2 @@ -Laravel Scout Sonic Driver | OhDoyleRules

Go home

If you haven't heard about the cool new search indexer Sonic you must be living under a rock! If you want to read about Sonic directly from the author, check out his blog post on why and how they went on building the tool.

According to the Sonic page, it is:

Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

There are some main points on the site that outline some of the main features:

  • Search terms are stored in collections, organized in buckets
  • Search results return object identifiers
  • Search query typos are corrected
  • Insert and remove items in the index at the same time
  • Auto-complete any word in real-time via the suggest operation
  • Full Unicode compatibility on 80+ languages
  • Built as a TCP protocol

All these items make this an ideal candidate for the Laravel Scout search tool. Scout expects your search engine to return IDs that then get mapped to your query builder which will apply further transformations like WHERE clauses and pagination.

I took it upon myself to write a driver for Sonic. You can find it on my Github at Laravel Scout Sonic Driver and you can also install it via composer with the following command:

composer require james2doyle/laravel-scout-sonic
+Laravel Scout Sonic Driver | OhDoyleRules

Go home

If you haven't heard about the cool new search indexer Sonic you must be living under a rock! If you want to read about Sonic directly from the author, check out his blog post on why and how they went on building the tool.

According to the Sonic page, it is:

Fast, lightweight & schema-less search backend. An alternative to Elasticsearch that runs on a few MBs of RAM.

There are some main points on the site that outline some of the main features:

  • Search terms are stored in collections, organized in buckets
  • Search results return object identifiers
  • Search query typos are corrected
  • Insert and remove items in the index at the same time
  • Auto-complete any word in real-time via the suggest operation
  • Full Unicode compatibility on 80+ languages
  • Built as a TCP protocol

All these items make this an ideal candidate for the Laravel Scout search tool. Scout expects your search engine to return IDs that then get mapped to your query builder which will apply further transformations like WHERE clauses and pagination.

I took it upon myself to write a driver for Sonic. You can find it on my Github at Laravel Scout Sonic Driver and you can also install it via composer with the following command:

composer require james2doyle/laravel-scout-sonic
 

You will need to add some extra config information to config/scout.php before you can use everything correctly. Just checkout the README.md because all the instructions are in there.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/laravel-sqlite-cache/index.html b/docs/web/laravel-sqlite-cache/index.html index 17adda4..de06e38 100644 --- a/docs/web/laravel-sqlite-cache/index.html +++ b/docs/web/laravel-sqlite-cache/index.html @@ -1,4 +1,4 @@ -Using Sqlite As A Cache In Laravel | OhDoyleRules

Go home

I was playing with a new project using Laravel 5.7 and I wanted to use sqlite for the cache feature that comes with the framework. If you didn't know, Laravel allows you to choose a cache "driver" and Laravel will handle writes, reads, and even locks, using that cache.

By default, Laravel includes drivers for "database", "redis", "memcached", "file", and "array". In typical Laravel fashion, you can even write your own drivers. All you need to do is implement the Illuminate\Contracts\Cache\Store interface using the technology you wish to turn into a cache driver.

To my surprise, the "database" cache driver does not support multiple database connections. Which means whatever DB_CONNECTION you are using is also going to be used for the cache database driver when it is selected. I took a look to see how the "database" driver was implemented (it's in the Illuminate\Cache\DatabaseStore class) to see if there is any way I can make the database driver use a different connection than the one set in DB_CONNECTION. Looking deeper, it turns out that by extending Illuminate\Cache\DatabaseStore, I was able to put together a "sqlite" cache driver really quickly.

Before you set this all up, make sure you create the empty database. From the root of your project run: touch database/cache.sqlite. This creates an empty file that sqlite will mount as the database. To learn more about sqlite and how the file works, check out the page at the sqlite site about the Single-file Cross-platform Database.

For the next step, you will need to make sure that the sqlite3 command line tool is installed. If you need help getting that going, check out the download page for the tool. Once that is installed, or if you already have it, you can then connect to the database file via the sqlite cli: sqlite3 database/cache.sqlite. If you want more info about the CLI tool, check out the sqlite docs.

Finally, you need to setup that sqlite database by executing the following SQL statement:

CREATE TABLE `cache` (
+Using Sqlite As A Cache In Laravel | OhDoyleRules

Go home

I was playing with a new project using Laravel 5.7 and I wanted to use sqlite for the cache feature that comes with the framework. If you didn't know, Laravel allows you to choose a cache "driver" and Laravel will handle writes, reads, and even locks, using that cache.

By default, Laravel includes drivers for "database", "redis", "memcached", "file", and "array". In typical Laravel fashion, you can even write your own drivers. All you need to do is implement the Illuminate\Contracts\Cache\Store interface using the technology you wish to turn into a cache driver.

To my surprise, the "database" cache driver does not support multiple database connections. Which means whatever DB_CONNECTION you are using is also going to be used for the cache database driver when it is selected. I took a look to see how the "database" driver was implemented (it's in the Illuminate\Cache\DatabaseStore class) to see if there is any way I can make the database driver use a different connection than the one set in DB_CONNECTION. Looking deeper, it turns out that by extending Illuminate\Cache\DatabaseStore, I was able to put together a "sqlite" cache driver really quickly.

Before you set this all up, make sure you create the empty database. From the root of your project run: touch database/cache.sqlite. This creates an empty file that sqlite will mount as the database. To learn more about sqlite and how the file works, check out the page at the sqlite site about the Single-file Cross-platform Database.

For the next step, you will need to make sure that the sqlite3 command line tool is installed. If you need help getting that going, check out the download page for the tool. Once that is installed, or if you already have it, you can then connect to the database file via the sqlite cli: sqlite3 database/cache.sqlite. If you want more info about the CLI tool, check out the sqlite docs.

Finally, you need to setup that sqlite database by executing the following SQL statement:

CREATE TABLE `cache` (
    `key` STRING PRIMARY KEY,
    `value` TEXT NOT NULL,
    `expiration` INT DEFAULT 0
diff --git a/docs/web/nuxt-firebase-starter/index.html b/docs/web/nuxt-firebase-starter/index.html
index f5654fd..1ac8dad 100644
--- a/docs/web/nuxt-firebase-starter/index.html
+++ b/docs/web/nuxt-firebase-starter/index.html
@@ -1,4 +1,4 @@
-Nuxt Firebase Starter | OhDoyleRules

Go home

Over the past few weeks, I have been working on a boilerplate/starting template for using the Nuxt.js framework with Firebase.

I plan to use this boilerplate for easily creating apps that require authentication, real-time feedback from the database (chats, threads, account balances, etc.), and proper modern support for the new PWA (progressive web app) conventions (service worker, offline, code-splitting, etc.) without having to worry too much about laying the ground work each time.

If you already know about Nuxt and Firebase, I suggest just checking out the project and playing around with it. By default, I have setup the Nuxt PWA module, social login support for Google and Github, and also setup a database convention called “accounts” where users manage their public profile inside the application.

If you are unfamiliar with the tools, keep reading!

+++

Nuxt

As you may be able to tell from this post and some of the other posts on this site, I am also a Vue.js fan. I have been using it since before version 1 was released.

About four months ago, I started using a relatively new framework created on top of Vue.js (and stolen from the Next.js React project) called Nuxt.js.

Essentially, Nuxt (and Next) setup conventions for routing pages, creating components, adding “stores” (flux, redux, etc.) based on a directory setup.

So, for example (these are taken from the Nuxt documentation), you have a structure as follows:

pages/
+Nuxt Firebase Starter | OhDoyleRules

Go home

Over the past few weeks, I have been working on a boilerplate/starting template for using the Nuxt.js framework with Firebase.

I plan to use this boilerplate for easily creating apps that require authentication, real-time feedback from the database (chats, threads, account balances, etc.), and proper modern support for the new PWA (progressive web app) conventions (service worker, offline, code-splitting, etc.) without having to worry too much about laying the ground work each time.

If you already know about Nuxt and Firebase, I suggest just checking out the project and playing around with it. By default, I have setup the Nuxt PWA module, social login support for Google and Github, and also setup a database convention called “accounts” where users manage their public profile inside the application.

If you are unfamiliar with the tools, keep reading!

+++

Nuxt

As you may be able to tell from this post and some of the other posts on this site, I am also a Vue.js fan. I have been using it since before version 1 was released.

About four months ago, I started using a relatively new framework created on top of Vue.js (and stolen from the Next.js React project) called Nuxt.js.

Essentially, Nuxt (and Next) setup conventions for routing pages, creating components, adding “stores” (flux, redux, etc.) based on a directory setup.

So, for example (these are taken from the Nuxt documentation), you have a structure as follows:

pages/
 --| _slug/
 -----| comments.vue
 -----| index.vue
@@ -29,4 +29,4 @@
     }
   ]
 }
-

You can then call this.$route.params.slug (for slug-comments) or this.$route.params.id (for users-id) inside the components or pages that are on that route.

Pretty slick right?

So Nuxt is cool. It lets me quickly create an SPA (single-page app) without worrying about setting up an elaborate route object or worrying about how to organize my projects folders and logic. You just toss files in folders, and everything pretty much works.

Some people hate this but I find the structure liberating. I plan to use that additional time/energy to focus on building my app and not organizing configs or fiddling with route logic.

Now onto the Firebase portion of the project:


Firebase

If you have some experience with Firebase you probably know that it is an excellent service offering some nice features for dealing with "real-time" data as well as some other features like storage, push notifications, serverless functions, and authentication. The best part is that it is usually free for most small projects as they don't use enough resources to qualify for the paid tiers of service.

When I first started using Firebase, I didn't like it. The concept of snapshots and paths was confusing for someone coming from a key-value store or a more traditional relational database. It also lacks higher order sorting and querying. For example, you can query for ranges, but you can't query for LIKE or "matches/patterns”.

Queries like that can be limiting, but once you shift your mentality to something more like reducing and filtering, these issues disappear. You also need to be conscious about how you structure your database, but I digress.

As I got more comfortable with it, I began to see how powerful and easy it is to build things like chats, threads/commenting systems, atomic counters (“like” systems, ratings, etc.), and even used it to do browser push notifications. I am using it right now to build an API rate limiter!


Combining Forces!

Using Nuxt and Firebase together has been easy. I was able to create a nice login flow (with support for Google and Github OAuth!) within about a day.

I also added support for this convention called “accounts”. When a new user signs up, we create an account on the Firebase database that is read-write for that user. This object contains their display name and profile image path.

Since we have a profile image (either pulled from the social login or assigned a default), I figured; why not add support for uploading a new profile image to the Firebase storage?

So I did! And now you can easily manage a mini-profile on the app without any extra configuration. It is there by default:

nuxt firebase account preview

Nuxt Firebase Account Preview

As you can see from the animation above, super simple interface with live updating thanks to the bindings from our application store to the Firebase database!

Again, you can check out the project at the repo on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

You can then call this.$route.params.slug (for slug-comments) or this.$route.params.id (for users-id) inside the components or pages that are on that route.

Pretty slick right?

So Nuxt is cool. It lets me quickly create an SPA (single-page app) without worrying about setting up an elaborate route object or worrying about how to organize my projects folders and logic. You just toss files in folders, and everything pretty much works.

Some people hate this but I find the structure liberating. I plan to use that additional time/energy to focus on building my app and not organizing configs or fiddling with route logic.

Now onto the Firebase portion of the project:


Firebase

If you have some experience with Firebase you probably know that it is an excellent service offering some nice features for dealing with "real-time" data as well as some other features like storage, push notifications, serverless functions, and authentication. The best part is that it is usually free for most small projects as they don't use enough resources to qualify for the paid tiers of service.

When I first started using Firebase, I didn't like it. The concept of snapshots and paths was confusing for someone coming from a key-value store or a more traditional relational database. It also lacks higher order sorting and querying. For example, you can query for ranges, but you can't query for LIKE or "matches/patterns”.

Queries like that can be limiting, but once you shift your mentality to something more like reducing and filtering, these issues disappear. You also need to be conscious about how you structure your database, but I digress.

As I got more comfortable with it, I began to see how powerful and easy it is to build things like chats, threads/commenting systems, atomic counters (“like” systems, ratings, etc.), and even used it to do browser push notifications. I am using it right now to build an API rate limiter!


Combining Forces!

Using Nuxt and Firebase together has been easy. I was able to create a nice login flow (with support for Google and Github OAuth!) within about a day.

I also added support for this convention called “accounts”. When a new user signs up, we create an account on the Firebase database that is read-write for that user. This object contains their display name and profile image path.

Since we have a profile image (either pulled from the social login or assigned a default), I figured; why not add support for uploading a new profile image to the Firebase storage?

So I did! And now you can easily manage a mini-profile on the app without any extra configuration. It is there by default:

nuxt firebase account preview

Nuxt Firebase Account Preview

As you can see from the animation above, super simple interface with live updating thanks to the bindings from our application store to the Firebase database!

Again, you can check out the project at the repo on Github.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/phalconphp-completions/index.html b/docs/web/phalconphp-completions/index.html index 58eb394..8e4509b 100644 --- a/docs/web/phalconphp-completions/index.html +++ b/docs/web/phalconphp-completions/index.html @@ -1,4 +1,4 @@ -PhalconPHP Completions | OhDoyleRules

Go home

I have created a package of Sublime Text completions for Phalcon PHP 1.3.*.

There are 414 total right now. This is pretty much a copy-paste from my sublime-node-snippets repo.

PhalconPHP Completions in action

PhalconPHP Completions in action

Installing

Via Package Control

Just look for phalconphp-completions on Package Control.

Manually Adding Repo

  • Open the Commands Palette (command+shift+p)
  • Package Control: Add Repository
  • Past in this repos URL
  • Press Enter
  • Open the palette again
  • press enter on "phalconphp-completions"
  • watch it install

By Download

Drop this folder in your Sublime Text packages directory.

Using

Pressing \ (backslash) or : will end the snippet lookup.

Therefore, you will have better results if you pretend the slashes and colons aren't needed. So if you are looking for Phalcon\Text::increment, you would type phalcontextincrement and you would see the results coming up.

See the GIF above!

Building

I went to each page of the PhalconPHP docs, and copied the functions. Then I wrote a converter to take each function and convert it to a snippet.

For Example, this line:

Phalcon\Text::endsWith($str, $end, $ignoreCase)
+PhalconPHP Completions | OhDoyleRules

Go home

I have created a package of Sublime Text completions for Phalcon PHP 1.3.*.

There are 414 total right now. This is pretty much a copy-paste from my sublime-node-snippets repo.

PhalconPHP Completions in action

PhalconPHP Completions in action

Installing

Via Package Control

Just look for phalconphp-completions on Package Control.

Manually Adding Repo

  • Open the Commands Palette (command+shift+p)
  • Package Control: Add Repository
  • Past in this repos URL
  • Press Enter
  • Open the palette again
  • press enter on "phalconphp-completions"
  • watch it install

By Download

Drop this folder in your Sublime Text packages directory.

Using

Pressing \ (backslash) or : will end the snippet lookup.

Therefore, you will have better results if you pretend the slashes and colons aren't needed. So if you are looking for Phalcon\Text::increment, you would type phalcontextincrement and you would see the results coming up.

See the GIF above!

Building

I went to each page of the PhalconPHP docs, and copied the functions. Then I wrote a converter to take each function and convert it to a snippet.

For Example, this line:

Phalcon\Text::endsWith($str, $end, $ignoreCase)
 

Is going to get converted to:

Phalcon\\Text::endsWith(\\$${1:str}, \\$${2:end}, \\$${3:ignoreCase});${0}
 

sources.txt

This file is cool. It is just a line-by-line output of the Phalcon docs functions. This is the file that is parsed to generate the snippets.

Running The Build

Just run node build.js and it will rake the sources.txt file and then write the new snippet in the snippets folder.

Everything before the first ( will be used as the filename.

Adding New Snippets

Here is how I quickly got all these snippets.

First, I went to the docs for the class, and I looked to see what the code examples were wrapped in. For the all the docs pages, the methods and properties are show in a p.method-signature element.

So to quickly get the list, I ran the following code:

Array.prototype.slice.call(document.querySelectorAll(".method-signature"), 0).map(function(item){
   return item.textContent.trim();
diff --git a/docs/web/radio-checkboxes/index.html b/docs/web/radio-checkboxes/index.html
index 3f8b1b7..f9133b9 100644
--- a/docs/web/radio-checkboxes/index.html
+++ b/docs/web/radio-checkboxes/index.html
@@ -1,4 +1,4 @@
-Styling radio and checkbox inputs | OhDoyleRules

Go home

Styling inputs can be pretty annoying. I don’t think I really have any consistent way of making custom inputs. Especially when it comes to radio and checkboxes. I will always prefer using CSS instead of images for obvious reasons. But for radios and checkboxes I normally use images. I usually just make a png sprite and use the :checked selector to move its position.

But now, I have discovered a new way! I took the idea from CSS Tricks/Wufoo Forms. It basically uses the label as the element. You hide the input tag and then style the label with checked and unchecked states.

End Results
/* style this span element so we can display nicely, this styling is not necessary */
+Styling radio and checkbox inputs | OhDoyleRules

Go home

Styling inputs can be pretty annoying. I don’t think I really have any consistent way of making custom inputs. Especially when it comes to radio and checkboxes. I will always prefer using CSS instead of images for obvious reasons. But for radios and checkboxes I normally use images. I usually just make a png sprite and use the :checked selector to move its position.

But now, I have discovered a new way! I took the idea from CSS Tricks/Wufoo Forms. It basically uses the label as the element. You hide the input tag and then style the label with checked and unchecked states.

End Results
/* style this span element so we can display nicely, this styling is not necessary */
 span {
     margin: 10px 0;
     display: block;
@@ -42,4 +42,4 @@
   <input id="Check1" name="Checks" type="checkbox" value="Item 1">
   <label for="Check1">Item 1</label>
 </span>
-

The text inside the label will be the text displayed as your button. You must have the ID and FOR attributes on the input and labels. Removing those will break the functionality. As always I have added a little jsFiddle demo into the mix.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

The text inside the label will be the text displayed as your button. You must have the ID and FOR attributes on the input and labels. Removing those will break the functionality. As always I have added a little jsFiddle demo into the mix.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/simple-benchmarks-with-apache-ab/index.html b/docs/web/simple-benchmarks-with-apache-ab/index.html index 35685e6..de555a0 100644 --- a/docs/web/simple-benchmarks-with-apache-ab/index.html +++ b/docs/web/simple-benchmarks-with-apache-ab/index.html @@ -1 +1 @@ -Simple Benchmarks With Apache AB | OhDoyleRules

Go home

Why Benchmark?

Did you know that Apache has a benchmarking tool for testing the HTTP server? It is called ab, and it is pretty great!

As your site grows in popularity, complexity, or size, you will want to test the site to see how it preforms. Having the site crash or lock up during peak time can be devastating for a small blog or e-commerce site. It means lost revenue, and can leave a visitor with a bad impression of your site. This can drive them to generate a bad referral, or worse, go to your competitor.

Users aren't going to care if your site is being bombarded with traffic constantly, they really only care about their own personal experience.

Studies and analytics show that the slower your site is, the impact on your sales or target actions is affected exponentially.

Getting ab

The ab tool can be found in most default Apache (httpd 2.2 and 2.4) setups. If you don't have it, you can install the apache2-utils package and get it from there.

Testing

I found a great article on this site that explains a lot of details about using ab, setting up Apache, configuring PHP, and information about the results. You should check it out.

I used the following script, based on that article above, that tests a site in succession and prints the results to a file.

To run the script, download it and unzip it. Then run chmod +x bench.sh to allow the script to run. Then you can use ./bench and the script will begin.

The article I linked to above mentioned using a delay of 4 minutes between running ab.

Be careful using ab, as it essentially emulates a DDOS attack, in that it generates as many requests as possible as fast as possible. There is no delay option in ab, so there is no way to emulate something like "10 hits every 10 seconds" or anything like that.

Results

Here are the things you are going to want to pay attention to. These definitions are taken from the ab site.

  • Failed requests: The number of requests that were considered a failure. If the number is greater than zero, another line will be printed showing the number of requests that failed due to connecting, reading, incorrect content length, or exceptions.
  • Non-2xx responses: The number of responses that were not in the 200 series of response codes. If all responses were 200, this field is not printed.
  • Requests per second: This is the number of requests per second. This value is the result of dividing the number of requests by the total time taken.
  • Time per request: The average time spent per request. The first value is calculated with the formula (concurrency * timetaken * 1000 / done) while the second value is calculated with the formula (timetaken * 1000 / done).

Failures and errors are generally not good. You should check your logs for anything that happened during your requests.

For Requests Per Second, this tells you how quickly your site was able to process all those requests. Higher is better because it means that your site was able to serve the content without hiccups.

The Time Per Request, although that article says it isn't important in the context of ab, I think it is still important to watch. This metric tells you how long the average request takes. Keep in mind we don't have javascript running, or

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Simple Benchmarks With Apache AB | OhDoyleRules

Go home

Why Benchmark?

Did you know that Apache has a benchmarking tool for testing the HTTP server? It is called ab, and it is pretty great!

As your site grows in popularity, complexity, or size, you will want to test the site to see how it preforms. Having the site crash or lock up during peak time can be devastating for a small blog or e-commerce site. It means lost revenue, and can leave a visitor with a bad impression of your site. This can drive them to generate a bad referral, or worse, go to your competitor.

Users aren't going to care if your site is being bombarded with traffic constantly, they really only care about their own personal experience.

Studies and analytics show that the slower your site is, the impact on your sales or target actions is affected exponentially.

Getting ab

The ab tool can be found in most default Apache (httpd 2.2 and 2.4) setups. If you don't have it, you can install the apache2-utils package and get it from there.

Testing

I found a great article on this site that explains a lot of details about using ab, setting up Apache, configuring PHP, and information about the results. You should check it out.

I used the following script, based on that article above, that tests a site in succession and prints the results to a file.

To run the script, download it and unzip it. Then run chmod +x bench.sh to allow the script to run. Then you can use ./bench and the script will begin.

The article I linked to above mentioned using a delay of 4 minutes between running ab.

Be careful using ab, as it essentially emulates a DDOS attack, in that it generates as many requests as possible as fast as possible. There is no delay option in ab, so there is no way to emulate something like "10 hits every 10 seconds" or anything like that.

Results

Here are the things you are going to want to pay attention to. These definitions are taken from the ab site.

  • Failed requests: The number of requests that were considered a failure. If the number is greater than zero, another line will be printed showing the number of requests that failed due to connecting, reading, incorrect content length, or exceptions.
  • Non-2xx responses: The number of responses that were not in the 200 series of response codes. If all responses were 200, this field is not printed.
  • Requests per second: This is the number of requests per second. This value is the result of dividing the number of requests by the total time taken.
  • Time per request: The average time spent per request. The first value is calculated with the formula (concurrency * timetaken * 1000 / done) while the second value is calculated with the formula (timetaken * 1000 / done).

Failures and errors are generally not good. You should check your logs for anything that happened during your requests.

For Requests Per Second, this tells you how quickly your site was able to process all those requests. Higher is better because it means that your site was able to serve the content without hiccups.

The Time Per Request, although that article says it isn't important in the context of ab, I think it is still important to watch. This metric tells you how long the average request takes. Keep in mind we don't have javascript running, or

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/simple-slash-commands/index.html b/docs/web/simple-slash-commands/index.html index 9508e28..063e530 100644 --- a/docs/web/simple-slash-commands/index.html +++ b/docs/web/simple-slash-commands/index.html @@ -1,4 +1,4 @@ -Simple Slack Slash Supplier | OhDoyleRules

Go home

This is a reprint of the README from my project simple-slack-slash-supplier.

This examples project showcases a simple way to create new slash commands without having to work too hard.

This example assumes you have access to AWS Lambda as well as have created a Slash command in Slack.

Handling application/x-www-form-urlencoded

Slack sends their POST requests using Content-Type: application/x-www-form-urlencoded. This is not a content type that Lambda supports out-of-the-box. We need to do some legwork in order to map that raw request body to JSON which we can understand more easily in our Node.js handler function.

Preliminary setup:

  • You must create a standalone POST resource action
  • Be sure "Integration type" is set to "Lambda Function"
  • Make sure Use Lambda Proxy integration is checked
  • Remove the original ANY resource action to be sure it doesn't conflict

Mapping templates

In order to properly handle application/x-www-form-urlencoded, we need to make a mapping template for it.

Here are the steps:

  • Click on "Integration Request" when viewing the details of your POST resource action
  • Pop open the "Mapping templates" accordion
  • Click "Add mapping template"
  • Use application/x-www-form-urlencoded in the field that appears
  • Scroll down and see the new form to enter in your template details
  • Select "Method request passthrough" from the "Generate template" dropdown
  • Put the following content in the textarea:
##  Ripped from "https://stackoverflow.com/a/52705985/1170664"
+Simple Slack Slash Supplier | OhDoyleRules

Go home

This is a reprint of the README from my project simple-slack-slash-supplier.

This examples project showcases a simple way to create new slash commands without having to work too hard.

This example assumes you have access to AWS Lambda as well as have created a Slash command in Slack.

Handling application/x-www-form-urlencoded

Slack sends their POST requests using Content-Type: application/x-www-form-urlencoded. This is not a content type that Lambda supports out-of-the-box. We need to do some legwork in order to map that raw request body to JSON which we can understand more easily in our Node.js handler function.

Preliminary setup:

  • You must create a standalone POST resource action
  • Be sure "Integration type" is set to "Lambda Function"
  • Make sure Use Lambda Proxy integration is checked
  • Remove the original ANY resource action to be sure it doesn't conflict

Mapping templates

In order to properly handle application/x-www-form-urlencoded, we need to make a mapping template for it.

Here are the steps:

  • Click on "Integration Request" when viewing the details of your POST resource action
  • Pop open the "Mapping templates" accordion
  • Click "Add mapping template"
  • Use application/x-www-form-urlencoded in the field that appears
  • Scroll down and see the new form to enter in your template details
  • Select "Method request passthrough" from the "Generate template" dropdown
  • Put the following content in the textarea:
##  Ripped from "https://stackoverflow.com/a/52705985/1170664"
 {
     "body-json" : $input.json('$'),
     "params" : {
diff --git a/docs/web/simple-spam-stopper/index.html b/docs/web/simple-spam-stopper/index.html
index d393303..8672f1a 100644
--- a/docs/web/simple-spam-stopper/index.html
+++ b/docs/web/simple-spam-stopper/index.html
@@ -1 +1 @@
-The Simple Spam Stopper | OhDoyleRules

Go home

For the last year at WARPAINT Media, we have been getting assaulted with spam. Everything from "Chinese Jerseys" and "Super SEO Ultra Elite Package Extreme" offers.

We are using PyroCMS for the website. The default contact plugin is pretty awesome. It has some really great features and couldn't be easier to use. There is a little honeypot for spam bots, but it seems to not be doing a great job, at least for us.

The great thing about the Pyro contact form is that it lets the developer define some validation without much work. In the past I have added questions like "what is one plus one? (use a number)", other times I have tried "are you a human?" with a dropdown. Both seemed fine. But I wanted something a little more transparent and more conventional than strange questions about math or your species.

The solution that I came up with was, 2 email fields. That's it. I have one that is called "email" and another field that is called "check". When the user submits the form, the email and check field and validated. The rules for them are that they need to be identical, but they also need to be valid emails.

So there is a sort of a double validation going on. They need to be putting in a real email and they need to know that the email needs to match the check field. The label for this second check field is just "Enter Your Email Again". Since it comes after the first field with an Email label, people tend to figure it out.

How about the results? Well we used to get about 10-20 spam per day. Now we get about 1-3 a week and none of them are from our websites contact form.

Tl;Dr

I added a second email input and practically eliminated our spam.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +The Simple Spam Stopper | OhDoyleRules

Go home

For the last year at WARPAINT Media, we have been getting assaulted with spam. Everything from "Chinese Jerseys" and "Super SEO Ultra Elite Package Extreme" offers.

We are using PyroCMS for the website. The default contact plugin is pretty awesome. It has some really great features and couldn't be easier to use. There is a little honeypot for spam bots, but it seems to not be doing a great job, at least for us.

The great thing about the Pyro contact form is that it lets the developer define some validation without much work. In the past I have added questions like "what is one plus one? (use a number)", other times I have tried "are you a human?" with a dropdown. Both seemed fine. But I wanted something a little more transparent and more conventional than strange questions about math or your species.

The solution that I came up with was, 2 email fields. That's it. I have one that is called "email" and another field that is called "check". When the user submits the form, the email and check field and validated. The rules for them are that they need to be identical, but they also need to be valid emails.

So there is a sort of a double validation going on. They need to be putting in a real email and they need to know that the email needs to match the check field. The label for this second check field is just "Enter Your Email Again". Since it comes after the first field with an Email label, people tend to figure it out.

How about the results? Well we used to get about 10-20 spam per day. Now we get about 1-3 a week and none of them are from our websites contact form.

Tl;Dr

I added a second email input and practically eliminated our spam.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/slack-url-meta-data/index.html b/docs/web/slack-url-meta-data/index.html index bac3abe..375a74d 100644 --- a/docs/web/slack-url-meta-data/index.html +++ b/docs/web/slack-url-meta-data/index.html @@ -1,13 +1,13 @@ -Slack Meta Data For URLs and Links | OhDoyleRules

Go home

If you use Slack, then you have probably noticed the awesome feature they have for generating nice meta data whenever you paste a URL or an image.

Slack meta tag fetching example

That is pretty slick! So how does it know what to grab for us?

From the Slack website:

It fetches as little of the page as it can (using HTTP Range headers) to extract meta tags about the content. Specifically, we are looking for oEmbed and Twitter Card / Open Graph tags. If a page's tags refer to an image, video, or audio file, we will fetch that file as well to check validity and extract other metadata.

I've never used oEmbed, so I am going to skip that.

Let's break down each part of the resulting meta fetch by Slack. Here is what we have, and the order it appears.

  • WARPAINT Media (Site Title)
  • Homepage | WARPAINT Media (Page Title)
  • WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development. (Meta Description)

Now we can look at the Open Graph meta tags that are being used to generate these results:

<meta property="og:site_name" content="WARPAINT Media">
+Slack Meta Data For URLs and Links | OhDoyleRules

Go home

If you use Slack, then you have probably noticed the awesome feature they have for generating nice meta data whenever you paste a URL or an image.

Slack meta tag fetching example

That is pretty slick! So how does it know what to grab for us?

From the Slack website:

It fetches as little of the page as it can (using HTTP Range headers) to extract meta tags about the content. Specifically, we are looking for oEmbed and Twitter Card / Open Graph tags. If a page's tags refer to an image, video, or audio file, we will fetch that file as well to check validity and extract other metadata.

I've never used oEmbed, so I am going to skip that.

Let's break down each part of the resulting meta fetch by Slack. Here is what we have, and the order it appears.

  • WARPAINT Media (Site Title)
  • Homepage | WARPAINT Media (Page Title)
  • WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development. (Meta Description)

Now we can look at the Open Graph meta tags that are being used to generate these results:

<meta property="og:site_name" content="WARPAINT Media">
 <meta property="og:title" content="Homepage | WARPAINT Media">
 <meta property="og:description" content="WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development.">
 <meta property="og:image" content="http://warpaintmedia.ca/img/meta-image.jpg">
 <meta property="og:url" content="http://warpaintmedia.ca">
-

Open Graph tags are pretty popular. The biggest user of Open Graph is Facebook. Here is the result when the link is shared on Facebook:

Facebook meta tag fetching example

Make sure you test your tags using the Facebook Open Graph Object Debugger. This will help you spot errors in your tags. If there are errors, Slack will not load the content. You need to have valid tags to make Slack work nicely.

How about the Twitter Card meta tags?

<meta name="twitter:card" content="summary_large_image">
+

Open Graph tags are pretty popular. The biggest user of Open Graph is Facebook. Here is the result when the link is shared on Facebook:

Facebook meta tag fetching example

Make sure you test your tags using the Facebook Open Graph Object Debugger. This will help you spot errors in your tags. If there are errors, Slack will not load the content. You need to have valid tags to make Slack work nicely.

How about the Twitter Card meta tags?

<meta name="twitter:card" content="summary_large_image">
 <meta name="twitter:title" content="Homepage | WARPAINT Media">
 <meta name="twitter:description" content="WARPAINT Media specializes in improving customer experience through marketing, design, analytics, and development.">
 <meta name="twitter:site" content="@warpaintmedia">
 <meta name="twitter:creator" content="@warpaintmedia">
 <meta name="twitter:url" content="http://warpaintmedia.ca">
 <meta name="twitter:image:src" content="http://warpaintmedia.ca/img/meta-image.jpg">
-

As you noticed, there is no "site title" being set. You can also see in the screenshot that Twitter doesn't really care about the site title, they just show the page title. If that was something you wanted to adjust, then you would tweak the twitter:title tag to reflect what you wanted for a page title.

Now you may have copied these Twitter meta tags and pasted them in your site and filled in your information. That will not work. You need to use the Twitter Card Validator to test, and then register, your account and URL.

When you

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

As you noticed, there is no "site title" being set. You can also see in the screenshot that Twitter doesn't really care about the site title, they just show the page title. If that was something you wanted to adjust, then you would tweak the twitter:title tag to reflect what you wanted for a page title.

Now you may have copied these Twitter meta tags and pasted them in your site and filled in your information. That will not work. You need to use the Twitter Card Validator to test, and then register, your account and URL.

When you

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/source-code-pro-sublime/index.html b/docs/web/source-code-pro-sublime/index.html index 6088393..f0fca96 100644 --- a/docs/web/source-code-pro-sublime/index.html +++ b/docs/web/source-code-pro-sublime/index.html @@ -1 +1 @@ -Source Code Pro on Sublime Text | OhDoyleRules

Go home

Github Gists Logo

I recently switched my font in sublime text to the new Adobe font, Source Code Pro.

Source Code Pro sublime text 2 screenshot

It looks fantastic! It is so smooth and crisp. To switch to it as your main font, first download the font. Then add the line “font_face”: “Source Code Pro” in your Preferences->User Settings(keyboard shortcut command+comma to open the settings) and that’s it! Pretty fly.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Source Code Pro on Sublime Text | OhDoyleRules

Go home

Github Gists Logo

I recently switched my font in sublime text to the new Adobe font, Source Code Pro.

Source Code Pro sublime text 2 screenshot

It looks fantastic! It is so smooth and crisp. To switch to it as your main font, first download the font. Then add the line “font_face”: “Source Code Pro” in your Preferences->User Settings(keyboard shortcut command+comma to open the settings) and that’s it! Pretty fly.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/sql-as-an-api/index.html b/docs/web/sql-as-an-api/index.html index 3df86e1..212af64 100644 --- a/docs/web/sql-as-an-api/index.html +++ b/docs/web/sql-as-an-api/index.html @@ -1,4 +1,4 @@ -SQL As An API | OhDoyleRules

Go home

There has been a lot of fanfare around the idea of GraphQL lately. For good reason in my opinion.

GraphQL allows you to essentially define your API with your query. For example, if you want to only include a single field then you can do that. If you want to nest related items, or not, you can do that as well.

If you take a quick look at the GraphQL website (as it is today) you can see how they lay this out:

# Describe your data
+SQL As An API | OhDoyleRules

Go home

There has been a lot of fanfare around the idea of GraphQL lately. For good reason in my opinion.

GraphQL allows you to essentially define your API with your query. For example, if you want to only include a single field then you can do that. If you want to nest related items, or not, you can do that as well.

If you take a quick look at the GraphQL website (as it is today) you can see how they lay this out:

# Describe your data
 type Project {
   name: String
   tagline: String
@@ -35,7 +35,7 @@
     "tagline": "A query language for APIs"
   }
 ]
-

So this really is just another way to store and query your data. We describe the structure, query it using the specified query language with our expected structure, and print out the results.

So can we create a GraphQL experience using just SQL?

So can we create a GraphQL experience using just SQL? Short answer, yes. This isn't a new concept though. There is a tool called datasette that does just this.

So where do we start? Well, we don't want users to be able to modify our database. We want to safely expose SQL to the internet. Crazy idea but it just might work.

So what we want to do is open a Sqlite database using the "ro" (read only) mode (see mode details here). This mode means that you cannot modify the database. You can only run queries that are reads. This is a great feature as we need to expose this database to the public internet if we are going to use it as an API.

Once we have a mounted sqlite database that is set to "read only" mode, we can then pass queries right to it and return the results.

Basic Example

We are going to whip up this example using PHP and the chinook database from this site. It is insanely easy to get this working using the SQLite3 class and a single json_encode call.

Let's see how to get this done:

<?php
+

So this really is just another way to store and query your data. We describe the structure, query it using the specified query language with our expected structure, and print out the results.

So can we create a GraphQL experience using just SQL?

So can we create a GraphQL experience using just SQL? Short answer, yes. This isn't a new concept though. There is a tool called datasette that does just this.

So where do we start? Well, we don't want users to be able to modify our database. We want to safely expose SQL to the internet. Crazy idea but it just might work.

So what we want to do is open a Sqlite database using the "ro" (read only) mode (see mode details here). This mode means that you cannot modify the database. You can only run queries that are reads. This is a great feature as we need to expose this database to the public internet if we are going to use it as an API.

Once we have a mounted sqlite database that is set to "read only" mode, we can then pass queries right to it and return the results.

Basic Example

We are going to whip up this example using PHP and the chinook database from this site. It is insanely easy to get this working using the SQLite3 class and a single json_encode call.

Let's see how to get this done:

<?php
 // be sure to open with `SQLITE3_OPEN_READONLY`
 $db = new SQLite3('chinook.sqlite', SQLITE3_OPEN_READONLY);
 // pull out the query from the POST request
@@ -214,4 +214,4 @@
     "total": 8
   }
 }
-

So there you go. A pretty robust solution that has excellent performance, a simple and well-known query language, and supports almost any combination of data.

In summation

So this could be a great option for a lot of applications. You can quickly imagine how this could be used for something like a simple search API. You could have an application hook to add data to this special database when your data is changed. Sqlite is a really viable option for a lot of things as it can grow to 140 TB, supports json_ functions, and even binary data.

Keep in mind that you can't really nest the same way you can in GraphQL. But you might be ok with that depending on your use case.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

So there you go. A pretty robust solution that has excellent performance, a simple and well-known query language, and supports almost any combination of data.

In summation

So this could be a great option for a lot of applications. You can quickly imagine how this could be used for something like a simple search API. You could have an application hook to add data to this special database when your data is changed. Sqlite is a really viable option for a lot of things as it can grow to 140 TB, supports json_ functions, and even binary data.

Keep in mind that you can't really nest the same way you can in GraphQL. But you might be ok with that depending on your use case.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/styling-input-redux/index.html b/docs/web/styling-input-redux/index.html index 967c927..4fa07e4 100644 --- a/docs/web/styling-input-redux/index.html +++ b/docs/web/styling-input-redux/index.html @@ -1 +1 @@ -Styling Radios and Checkboxes. Redux. | OhDoyleRules

Go home

A few weeks ago, or something like that, I made a post about styling your labels to act as checkboxes/radios. I recently saw a post on Dribbble by Mikael Eidenberg which inspired me to make some nicely styled examples of that method in action.

Synth App Buttons in pure CSS

Here is the pen on Codepen.io. I usually use jsFidddle but I decided to change it up this time. Maybe my next post will be hosted on tinker.io or dabblet… although tinker does not have accounts like the others.

This is the exact same method as my previous article, only there is a lot more css.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Styling Radios and Checkboxes. Redux. | OhDoyleRules

Go home

A few weeks ago, or something like that, I made a post about styling your labels to act as checkboxes/radios. I recently saw a post on Dribbble by Mikael Eidenberg which inspired me to make some nicely styled examples of that method in action.

Synth App Buttons in pure CSS

Here is the pen on Codepen.io. I usually use jsFidddle but I decided to change it up this time. Maybe my next post will be hosted on tinker.io or dabblet… although tinker does not have accounts like the others.

This is the exact same method as my previous article, only there is a lot more css.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/the-100-dollar-website/index.html b/docs/web/the-100-dollar-website/index.html index bd3b2a6..237f6a5 100644 --- a/docs/web/the-100-dollar-website/index.html +++ b/docs/web/the-100-dollar-website/index.html @@ -1 +1 @@ -The $100 Website | OhDoyleRules

Go home

I wrote a post on WARPAINT Media about people who ask about getting a website for $100. It isn't an angry rant, although there is some frustration. It is more about courtesy.

I wouldn't ask you to take $100 for an entire weeks worth of work. So please, don't ask me. Here is the article.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +The $100 Website | OhDoyleRules

Go home

I wrote a post on WARPAINT Media about people who ask about getting a website for $100. It isn't an angry rant, although there is some frustration. It is more about courtesy.

I wouldn't ask you to take $100 for an entire weeks worth of work. So please, don't ask me. Here is the article.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/tips-for-using-svgs/index.html b/docs/web/tips-for-using-svgs/index.html index 6aadc8b..6f9eea5 100644 --- a/docs/web/tips-for-using-svgs/index.html +++ b/docs/web/tips-for-using-svgs/index.html @@ -1,4 +1,4 @@ -Tips For Using SVGs | OhDoyleRules

Go home

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Use Viewbox

This one is a little gem. The viewBox property allows you to set the dimensions of the image but it will also allow you can have responsive SVGs.

They maintain their ratios, but they will scale to 100% width and height. If you have an SVG that is 64 by 64, the syntax for the viewBox property would be viewBox="0 0 64 64". Pretty simple. Just make sure you remove the width and height on the base SVG tag when using viewBox.

If you open an SVG in a new window, like opening a new file in the browser, you will notice that when zooming in, if viewBox property set properly, the image stays the same size. It won't zoom.

Use base64 Images

So there are a couple ways you can embed an image in an SVG. I have found that the best way is to use a base 64 encoded string as the image href.

<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+Tips For Using SVGs | OhDoyleRules

Go home

I found using SVGs can be both amazing and extremely frustrating, so I have to share this information so no one looses their mind.

Use Viewbox

This one is a little gem. The viewBox property allows you to set the dimensions of the image but it will also allow you can have responsive SVGs.

They maintain their ratios, but they will scale to 100% width and height. If you have an SVG that is 64 by 64, the syntax for the viewBox property would be viewBox="0 0 64 64". Pretty simple. Just make sure you remove the width and height on the base SVG tag when using viewBox.

If you open an SVG in a new window, like opening a new file in the browser, you will notice that when zooming in, if viewBox property set properly, the image stays the same size. It won't zoom.

Use base64 Images

So there are a couple ways you can embed an image in an SVG. I have found that the best way is to use a base 64 encoded string as the image href.

<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
   <defs>
     <pattern id="background-image" patternUnits="userSpaceOnUse" width="64" height="64">
       <!-- the width and height of the image should match the pattern, in most cases -->
diff --git a/docs/web/typeform-vector-logo/index.html b/docs/web/typeform-vector-logo/index.html
index 16ae63d..084c8e6 100644
--- a/docs/web/typeform-vector-logo/index.html
+++ b/docs/web/typeform-vector-logo/index.html
@@ -1 +1 @@
-Typeform Vector Logo | OhDoyleRules

Go home

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

typeform svg vector

If you are a photoshopper, you can also grab the EPS file.

Update

Here is the official repository of Typeform branding graphics.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +Typeform Vector Logo | OhDoyleRules

Go home

Another difficult logo to find. This one is for the fun new Typeform service. Typeform allows you to create dynamic and fun forms for clients, events, and other general uses.

typeform svg vector

If you are a photoshopper, you can also grab the EPS file.

Update

Here is the official repository of Typeform branding graphics.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/update-laravel-pagination-with-new-collection/index.html b/docs/web/update-laravel-pagination-with-new-collection/index.html index e6a7b5c..b4bbcaa 100644 --- a/docs/web/update-laravel-pagination-with-new-collection/index.html +++ b/docs/web/update-laravel-pagination-with-new-collection/index.html @@ -1,4 +1,4 @@ -Update Laravel Pagination With New Collection | OhDoyleRules

Go home

Have you even done a database search (using the DB facade) and got back an array of results that wasn't wrapped in lovely little Eloquent Models? After some googling, I am sure you probably found out about the hydrate method. Using hydrate, you can turn an array of results from the database into real models from your application. This works phenomenally.

But what if you want to return paginated results? How do you used the results after they are already paginated? Well there is a method called setCollection on the LengthAwarePaginator class. What it will let you do actually set the results of the pagination to a new collection.

Here is an example of how that works in practice:

<?php
+Update Laravel Pagination With New Collection | OhDoyleRules

Go home

Have you even done a database search (using the DB facade) and got back an array of results that wasn't wrapped in lovely little Eloquent Models? After some googling, I am sure you probably found out about the hydrate method. Using hydrate, you can turn an array of results from the database into real models from your application. This works phenomenally.

But what if you want to return paginated results? How do you used the results after they are already paginated? Well there is a method called setCollection on the LengthAwarePaginator class. What it will let you do actually set the results of the pagination to a new collection.

Here is an example of how that works in practice:

<?php
 /**
  * Hydrates the paginated results for the query
  */
diff --git a/docs/web/use-laravel-valet-for-wordpress-multisites/index.html b/docs/web/use-laravel-valet-for-wordpress-multisites/index.html
index 14c6f47..4766663 100644
--- a/docs/web/use-laravel-valet-for-wordpress-multisites/index.html
+++ b/docs/web/use-laravel-valet-for-wordpress-multisites/index.html
@@ -1,4 +1,4 @@
-Use Laravel Valet for WordPress Multisites | OhDoyleRules

Go home

Working with WordPress multisites can sometimes be a pain. Especially if you are not using the amazing Laravel Valet tool to manage your local sites.

From the site:

Valet is a Laravel (or any site really) development environment for Mac minimalists. No Vagrant, no /etc/hosts file.

I would add that everything can be managed from the command line as well. Very handy.

I'm going to make some assumptions for the rest of the post. Firstly, you already have a WordPress multisite setup at ~/Sites/my-blog (or wherever your root folder is) that is already resolving with my-blog.localhost. The TLD (in my case .localhost) is not important either. Just imagine yours there instead.

We are going to want to add a new domain name that resolves to the exact same folder as our current blog setup. We can do that with valet link.

So let's run some commands:

  • cd ~/Sites/my-blog
  • valet link my-other-blog.localhost

Now we have 2 domains pointing to the same folder:

  • my-blog.localhost
  • my-other-blog.localhost

They are both going to resolve to the same site right now. That's OK. We need to tweak our wp-config.php to work properly with the requests.

If we have multisite already setup, we should see some sites in the wp_blogs table of our database. We want each of these "sites" to reflect the domains on our system. You can update those now and make sure that the domains match the ones we are setting up.

Then we need to update our wp-config.php with some changes:

<?php
+Use Laravel Valet for WordPress Multisites | OhDoyleRules

Go home

Working with WordPress multisites can sometimes be a pain. Especially if you are not using the amazing Laravel Valet tool to manage your local sites.

From the site:

Valet is a Laravel (or any site really) development environment for Mac minimalists. No Vagrant, no /etc/hosts file.

I would add that everything can be managed from the command line as well. Very handy.

I'm going to make some assumptions for the rest of the post. Firstly, you already have a WordPress multisite setup at ~/Sites/my-blog (or wherever your root folder is) that is already resolving with my-blog.localhost. The TLD (in my case .localhost) is not important either. Just imagine yours there instead.

We are going to want to add a new domain name that resolves to the exact same folder as our current blog setup. We can do that with valet link.

So let's run some commands:

  • cd ~/Sites/my-blog
  • valet link my-other-blog.localhost

Now we have 2 domains pointing to the same folder:

  • my-blog.localhost
  • my-other-blog.localhost

They are both going to resolve to the same site right now. That's OK. We need to tweak our wp-config.php to work properly with the requests.

If we have multisite already setup, we should see some sites in the wp_blogs table of our database. We want each of these "sites" to reflect the domains on our system. You can update those now and make sure that the domains match the ones we are setting up.

Then we need to update our wp-config.php with some changes:

<?php
 // wp-config.php
 
 // ... there is going to be other stuff in here at the top...
diff --git a/docs/web/use-nginx-for-a-b-testing/index.html b/docs/web/use-nginx-for-a-b-testing/index.html
index 22d5eae..e1c9ecc 100644
--- a/docs/web/use-nginx-for-a-b-testing/index.html
+++ b/docs/web/use-nginx-for-a-b-testing/index.html
@@ -1,4 +1,4 @@
-Use Nginx for A/B Testing | OhDoyleRules

Go home

I was starting a new project the other day that had a heavy marketing influence. The team was wondering about possibly A/B testing the main content section of the website.

It got me thinking about doing A/B test with content and how that works.

There are a couple of ways that A/B testing can be accomplished. After some quick Google-ing, I found this cool feature of Nginx called split_clients.

Here is a little breakdown of that module:

The ngx_http_split_clients_module module creates variables suitable for A/B testing, also known as split testing.

There is a great article at DigitalOcean about getting split_clients setup.

Nginx will allow you to apply certain variables to a segment of your traffic. For example:

http {
+Use Nginx for A/B Testing | OhDoyleRules

Go home

I was starting a new project the other day that had a heavy marketing influence. The team was wondering about possibly A/B testing the main content section of the website.

It got me thinking about doing A/B test with content and how that works.

There are a couple of ways that A/B testing can be accomplished. After some quick Google-ing, I found this cool feature of Nginx called split_clients.

Here is a little breakdown of that module:

The ngx_http_split_clients_module module creates variables suitable for A/B testing, also known as split testing.

There is a great article at DigitalOcean about getting split_clients setup.

Nginx will allow you to apply certain variables to a segment of your traffic. For example:

http {
     split_clients "${remote_addr}" $variant {
             0.5%    .one;
             2.0%    .two;
diff --git a/docs/web/using-digitalocean-spaces/index.html b/docs/web/using-digitalocean-spaces/index.html
index 3cf7e57..4927203 100644
--- a/docs/web/using-digitalocean-spaces/index.html
+++ b/docs/web/using-digitalocean-spaces/index.html
@@ -1,4 +1,4 @@
-Using DigitalOcean Spaces | OhDoyleRules

Go home

If you are a fan of DigitalOcean, or you keep an eye on DevOps news, then you probably heard about the new DigitalOcean Spaces offering.

Spaces is essentially an AWS S3-compatible service but with that special DigitalOcean touch.

Now Spaces is not a 1:1 replacement for S3. There are quite a few features that have not yet been implemented. They also don't have any GUI interfaces for things like managing bucket policies or CORS configurations.

I made an example project for how to use Spaces with an S3 node module (which you can find here) to see what the differences were when actually using the service.

I was really surprised to find that it was almost identical.

Here is an example of how you might tweak your config from an S3 project:

{
+Using DigitalOcean Spaces | OhDoyleRules

Go home

If you are a fan of DigitalOcean, or you keep an eye on DevOps news, then you probably heard about the new DigitalOcean Spaces offering.

Spaces is essentially an AWS S3-compatible service but with that special DigitalOcean touch.

Now Spaces is not a 1:1 replacement for S3. There are quite a few features that have not yet been implemented. They also don't have any GUI interfaces for things like managing bucket policies or CORS configurations.

I made an example project for how to use Spaces with an S3 node module (which you can find here) to see what the differences were when actually using the service.

I was really surprised to find that it was almost identical.

Here is an example of how you might tweak your config from an S3 project:

{
   region: 'nyc3', // very familiar setting
   endpoint: 'https://nyc3.digitaloceanspaces.com', // something you probably ignored before
   signatureVersion: 'v4', // spaces supports the V4 API
diff --git a/docs/web/using-slots-in-vue-js/index.html b/docs/web/using-slots-in-vue-js/index.html
index 7b1fa19..fba23a0 100644
--- a/docs/web/using-slots-in-vue-js/index.html
+++ b/docs/web/using-slots-in-vue-js/index.html
@@ -1,4 +1,4 @@
-Using slots in Vue js | OhDoyleRules

Go home

If you are working with server-rendered apps (your view is compiled on the server down to HTML), and you are a Vue.js user, then you should definitely learn to use a Vue feature called slots! Not only will it allow you to make more reusable and flexible components, but you will also improve the rendering performance of your apps as well.

Basic Example

Here is the example we will be refactoring later. It is a "profile list" which takes in an array of users from a prop, and then creates "profile cards" for each one. Finally, it adds a button in each card that emits an event with the user data when someone clicks on it.

<!--
+Using slots in Vue js | OhDoyleRules

Go home

If you are working with server-rendered apps (your view is compiled on the server down to HTML), and you are a Vue.js user, then you should definitely learn to use a Vue feature called slots! Not only will it allow you to make more reusable and flexible components, but you will also improve the rendering performance of your apps as well.

Basic Example

Here is the example we will be refactoring later. It is a "profile list" which takes in an array of users from a prop, and then creates "profile cards" for each one. Finally, it adds a button in each card that emits an event with the user data when someone clicks on it.

<!--
   This is the way you might typically build a component like this
  -->
 <template>
@@ -40,7 +40,7 @@
 <profile-list v-bind:users="<?= htmlspecialchars(json_encode($users), ENT_QUOTES, 'UTF-8') ?>"></profile-list>
 <!-- laravel blade example, note single quotes -->
 <profile-list v-bind:users='@json($users)'></profile-list>
-

As you can see the main approach here is to encode the users as JSON and pass them to the components users prop. This can create quite a mess of JSON if you look at the source of your pages if you have lots of components that do this.

Why Use Slots?

As I mentioned, with slots you can create more reusable and flexible components and improve the rendering performance of your apps. I'll outline some of the details below.

On Reusability and Flexibility

Normally, you would send data to your Vue component using props, events, or a store. But using slots, you can essentially skip a rendering step and send your pre-compiled Vue into your Vue components template. This allows you to create components with flexible templates instead of adding tons of props and lots of switches in the templates.

What I've typically seen in the past with larger apps, developers end up creating super-components with tons and tons of props. The reason this happens is usually because the components require flexibility. If you have lots of props, that is probably a code smell. A way to reduce the number of props is to use events, new components, and of course, slots, to split things up!

On Passing Data

Since a slot is rendered on the server, you can also do some handy things with the template. Say you want to show or hide different content based on whether the user is authenticated. Well, this is super easy when working in server-rendered templates since the session is so easy to access. You might even have entire blocks of the UI that are not shown if the user is not logged in or is logged out.

This can help to dramatically reduce the work you need to do on your frontend to access data or building token-based APIs.

On Rendering

Since a slot is prerended HTML, your component ends up changing to really only encapsulating the logic of your component and less "template" of your component. You'll notice that when using slots instead of props, your page with show the content inside the slot even before Vue has finished loading. This is great because it can help reduce flashes of unstyled content that make your app look a little janky.

Although browers will run your javascript, a by-product of using slots is that you could improve the SEO of your site, as you are trying to fill the page with the most HTML possible.

Mostly slots are good for making more flexible components. The performance benefit is super helpful though.

Refactored to Slots

<!--
+

As you can see the main approach here is to encode the users as JSON and pass them to the components users prop. This can create quite a mess of JSON if you look at the source of your pages if you have lots of components that do this.

Why Use Slots?

As I mentioned, with slots you can create more reusable and flexible components and improve the rendering performance of your apps. I'll outline some of the details below.

On Reusability and Flexibility

Normally, you would send data to your Vue component using props, events, or a store. But using slots, you can essentially skip a rendering step and send your pre-compiled Vue into your Vue components template. This allows you to create components with flexible templates instead of adding tons of props and lots of switches in the templates.

What I've typically seen in the past with larger apps, developers end up creating super-components with tons and tons of props. The reason this happens is usually because the components require flexibility. If you have lots of props, that is probably a code smell. A way to reduce the number of props is to use events, new components, and of course, slots, to split things up!

On Passing Data

Since a slot is rendered on the server, you can also do some handy things with the template. Say you want to show or hide different content based on whether the user is authenticated. Well, this is super easy when working in server-rendered templates since the session is so easy to access. You might even have entire blocks of the UI that are not shown if the user is not logged in or is logged out.

This can help to dramatically reduce the work you need to do on your frontend to access data or building token-based APIs.

On Rendering

Since a slot is prerended HTML, your component ends up changing to really only encapsulating the logic of your component and less "template" of your component. You'll notice that when using slots instead of props, your page with show the content inside the slot even before Vue has finished loading. This is great because it can help reduce flashes of unstyled content that make your app look a little janky.

Although browers will run your javascript, a by-product of using slots is that you could improve the SEO of your site, as you are trying to fill the page with the most HTML possible.

Mostly slots are good for making more flexible components. The performance benefit is super helpful though.

Refactored to Slots

<!--
   All we need to do is cut that inside layout and replace it with a "default" slot
  -->
 <template>
diff --git a/docs/web/varnish-for-static-sites/index.html b/docs/web/varnish-for-static-sites/index.html
index 6201648..2336480 100644
--- a/docs/web/varnish-for-static-sites/index.html
+++ b/docs/web/varnish-for-static-sites/index.html
@@ -1,4 +1,4 @@
-Varnish For Static Sites | OhDoyleRules

Go home

Recently, my company had a request to build a series of sites that could handle huge bursts of traffic. I asked some friends of mine, what a good solution for this would be. All of them said Varnish.

If you don't know what Varnish is, check out this definition from their documentation:

Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy. You install it in front of any server that speaks HTTP and configure it to cache the contents. Varnish Cache is really, really fast. It typically speeds up delivery with a factor of 300 - 1000x, depending on your architecture.

So you can see that having this tool would be really nice. A simple way to get started with Varnish is to set it up on a flat-file site. Maybe something like PhileCMS perhaps? Here is a nice curated list of flat-file site generators.

Setting Up

This tutorial assumes the following:

* This tutorial is for Ubuntu 12.04. Replace this command: deb http://repo.varnish-cache.org/ubuntu/ lucid varnish-3.0 with this one deb http://repo.varnish-cache.org/ubuntu/ trusty varnish-4.0

Apache Setup

First, you will want to serve Apache on a different port, because Varnish is going to act as our "web server" and Apache will only be used if the cache is stale or there is no item in the Varnish cache.

We can open /etc/apache2/ports.conf and make the following change:

# Listen 80
+Varnish For Static Sites | OhDoyleRules

Go home

Recently, my company had a request to build a series of sites that could handle huge bursts of traffic. I asked some friends of mine, what a good solution for this would be. All of them said Varnish.

If you don't know what Varnish is, check out this definition from their documentation:

Varnish Cache is a web application accelerator also known as a caching HTTP reverse proxy. You install it in front of any server that speaks HTTP and configure it to cache the contents. Varnish Cache is really, really fast. It typically speeds up delivery with a factor of 300 - 1000x, depending on your architecture.

So you can see that having this tool would be really nice. A simple way to get started with Varnish is to set it up on a flat-file site. Maybe something like PhileCMS perhaps? Here is a nice curated list of flat-file site generators.

Setting Up

This tutorial assumes the following:

* This tutorial is for Ubuntu 12.04. Replace this command: deb http://repo.varnish-cache.org/ubuntu/ lucid varnish-3.0 with this one deb http://repo.varnish-cache.org/ubuntu/ trusty varnish-4.0

Apache Setup

First, you will want to serve Apache on a different port, because Varnish is going to act as our "web server" and Apache will only be used if the cache is stale or there is no item in the Varnish cache.

We can open /etc/apache2/ports.conf and make the following change:

# Listen 80
 Listen 127.0.0.1:8080
 

We commented out the original listening port, and added our own.

If we have any sites setup, we should change their virtual host files as well. These files live in /etc/apache2/sites-available and end in .conf, so this demo file might be called example.com.conf

<VirtualHost *:8080>
   ServerAdmin hello@example.com
@@ -11,4 +11,4 @@
              -f /etc/varnish/default.vcl \
              -S /etc/varnish/secret \
              -s malloc,256m"
-

Most likely, you will only change the -a part to 80.

Our Varnish configuration for our site lives at /etc/varnish/default.vcl, here is the one I am using:

It is very basic. I really only want to cache text files (HTML, CSS, Javascript/JSON) and images.

To restart Varnish, use service varnish restart.

Restarting Varnish

If you restart or run out of memory, Varnish will rebuild the cache. This isn't great because you are trying to keep Varnish alive and the cache enabled.

Testing

At this point we are ready to test.

I would suggest running the command vanrnishstat on your remote server so you can see things happening in the Varnish cache. Pressing the arrow keys up and down will give you a description of the item.

Then you can go to you site and click around. You should see the Varnish Stat table getting updated. You will want to watch the MAIN.cache_hit and MAIN.cache_miss numbers.

You want the MAIN.cache_hit to be as high as possible. This means that your Apache is not getting tapped for information, but Varnish is serving it straight to the client.

For the MAIN.cache_miss, you want that to be as low as possible. This number represents the number of times that Varnish had to hit Apache. Having a low MAIN.cache_miss means that we are only tapping Apache when we must.

Troubleshooting

Since we added that line in our default.vcl file for X-Cache, we can see which files are being served by Varnish. Using dev tools in Chrome/Safari or Firefox, we can look for a header in our request called X-Cache.

Varnish x-cache header example

You can see that this item was HIT. This means that it will be counted in the MAIN.cache_hit column and not MAIN.cache_miss column. Good!

Things that mess up Varnish are headers and cookie headers especially. Sometimes you want or need some headers though. This setup does not allow any cookies to get through. If you had a normal CMS with this setup, you would find you wouldn't be able to log in, or there might be some CSRF Token (Cross Site Request Forgery) issues with form submissions if you use CSRF.

Varnish will let you control different areas where cookies or other things can change. You will want to refer to the Varnish Documentation for these advanced features.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Most likely, you will only change the -a part to 80.

Our Varnish configuration for our site lives at /etc/varnish/default.vcl, here is the one I am using:

It is very basic. I really only want to cache text files (HTML, CSS, Javascript/JSON) and images.

To restart Varnish, use service varnish restart.

Restarting Varnish

If you restart or run out of memory, Varnish will rebuild the cache. This isn't great because you are trying to keep Varnish alive and the cache enabled.

Testing

At this point we are ready to test.

I would suggest running the command vanrnishstat on your remote server so you can see things happening in the Varnish cache. Pressing the arrow keys up and down will give you a description of the item.

Then you can go to you site and click around. You should see the Varnish Stat table getting updated. You will want to watch the MAIN.cache_hit and MAIN.cache_miss numbers.

You want the MAIN.cache_hit to be as high as possible. This means that your Apache is not getting tapped for information, but Varnish is serving it straight to the client.

For the MAIN.cache_miss, you want that to be as low as possible. This number represents the number of times that Varnish had to hit Apache. Having a low MAIN.cache_miss means that we are only tapping Apache when we must.

Troubleshooting

Since we added that line in our default.vcl file for X-Cache, we can see which files are being served by Varnish. Using dev tools in Chrome/Safari or Firefox, we can look for a header in our request called X-Cache.

Varnish x-cache header example

You can see that this item was HIT. This means that it will be counted in the MAIN.cache_hit column and not MAIN.cache_miss column. Good!

Things that mess up Varnish are headers and cookie headers especially. Sometimes you want or need some headers though. This setup does not allow any cookies to get through. If you had a normal CMS with this setup, you would find you wouldn't be able to log in, or there might be some CSRF Token (Cross Site Request Forgery) issues with form submissions if you use CSRF.

Varnish will let you control different areas where cookies or other things can change. You will want to refer to the Varnish Documentation for these advanced features.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/view-folder-tree-in-macosx-terminal/index.html b/docs/web/view-folder-tree-in-macosx-terminal/index.html index 573f6d8..eb1a52d 100644 --- a/docs/web/view-folder-tree-in-macosx-terminal/index.html +++ b/docs/web/view-folder-tree-in-macosx-terminal/index.html @@ -1 +1 @@ -View Folder Tree in MacOSX Terminal | OhDoyleRules

Go home

View Folder Tree in MacOSX Terminal

zsh tree alias

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +View Folder Tree in MacOSX Terminal | OhDoyleRules

Go home

View Folder Tree in MacOSX Terminal

zsh tree alias

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/vue-omnibar-component/index.html b/docs/web/vue-omnibar-component/index.html index 36bb4f1..8a73dcc 100644 --- a/docs/web/vue-omnibar-component/index.html +++ b/docs/web/vue-omnibar-component/index.html @@ -1,4 +1,4 @@ -Vue Omnibar Component | OhDoyleRules

Go home

I'm working on a project right now that requires us to have a search modal. The feature is actually inspired by the "Quick Search" experience in Notion. I looked around for a component that was already created that would do this for me. I couldn't find one, so I wrote my own!

This component allows you to create modal popups that emulate omnibar, command palette, open anywhere, or other "search and act" functions/features. It is really simple and uses slots to make it easier to customize. It comes with some basic styling so you don't have to fight with it too much.

One of the cool things about the search box in the modal is that it is using Fuse.js for the filtering part. This means you can search complex objects really easy. You can even rank-order properties that are being searched! Pretty slick, right?

Demo

demo of the omnibar modal

Check out the website for the component in order to view the demo.

Features

  • built-in filtering using Fuse.js
  • custom key combo support
  • listens for esc key
  • bring your own styling (basic styles included)
  • arrow key support
  • uses slots for best flexibility
  • "off-click" closes the modal

Installation

npm install vue-omnibar
+Vue Omnibar Component | OhDoyleRules

Go home

I'm working on a project right now that requires us to have a search modal. The feature is actually inspired by the "Quick Search" experience in Notion. I looked around for a component that was already created that would do this for me. I couldn't find one, so I wrote my own!

This component allows you to create modal popups that emulate omnibar, command palette, open anywhere, or other "search and act" functions/features. It is really simple and uses slots to make it easier to customize. It comes with some basic styling so you don't have to fight with it too much.

One of the cool things about the search box in the modal is that it is using Fuse.js for the filtering part. This means you can search complex objects really easy. You can even rank-order properties that are being searched! Pretty slick, right?

Demo

demo of the omnibar modal

Check out the website for the component in order to view the demo.

Features

  • built-in filtering using Fuse.js
  • custom key combo support
  • listens for esc key
  • bring your own styling (basic styles included)
  • arrow key support
  • uses slots for best flexibility
  • "off-click" closes the modal

Installation

npm install vue-omnibar
 

Global Usage

import Vue from 'vue';
 import Omnibar from 'vue-omnibar';
 
@@ -12,4 +12,4 @@
   },
   // ...
 };
-

To learn more, check out the website for the component.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

To learn more, check out the website for the component.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/vue-toggle-component/index.html b/docs/web/vue-toggle-component/index.html index e2ad4db..2492100 100644 --- a/docs/web/vue-toggle-component/index.html +++ b/docs/web/vue-toggle-component/index.html @@ -1,4 +1,4 @@ -Vue Toggle Component | OhDoyleRules

Go home

Have you ever created a switch component that just shows and hides an element? How about an accordion? Or maybe a slider? If you distill down these components into their core offering, it is really just a simple state toggle that is either a boolean or a index/key value that is being used.

I created this component because it is something I use all the time. I wanted to share it with other people so I released it as a package. The state can be either a boolean or a string. This means you can use it to create experiences that are not just on/off and show/hide.

With a simple toggle, you can build almost any UI experience. Think about experiences like show/hide, accordions, nested menus, and even sliders, they mostly revolve around a single piece of state.

Demo

Check out the website for demos.

Installation

npm install -S vue-ui-toggle
+Vue Toggle Component | OhDoyleRules

Go home

Have you ever created a switch component that just shows and hides an element? How about an accordion? Or maybe a slider? If you distill down these components into their core offering, it is really just a simple state toggle that is either a boolean or a index/key value that is being used.

I created this component because it is something I use all the time. I wanted to share it with other people so I released it as a package. The state can be either a boolean or a string. This means you can use it to create experiences that are not just on/off and show/hide.

With a simple toggle, you can build almost any UI experience. Think about experiences like show/hide, accordions, nested menus, and even sliders, they mostly revolve around a single piece of state.

Demo

Check out the website for demos.

Installation

npm install -S vue-ui-toggle
 
const Toggle = require('vue-ui-toggle'); // es5/node
 // import Toggle from 'vue-ui-toggle'; // es6
 
@@ -12,4 +12,4 @@
   },
   // ...
 };
-

To learn more, check out the website for the component.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

To learn more, check out the website for the component.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/wordpress-browser-body-class/index.html b/docs/web/wordpress-browser-body-class/index.html index 5e6eab1..1a4f53e 100644 --- a/docs/web/wordpress-browser-body-class/index.html +++ b/docs/web/wordpress-browser-body-class/index.html @@ -1 +1 @@ -WordPress Browser Body Class | OhDoyleRules

Go home

Sometimes, browsers just don't behave. While working on a WordPress site, I had a particular styling issue that was affecting Safari. I was wondering what the best way to target the browser was.

If you didn't know, there are some primitive browser detection booleans that are built into WordPress.

Using those primitives, I was able to whip up a little snippet that adds classes to the body tag depending on the browser - no plugins required.

As you can see, we simply loop over each one of those browser booleans and append them to the output.

Easy, and no plugin needed. Just add that code to your functions.php file.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +WordPress Browser Body Class | OhDoyleRules

Go home

Sometimes, browsers just don't behave. While working on a WordPress site, I had a particular styling issue that was affecting Safari. I was wondering what the best way to target the browser was.

If you didn't know, there are some primitive browser detection booleans that are built into WordPress.

Using those primitives, I was able to whip up a little snippet that adds classes to the body tag depending on the browser - no plugins required.

As you can see, we simply loop over each one of those browser booleans and append them to the output.

Easy, and no plugin needed. Just add that code to your functions.php file.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/wordpress-plugin-swipe-js/index.html b/docs/web/wordpress-plugin-swipe-js/index.html index 05c490d..e15e467 100644 --- a/docs/web/wordpress-plugin-swipe-js/index.html +++ b/docs/web/wordpress-plugin-swipe-js/index.html @@ -1 +1 @@ -WordPress Plugin Swipe.js | OhDoyleRules

Go home

I have made my first WordPress plugin and github project: A WordPress plugin for Swipe.js.

This is my first attempt at a WordPress plugin and a Github project.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +WordPress Plugin Swipe.js | OhDoyleRules

Go home

I have made my first WordPress plugin and github project: A WordPress plugin for Swipe.js.

This is my first attempt at a WordPress plugin and a Github project.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/wordpress-wp-content-500-errors/index.html b/docs/web/wordpress-wp-content-500-errors/index.html index 001c827..cff26e3 100644 --- a/docs/web/wordpress-wp-content-500-errors/index.html +++ b/docs/web/wordpress-wp-content-500-errors/index.html @@ -1,4 +1,4 @@ -Fixing Wordpress wp-content 500 Errors | OhDoyleRules

Go home

Yesterday, a friend emailed me about her Wordpress site acting crazy. For some reason all the site assets weren't loading and were returning 500 errors.

Troubleshooting

I had her check the usual things: .htaccess is there, the asset actually existed, some plugin wasn't busting the site. Each one was throwing a 500 error, so it was something else.

Eventually she gave me her log in, which had a super simple password, and I logged in to see what was up. Nothing seemed to be wrong, no crazy plugins or weird issues.

I then logged into the FTP server to sniff around. Checked the .htaccess, nothing was wrong. I checked the index.php, didn't see anything weird.

I opened the wp-config.php and added all the debugging constants, here there are for laziness:

// Enable WP_DEBUG mode
+Fixing Wordpress wp-content 500 Errors | OhDoyleRules

Go home

Yesterday, a friend emailed me about her Wordpress site acting crazy. For some reason all the site assets weren't loading and were returning 500 errors.

Troubleshooting

I had her check the usual things: .htaccess is there, the asset actually existed, some plugin wasn't busting the site. Each one was throwing a 500 error, so it was something else.

Eventually she gave me her log in, which had a super simple password, and I logged in to see what was up. Nothing seemed to be wrong, no crazy plugins or weird issues.

I then logged into the FTP server to sniff around. Checked the .htaccess, nothing was wrong. I checked the index.php, didn't see anything weird.

I opened the wp-config.php and added all the debugging constants, here there are for laziness:

// Enable WP_DEBUG mode
 define('WP_DEBUG', true);
 
 // Enable Debug logging to the /wp-content/debug.log file
@@ -10,4 +10,4 @@
 
 // Use dev versions of core JS and CSS files (only needed if you are modifying these core files)
 define('SCRIPT_DEBUG', true);
-

I then navigated around the site. The content (text, database data) was loading fine, but all the assets were busted. There was a map plugin that was hitting Google Maps and that loaded fine. I then knew it was this specific site.

After poking around some more, I noticed an index.php and a .htaccess in the root of /wp-content.

The Fix

Well, I have come across this problem before. Wordpress was hacked. Hacked might be a strong word, since the password was really weak. A bot probably had that password in it's script and guessed it without issue.

The "hacker" then uploaded a file to /wp-content/.htaccess and /wp-content/index.php. These files were intercepting request to the wp-content folder.

When I removed the files, the site came back to life!

So if your Wordpress site is suddenly getting 500 errors, check for rogue index.php or .htaccess files.

Create A Better Password

If you are looking to create a strong (but human readable) password, you should take a look at Diceware. In short, it is a huge list of uncommon words that get randomly combined to give you a strong password you can read.

Here is the definition from Wikipedia:

Diceware is a method for creating passphrases, passwords, and other cryptographic variables using an ordinary die from a pair of dice as a hardware random number generator. For each word in the passphrase, five rolls of the dice are required. The numbers from 1 to 6 that come up in the rolls are assembled as a five-digit number, e.g. 43146. That number is then used to look up a word in a word list.

Here is an online tool that can generate passwords for you. This site has the default set to word count set to 5. As of today, you should probably use 6 words to generate your password.

Has Your Wordpress Been Hacked?

Do you know any common Wordpress hacks that occur?

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

I then navigated around the site. The content (text, database data) was loading fine, but all the assets were busted. There was a map plugin that was hitting Google Maps and that loaded fine. I then knew it was this specific site.

After poking around some more, I noticed an index.php and a .htaccess in the root of /wp-content.

The Fix

Well, I have come across this problem before. Wordpress was hacked. Hacked might be a strong word, since the password was really weak. A bot probably had that password in it's script and guessed it without issue.

The "hacker" then uploaded a file to /wp-content/.htaccess and /wp-content/index.php. These files were intercepting request to the wp-content folder.

When I removed the files, the site came back to life!

So if your Wordpress site is suddenly getting 500 errors, check for rogue index.php or .htaccess files.

Create A Better Password

If you are looking to create a strong (but human readable) password, you should take a look at Diceware. In short, it is a huge list of uncommon words that get randomly combined to give you a strong password you can read.

Here is the definition from Wikipedia:

Diceware is a method for creating passphrases, passwords, and other cryptographic variables using an ordinary die from a pair of dice as a hardware random number generator. For each word in the passphrase, five rolls of the dice are required. The numbers from 1 to 6 that come up in the rolls are assembled as a five-digit number, e.g. 43146. That number is then used to look up a word in a word list.

Here is an online tool that can generate passwords for you. This site has the default set to word count set to 5. As of today, you should probably use 6 words to generate your password.

Has Your Wordpress Been Hacked?

Do you know any common Wordpress hacks that occur?

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file diff --git a/docs/web/zapier-webhooks-for-html-forms/index.html b/docs/web/zapier-webhooks-for-html-forms/index.html index 7d04ab7..1823daa 100644 --- a/docs/web/zapier-webhooks-for-html-forms/index.html +++ b/docs/web/zapier-webhooks-for-html-forms/index.html @@ -1,4 +1,4 @@ -Using Zapier Webhooks for HTML forms | OhDoyleRules

Go home

If you don't know about Zapier, it's a service that allows you to "automate everything". I've been using it for over 4 years to automate tons of different things like posting daily Trello tasks to Slack, activity in Intercom to Emails, and even a custom scheduled job that gives me task information for the current day.

One of the best built-in zaps (that's what they call the tasks) is the webhook zap which let's you send a POST request to Zapier and then connect that with the 100s of other integrations. You can use this for all types of things. One thing I have found it to be good for is handling form submissions on static sites.

As the concept of static sites and "serverless" keeps getting more and more popular, so do the services surrounding those concepts. But if you know where to look, and how to set things up, most of those "services" that supplement static sites can be replace with some savvy Zapier setups.

There are tons of "form" apps out there to allow you to create endpoints for your site to submit to. A lot of them even suggest using Zapier to handle the post-submission step. It's funny, because you can get a pretty robust setup just using Zapier on it's own and not having to use these services. Also, Zapier supports file uploads! So you can even handle attachments in your forms or POST request. Handy!

Anyway, on to the form. Here is an example of an HTML form that will successfully:

<form action="https://hooks.zapier.com/hooks/catch/1234567/abcd123/"
+Using Zapier Webhooks for HTML forms | OhDoyleRules

Go home

If you don't know about Zapier, it's a service that allows you to "automate everything". I've been using it for over 4 years to automate tons of different things like posting daily Trello tasks to Slack, activity in Intercom to Emails, and even a custom scheduled job that gives me task information for the current day.

One of the best built-in zaps (that's what they call the tasks) is the webhook zap which let's you send a POST request to Zapier and then connect that with the 100s of other integrations. You can use this for all types of things. One thing I have found it to be good for is handling form submissions on static sites.

As the concept of static sites and "serverless" keeps getting more and more popular, so do the services surrounding those concepts. But if you know where to look, and how to set things up, most of those "services" that supplement static sites can be replace with some savvy Zapier setups.

There are tons of "form" apps out there to allow you to create endpoints for your site to submit to. A lot of them even suggest using Zapier to handle the post-submission step. It's funny, because you can get a pretty robust setup just using Zapier on it's own and not having to use these services. Also, Zapier supports file uploads! So you can even handle attachments in your forms or POST request. Handy!

Anyway, on to the form. Here is an example of an HTML form that will successfully:

<form action="https://hooks.zapier.com/hooks/catch/1234567/abcd123/"
   method="post"
   enctype="multipart/form-data">
     <label for="name">Name:</label>
@@ -9,4 +9,4 @@
     <input type="file" name="attachment" id="attachment">
     <input type="submit" name="submit" value="Submit">
 </form>
-

Let's break down the content above and talk about the details.

Action

Pretty straightforward. This is the URL of your webhook. You get this after you create the zap for the first time.

Method

Obviously, we need this to be a POST since that is what the webhook is expecting. Nothing special here.

Enctype

Here is the weird part. If you are familiar with file uploads, you already know that in order to properly handling files, you need to make sure your request is using the correct encoding. Here is a description from the HTML Spec:

This attribute specifies the content type used to submit the form to the server (when the value of method is "post"). The default value for this attribute is "application/x-www-form-urlencoded". The value "multipart/form-data" should be used in combination with the INPUT element, type="file".

So there you go. Use this for files.

Caveats

One thing to keep in mind is, like all forms, the name of each field will be assign that value that was used on submission. So you can't really handle dynamic fields with Zapier. You also don't have any validation other than the HTML validation (or javascript validation, if applied) to rely on. People could submit values you don't expect or mess with the rules and submit other things you aren't expecting.

Something to keep in mind. I wouldn't use this method for anything other than simple contact forms that send emails or maybe forms that fill rows in a spreadsheet or something. Basic stuff.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file +

Let's break down the content above and talk about the details.

Action

Pretty straightforward. This is the URL of your webhook. You get this after you create the zap for the first time.

Method

Obviously, we need this to be a POST since that is what the webhook is expecting. Nothing special here.

Enctype

Here is the weird part. If you are familiar with file uploads, you already know that in order to properly handling files, you need to make sure your request is using the correct encoding. Here is a description from the HTML Spec:

This attribute specifies the content type used to submit the form to the server (when the value of method is "post"). The default value for this attribute is "application/x-www-form-urlencoded". The value "multipart/form-data" should be used in combination with the INPUT element, type="file".

So there you go. Use this for files.

Caveats

One thing to keep in mind is, like all forms, the name of each field will be assign that value that was used on submission. So you can't really handle dynamic fields with Zapier. You also don't have any validation other than the HTML validation (or javascript validation, if applied) to rely on. People could submit values you don't expect or mess with the rules and submit other things you aren't expecting.

Something to keep in mind. I wouldn't use this method for anything other than simple contact forms that send emails or maybe forms that fill rows in a spreadsheet or something. Basic stuff.

Go home


James Doyle

I'm a full-stack developer, co-organizer of PHP Vancouver meetup, and winner of a Canadian Developer 30 under 30 award. I'm a huge Open Source advocate and contributor to a lot of projects in my community. When I am not sitting at a computer, I'm trying to perfect some other skill.


comments powered by Disqus
\ No newline at end of file