My initial thoughts about the Apple Vision Pro

Just got out of my 15 minute facilitated tour of the Apple Vision Pro at my local Apple Store in Tyson’s Corner. If you haven’t gotten the free demo, it’s definitely worthwhile – particularly if you occasionally wear the CTO title. The experience was exactly what I thought it would be, which is to say that I knew Apple wouldn’t release garbage and I’m pleased to report that their equipment is the first VR/AR setup that has high enough resolution and refresh rates to really put you in the middle of the action. The demo was almost all focused on virtual-reality experiences with hardly any augmented reality, which I attribute to A/R requiring more forethought and programming to make usable but I’m sure those apps will come in time.

Here’s a few initial thoughts:

  1. Large Screen TV manufacturers should be worried. Theater owners should sell while they still can.

    As other folks have reported, the visual experience of watching movies is better than IMAX from the comfort of your couch. And, I’m sure that game makers are looking forward to delivering on experiences similar to those described in the book “Ready Player One” where one can ultimately act out one of the roles in a movie. I look forward to embracing my inner Marty McFly and, well, every role ever played by Bill Paxton and Bruce Campbell sometime during the next decade. Hail to the king, baby.

  2. The impact on ed-tech will be ginormous

    Back in the ancient days of the early 1990’s, students often complained about freshman and sophomore college lectures that they had to endure with 400 of their closest acquaintances. I suppose that we could still rationalize this experience as being helpful in the sense that it got us out of our dorm rooms and enabled us to meet new people, but the same educational value can now certainly be gotten through the Vision Pro’s 3D video playback experience and enhanced through adding interactive widgets. Imagine watching an aeronautical engineering lecture and then suddenly having a virtual rocket engine materialize in front of you that that you can dynamically interact with in real-time? Change the math and watch the behavior of the virtual engine change. For those of us who always needed the answer of how abstract math and science topics could be used in real life, this is the way. Maybe I’ll be able to see the practical applications of calculus… maybe.

  3. Try a vacation before you buy a vacation

    The demo has some truly amazing video that aptly demonstrates it can put you in the middle of any environment. But, at the end of the day, being in a scuba video where you’re nose-to-nose with sharks isn’t quite like actually being there. What the tech does do, however, is greatly incentivize us to get out of our comfort zone and actually go visit those places. The Tic-Tok generation will be ALL OVER THIS.

    Experiences like driving a car, flying a plane and even launching into space can be easily recorded. This tech will take home video tours to a new level of effectiveness. There will be many, many new technology-driven marketing jobs becoming available.

  4. Drucker’s Law of Consumer Products #6

    In order to reach critical mass, any sufficiently advanced consumer technology must have applications that provide better experiences for watching porn and … uh… sports?

    I know it. You know it. And you know I know it.

    The demo included putting you on the field behind a soccer goal as well as just behind 3rd base. No, that’s not a euphemism you sick bastard.

  5. Introverts are going to love it, and therein lies the problem

    Travel without meeting people. Get fully immersed in football and movies. Kill the bad guys and be the hero, or kill the good guys and be the villain. Virtual reality carries a promise of adventure without ever leaving your couch. But if COVID and social media have taught us anything, it’s that shared, in-person experiences are what bind societies together. You’re much more likely to make efforts to understand and empathize with someone’s alternative viewpoint when they’re standing in front of you and much more likely to tear them down in 280 characters on Twitter when they’re not. Being sociable is a use-it or lose-it skill. People (especially Millennials) seem to talk a lot about work/life balance as they stare into their phones, but the real damaging imbalance that exists today is the time spent on-screen/off-screen. And I fear that VR tech is another technology with good intentions that’s ultimate going to drive us further apart as a society.

But we’re just getting started…

I’m hoping to spend a bit more time with the Apple Vision Pro over the next month, focusing on whether/how it can be used to help with general office productivity and data visualization. The creative possibilities for this hardware seem endless. Stay tuned!

Fly on a private jet for free with Vaunt

I recently flew to Jacksonville, FL from Reagan National on American Airlines for a business trip. While I was sitting in a meeting in St. Augustine FL, my Vaunt app buzzed with an alert that a free flight on a private jet was potentially available from St. Augustine’s airport to Washington Dulles for the day after my meetings were scheduled to finish…


The TLDR:

  • Flights on light jets typically cost 6K-7K per hour.
  • Vaunt is an app-based service that enables you to fly one-way on private jet repositioning legs for free after paying a $995 annual subscription.
  • Repositioning flights, sometimes referred to as “empty legs” or “deadheads” occur when the plane would normally fly empty in order to pick up a charter passenger.
  • A waitlist priority algorithm helps ensure that members get opportunities to fly.
  • Flights are awarded the day before takeoff.
  • All flights are currently operated by Vaunt’s parent company, Volato, the 4th largest light-jet operator in the United States (ranked by flight hours) and a publicly traded company. Vaunt hopes to expand its service by including other lift operators in the near-future.
  • All flights have two pilots.
  • All flights are carbon-offset.
  • Flying in a Hondajet is a bit like traveling in a stretch limo… if your limo traveled at 500 MPH at 42,000 feet and had a potty, wi-fi, and galley.

Disclaimer: I am the Chief Technology Officer at Volato and oversee the development of the Vaunt and Volato app ecosystem. The views reflected in this article are strictly my own.

Introducing Vaunt

Vaunt is an app-based service that my software development team at Volato created in order to reduce the inefficiencies of flying empty-leg routes. Initially conceived as an awesome internal-only employee benefit, we ultimately decided to roll out the service to the public a few months ago. 

Step 1: Downloading the App

You can download the Vaunt app for free from the iOS app store and Google Play, however, you must be a subscriber in order to add yourself to a flight waitlist.


Vaunt offers corporate discounts
While you can subscribe to Vaunt as an individual for $995/year, Vaunt also offers a corporate program where memberships can be made available to employees for free as a company benefit. Have your HR team send an email to info@flyvaunt.com if interested.

Snagging a flight
While waitlist priority is determined by a number of factors, the most important is the time that has elapsed since you’ve taken a flight. Members who haven’t flown on Vaunt typically have a higher priority than those who have — your “status” increases every day that you’re a member and haven’t been on a flight.

Step 2: Joining a Waitlist

To join a waitlist, simply tap the join button in the bottom right of each flight card. Your joined flights will then appear in the “Your List” tab where you’ll be able to monitor your position on the waitlist. If you want to “lock-in” the #1 position in the waitlist so that no one with a higher priority can leap-frog you, you can opt to pay an additional $995. This feature can only be used once per year, however, as our overall goal is to democratize private air travel.

Flight closes the day before takeoff. I was notified that I’d gotten the flight roughly 24 hours in advance. I promptly canceled my return trip on American for a flight credit and confirmed my trip on Vaunt.

Step 3: Set the Departure Time and Confirm Your Itinerary

Shortly after confirming the flight in the app I was contacted by Volato’s concierge team who collected additional details for TSA approval (# of passengers, date of birth, weight, luggage weight), confirmed the departure time, and sent my trip sheet via email. The entire process took less than 2 minutes.

Vaunt trip sheet

Step 4: Arrive at the FBO

One of the best things about flying private is that you completely avoid going to the public terminal. Instead, you arrive at an FBO (Fixed Base of Operation) on airport property and walk right onto the plane. There’s no crowds, no TSA scanning/undresssing, and you can bring whatever liquids you want onboard. I used the opportunity to pack a case of a local St. Augustine delicacy – Cowgill’s Hot Sauce… because that’s how I roll.

Everything about the experience has been specifically designed to reduce stress and not waste time. While you can arrive as late as 15 minutes before departure, I opted to show up a little early just to have a friendly chat with my pilots, “Coop” and Caesar.

Signature FBO at Dulles on a busy day

Signature FBO at Dulles on a busy day

Step 5: Talk to me, Goose!

The 2022 HondaJet Elite 2 that I flew on is a “light jet” capable of traveling nearly 500 mph and as high as 42,000 feet. It can fly up to 2.5 hours without refueling and is the rock-star of the industry with it’s unique engine nacelles that give it the distinction of having the quietest cabin in-class. Tom Cruise owns and flies one!

The HondaJet has the most comfortable seating in class, optimized for 4 travelers. You can even bring your pets on Vaunt flights. Look at all that legroom!

There’s even a potty at the rear of the plane with a sink and skylight!

While many of the jets in Volato’s fleet have galleys with a coffee maker, refrigerator, and a full complement of snacks and beverages, mine was instead outfitted to support a 5th passenger seat. A Yeti cooler contained a number of assorted sodas and craft-beers.

But my favorite feature of the Hondajet is the cabin-management system and BonJiovi sound system that places hidden speakers throughout the cabin. Controls are located in one of the armrests and are also available through a web-based app. You can control the lights, window shades, cabin temperature, and view your time to destination.

I was able to easily connect my phone to the system through Bluetooth and blast Kenny Loggins “Danger Zone” in the cabin during takeoff…just because I could.

While the cabin did have wifi, the bandwidth was somewhat limited. I was able to email, text chat, and do some light web browsing, but it’s not really suitable for streaming. Volato has announced that they’re in the process of upgrading the fleet to SmartSky which will give passengers broadband-level access.


Step 6: Parting is such sweet sorrow
While I can’t wait to jump out of a commercial flight, landing in a private jet is always a little bittersweet. When flying private, I find that the journey is sometimes more enjoyable than the destination.


Nevertheless, I grabbed my bags, thanked the pilots, walked 100 feet to the front of the Signature FBO at Dulles, and hopped in a Lyft to drive home. Along the way, I opened the Vaunt app back up hoping to snag another free trip on the absolute best way to travel anywhere.

Crush your enemies
See them driven before you
And hear the lamentation
Of their women…

…and flying on private jets for free.

Conan the Barbarian – Answering “What is best in life”



Click here to sign up for Vaunt today!





Sometimes you just gotta put on the cape

Over a year ago I guessed correctly that Covid would be in remission by July 2021 and booked a week-long, highly-discounted getaway to the beautiful island of St. Lucia in the Caribbean. We stayed two nights at the Fond Doux Eco Resort in the rainforest followed by 7 nights at the Windjammer Landing beach resort. I strongly recommend both.

The manly Drucker men with their girly tropical drinks at Fond Doux. The drinking age in St. Lucia is 16.

The island and our accommodations were stunningly beautiful, the locals are friendly, and the quality of the produce and seafood is unparalleled. The best thing that I ate all week was a ripe papaya that we plucked right off a tree. Our experience was consistent with all of the advertising and brochures available online.

Petit Piton – photo taken at sundown from one of the nature trails at Fond Doux.

The accommodations at the WindJammer Landing beach resort vastly exceeded our expectations. We got a 2-bdrm villa with plunge pool through a Disney Vacation Club timeshare exchange with RCI. I mean… it just doesn’t get any better than this.

My family enjoyed the private infinity pool at our 2-bedroom villa at WindJammer Landing, facing the Caribbean Sea

While St Lucia is paradise for well-heeled tourists, the number of neglected cats and dogs on the island are difficult to ignore. Felines regularly greeted us at dinner and roamed around our villa. Some had obvious health issues, all were thin — sometimes distressingly so, although I noted that their weight seemed directly proportional to how close they roamed to the hotel restaurants. One night I fed a very appreciative half-blind, painfully thin cat who moseyed up to our beach-side table the leftovers from my beef stew.

On our last night on the island, while we waited in one of the highly-rated restaurant parking lots for our taxi to arrive, a very cute dog approached out of the darkness, wagging its tail and begging for food.

The modern web floods us with a tsunami of news – much of it purposely designed to infuriate, frustrate, and divide us. Our typical go-to action is to note our disgust on social media, but this only constitutes a fulfilling response if you happen to be a trash talking-head in the ever-booming rage-monetization business. Perhaps the biggest irritant of all is that hardly any of it seems actionable by the reader.

Every once in awhile, however, I’m reminded that social media can also be used as a force for good. Prior to visiting St Lucia, I joined the WindJammer Landing Guests and Owners facebook group. And it was there where I saw the following post from Doris Jolicoeur of the Bruno Project who coordinates with visitors to the island in sending dogs and cats from St. Lucia to the US, Canada, and the UK. While I was initially hesitant to help someone who I’d just met online, what I saw happening to the cats and dogs in St. Lucia compelled me to act.

May be an image of dog and text that says 'PLEASE HELP OUR RESCUE DOGS NEED ESCORTS HOME! Our volunteers meet you at both airports. WhatsApp 758-723-2174 (min 2 bus days prior pls) www.brunoproject.ord'

The Bruno project will deliver a vetted and certified healthy cat or puppy to your hotel with a carrier, food, water, and all of the export paperwork for you to carry from St. Lucia to your final destination airport where a foster will pick it up.

Meeting up with “Blondie” an 8-week old Golden Retriever Mix outside the gate at WindJammer Landing on our way to the airport.

You take the dog/cat as carry-on luggage on your plane, placing it under the seat in front of you. The dog cannot be removed from the carrier during flight.

Blondie was an absolute angel – no barking, no accidents. When I pulled her out from under the seat at the end of the flight, the passengers sitting next to me were shocked — they had no idea that they were sitting next to a dog. She just curled up and tried to sleep for most of the trip. Frankly, she was probably a lot more comfortable than most of the humans sitting in coach-class on the airplane.

Blondie in her carrier on the van ride to the airport. LOOK AT THAT FACE!!!!

We had a long layover at Miami International, so we used the opportunity to take Blondie to one of their pet relief areas that was conveniently located near our departure gate. We helped her relax from the always stress-inducing chaotic baggage checking process and gave food and water.

Blondie makes everyone happy – with my elder son Dylan in the Pet Relief room

After one more boarding process and a 2 hour flight to Reagan National, we delivered her safe and sound to a wonderful foster family who met us in baggage claim.

Meeting the foster parent (left) who drove down from Pennsylvania to meet us at Reagan National and will hopefully be on her way soon to her “furever” home.

Blondie was dumped on July 5th 2021 on a St Lucian beach. She was only about 4 weeks old. She followed a local man and his dog back to their home. They allowed her to stay just one night with them and then kicked her out. An old neighbor of Doris’ called her to go and pick Blondie up, otherwise the local was going to put our little damsel in distress back on the beach where they found her.

Her story is not uncommon.

If you’re planning on vacationing in St. Lucia or any Caribbean/Latin American country, budget just a little bit of extra time to put on your superhero cape, and save a life.

Click here for more information about the Bruno Project

Building an E-Commerce Site as a Single Page App with Vue.js

TriGrow Supply is a custom-built e-commerce SaaS platform. The front-end is a single-page app built using Vue.js and Element.ui. You can access the site at https://www.trigrowsupply.com.

It features over 6,000 products across a broad spectrum of categories with ultra-competitive, tiered volume pricing. As a single-page app, response-time is lightning quick, which we accomplished largely by scaling the application server on Amazon elastic beanstalk and optimizing all of our product images to be as small as possible. We’re currently working on additional features to make it run even faster.

My next few blog posts will cover some of the challenges associated with developing a public-facing single page app and how to deal with them.

Go check it out and let me know what you think!

Defining Roles-based Security ACLs and Supporting Multitenancy in the Node.js Strongloop Loopback framework.

Strongloop Loopback (https://loopback.io) is a Node.js framework that extends Express.js and makes it easy for developers to create REST-based CRUD APIs in minutes.

In my experience, their proclamations about RAD API development generally holds true, however, their documentation and, in particular, their tutorials, really need a lot of work in terms of organization. While there’s a lot of information that’s presented, it doesn’t really flow to tell the complete story of how you would solve real-world problems and how all of the pieces really fit together.

Hopefully you’ll find that this post helps bridge that gap. I assume that you already have a very basic understanding of the Loopback framework and that you are also a movie nerd.

Let’s say, for example, that you have an e-commerce website that you plan to offer as Software as a Service. It has the following three tables:

  • Stores – a registry containing the stores that are being hosted
  • Users – including username, password, email, and a foreign key store_id that maps back to a record in the Stores table.
  • Orders – Orders placed by a user. Contains a foreign key to the Users table and a foreign key to the Stores table.

We need to create CRUD services for each of these tables, secure them with roles-based security and also support multi-tenancy. For example, an administrator for Store ID #2 should only be able to see Orders and Users that were added store ID #2. A superuser should be able to see everything.

Creating Tables to Support Roles-Based Security

Loopback has connectors for most commonly used databases. Connection data is stored in your project’s server/datasources.json file. In this case, I’ve created a custom datasource named “ecommerce”:

{
  "db": {
    "name": "db",
    "connector": "memory"
  },
  "ecommerce": {
    "host": "[some mysql host on Amazon]",
    "port": 3306,
    "database": "ecommerce",
    "name": "ecommerce",
    "user": "quizartshaderach",
    "password" : "ForH3IsTh3"
    "connector": "mysql"
  }
}

Loopback natively supports roles-based security out of the box. You simply need to have the framework automagically add its tables to your database by placing the following script in your project’s /server/ folder and running it.

var server = require('./server');
var ds = server.dataSources.ecommerce;
var lbTables = [
 'Application','User', 'AccessToken', 'ACL', 'RoleMapping', 'Role'
];
ds.automigrate(lbTables, function(er) {
  if (er) throw er;
  console.log('Loopback tables [' - lbTables - '] created in ', ds.adapter.name);
  ds.disconnect();
});

COOL!

Defining Roles

In the generated Role table, we’re going to add two roles — a “superuser” who is the ultimate supreme being (think Keanu Reeves as John Wick), and a “storeadmin” who manages a specific store or stores in our SaaS system. Loopback has $unauthenticated, $authenticated, and $everyone as built-in, immutable roles. So, we got that going for us:

Roles

Defining Users

You may have noted that Loopback has its own User table definition. You should actually hide this from the API and instead extend its properties into your own custom table as illustrated by the following screenshot:

UsersTableDef

Your corresponding /common/models/users.json file should resemble the following snippet and only list out properties that are *not* baked into the native loopback User model. Also note that I’m already using ACLs to deny general access to unauthenticated users (a built-in immutable loopback role) and grant full access to John Wick, my superuser who can kill five angry customers in a bar with a pencil. A f****ing pencil.


 {
  "name": "users",
  "plural": "users",
  "base": "User",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "firstname": {
      "type": "string"
    },
    "lastname": {
      "type": "string"
    },
    "disabled": {
      "type": "boolean"
    },
    "creationDate": {
      "type": "date"
    },
    "store_id": {
      "type": "number"
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": {}
}

Creating a Superuser/SuperAdmin Account

Now that the basics of your security framework are in place, you can use Loopback’s APIs to create the superuser account. Loopback automatically generates a Swagger ui for all of its services and will automatically hash the password and store it in your database.

explorer

Note that Loopback will default to using the npm crypto library for hashing passwords. This dependency caused us some issues when deploying on Amazon elastic beanstalk, so we had to use the pure javascript cryptojs library instead, which is also supported by the framework. So you might need to execute the following commands to install the appropriate library for your deployment platform:


npm uninstall crypto
npm install cryptojs

After your superuser account has been added to your users table, you can assign it the superuser role by inserting a record to the RoleMapping table:

addingARoleMapping

Logging In

After you’ve added your account, verify that you can use it to login by executing the users/login service from the Loopback explorer. If your login was successful, the service will return an access token id:

So the following post call: http://localhost:3000/api/v1/users/login?include=user would return something similar to:

{
  "id": "OcCGkGHCKKQrLNr2mS1DskVMXxae7IJlOezkEttVvvYXjk74gwRdpBrW7LEBufG8",
  "ttl": 1209600,
  "created": "2018-09-12T11:17:14.824Z",
  "userId": 1,
  "user": {
    "firstname": "John",
    "lastname": "Wick",
    "disabled": false,
    "creationDate": "2018-09-05T01:47:07.000Z",
    "store_id": 1,
    "realm": null,
    "username": "Administrator",
    "email": "jwick@thecontinentalhotel.com",
    "emailVerified": true,
    "id": 1
  }
}

You can then copy and paste the returned id into Loopback Explorer’s accessToken field. Loopback Explorer will subsequently automatically append a query string variable named access_token to every request.

Securing Services

There are at least 4 ways to secure services:

  1. Statically apply ACLs
  2. Define ACLs in the ACL database table
  3. Programmatically hide public methods
  4. Dynamically verify permissions at runtime with an observer event

Statically applying ACLs

We use static ACLs to restrict methods of our ORDERS table to specific groups. In this particular user story, we want to deny access to anyone who hasn’t logged in, allow full access for anyone in the “superuser” role, and allow read/write access to anyone who’s authenticated:

(common/models/orders.json)

"acls": [
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "$unauthenticated",
      "permission": "DENY"
    },
    {
      "accessType": "*",
      "principalType": "ROLE",
      "principalId": "superuser",
      "permission": "ALLOW"
    },
    {
      "accessType": "READ",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW"
    },
    {
      "accessType": "WRITE",
      "principalType": "ROLE",
      "principalId": "$authenticated",
      "permission": "ALLOW"
    }
  ]

Defining ACLs in the Database

Note that you could also implement these rules by adding records to the ACL database table, which would give you a little bit more flexibility for tweaking security without having to modify source code or restart the node service.

dbsecurity

Hiding Public Methods

Our user story dictates that no user should be able to delete an order, so we attack this requirement by stripping the programatically hiding the method in the common/models/orders.js file. We also want to disable a user’s ability to update all records and any other services that we’re not intending to use in our application:

'use strict';

module.exports = function(Orders) {
	
// remove unused methods
Orders.disableRemoteMethodByName('deleteById'); // Removes (DELETE) /products/:id
Orders.disableRemoteMethodByName("prototype.patchAttributes"); // Removes (PATCH) /products/:id
Orders.disableRemoteMethodByName('createChangeStream'); // Removes (GET|POST) /products/change-stream
Orders.disableRemoteMethodByName("updateAll"); // Removes (POST) /products/update
}

Handling Multi-tenancy and Record-Level Permissions with Observers

Loopback includes a series of Operation Hooks that are triggered from all methods that execute a particular high-level create, read, update, or delete operation. For our user story, we want to implement the following business rules:

  1. John Wick can do whatever he needs to do because he’s wearing bulletproof eveningwear.
  2. A store admin can only access records that are related to their specific store Id.
  3. Only a superadmin or a storeadmin can update an Order record.
  4. A user can only only access records that they “own” (user_id foreign key relationship).
  5. Any authenticated user can create a new order.

Each observer gets passed a context (ctx) object. Through the context object, you can access the user’s ID and dynamically modify the SQL WHERE clause before it is passed to the database server. To implement our business rules, however, we also need additional information from the users table for the logged in account. This necessitates adding the following script to the server/boot folder which will lookup additional user info on every request and add it to the context object:

(server/boot/attach-user-info.js)

module.exports = function(app) {
  app.remotes().phases
    .addBefore('invoke', 'options-from-request')
    .use(function(ctx, next) {
      if (!ctx.args.options || !ctx.args.options.accessToken) return next();

      // attach user info to context options
      const User = app.models.users;
      User.findById(ctx.args.options.accessToken.userId, function(err, user) {
        if (err) return next(err);
        ctx.args.options.currentUser = user;
        next();
      });
    });
};

Now that we’ve marshalled all of our user information, dynamically adding where clauses to queries at runtime through observers becomes a relatively trivial process:

(common/models/orders.js)


// handle multitenancy read operations
Orders.observe('access', function limitToTenant(ctx, next) {
  let authorizedRoles = ctx.options.authorizedRoles;
  let userId = ctx.options.accessToken.userId;
  let storeId = ctx.options.currentUser.store_id;

  if (!authorizedRoles.superuser) {
   // non super-duper admins ("storeadmin") can only see orders bound to their "store"
   ctx.query.where = ctx.query.where || {};
   ctx.query.where.store_id = storeId;

   if (!authorizedRoles.storeadmin) {
    // non store admins can only see orders that they "own"
    ctx.query.where = ctx.query.where || {};
    ctx.query.where.user_id = userId;
   }
  }
  next();
});

// only allow admins to update records
Orders.observe('before save', function preventUpdatesFromNonAdmins(ctx, next) {  
  let authorizedRoles = ctx.options.authorizedRoles;
		
  // the presence of ctx.instance indicates an "insert" operation
  // the presence of ctx.instance.id indicates an update
  if (!ctx.instance || ctx.instance.id) { 
    if (!authorizedRoles.superuser && !authorizedRoles.storeadmin) {
     throw new Error("Security Exception - only admins can update an order record.");
    }
  }
  next();
});

And in the end…

John was once an associate of ours. They call him Baba Yaga. Well, John wasn’t exactly the boogeyman. He was the one you sent to kill the f***ing boogeyman!

If you’ve found this post to be helpful in defeating the Strongloop Looopback learning curve boogeyman, please add a comment below.

Happy coding!

Introducing the C.R.A.P. Framework for ColdFusion

The CRUD Access Protocol framework enables you to quickly and easily make your ColdFusion data available to web services securely.

It’s a framework for people who hate frameworks.

Let’s say, for instance, that you have 20 data tables in ORACLE that you want to be able to create new records, edit existing records, logically delete records, and read records (with pagination) through a javascript-based GUI and common interface. It also enables you to alias database column names in the event that an angry DBA decides to start renaming columns on you!

My framework can make this back-end process simple!

You simply need to generate a CFC that extends an AbstractGateway class and has the following properties:

  • tableName – the name of the table in the database
  • pkField – describes the primary key field, and whether it’s an autonumber/identity/sequence
  • relationships – (optional), describes the join conditions (supports inner and outer)
  • permissions – (optional), describes which user roles will be granted access to the create/read/update/delete methods.
  • columnmappings – describes each column, it’s data type, whether it’s writeable, and whether it should be encrypted in the database

A typical piece of CRAP would resemble the following:

<cfcomponent  output="false" 
              extends="components.components.gateway.AbstractGateway">

    <cfset this.tableName = "USA_CLAS">
    
    <cfset this.pkField = {
	 "columnName" = "#this.tableName#.CLASS_CODE",
	 "fieldName" = "code",
	 "type" = "VARCHAR"
    }>

    <cfset this.relationships = [
      {
	field1 = "#this.tableName#.CLASS_GROUP",
	field2 = "USA_CLAS_GROUP.GROUP_ID"
      }
     ]>

     <cfset this.permissions = {
  	"create" = "admin",
  	"read"   = "authenticated",
  	"update" = "admin",
  	"delete" = "admin"
     }>

    <cfsavecontent variable="this.columnMappings">
	 <cfoutput>
	 {
	   "code": {
		  "columnName": "#this.tableName#.CLASS_CODE",
		  "type": "VARCHAR",
		  "persist": true
	   },
	   "type": {
		  "columnName": "#this.tableName#.CLASS_TYPE",
		  "type": "VARCHAR",
		  "persist": true
	   },
	   "description": {
		  "columnName": "#this.tableName#.CLASS_DESC",
		  "type": "VARCHAR",
		  "persist": true
	   }
	   "group": {
		  "columnName": "#this.tableName#.CLASS_GROUP",
		  "type": "NUMERIC",
		  "persist": true
	   },
	   "createdBy": {
		  "columnName": "#this.tableName#.CREATED_BY",
		  "type": "VARCHAR",
		  "persist": true
	   },
	   "createdDate": {
		  "columnName": "#this.tableName#.CREATED_DATE",
		  "type": "TIMESTAMP",
		  "persist": true
	   },
	   "lastModifiedDate": {
		  "columnName": "#this.tableName#.LAST_MODIFIED_DATE",
		  "type": "TIMESTAMP",
		  "persist": true
	   },
	   "lastModifiedBy": {
		  "columnName": "#this.tableName#.LAST_MODIFIED_BY",
		  "type": "VARCHAR",
		  "persist": true
	   },
	   "active": {
		  "columnName": "#this.tableName#.ACTIVE_FLAG",
		  "type": "VARCHAR",
		  "persist": true
	   },
	   "groupName" : {
	      "columnName": "USA_CLAS_GROUP.GROUP_DESC",
	      "type": "VARCHAR",
	      "persist": false
	   }
    }
	</cfoutput>
    </cfsavecontent>

    <cfset this.columnMappings = deserializeJson(this.columnMappings)>

</cfcomponent>

After dropping your CRAP onto the server, you can make advanced data requests like the following:

 <cfset local.results = mycrap.read(
    start = 0,
    limit = 5,
    filters = [
      { property = "active", value="Y" },
      { property = "description", value="test", operator="like"}
    ],
    sorters = [
      { property = "code", direction="ASC" }
    ]
)>

Of course, you can also easily perform inserts, updates, and deletes…

  <cfset local.rec = mycrap.create("code" = "hey", type="example")>

Note that you only have to pass data to the create/update functions that has changed.

So keep an eye out for my C.R.A.P. — I’ll be posting it to Github within the next week after I, uh… clean it up just a little bit and enhance it with field-level and row-level security!

A Salesforce Lightning Component Design Pattern for Optimum Code Reuse

As the author of Salesforce.com’s official instructor-led course for Lightning Developers – Dev601: Programming Lightning Components as well as the Trailhead Lightning Component Framework Superbadge, I’ve spent a lot of time developing hands-on exercises that illustrate the mechanics of custom app development on the Salesforce.com platform.

What that course doesn’t officially cover, however, are design patterns for creating components for optimum re-use and application maintainability. Therefore I decided to author this blog article to act as additional reading materials for that course. Note that this post does assume that you are already quite familiar with Salesforce Lightning Component development.

Before we can discuss a pattern strategy, let’s define a couple of terms.

Tightly Coupled Components

A Tightly Coupled App is one where all of the components are nested within a container-component.In effect, you can treat the parent component as a de facto controller, handling any Lightning component events thrown by its members (typically by setting public properties and calling public methods of components other than the one that threw the event).

For instance, consider a scenario where a user chooses from a select-box of beers as illustrated by Figure 1. The BeerSelector component throws a component event containing the beer selection back to FriendsWithBeer.cmp. FriendsWithBeer.cmp then takes the beer selection and programmatically sets a beerId attribute in c:FWB_BeerContacts which triggers and APEX request, displaying a list of all contacts who like that particular beer.

app_org1

Figure 1: Tightly Coupled App Wireframe for our sample app, Friends with Beer

Loosely Coupled Components

Loosely Coupled components, illustrated by Figure 2, are assembled into an application with Lightning App Builder and therefore have no direct parent component other than a Lightning App Builder template. The only way these components can therefore communicate with each other is via Lightning Application events. In this scenario, encapsulation of functionality is an absolute-must as business users have the freedom to include or exclude any installed Lightning App Builder-enabled components in your Salesforce org.

app_org2

Figure 2: Loosely Coupled App Wireframe

Why Bother?

It’s a fair question. If you’ve tracked the progress of Lightning App Builder over the last few release cycles, it’s pretty clear that Salesforce is expending a lot of effort in this particular area of the framework. Having components that can be surfaced by both developers (as strongly-coupled apps), yet retain the flexibility to be deployed in App Builder, helps future-proof your code. It would not surprise me if within a couple of years, the vast majority of custom lightning applications are assembled using App Builder by both developers and power-users/admins.

Bringing it all together

I therefore suggest that you organize your Lightning applications around three separate tiers, as illustrated in Figure 3:

  • The Functional Tier are comprised of utility components that do all the actual output within your application. I’m talking about Lightning Base Components, utility components such as custom grids/tables, custom buttons, and whatnot.
  • The Declarative Application Tier are components that invoke Apex methods, encapsulate business logic, and pass data to the functional tier components for output to the user. These components also fire and listen to component events. A strongly-coupled Lightning application would consist of only components from the Declarative Application Tier and the Functional Tier.
  • The Orchestration Tier invokes components from the Declarative Application Tier, re-firing custom Lightning Component events as custom Lightning Application events. They serve as a wrapper for the Declarative Tier components that you want to surface in Lightning App Builder. They also wrap components in lightning:card, as this is the preferred methodology (in Winter 18) to ensure that your component is not transparent against the new Winter 18 Lightning custom background.

app_org3

Figure 3: Code Organization Pattern

Naming Conventions

You’ve probably inferred from the slides above that I’ve settled on a few naming conventions:

  • Functional component names start with a lower-case letter and are camel-cased.
  • Declarative Application Tier components start with applicationName_ComponentName. Since the name of my sample app is Friends With Beer, I’ve abbreviated the app name to FWB. Therefore, FWB_BeerSelector, FWB_BeerContacts, and so forth.
  • Orchestration Tier components are prefixed with applicationName_App_ComponentName
  • Component event names are prefixed with applicationName_evt_ComponentEventName
  • Application event names are prefixed with applicationName_aEvt_ApplicationEventName

Feel free to critique these choices and concepts in the comments section below. In (hopefully) about 30 days, you’ll be able to view my detailed explanations of these concepts (including a code walkthrough) as a Pluralsight video play-by-play with my fellow Lighting Component instructor Don Robins!

Need immediate help? For Lightning Component consulting, development, and best-practices code-reviews please send an email to info@figleaf.com. My company, Fig Leaf Software is a certified Service-Disabled Veteran-Owned Small Business located in Washington, D.C.

Building Uniquely Branded Lightning Component Single Page Apps for Salesforce Mobile

(part 1 of a 2-part series)

fwbfinalAt Dreamforce last week, Salesforce introduced a new feature that would enable administrators to more distinctly brand their Salesforce mobile apps.

As illustrated by the screenshot at right, that capability is actually available today through various CSS hacks, hand-wringing, and lots of tears.

Other titles that I’d considered for this post:

  • “Stuffing a round peg into a square hole”
  • “How to pull out all of your hair with maximum effort”
  • “I like to suffer”
  • “I should have become a patent lawyer”
  • “There be dragons here”

The fundamental problem is that the platform formerly known as Salesforce1 was clearly designed to render web pages (commonly referred to as Flexipages) and not single-page apps (SPAs). That’s why there’s no officially supported methodology (yet) for disabling the built-in “pull to refresh” feature, which can be quite convenient for refreshing web pages, but is inappropriate for single-page apps. There’s also lack of direct support for a “viewport” that would grab 100% of the visible screen, enabling developers to explicitly manage scrolling within their Lightning app.

Implementing Custom Branding in a Lightning App Builder Template

The first step in our branding odyssey is to create a Lightning App Builder template. This is a new feature in Winter ’18. The template defines drop target regions for Lightning App Builder components. About the only functionality that I’ve found that the app builder template will not support is (ironically) defining DESIGN properties on the template.

To define a template you need to:

  1. Add an implements=”lightning:appHomeTemplate” to the tag.
  2. Define a aura:attribute for each template region
  3. Define the template region in the DESIGN file

In order to implement the crazy branding, I also added the following global style to hide the flexipage page header, which by default consumes a fair amount of space at the top of the page:

<style>
 .oneAnchorHeader {
   display: none;
 } 
</style>

I also set a series of overrides to try and establish a “viewport” that occupied 100% of the width *and* height of the visible screen:

<style>
 .scroller.actionBarPlugin, 
 .oneFlexipage, 
 .flexipagePage,  
 .pageBody,  
 .mainbody, .mainbody > div {
    height: 100%
  }
</style>

Are we having fun yet?

Other global style overrides were added to remove some extraneous white-space as I’m trying to make use of every pixel in the phone form-factor.

Here’s the complete source code listing for the CMP file of the Lightning App Builder template:

<aura:component 
 implements="lightning:appHomeTemplate" 
 description="Friends with Beer Mobile Phone Template">
    
 <aura:attribute name="region1" type="Aura.Component[]" /> 
    
    <style>
        .oneAnchorHeader {
        	display: none;
        } 
        .scroller.actionBarPlugin  {
        	padding-bottom: 0px !important;
        }
        
        .scroller.actionBarPlugin, 
        .oneFlexipage, 
        .flexipagePage,  
        .pageBody,  
        .mainbody, .mainbody > div {
        	height: 100%
        }
        
        .oneFlexipage .pageBody {
        	margin-top: 0px;
        }
          
        .spacer-pull-to-load-more {
        	height: 0px !important;
        }
        
        .slds-table {
        	background-color: rgba(255, 255, 255, 0.3) !important;
        }
        
        .slds-card__header {
            margin: 0px !important;
            padding: 0px !important;
        }
        
    </style>
    
    <aura:handler name="render" 
                  value="{!this}" 
                  action="{!c.onRender}"/>
    
    
    <ui:scrollerWrapper class="maxHeight">
        <div class="mainbody" aura:Id="mainbody">
            <div class="fwbheader slds-align_absolute-center">
              Friends with Beer
            </div>
            {!v.region1}
        </div>
    </ui:scrollerWrapper>
    
</aura:component>

The corresponding design resource indicates the formfactors under which the region will be present:

<design:component label="Friends with Beer">
    <flexipage:template >
            <flexipage:region name="region1" defaultWidth="Small">
                <flexipage:formfactor type="MEDIUM" width="SMALL" />
            </flexipage:region>
    </flexipage:template>
</design:component>

Our template’s CSS resource is relatively tame by comparison. It loads a web font, styles the “Friends with Beer” bar, and sets the background image (beer, of course) for the app:

@font-face {
    font-family: 'Rambla';
    src: url('/resource/fwb/rambla/Rambla-Regular.otf');
}

.THIS .fwbheader {
    background-image: url('/resource/fwb/top-grain.png'), -webkit-radial-gradient(top center, circle, #9f5628, #5d2f17);
    background-repeat: repeat-x;
    color: #3a1515;
    text-shadow: 0px 1px rgba(255,255,255,.3);
    height: 50px;
    width: 100%;
    font-family: Rambla;
    font-size: 1.5em !important;
}

.THIS {
    background-image: url('/resource/fwb/background.png');
    background-size: cover;
}

.THIS .mainbody {
    position: absolute;
    width: 100%;
    height: 100%;
}

.THIS.maxHeight {
    height: 100%;
}

Disabling Pull to Refresh

You may have noticed that the template listens for the render event and fires off a controller function. The function, along with the use of ui:scrollerWrapper, intercepts the dragmove event that is fired when a user taps and drags the screen and prevents it from bubbling up to the native Salesforce mobile app.

({
    onRender: function(component,event,helper) {
        var cmpEl = component.getElement();
         cmpEl.addEventListener("touchmove", function(e) {
            e.stopPropagation();
        }, false); 
        var targetEl =  component.find("mainbody").getElement();
        targetEl.addEventListener("touchmove", function(e) {
            e.stopPropagation();
        }, false);       
    }
    
})

 
In my next post, we’ll review the Lightning components that were used to render data from Force.com, and how I was able to dock them into specific locations within the app. We’ll also discuss whether this approach is really viable on a going-forward basis or whether we should just file it away as an interesting hacking exercise that has run-amok.

And now, it’s time for a beer.

Happy coding!
 

 

 

Salesforce Lightning Components: Creating a Select Box/Tree Component

Need a user to select an item from a 2-tier hierarchy? Typically, you’d either use a lightning:select with embedded optgroup tags, or use the new lightning:tree component. But, for optimal code reuse, consider building a single component that enables a developer to toggle the input mechanism that they think is the most appropriate for their use-case. For instance, while lightning:tree is great for desktop and tablet apps, it’s not appropriate for mobile phones, or for input forms.

fwb1

lightning:tree (left)

IMG_2170

lightning:select (in Salesforce Mobile)

The following component enables a user to either select from a list or a tree, depending on the value of the “mode” attribute.

<aura:component controller="BeerContacts" access="global" extends="c:Base">

<aura:attribute 
 name="mode" 
 type="string" 
 access="global" 
 default="select" />

<aura:attribute 
  name="beers"       
  access="private"       
  type="Object[]"      
  description="hierarchical data to display" />

 <aura:attribute       
  name="selectedBeerId"       
  access="public"       
  type="Id"       
  default=""       
  description="User selection"/>

 <aura:attribute       
  name="labelPrefix"       
  access="public"       
  type="String"       
  default="Friends with "      
  description="Text prefix for select options"/>

 <aura:handler    
  name="change"         
  value="{!v.selectedBeerId}"         
  action="{!c.onBeerChange}" />

 <aura:handler         
  name="init"         
  value="{!this}"         
  action="{!c.doInit}" />

 <!-- fire event when a selection is made -->
 <aura:registerEvent       
  name="onBeerSelected"       
  type="c:FWB_BeerSelected"/>

 <aura:if isTrue="{!v.mode == 'select'}">
   <div>
    <lightning:select     
     name="beerId"           
     label="Select Beer"      
     variant="label-hidden"      
     value="{!v.selectedBeerId}">

    <option value="">Select Beer</option>
    <aura:iteration items="{!v.beers}" var="thisBeerGroup">
        <optgroup label="{!thisBeerGroup.label}">
        <aura:iteration items="{!thisBeerGroup.items}"               
                        var="thisBeer">

            <option value="{!thisBeer.name}">
             {!v.labelPrefix}{!thisBeer.label}
            </option>
        </aura:iteration>
        </optgroup>
     </aura:iteration>
   </lightning:select>
  </div>
  <aura:set attribute="else">
    <lightning:tree items="{!v.beers}"                               
       header=""                               
       onselect="{!c.onTreeSelect}"/>
  </aura:set>
 </aura:if>
</aura:component>

While you can’t prevent a user from selecting a category in lightning:tree, you can provide them with guidance that they need to select a leaf node as illustrated by the component’s controller onTreeSelect function:

({
    doInit : function(component, event, helper) {
        helper.onInit(component,event,helper);
    },
    onBeerChange: function(component,event,helper) {
        var compEvent = component.getEvent("onBeerSelected");
        compEvent.setParam("beerId",component.get('v.selectedBeerId'));
        compEvent.fire();
    },
    onTreeSelect: function(component,event,helper) {
        var compEvent = component.getEvent("onBeerSelected");
        var selectedName = event.getParam('name');
        if (selectedName.indexOf('type:') == -1) {
            compEvent.setParam("beerId",selectedName);
            compEvent.fire();
        } else {
            var toastEvent = $A.get("e.force:showToast");
            if (toastEvent) {
                toastEvent.setParams({
                    "type" : "warning",
                    "message": "Please select a beer, not a category."
                });
                toastEvent.fire();
            }
        }
    }
})

And finally, you can use the same data structure to populate both the select box and tree control, as outlined by the following helper method, which is invoked by the controller’s doInit() method:

({
    onInit : function(component,event,helper) {
        helper.runApex(
            component,
            "c.getBeers",
            function(response) {
                var result = [];
                var groupName = "";
                var thisGroup = {items : []};
                for (var i=0; i<response.length; i++) {
                  if (groupName != response[i].Type__c) {                          
                        if (thisGroup.items.length > 0) {
                            result.push(thisGroup);
                        }
                        groupName = response[i].Type__c;
                        thisGroup = {
                            label: response[i].Type__c,
                            name: 'type:' + response[i].Type__c,
                            expanded : true,
                            items : []
                        };
                    }
                    thisGroup.items.push({
                        name: response[i].Id,
                        label: response[i].Name
                    });

                }
                component.set('v.beers',result);
            }
        );
    }
})

For reference, the getBeers() Apex method simply retrieves a category and beer name from a custom object. Note that the data is sorted by the fields that we are going to use for grouping in both the tree and select box:

public with sharing class BeerContacts {

    @AuraEnabled
    public static List<Beer__c> getBeers() {
      return [
         select Id,Name,Type__c,Country__c
         from Beer__c
         order by Type__c,Name
      ];
    }
}

Happy coding!