I think it’s best to avoid using plug-ins when possible. It reduces bloat and “black-box” code.
The mobile “hamburger” menu is a staple of responsive user interface design. Users know that clicking on that three-lined icon will show a menu. It’s a modern solution to displaying long navigation lists on smaller screens.
The web, as a platform, is open and free. Unlike native app markets, we don’t have to wait for software to be approved by any third-party. It works across any device or operating system that has a web browser. (Which is why standards across browsers is so important). But, until recently web-apps faced limitations. Not having full access to a device’s hardware and operating system was an issue – but that’s being fixed as more native APIs are being added to modern web browsers.
A disadvantage of having a web-only app was losing out on the discoverability that comes with having it listed in a searchable marketplace. Adding a web-app to your device home screen, from a web browser, is not intuitive to average users. Fortunately, the Google Play Market allows us to upload an app file that links to a progressive web app.
I had to make sure it qualified as a PWA. It needed offline support, as well as any other features that would make it feel like a native app. Google Chrome’s developer tools has a section called “Audits” that helped me identify such opportunities.
The first step was to create a “service worker” JavaScript file, and register it when BJJ Tracker loads.
I added the above code to a shared file that loads on every page of my app. Below is an example service worker file. This file downloads any vital assets to a user’s device, and later loads them from the cache. Including a polyfill ensures that the cache methods exist (in case the browser does not support them natively). “We need to use the polyfill because the Cache API is not yet fully supported in all browsers.”
Next, I created a “manifest” file. This file is written in JSON format. It helps describe how the web-app behaves once “installed”. It handles things such as app icon images and meta data.
The manifest needs to be referenced by the app. I added a link tag to a shared <head> file. Additionally, I included a few other meta tags that let browsers know to treat this website as an app.
When creating the app bundle (“Build > Generate Signed Bundle/APK”) we’ll need a signing key. I created a new one, and named the file mykeystore.keystore.
That command shows us the certificate fingerprints. Copy the SHA256 value. It is used with Google’s Statement List Generator to create the contents of the assetlinks.json file. The statement file is then placed in a “.well-known” directory on the root of our PWA domain (eg.https://www.bjjtracker.com/.well-known/assetlinks.json)
This app is a side project I use to toy with new web technologies. I’m trying to drive traffic to it so that I can experiment with optimizing conversions. I’m using it as a trial grounds for another software service called SplitWit. SplitWit is focused on optimizing conversions for the web, and helping digital marketers reach their goals. You can read about it on another post from this blog.
SplitWit is a digital product. It is a “software as a service” platform that helps split test websites and apps. That means it allows us to make changes to a website, that only half of visitors will see, and then determine which version has better results (sales, sign-ups, etc.).
The front-end design utilizes basic principles that focus on user experience. I iterated through various color pallets, and ended with a blue-shaded scheme. Subtle textured patterns applied to background sections help add a finished look. And of course, FontAwesome is my go-to icon set.
I used a CSS rule on the main container of each page to have a minimum height of 100% of the viewport. This ensures that the page footer doesn’t end up in the middle of the screen if there is not enough content.
After setting up an account, users can create experiments that target certain pages of a website. The visual optimizer lets changes be made easily between the control and variation versions.
The editor loads up a website as an iFrame on the right side of the page. Once a page is loaded, SplitWit adds an overlay to the iFrame. This way, instead of interacting with the page, clicks can be intercepted. Any elements that get clicked are loaded up as HTML into the “make a change” section of the editor. Any changes made are saved to that variation, and will be displayed to half of visitors.
Here is an example of the code that powers the overlay and connects it to the editor:
The editor has lots of built in options, so users can change the style and behavior of a page without needing to know how to code. A marketer can use this tool without the help of a developer.
Metrics and statistical significance
A key feature of SplitWit is to measure conversion metrics and performance indicators. The platform determines which variation is a winner based on the metrics set. Three types of metrics are offered: page views, click events, and custom API calls.
Algorithms calculate statistical significance based on the number of visitors an experiment receives and the conversion metrics configured. This makes sure that the result is very unlikely to have occurred coincidently.
The code snippet
Each project setup in SplitWit generates a code snippet. Once this snippet is added to a website, SplitWit is able to do its magic. Using JavaScript, it applies variation changes, splits user traffic between versions, and measures key metrics about the experiments running.
The platform uses a relational database structure. As changes are made to experiments, the details are saved and written to a unique snippet file. When the snippet file loads, the first thing is does is check to see if there are any experiments that should be running on the current page. Each experiment can be configured to run on various URLs. The configuration rules contain three parts: a URL pattern, a type (target or exclude), and a match type (exact, basic, or substring). You can read SplitWit documentation to find an explanation of these match types.
Here is the code used to test a URL against an experiment’s configuration rules:
Stripe is used to bill customers. In the billing dashboard we can create a product, and assign it a monthly pricing plan.
The payment processor handles re-billing customers each month. Our software is responsible for keeping track of each account’s payment status. In the database we record the date of when an account will be considered delinquent. Upon registration each account has this field set to 15 days in the future, affording a two week trial. At this point, users have not entered any credit card information.
Initial payment
Stripe’s JavaScript SDK is used during initial payment to tokenize credit card information before passing it along to the server.
Below is the HTML used for a Stripe payment element:
<div id="stripe-payment-modal" class="modal stripe-payment-modal" style="display: none;">
<!-- Modal content -->
<div class="modal-content">
<p>
<button type="button" class="dismiss-modal close" >×</button>
</p>
<p>Activate your account subscription.</p>
<form id="payment-form">
<div class="form-row">
<!-- <label for="card-element">
Credit or debit card
</label> -->
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>
<!-- Used to display Element errors. -->
<div id="card-errors" role="alert"></div>
</div>
<button type="button" class="btn submit-payment">Submit Payment</button>
</form>
</div>
</div>
And the JavaScript:
<script src="https://js.stripe.com/v3/"></script>
<script type="text/javascript">
var stripe = Stripe('your-public-key-goes-here');
var elements = stripe.elements();
// Custom styling can be passed to options when creating an Element.
var style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` div.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
var data = $("#payment-form").serialize();
$.ajax({
url:"stripe-payment-service.php",
method: "POST",
data: data,
complete: function(response){
console.log(response);
window.location.reload();
}
})
}
$(".submit-payment").click(function(){
stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the customer that there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
$(".submit-payment").attr("disabled", "disabled").html('Working...');
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
</script>
The above code creates a new Stripe object using a public API key. That object injects a credit card form into our ‘#card-element’ div, with custom styles attached. It listens for any changes, and displays validation errors. When the form is submitted, the Stripe object creates a token from the payment information. That token is passed to our back-end. Stripe’s PHP library is used to finish the transaction:
<?php
function subscribe(){
require_once('stripe-php-6.43.0/init.php');
\Stripe\Stripe::setApiKey('sk_XXXX');
$stripe_token = $_POST['stripeToken'];
$conn = $this->connection;
if(isset($_SESSION['email'])){
$email = $_SESSION['email'];
}else{
die("No email found.");
}
if(strlen($email)>0){
$sql = "SELECT * FROM `account` WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute(array($email));
$row = $result->fetch(PDO::FETCH_ASSOC);
}
$customer_id = $row['stripe_customer_id'];
//check if this account already has a stripe_customer_id
if(strlen($customer_id) < 1){
//if not, create the customer
$customer = \Stripe\Customer::create([
'email' => $email,
'source' => $stripe_token,
]);
$customer_id = $customer['id'];
//write stripe ID to db
$sql = "UPDATE `account_table` SET stripe_customer_id = ? WHERE email = ?";
$result = $conn->prepare($sql);
$result->execute(array($customer_id, $email));
}
// Create the subscription
$subscription = \Stripe\Subscription::create([
'customer' => $customer_id,
'items' => [
[
'plan' => 'plan_XXX', //setup in Stripe dashboard.
],
],
'expand' => ['latest_invoice.payment_intent'],
'billing_cycle_anchor' => time()
]);
$subscription_status = $subscription['status'];
$subscription_id = $subscription['id'];
if($subscription_status == "active"){
//set current_period_end to 32 days (1 month plus some leeway) in the future. set past_due as false
$sql = "UPDATE `account_table` SET stripe_subscription_id = ?, current_period_end = ?, past_due = 0 WHERE email = ?";
$result = $conn->prepare($sql);
$past_due = false;
$current_period_end = new DateTime;
$current_period_end->modify( '+32 day' );
$current_period_end = $current_period_end->format('Y-m-d H:i:s');
$result->execute(array($subscription_id, $current_period_end, $email));
}
}
?>
On the server side our secret API key is used. A customer record is created in Stripe using the payment token and user’s email. The Stripe customer ID is then used to create a subscription. We record the the customer ID and subscription ID to our database. The account’s new subscription period end is updated to 32 days in the future.
Cancel a subscription
The user is able to cancel their subscription from the SplitWit account dashboard.
We retrieve their subscription from Stripe, and cancel it, using their subscription ID. They will no longer be billed. We update our database to turn off the account’s experiments, delete any Stripe details, mark their subscription as delinquent, and re-write their snippet file.
<?php
function cancelSubscription(){
require_once('stripe-php-6.43.0/init.php');
\Stripe\Stripe::setApiKey('sk_XXXX');
$conn = $this->connection;
if(isset($_SESSION['userid'])){
$accountid = $_SESSION['userid'];
}else{
die("No userid found.");
}
if(strlen($accountid)>0){
$sql = "SELECT * FROM `account` WHERE accountid = ?";
$result = $conn->prepare($sql);
$result->execute(array($accountid));
$row = $result->fetch(PDO::FETCH_ASSOC);
}
$stripe_subscription_id = $row['stripe_subscription_id'];
$subscription = \Stripe\Subscription::retrieve($stripe_subscription_id);
$subscription->cancel();
//turn off experiments and update snippets. clear stripe IDs. set current_period_end to yesterday. set past_due = 1
$current_period_end = new DateTime;
$current_period_end->modify( '-1 day' );
$current_period_end = $current_period_end->format('Y-m-d H:i:s');
$sql = "UPDATE `account` SET stripe_customer_id = '', stripe_subscription_id = '', past_due = 1, current_period_end = ? WHERE accountid = ?";
$result = $conn->prepare($sql);
$result->execute(array($current_period_end, $accountid));
//turn off all experiments
$status = "Not running";
$sql = "UPDATE `experiment` set status = ? where accountid = ?";
$result2 = $conn->prepare($sql);
$result2->execute(array($status, $accountid));
//update all snippets for this account (1 snippet per project)
$sql = "SELECT * FROM `project` WHERE accountid = ?";
$result3 = $conn->prepare($sql);
$result3->execute(array($accountid));
$rows3 = $result3->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows3 as $key3 => $value3) {
$projectid = $value3['projectid'];
$databaseProjectService = new DatabaseProjectService();
$databaseProjectService -> writeSnippetFile(false, false, $projectid);
}
$this->status = "complete";
}
?>
Re-billing subscriptions
As long as an account has an active subscription in Stripe, they will be automatically re-billed each month. When this event takes place, Stripe can deliver data about it to an end-point of our choice (commonly known as a webhook).
SplitWit listens for an event called “invoice.payment_succeeded”, which occurs when a customer’s monthly payment is successful. When that happens the account’s subscription period end is updated to 32 days in the future.
What if payment fails or never happens? The account’s subscription period end never gets updated.
A daily scheduled task checks each active account’s subscription period end date. If that date is in the past, we mark the account as past due, turn off all experiments, and update its snippet files.
The value of experimentation
Driving digital conversions is a science. Experimentation should be a constant exercise in this respect. Take any field and we can benefit from testing the waters and adjusting our sail. Our ability to interpret that data is the bottle neck to making good decisions. The best lesson I’ve learned is that intuition is usually not enough. It’s better to look at the numbers and trust data.
Influencing users through a funnel of action, finally leading to a conversion, is a challenge. Optimizing conversions, sales, and leads can be broken down into a system based approach. SplitWit focuses on that point.
I use GitHub to manage code that I’ll want to re-use. I had trouble finding a canned function to remove the subdirectory path from a URL string – so I wrote one and added it to my latest public repository: https://github.com/pacea87/ap-utils
I’ll keep adding useful code to it – and feel free to make a pull request and contribute yourself. This code should focus on utility functions for manipulating data in interesting ways. Below is the JavaScript code for removing the subdirectories from a URL string. This will also strip away any query string parameters.
Now, you can get the current page’s URL, and strip off everything after the host name:
var url = window.location.href;
var baseUrl = removeSubdirectoryFromUrlString(url);
console.log(baseUrl);
Another example:
var url = "https://www.antpace.com/blog/index.php/2018/12/";
var baseUrl = removeSubdirectoryFromUrlString(url);
//This will return "https://www.antpace.com"
console.log(baseUrl);
I used this code to re-write all URL references in an iFrame to be absolute. My implementation loops through all image, anchor, and script tags on the source site. It determines if each uses an absolute reference, and if not re-writes it as one. This was part of a project that uses a visual editor to allow users to manipulate a remote site. Check out my source code below.
pageIframe.contents().find("img").each(function(){
var src = $(this).attr("src");
if(src && src.length > 0 && src.indexOf("//") == -1){ //if not absolute reference
var url = iframeUrlString;
if(src.charAt(0) == "/"){ //only do this if the src does not start with a slash
url = removeSubdirectoryFromUrlString(url);
}
src = url+"/"+src
}
$(this).attr("src", src);
});
pageIframe.contents().find("script").each(function(){
var src = $(this).attr("src");
if(src && src.length > 0 && src.indexOf("//") == -1){
var url = iframeUrlString;
if(src.charAt(0) == "/"){
url = removeSubdirectoryFromUrlString(url);
}
src = url+"/"+src
}
$(this).attr("src", src);
});
pageIframe.contents().find("link").each(function(){
var src = $(this).attr("href");
if(src && src.length > 0 && src.indexOf("//") == -1){
var url = iframeUrlString;
if(src.charAt(0) == "/"){
url = removeSubdirectoryFromUrlString(url);
}
src = url+"/"+src
}
$(this).attr("href", src);
});
Modern software has given creators the tools they need to showcase their work to the world. Here are the best free apps that I’ve been using that will help your talent shine in 2019:
AppWrap – Do you want to feature your latest website or app design to your followers? Are you building a portfolio for the UI/UX projects you worked on? This app is a great way to wrap your screenshots in a mobile device view. You can add effects, backgrounds, and text to really polish the look and feel. Their template gallery will give you inspiration to make something gorgeous. http://www.appwrap.in/
Canva – This is one of my favorites. With a library of over 60,000+ templates, this app has something for every platform. Whether you need to create a great looking post, story, or cover image, this app has designs for Instagram, Facebook, YouTube and much more. If you want your online presence to look professionally designed, check this one out! https://www.canva.com/
Hatchful – Do you need a logo for your brand, business, or product? This app let’s you create one quickly. By customizing templates, you can draft, and iterate designs. Having logo design done fast, cheap, and easily allows you to focus on the actual product. It’s important to not get hung up on the logo, especially early into your venture, and instead focus on the actual value your service proposes. https://hatchful.shopify.com/
I’ve used all of these apps, and personally gained value from them. What apps do you use for your graphic design?
Having a framework in place when you start up will let you hit the ground running. This applies not just to software, but also business, health, fitness, and just about everything else in life. Having the dots ready to connect helps you to draw the right picture.
I wanted to create a template to rapidly roll out digital products and software. This source code is a starting point. The goal is to be quick and cheap, without sacrificing quality. It runs in a LAMP environment. If you want to run this software on your computer, look into WAMP or MAMP.
This code base provides a front-end that leverages modern web technologies and standard best practices. A basic layout is described, including a header, menu drawer, feature buttons, and detail pages. It uses Bootstrap, jQuery, Font Awesome, Google Fonts, and Google Charts.
The back-end is object oriented, RESTful, and secure. Code that talks to the database, or to 3rd party APIs, has been separated out into *-service.php files. It includes SQL to create a user database. The database interacts with a custom registration and login engine. It allows for anonymous users, so that data can be saved before signing up, and a password is not needed to get started. It provides a reset password mechanism for users. It seamlessly integrates with Mailchimp and Facebook login. Redirects are in place to force SSL and WWW, and to remove file extensions from URLs. Next versions will address technical SEO and new API integrations.
If you’d like to contribute to this repo, feel free to fork it, and make a pull request.
BJJ Tracker is a fitness app for tracking Brazilian jiu jitsu training. It’s the sort of fitness app I was looking for, but couldn’t find. Version 1.0 is a bare bones MVP, but has a list of features on the way. Future versions will add gamification (including challenges and goals), UX/UI enhancements, training recommendations, and more.
The app allows users to record their training sessions, with details about drilling and sparring, as well as competition. This data is visualized over charts and calendars. The idea started from physically writing my training sessions onto an actual calendar, with a minimum goal per week. Building it has been a great exercise in digital product development, software design, and UI/UX strategy.
Software
BJJ Tracker is a web app, hosted on a AWS Linux server, running Apache, PHP, and MySql. I used Initializr to generate a bootstrap template to get my front-end started. One goal of this project was to build a web app framework that I could use to quickly get future projects running. This code would include user registration and login services, as well as other back-end concerns, on top of a front-end. I’ve cleaned most of this code into a generic repo on GitHub. You can read my post explaining its features.
Design
This app was designed with “mobile first” in mind, assuming that most users will be on a smart phone. The look and feel of the color palette, font-choice, and UI layout took some experimenting and visual research. It’s not final, and will be subject to split testing over time. I used Font Awesome to add icons as visual cues, giving the app a more finished look. The three lined (hamburger) menu in the top right comes as standard UI, using Simple MobileMenu, a jQuery plugin. Other UI elements include a top status message, and “In-Your-Face” status message, both of which are custom built notifications that I’ve wrapped as javascript plugins. Having a calendar section was important to me, and I consider to be a primary feature of the app. I use Full Calendar to generate the full month view. The homepage (dashboard) focuses on a week view. Google charts is used for the “techniques” graph.
The logo is a work-in-progress. The textual part was easy – pick a font, add a sharp outline, and a drop shadow. I always start with a 1024×1024 canvas. The symbol begins with simple shapes, triangles and circles. I left this process for last, saving my focus for the actual product. This allowed me to rapidly iterate design versions, and see how it would look directly in the user interface. Below is the current portrayal – and I’m excited for next versions.
Full Calendar
Fullcalendar.io has been my go-to solution for adding Calendars to websites. It’s free, and only needs two file references to work (a CSS file and a JavaScript file). You can host those files your self, or use a CDN. And, the UI is easily customized with a bit of <style> code:
You can see I get the back-end data through my PHP code (view_record_response), and pass it along on the front-end (eventsArray) to FullCalendar.
Challenges and next steps
One goal of this project was to get started fast, so people could begin using it. Deciding what to include out of a long list of ideas proved challenging. I could have kept adding features, and never been ready to make the site public. I meant to keep functionality basic, but still wanted the thing to be useful. The design needed to be simple, yet still had to look finished. I won’t know how close I came to getting this right until I analyze user feedback. The real plan is to do a little bit better next time, and to keep iterating. Using this as foundation will let future ventures start a step ahead. Already, I’ve begun implementing updates, and getting ready to deploy to the App Store and Google Play. Look out for coming updates and other products that are in the works! Don’t forget to visit the BJJ Tracker blog.
A vendor (video producer) to the company I worked for, who had is office on the same floor as us, mentioned in the hall way that he had a friend who needed a website. His friend was an author who just had a book published by Simon and Schuster. Joshua Horwitz released “War of the Whales” in 2014. I built his website from scratch using Bootstrap CSS and HTML5 boilerplate. It’s responsively designed, so it adjusts for mobile devices.
I even implemented a custom CMS mechanism, powered by TinyMCE, that was super light weight. It allowed him to update a few pieces of small content through out the site. It used basic authentication, and wrote to a MySQL database.
I used some cool visual effects to add animation and make it feel like an immersive experience. The design process took many iterations, but we got it to a place that made sense for the project. The marquee jQuery plugin used the following code:
$('.marquee')
.bind('beforeStarting', function(){
})
.bind('finished', function(){
$('.marquee').marquee("destroy");
$(".marquee").css("overflow", "scroll")
})
.marquee({
//speed in milliseconds of the marquee
duration: 7000,
//gap in pixels between the tickers
gap: 0,
//time in milliseconds before the marquee will start animating
delayBeforeStart: 0,
//'left' or 'right'
direction: 'up',
//true or false - should the marquee be duplicated to show an effect of continues flow
//pauseOnHover: true
})
Project proposal
Looking back at the original agreement, this is what be planned before the project began:
“I will provide two initial design direction samples. You can choose either direction, request changes, and/or combine elements from each sample. Prior to this step, you can send me examples of what you would like your website’s look-and-feel to be similar to, as well as any other specific requests regarding functionality, style, and layout. Following this, we can go through up to two more rounds of revisions regarding the style, layout, and functionality of your website. You will provide any information, text, and images (photos, logo, etc.) that need to be displayed on this website. Any stock images that we may choose to purchase for this website will cost extra.”
It was a fixed price agreement, but I added this paragraph to our documentation:
“I know from plenty of experience that fixed-price agreements often limit you to your first idea about how something should look, or how it might work. I don’t want to limit either your options or your opportunities to change your mind. If you do want to change your mind, add extra sections or content or even add new functionality, that won’t be a problem. You will be charged an hourly rate.”
Reusable components are a staple of modern front-end web development. On my simple PHP website, I wanted to build user interface pieces, and reuse them across multiple pages. When I was creating a new page for a newsletter signup form, I realized that I was repeating a lot of code for a contact form section that is displayed on almost every page.
This website is so simple, it does not use any modern framework. The contact form itself is powered by AWS SES. I created a directory in the root folder of the website called “components”. There, I put files containing HTML, CSS, and JavaScript code that would otherwise be repeated. Implementing this pattern will help my code adhere to the DRY (don’t repeat yourself) principle, and make it quicker and easier to make changes in the future. Centralizing code ensures quality and scalability.
Searching the code base for references to this particular HTML revealed ten instances that could be cleaned up.
In the new component file, I copy and paste my HTML and CSS code. Then, I go through each of the offending files, and replace the markup with a reference:
<?php include $_SERVER["DOCUMENT_ROOT"] . '/components/contact-section.php'; ?>
I also delete any CSS and JavaScript for this section that’s on the page. At first, I tried adding the JavaScript that controls this form’s functionality to that same file. It failed because it relies on a jQuery reference that is not loaded until lower in the document. Separating the JS code into its own file, similarly named as `contact-section-js.php`, and calling it below the library reference solved the issue. That code is responsible for passing the message along to the back-end, handling UI success/error notifications, and implementing CAPTCHA to thwart bots. Since it was a lot of files were morphed, I ran a quality assurance protocol to ensure nothing broke.