Implementing Bootstrap 3.X from Scratch

Generally, I start a website project with an existing template that I then adapt and modify to suit the project’s needs. This weekend, I decided to start a new project from scratch so that I could sharpen both my Bootstrap and DevOps skills.

Rather than begin with a blank screen, I took advantage of several ‘state-of-the-craft’ frameworks and tools to help me along more quickly:

This tutorial describes how to launch a new website project based on HTML5BoilerPlate and Bootstrap 3 with LESS. It will also touch on installing and using Node.js and Grunt.js – if these are already installed on your system, you can skip steps two and three.

Table of Contents:

  1. Starting Assumptions
  2. Install Node.js
  3. Install the Grunt CLI
  4. Set Up the Project Folder
  5. Install Bootstrap
  6. Test the Bootstrap Build Process
  7. Add .htaccess to the Fonts Folder
  8. Include the CSS & JS Files
  9. Add Content & Test
  10. Conclusion

1: Starting Assumptions

Before starting, be sure the following is true:

  • You are comfortable workingHTML, CSS, and JS
  • You are familiar with command-line basics *
  • Your system uses an Apache server

* Command-line instructions are shown for Linux Ubuntu 16.04. You may need to make adjustments if you are using a different OS, or if you prefer to use an IDE or GUI-based text-editor (i.e SublimeText).


2: Install Node.js

First thing is to make sure node.js is installed. Node.js includes a package manager that will be used to install both Grunt and Bootstrap, namely npm.

At the command line, navigate to the head of the root directory, then enter the following commands: *

curl -sL | sudo -E bash -
sudo apt-get install -y nodejs

This will install the current stable version of node.js.

* Note these commands will work for Ubuntu 14.04 and 16.04. If you are on a different base, check out the node.js website for the correct commands.

3: Install the Grunt CLI

First, make sure your version of npm is up-to-date. At the command line, navigate to the head of the system’s root directory and run:

sudo npm update -g npm

Next, install Grunt’s command line interface globally. From the same location as above, enter the following command:

sudo npm install -g grunt-cli

This will put the grunt command in the system path, allowing it to be run from any directory.

If you run into any trouble with this or just want to know more, check out Grunt’s ‘getting started’ guide.

4: Set Up the Project Folder

Rather than starting from zero, we’ll use HTML5 Boiler Plate as a starting point.

Go to H5BP’s GitHub page, and download and unzip the source file package. Open the extracted folder and navigate into the dist subfolder. Copy the contents of this subfolder and paste them into the root folder for your project.

Your project folder should now look something like this:

Open and review the .htaccess file. This file contains code designed to maximize your site’s performance. Depending on your project’s hosting setup, some or all of what this file contains may not be needed.

If you’re not comfortable making changes, or if you don’t have permission to edit the server files, leave the .htaccess file as-is. It will make the server work just a bit harder, but not so much that you’ll notice under most circumstances.

5: Install Bootstrap

This is where the power of node.js shows it face. Navigate to the project’s root directory and type:

npm install bootstrap@3

This will create a directory named node_modules and then install Bootstrap in a subdirectory named /bootstrap.

Now navigate into the /bootstrap folder and type:

npm install

This tells npm to open the package.json file that was installed as part of Bootstrap, and automatically install and update any dependencies that are listed.

6. Test the Bootstrap Build Process

At this stage, the Bootstrap code has been installed. We should now be able to make sure the Bootstrap build processes are working properly.

This is where Grunt starts to show its power. Out of the box, Bootstrap makes the following grunt commands available:

  • grunt dist (Just compile CSS and JavaScript)

… Regenerates the /dist/ directory with compiled and minified CSS and JavaScript files. As a Bootstrap user, this is normally the command you want.

  • grunt watch (Watch)

… Watches the Less source files and automatically recompiles them to CSS whenever you save a change.

  • grunt test (Run tests)

… Runs JSHint and runs the QUnit tests headlessly in PhantomJS.

  • grunt docs (Build & test the docs assets)

… Builds and tests CSS, JavaScript, and other assets which are used when running the documentation locally via bundle exec jekyll serve.

  • grunt (Build absolutely everything and run tests)

… Compiles and minifies CSS and JavaScript, builds the documentation website, runs the HTML5 validator against the docs, regenerates the Customizer assets, and more. Usually only necessary if you’re hacking on Bootstrap itself.

Not all of these commands will work out-of-the-gate – some will require extra packages, etc., to be installed. However, grunt dist and grunt watch – the two most important commands – work just fine.

The grunt dist command takes care of compiling, compressing, and updating the CSS and JS files. The grunt watch command automatically triggers grunt dist whenever it detects changes to the CSS and JS files.

To execute these commands, go to the command line and navigate to ../node_modules/bootstrap, and enter the following:

grunt dist

If all has gone well so far, grunt will run the basic build processes involved in compiling and minifying the project’s LESS, CSS, and JS files.

7: Add .htaccess to the Fonts Folder

Create a new file in your code editor, and add the following lines:

<FilesMatch "\.(ttf|otf|eot|woff|woff2|svg)$">
  <IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "*"

Save the newly created file directly inside the fonts folder naming it .htaccess.

FYI, the .htaccess file stays within the fonts folder, ensuring web fonts work will in all browsers across all hosting and CDN services the project uses.

8: Include the CSS & JS Files

Open the project’s index.html file, and make the following change in the <head> section:

<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap-theme.css">

This will instruct the index page to load the new Bootstrap CSS files instead of the defaults that come with H5BP.

Now go to the bottom of index.html to just before the Google Analytics script block, and make the following changes:

<script src="js/plugins.js"></script>
<script src="js/main.js"></script>
<!-- ADD THIS -->
<script src="node_modules/bootstrap/dist/js/bootstrap.min.js"></script>

Save the file, but don’t close it just yet. We’ll be adding more to this page in the next step.

9: Add Content & Test

While we’re far from done, the basic setup can now be tested to make sure everything was properly installed and included. In the body section of index.html, add this simple markup:

Push index.html to the server and open the page in a browser. If all has gone well, you should now see a styled page with a top menu bar, like so:

Shrink the browser to the size of a smartphone. You should now see a button that exposes a drop-down version of the main menu when clicked, like so:

If something has gone wrong, you’ll likely see un-styled content. Go back and keep debugging until it works.

10: Conclusion

At this stage, you should now have a robust, fully-configured – albeit ‘vanilla’ – Bootstrap project ready for development to begin.

Next Steps:

Although everything should be working just fine with this setup, there are still many things that can be done to improve the project’s infrastructure before jumping right into development.

For example:

  • Modify gruntfile.js to save the compressed and minified CSS and JS files to the locations and filenames initially specified in the original H5BP template, allowing for more efficient script and file loading in production
  • Modify the LESS infrastructure to extend (rather than modify) Bootstrap’s original files, allowing for easier upgrades and more efficient development


Avoid using Initializer to do the install – great idea, but the included versions of the various packages are out of date.

Integrating the SparkPost API With CakePHP 3.4

After setting up a contact form for a project recently, I decided to step up performance by integrating the latest version of the SparkPost API (V2).

By using the API, the project will handle mail a lot more efficiently. Plus, the API opens up a world of new possibilities for the project’s email communications as a whole.

SparkPost is a highly respected provider of transactional email services, and has a well-documented developer-friendly API. They also have a very generous ‘free’ plan that lets you send up to 100,000 emails per month – great for startups and small businesses that don’t do a lot of transactional emailing.

In this tutorial, I will build on work done earlier which covers how to implement Modelless Forms With Ajax & JSON in CakePHP 3.4. In the earlier tutorial, the project sent mail either via SMTP relay or the PHP mail() function, or to a debug log.

Table of Contents

  1. Starting Assumptions
  2. Get a SparkPost API Key
  3. Update the Project Config File
  4. Install the SparkPost PHP Library
  5. Create a Custom Transport
  6. Update the Mailer
  7. Implement and Test
  8. Conclusion


1: Starting Assumptions

Before starting, be sure you have at least read through and understand the previous tutorial. Although implementing the SparkPost API doesn’t depend on the work done there, this tutorial will build on that work extensively.

2: Get a SparkPost API Key

The very first thing is to set up a SparkPost account, validate a sending domain for the account, and generate a private API key.

SparkPost does a good job of explaining how this goes, so I won’t repeat the instructions here.

3: Update the Project Config File

Open /src/config/app.php for your project and add the following code near the bottom, just before the closing brace:

 * SparkPost API Key
'SparkPost' => ['Api' => ['key' => '<your key here.>']]

Make sure to insert your own key as indicated.

While here, scroll up to the Email Profiles section and optionally add a new profile. This generally isn’t necessary but there are use cases where a different profile is required. Here’s an example – just be sure to insert your own from and to addresses:

'Email' => [
    'default' => [
    'admin' => [
    'user' => [
    'contact' => [
        'from' => [''=>'My Contact Form'],
        'to' => '',

Whether or not you create a new profile, make sure to have at least one profile set up that includes a ‘from’ address for the domain that was validated earlier with SparkPost. Make sure also that a ‘to’ address is specified here.

Note the name of the profile you plan to use – this needs to be inserted into the Mailer shortly to provide valid from and to addresses.

4: Install the SparkPost PHP Library

Next, download and install the SparkPost PHP library that can be found at Instructions are provided there for installing this library to your project using Composer.

Make sure the appropriate dependencies are installed as well, particularly Guzzle.

5: Create a Custom Transport

At the command line, navigate to /src/Mailer. Enter the following code to create a directory to contain the new Custom Transport, then go into this new directory and create an empty Transport file called SparkPostTransport.php:

mkdir Transport
cd Transport
touch SparkPostTransport.php

Make sure permissions and ownership are set correctly for both the directory and the file.

Now open /src/Mailer/Tansport/SparkPostTransport.php, add the following code, and save:

 * This transport derives from an earlier project created by Syntax Era 
 * Development Studio. (
namespace App\Mailer\Transport;

use Cake\Core\Configure;
use Cake\Mailer\AbstractTransport;
use Cake\Mailer\Email;
use Cake\Network\Exception\BadRequestException;
use SparkPost\APIResponseException;
use SparkPost\SparkPost;
use GuzzleHttp\Client;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;

 * Spark Post Transport Class
 * Provides an interface between the CakePHP Email functionality and the SparkPost API v.2.
 * @package SparkPost\Mailer\Transport
class SparkPostTransport extends AbstractTransport
     * Send mail via SparkPost REST API
     * @param \Cake\Mailer\Email $email Email message
     * @return array
    public function send(Email $email)
        // instantiate a client adapter
        $key = Configure::read('SparkPost.Api.key');
        $httpClient = new GuzzleAdapter(new Client());

        // instantiate a SparkPost transmission entity
        $sparky = new SparkPost($httpClient, ['key'=>$key]);
        $sparky->setOptions(['async' => false]);

        // Convert the email object's data into the API's required format
        $from = (array) $email->from(); // needs to be a sending domain that is cleared with Sparkpost
        $sender = sprintf('%s <%s>', mb_encode_mimeheader(array_values($from)[0]), array_keys($from)[0]);
        $to = (array) $email->to(); // the email's delivery target(s)
        foreach ($to as $toEmail => $toName) 
            $recipients[] = ['address' => [ 'name' => mb_encode_mimeheader($toName), 'email' => $toEmail]];
        $replyTo = (array) $email->replyTo(); // the person who actually sent the message in the first place
        $reply = sprintf('%s <%s>', mb_encode_mimeheader(array_values($replyTo)[0]), array_keys($replyTo)[0]);

        // Assemble the transmission
        $message = [
            'content'=> [
                'from'=> $sender,
                'subject' => mb_decode_mimeheader($email->subject()),
                'html'=>empty($email->message('html')) ? $email->message('text') : $email->message('html'),
            'recipients'=> $recipients

        // Send message
        catch(APIResponseException $e) 
            throw new BadRequestException(sprintf('SparkPost API error %d (%d): %s (%s)',
                $e->getAPICode(), $e->getCode(), ucfirst($e->getAPIMessage()), $e->getAPIDescription()));

Most of the above is self-evident, otherwise the CakePHP book and API documentation will be helpful.

6: Update the Mailer

A Mailer class was created at /src/Mailer/ContactUsMailer.php as part of the work in the previous tutorial. Open this file and modify the code near the top, just below the ‘namespace’ statement, to look like this:

namespace App\Mailer;

use Cake\Core\Configure; // ADDED
use Cake\Mailer\Email; // ADDED
use Cake\Mailer\Mailer;
use Cake\Mailer\AbstractTransport; //ADDED
use App\Mailer\Transport; //ADDED

 * ContactUs mailer.

Now scroll down into the submission function, and modify the code to look like this:

// configure the email transport
Email::setConfigTransport('sparkpost', [
]); // ADDED
// configure the email
    ->setTransport('sparkpost') // ADDED
    ->setReplyTo($data['email'], $data['name']) 
    ->setSubject($data['name'].' just sent us a contact message')
    ->set(['content' => (array_key_exists('phone',$data) && $data['phone'] != "") ? '<p>Phone#: '.$data['phone'].'</p>'.$data['message'] : $data['message']])
    ->setTemplate('default') // .../src/Template/Email/[html|text]/default.ctp
    ->setLayout('default') // .../src/Template/Layout/Email/[html|text]/default.ctp

Make sure to insert the name of the email profile to be used. This is necessary to pick up the from and to addresses.

Now save the file.

7: Implement and Test

At this point, the coding is done and you should be ready to go. All that remains is to upload and test what you’ve done.

Again, I purposely avoided using a specific testing regime in this tutorial. This is because just about everyone has their own approach and toolset for testing. Simply adjust the instructions provided to suit your preferred methodology.


If you’ve been following both tutorials, you can now send your project’s contact form messages via the SparkPost API.

More importantly, whether or not you built out the contact form, you can now use this new custom email transport with all of your project’s email communications.

Modelless Forms With Ajax & JSON in CakePHP 3.4

Earlier this week, I needed to implement a simple contact form on a project’s landing page. Since I didn’t want the form to interact with a database, this was the perfect opportunity for what CakePHP calls a ‘modelless form’.

This tutorial describes how to implement a modelless form in CakePHP 3.4 using Ajax and JSON to communicate between the client and server.

(In a follow-up to this tutorial, I take the project one step further by Integrating the SparkPost API.)

Table of Contents

  1. Starting Assumptions
  2. Bake a Contact Form
  3. Bake a Mailer Class for the Form
  4. Bake a Form Controller With No Actions
  5. Create the Contact Form View
  6. Create the Ajax Script
  7. Implement and Test
  8. Conclusion

1: Starting Assumptions

Before starting, be sure the following is true:

  • CakePHP 3.4 is installed for your project, and you are familiar with how it works
  • A ‘typical’ email transfer has been implemented and tested (i.e. SMTP, Debug, etc)
  • jQuery is installed for your project
  • You are comfortable working with PHP, Javascript, jQuery, and HTML
  • You are familiar with command-line basics *

* Command-line instructions are shown for Linux Ubuntu 16.04. You may need to make adjustments if you are using a different OS, or if you prefer to use an IDE or GUI-based text-editor (i.e SublimeText).

2: Bake a Contact Form

At the command line, go to the site’s root directory and enter the following to bake a new form:

bin/Cake bake Form Contact

By default, CakePHP saves the new form as src/Form/ContactForm.php. Open the file, and you should see something like this:

namespace App\Form;

use Cake\Form\Form;
use Cake\Form\Schema;
use Cake\Validation\Validator;

class ContactForm extends Form
    protected function _buildSchema(Schema $schema)
        return $schema;

    protected function _buildValidator(Validator $validator)
        return $validator;

    protected function _execute(array $data)
        return true;

This file includes three magic function signatures to use as a starting point for coding. CakePHP will run these functions automatically when creating, validating, and executing the form.

Let’s start adding some code to this class. First, add the following two namespaces at the top of the file:

use Cake\Log\LogTrait;
use Cake\Mailer\MailerAwareTrait;

We’ll be using both of these shortly.

Now replace the return statement in the _buildSchema function with the following:

return $schema->addField('name', 'string')
            ->addField('email', ['type' => 'email'])
            ->addField('phone', ['type' => 'tel'])
            ->addField('message', ['type' => 'text']);

This bit of code tells CakePHP’s FormHelper about the inputs the form needs to include.

Next, add the following validators to the _buildValidator function, just above the return statement:

// 'name' field
        'message' => __('Please provide your name'),
    ->notBlank('name',__('Name cannot be blank'))
    ->add('name', [
        'minlength' => [
            'rule' => ['minLength', 2],
            'message' => __('Name must be at least 2 characters long')
        'maxlength' => [
            'rule' => ['maxLength', 50],
            'message' => __('Name cannot be more than 50 characters long')
// 'email' field
        'message' => __('Please provide your email address'),
    ->notBlank('email',__('Email address cannot be blank'))
    ->add('email', 'format', [
        'rule' => 'email',
        'message' => __('Please provide a valid email address')
// 'phone' field
    ->add('phone', [
        'minlength' => [
            'rule' => ['minLength', 7],
            'message' => __('Your phone# must be at least 7 characters long'),
            'on' => function ($context) {return !empty($context['data']['phone']);} // conditional on presence
        'maxlength' => [
            'rule' => ['maxLength', 30],
            'message' => __('Your phone# cannot be more than 30 characters long')
// 'message' field
        'message' => __('Please include your message'),
    ->notBlank('name',__('Your message cannot be blank'))
    ->add('message', [
        'minlength' => [
            'rule' => ['minLength', 4],
            'message' => __('Your message must be at least 4 characters long')
        'maxlength' => [
            'rule' => ['maxLength', 2048],
            'message' => __('Your message cannot be more than 2048 characters long')

Basically, this bit of code takes care of the validations that would normally be found in a Model class. These validators help CakePHP’s FormHelper set up the form properly, and are used to validate whatever data the form sends when submitted.

Last, add the following statements in the _execute function above the return statement:


$this->log('Someone just sent us a contact message', 'info');

The first statement uses the Mailer to send the form, the second takes care of logging the event.

3: Bake a Mailer Class For the Form

At the command line, go to the site’s root directory and enter the following to bake a new Mailer class dedicated to the form:

bin/Cake bake mailer ContactUs

By default, CakePHP saves the new Mailer as src/Mailer/ContactUsMailer.php. Open the file, and you should see something like this:

namespace App\Mailer;

use Cake\Mailer\Mailer;

class ContactUsMailer extends Mailer
    static public $name = 'ContactUs';

Now add the following public function to the class:

public function submission(array $data)
        ->setReplyTo($data['email'], $data['name'])
        ->setSubject($data['name'].' just sent us a message')
        ->set(['content' => (array_key_exists('phone',$data) && $data['phone'] != "") ? '<p>Phone#: '.$data['phone'].'</p>'.$data['message'] : $data['message']])

This new Mailer class sets up the email submission defaults needed to post the message. The mailer doesn’t actually send the email – that happens in the controller.

4: Bake a Form Controller With No Actions

At the command line, go to the site’s root directory and enter the following to bake a new controller without any actions:

bin/Cake bake controller Contacts --no-actions

By default, CakePHP saves the new form as src/Controller/ContactsController.php. Open the file, and you should see something like this:

namespace App\Controller;

use App\Controller\AppController;

class ContactsController extends AppController

Once again, this file serves as a starting point for coding. First, add the following namespaces at the top of the file:

use App\Form\ContactForm;
use Cake\Validation\Validator;

Here, we take a step back from the suggested approach in the CakePHP documentation to follow a slightly different path. Instead of creating an index() function, create a function called contactUs() by adding the following public function inside the ContactsController class:

public function contactUs()
    $this->autoRender = 'false';
    $this->viewBuilder()->layout('ajax'); // src/Template/Layout/ajax.ctp
    // Disallow direct access via browser URL

    $mailsent = [
        ,'message'=>__('Your message could not be sent')

    $form = new ContactForm();

    if ($this->request->is('post')) 
        if ($form->execute($this->request->data)) 
            $mailsent = [
                ,'message'=>__('Your message has been sent')
            $errors = $form->errors();

            if (count($errors) > 0) 
                $mailsent['message'] .= ' &mdash; ' . __('Please correct any errors and try again.');

                foreach ($errors as $key => $value) 
                    $mailsent['errors'][$key] = $value;
    } // this is not a post request (error condition)

    $mailsent = json_encode($mailsent, JSON_FORCE_OBJECT);

This function will only be callable via Ajax, and contains the code needed to process the form. It returns a status code, a message, and potentially an array of error objects, all in a JSON format.

5: Create the Contact Form View

At the command line, go to the site’s src/Template directory and enter the following to create a new folder for the view:

mkdir Contacts

Once again, we depart from the suggested approach here. The documentation calls for creating a src/Templates/Contacts/index.ctp page to contain the form. Instead, go into the src/Template/Contacts folder and enter the following to create a new view file that CakePHP can match up with the contactUs() controller function:

touch contact_us.ctp

Open the file, and add the following:

<?= $mailsent ?>

The controller will use this file to pass Ajax responses back to the caller.

Also, before moving on, make sure ownership and permissions are set correctly on both the folder and the file.

Next, embed the form into whatever view file you want. In my case, I placed the form on a website landing page. Here’s the code to generate the form:

<?= $this->Form->create($form,['url'=>['controller'=>'Contacts','action'=>'contactUs'],'id'=>'contactForm','class'=>'async','role'=>'form','name'=>'sentMessage']); ?>
    <?= $this->Form->control('name',['id'=>'name','placeholder'=>'Your Name (required)']); ?>
    <?= $this->Form->control('email',['id'=>'email','placeholder'=>'Your Email (required)']); ?>
    <?= $this->Form->control('phone',['id'=>'phone','placeholder'=>'Your Phone # (optional)']); ?>
    <?= $this->Form->control('message',['id'=>'message','placeholder'=>'Your Message (required)']); ?>
    <div id="responseMessage"></div>
    <?= $this->Form->button('Send Message', ['type' => 'submit','class'=>'btn btn-xl submit']);?>
<?= $this->Form->end() ?>

This code will tell CakePHP everything it needs to know in order to generate an Ajax-ready form inside the view.

6: Create the Ajax Script

Next step is to create the Ajax script that will handle the to-and-fro between client and server. At the command line, go the folder in the site’s webroot where the .js files are stored and enter the following to create the file:

touch ajax.js

Again, make sure ownership and permissions are set correctly, then include this file in your site’s layout template:

<?= $this->Html->script('/js/ajax.js') ?>

Adjust this as needed to target the correct folder for your project. Also, ajax.ctp will use jQuery, so make sure this is included as well.

Now open ajax.js and add the following script:

$(function() {
    "use strict";

    var contentTarget = $("div#responseMessage");

    // handle form submissions
        // prevent the form from submitting 

        // set a convenience variable for the form
        var form = $(this);

        // disable the submit button to prevent repeated clicks
        form.find('.submit').prop('disabled', true);

        // create and post a temporary spinner
        var spinner = '<div class="alert alert-info" role="alert" style="text-align: center;">';
        spinner += '<p class="lead"><span class="fa fa-spinner fa-spin"></span></p>';
        spinner += '</div>';

        // create a text string of form variables in standard URL-encoded notation
        var data = form.serialize(); 

        // set up the Ajax submission defaults
        var type = "POST";
        var url = form.attr('action');

        // submit the form

    // makeAjaxCall function
    function makeAjaxCall(data,type,url,form)
            type    : type,
            url     : url,
            data    : data,
            cache    : false,

    // success function (when the server responds)
    function successFunction(data,textStatus,jqXHR) 
        // create a JS object from the returned string
        var jsonData = JSON.parse(data); // best practice: use native JS instead of jQuery

        // create the server response message
        var message = '<div class="alert alert-'+jsonData.alert+' alert-dismissible" role="alert">';
        message += '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
        message += '<p class="lead">'+jsonData.message+'</p>';
        message += '</div>';

        // process the server response
         if (jsonData.status == 'sent')
            // reset the form fields back to their defaults
            $(".async")[0].reset(); // best practice: using [0] will return the native html element, not the DOM element
        else // aka. status is 'error'
             * @TODO: on (re)submit, clear any old error messages that were appended to the inputs
            // append error messages to matching inputs 
            $.each(jsonData.errors, function(input,error)
                    $.each(error, function(type,message)
                            var notice = '<p class="help-block text-danger">'+type+' : '+message+'</p>';
                            var errorTarget = "#"+input;
        // post the server response message

    // fail function (when Ajax fails to make the connection to the server)
    function failFunction(request, textStatus, errorThrown) 
        // create & post the 'failed request' message
        var message = '<div class="alert alert-danger alert-dismissible" role="alert">';
        message += '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>';
        message += '<p class="lead">OOPS! An error occurred during your request: ' + request.status + ' ' + textStatus + ': ' + errorThrown+'</p>';
        message += '</div>';
        // post the fail message

    // always function, executes regardless of success or failure
    function alwaysFunction(form) 
        // re-enable the submit button
        form.find('.submit').prop('disabled', false);

Note, the alert messages in this script are marked up to work with Bootstrap 3 and FontAwesome 4. Adjust these as needed for your project.

Also, look closely at this script and you’ll notice it can handle any form with a class of .async, not just the contact form!

7: Implement and Test

At this point, the coding is done. All that remains is to implement and test what you’ve done.

Note that I purposely avoided using a specific testing regime in this tutorial. This is because just about everyone has their own approach and toolset for testing. Simply adjust the instructions provided to suit your preferred methodology.


Assuming all has gone well, you should now have in hand a simple, robust, and highly-performant contact form that uses Ajax, jQuery, and JSON to leverage many of CakePHP’s powerful new features.

Next Steps…

In a follow-up to this tutorial, I take the project one step further by Integrating the SparkPost API With CakePHP 3.4.

SkiddleBop: My Latest Project

Since the beginning of April this year I’ve been working on a new app under the working title, ‘SkiddleBop’.

SkiddleBop is designed to be a high-functioning configurable ‘business starter template’ that takes care of managing users, subscriptions, and recurring payments right out of the box.

Designed specifically with subscription-based ventures in mind, the app will handle demos, trials, subscriptions, payments, plans, coupons, discounts, user authorization and authentication, transactional emailing, and more.

As sole practitioner on this project, my role covers the entire development life-cycle from concept development through operational deployment. The project is slated for completion by October 2017.

Core technologies include:

  • Cakephp 3.4
  • Php 7/MySQL5
  • Javascript/jQuery
  • Bootstrap 3 w/ BeyondAdmin theme
  • Stripe API
  • Sparkpost API
  • Apache 2.4.18
  • Ubuntu 16.04.3

The entire month of April was spent on gathering requirements and developing an object-oriented design for the app. The following first two weeks in May were spent transforming this design into a database and application architecture.

Since then, roughly three weeks were spent on building out the core platform in CakePHP 3 to the point where app development can actually get moving. In late May, I was pulled away from the project to deal with a bunch of other things.

It’s now late June and I’m finally getting back in the saddle.

So, basically, much of the app’s core framework has been engineered and implemented. There’s still plenty to do but most is stuff I’ve done before, so I expect things should go somewhat smoothly.

To build out the app from here, I think it makes sense to:

  1. Implement the email messaging capabilities (done)
  2. Set up authorization & authentication stuff
  3. Set up the user management stuff
  4. Implement the login stuff (anonymous & known)
  5. Implement plans
  6. Implement subscriptions
    1. Anonymous demos
    2. Sign-ups & trials
    3. Coupons & discounts
    4. Up/downgrade
    5. Cancel/Reinstate
  7. Implement account management
    1. Invoices & payments
    2. Card replacement
  8. Finish implementing the app configurator
  9. Tidy up the landing site

Implementing SSL Using Let’s Encrypt

I recently decided to implement SSL certificates for all of my published websites. Rather than pay the high costs that commercial cert providers charge, I opted for using Let’s Encrypt. This turned out to be a lot easier than I expected, though I did run into a few gotchas.

First, go the EFF’s Certbot website and use the interactive selector to find the appropriate instructions for the OS/server combo for your system. In my case, I’m running Apache on Ubuntu 16.04. Here’s how I installed the app on my server:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-apache

Second step is to run the app. The simplest way to do this (for my configuration) is with the following code:

$ sudo certbot --apache

Although this will work fine, I host a number of different projects on the same server. Best practice in this scenario is to certify each hosted domain individually. It’s also best practice to include any aliases and sub-domains as well. Here’s an example for one of my projects:

$ sudo certbot --apache -d -d -d

Note that it’s important to start the list of domains with simple version of the name first as show above.

This is where my first ‘gotcha’ happened. Silly me, I never thought to set my router to direct https requests to Port 443. Once I fixed this, certbot ran just perfectly, although it now threw a different error.

My second ‘gotcha’ was that I had never enabled the mod_ssl module in Apache. In short, Apache was not listening for anything on Port 443. This turned out to be a super-easy fix as well:

sudo a2enmod ssl

Followed by:

sudo service apache2 restart

Then followed by:

apache2ctl -M

… to confirm the module was enabled.

When the instruction runs successfully, the app will ask for some details. Among these, you’ll be asked to choose between allowing both http and https requests or forcing all requests to https. I went with the second, more secure option.

Third, I confirmed that all was working properly using’s SSL testing tool at

Fourth, and finally, I set up a cron job to update the certs when they expire. (Let’s Encrypt certs only last for 90 days, so this is important.)

At the command line, I entered the following instruction to first confirm that cert updating actually works properly for my install. Here’s the code:

$ sudo certbot renew --dry-run

With confirmation in hand, the next thing is to set up a cron job to run this on a daily basis. This involved adding something like the following instruction to /etc/crontab:

15 3 * * * /usr/bin/certbot renew --quiet

Choose whatever run schedule you want. I went for 3:15 every afternoon. Note this will only renew certs that are about to expire. This helps to avoid into any over-quota situations.


This tutorial at Digital Ocean was quite useful in understanding the above process. I also found the Certbot documentation, the community forum at Let’s Encrypt, and the documentation at Let’s Encrypt to be very useful.