Elevator Pitch
In a step-by-step talk, I’ll walk through the entire stack of a highly polished component in Little CRM to show how to ship high-quality software with real, production code. We’ll talk about the nitty-gritty, what architectural decisions I made (and why!), and how I recovered from mistakes.
Description
Let’s learn how to ship high-quality software with real, production code. I’ll put a production component under the microscope for everyone to see. You’ll get to see the nitty-gritty technical details, the architectural decisions made (and why!), and how to dug yourself out of holes from early prototyping.
With high production value and a step-by-step walkthrough, I’ll guide you through the entire stack: stopping to pop the hood when needed, but never getting bogged down in the superfluous details. I’ll show my mistakes and the work in progress, because even seniors struggle.
By the end, you’ll have a firm grasp on the interplay between design systems, user experience, making the appropriate concessions, lightweight Javascript, using CSS to transform wholly declarative HTML, ViewComponent design, and leaning into server-side rendering. And you’ll feel empowered to apply this same level of polish in your everyday work: no “draw the rest of the owl” gaps here!
Notes
All the code from this will be available for attendees to look through after the talk.
These components, and this work, sprung out of years-long work I’ve done to level up my web development across the board. I’m calling the entire pattern library “The Practical Framework,” and I’d like to mention that during the talk so attendees have an extra place to go afterwards.
This talk was originally going to cover 2 components, but as I outlined their design and lessons for my guides, I realized that would be nigh impossible in 30 minutes. Instead, y’all can pick which one suits the conference best. I recommend the first one!
A “basic contact info” field component (name + avatar)
While this component is very frontend heavy, I still think it’s important for a Ruby conference because if we’re building for the web, we do need to use HTML, CSS, and Javascript. We can take Ruby/Rails’ focus on developer happiness and convention over configuration and apply them to the rest of the stack: having a solid foundation and example to point to for these helps make writing frontend code less painful, maybe even somewhat as joyful as writing Ruby (a tall order, I know!)
The core talking points will be:
- Why I use Shrine for file uploads and the architecture of the client <=> server communication for cached file uploads using XHR
- A teardown of my initial prototype’s faults
- Cobbled together UI
- Lack of support for mildly complex scenarios
- Using an off-the-shelf library (Uppy) that was a bad choice for the job
- The design goals for my new component
- HTML: setup everything declaratively. All error messages exist on the document, making it easy to review & customize them. For dynamic elements, like the gravatar dropdown items, use a
templateelement that we write the text into. - CSS: controls what’s rendered by using the cascade & selectively rendering based on the state of the Custom Element
- JS: event delegation & dispatching (uses events to signal changes in the state). Also the Gravatar retrieval is done client-side
- HTML: setup everything declaratively. All error messages exist on the document, making it easy to review & customize them. For dynamic elements, like the gravatar dropdown items, use a
- Detailed walkthroughs of version 1 (the
set-avatar-element)’s:- ViewComponent’s markup
- Data flow for failsafe file uploads
- Usage of custom events
- Refactoring to support loading Gravatars from multiple email addresses within the browser to reduce request time, and compressing the complexity of avatar processing into a single pipeline
- Adding desktop-grade polish with drag/drop and copy/paste support, in less than 100 lines of vanilla JS! Including how to avoid bugs that ship in even the richest SPAs like Linear.
- How I got to version 2 (a combined avatar upload + name field):
- The impetus: reducing wasted space on the entire “Edit Person” form
- Researching for inspiration:
- The macOS Contacts app for the inline photo + name pattern
- The 2013 OS X Human Interface Guideline for the “Image Well” component
- How my use of
data-*attributes and duck-typing expectations for the Custom Element’s markup allowed me to reorganize the ViewComponent to include the “Name” field without any significant changes to the CSS or JS - Refactoring the ViewComponent so that it’s ready for reuse when I add avatars to the Team model
- Other implementation notes to improve the CSS and JS you write :
- Using JS hashes to reference custom event names
- Reducing your memory footprint by avoiding anonymous functions for event handlers
- Remembering that javascript is a functional programming language
- Why I use
data-*attributes in some places, classes in others, and how minimal SCSS is used to control rendering while still allowing for easy debugging
A series of components for contact information (emails, phone numbers, social media handles, URLs)
This version covers data modeling pitfalls, ActiveModel forms, Rails form builders, leveraging ViewComponent slots, and migrating production data from bad decisions.
The core talking points will be:
- A demo of the final result
- A teardown of my initial prototype’s faults:
- The cobbled together UI
- The incomplete parsing logic
- Strongly negative user feedback and lack of usage
- An overview of the server-side modeling:
- A
Handlemodel, with alabelandLink(upserted model for URIs) - A concern that can be included in
ActiveModelforms for processingHandleline items, including:- Using a “nested”
ActiveModelfor the “line item” - Validating each line item and the list of handles generally (limiting the number of handles)
- Saving the added handles, and removing the ones marked for removal
- Using a “nested”
- How that concern is mixed into the
ActiveModelform that encapsulates the “Edit Person” form’s behavior. A standaloneActiveModelis used so that the complexities of form processing do not bleed into the underlying data models.
- A
- The pitfalls of my prototype’s data modeling
- A cautionary tale of data modeling & abstraction/contextual compression. Essentially: I tried to cram everything into a single model (a
Handlewith alabelandURI), which is beautifully simple from a data model standpoint, future-proof, and maximally compressable; but was unworkable in real-world usage.
- A cautionary tale of data modeling & abstraction/contextual compression. Essentially: I tried to cram everything into a single model (a
- Show my Balsamiq sketch for what the new UI should look like
- Quickly outline my mitigation plan for framing:
- Break out
EmailAddressandPhoneNumberinto their own ActiveRecord Model - Add support for standalone
Links on aPerson - Keep the
Handlemodel, but dramatically refactor it to only cover social media handles - Copy the pattern of
Handleprocessing; but forEmailAddress,PhoneNumber, andLinkindividually - Add the new view layer for
EmailAddress,PhoneNumber, andLinkline items - Write
ActiveJobs to migrate the existing production data forEmailAddress,Phone, andurlHandlerecords to the new models
- Break out
- Breaking out the
EmailAddressandPhoneNumberinto their own ActiveRecord Model:- Straightforward ActiveRecord models, with customized processing for each
- Adding support for standalone
Links on a person- Again, straightforward Rails code
- Copy the pattern of
Handleprocessing; but forEmailAddress,PhoneNumber, andLinkindividually- Show how the code is very similar between all 4 versions; emphasizing DRY for cognitive complexity, not LoC.
- Add the new view layer for
EmailAddress,PhoneNumberline items- Show the current
HandleViewComponents - Start with the
EmailAddressViewComponents, using this new ground as a “clean room” implementation - Refactor shared “framing” markup into another ViewComponent for reuse
- Show how the “destroy” and “undo” markup is declarative and CSS is used to selectively render them without involving JS. Also show that it’s based on a native
checkbox, with inline JS until the Invoker Commands API is Baseline. - Show the Custom Element that wraps each line item for auto-removing empty lines, and updating the label in the “destroy” callout.
- Show the current
- Show how a standalone form is used with Mrujs to request a new line for contact info, reusing the ViewComponent and using CableCar to append the new line.
- Quickly summarize how the same pattern is used for the
Linkview layer - Show the jobs to migrate the production data, and why I used an
ActiveJobso I could easily write tests