This article was created in partnership with JotForm. Thank you for supporting the partners who make SitePoint possible.
It all started in October 2017, when we were searching for HackWeek project ideas.
Basically, the design of JotForm Cards wasn’t a great fit for the website in question. Although the form itself looked beautiful, using the design in a context that was so different to the standard use case was ruining it.
And as we continued embedding JotForm Cards into websites, we became aware that this situation was pretty common. Double uh oh.
We noticed things like forms being squeezed into columns…:
And feature colors that didn’t match the website:
And repeated titles with different typefaces.
These issues don’t seem huge at first glance, but because we only had our 7 day HackWeek to fix them, we were nervous, real nervous.
Before I dive into implementation, I’ll share some structural information about JotForm Cards.
The styling structure on the front-end uses SCSS (basically SASS with fancy syntax) as a preprocessor and PostCSS as post processor. The remaining front-end structure (markup, logics) is mostly based on React.
Side note: the CSS files of the forms are prepared and served by PHP, which is the main back-end language for this implementation.
We decided to automate the design to achieve the following goals:
- Generate a clean template for JotForm Cards (without spoiling its design)
- Create room to manipulate the form to match the design of each website
- Understand the design choice of the websites we were embedding the form in
- Apply this design choice to our template without decreasing the quality of JotForm Cards’ standalone design
And finally, to do all of this in a reasonable amount of time.
Our first obstacle was to clean up JotForm Cards. Luckily, generating the template we wanted was easy, thanks to the form’s already dynamic structure.
We started by structuring our styling entirely on SCSS variables. This allowed us to generate cleaner JotForm Cards template simply by adding the !default flag on necessary variables.
Default JotForm Cards template (left) and cleaned template (right).
Inference of design choice
Since our primary focus was to dress up a clean template in an easily adjustable way, we decided to take a few styling aspects of website design into consideration:
- Shape of containers in webpage (border, size, etc.)
First step: let’s fetch from CSS
We decided to use PHP to read the CSS content of websites and extract information from it. The reason we did this on the back-end was because our entire system, CSS generation, is already based on PHP.
This meant merging a default form style and user defined style properties into one file by using the SCSS parser. Therefore, the plan was to add another layer — to merge the process — in order to override the default styling properties with information from the website’s CSS.
This implementation consisted of three steps:
- Get the page’s HTML
- Parse style/link tags
- Fetch related properties from curated CSS content
The simplified code can be found in this gist.
In order to pack this CSS content into a mergeable format (as well as filtering out unnecessary style variables), we converted all the content into SCSS with a customized Css2less library.
The next step was to get meaningful information from the SCSS variables. Our first approach was the ‘naive’ one. Basically, we wanted to make sure we were on the right track — without investing too much time (yet).
This approach consisted of:
- Getting an average of numerical properties, such as color, shadow, border, size. etc.
- Using the most used font as our default
Surprisingly, the results weren’t bad.
Although, there were some hilarious outcomes that we weren’t expecting.
Like too much border radius…
And icons instead of a regular font family…
And lack of an action bar:
Based on these examples, we placed boundaries on permissible values for variables, so as not to stray away from our initial design. A few of these tweaks were:
- Placing a maximum limit on border radiuses in order to consider them as usable, while calculating the average radius
- Showing our default shadow if any shadow existed, and removing it if not
- Eliminating icon fonts and promoting certain font families
There was only one problem left: color.
Color is a completely different story compared to the other properties. It’s impossible to know how much of a page will be covered by a colored HTML container or what the colors of images used in web pages will be. We needed to place the HTML code into a browser to find out. It was at this point when we asked ourselves, “Why don’t we just use a headless browser for finding out colors?”
We then decided to do a color extraction of web pages, which entailed taking full page screenshots of web pages and extracting swatches from them to develop our color schemes.
After testing out a couple of headless browsers and test suites for screenshots, we decided to go with Puppeteer because its colors are high quality and it features correct web page heights in its full page screenshots.
We then developed a simple Node application for Puppeteer and color extraction. Implementing color extraction on Node ensures that time isn’t lost while sending screenshots to PHP.
See this gist for screenshot and color extraction sample code when using Puppeteer and get-image-colors.
After passing this information to PHP, we were finally ready to dress our forms using websites’ color schemes.
Automation and Performance
We had one final question: How can we avoid disturbing users while automating these processes?
The process was taking about 5 to 10 seconds depending on the website. Although it wasn’t disturbing our users that much, it would have been overkill to process web pages in every form load.
That’s why we decided to keep extra styling file caches for each web page based on their URLs. Separate styling caches would allow users to embed the same form into multiple pages with different designs on each page. This would also help us avoid damaging standalone style files.
Aside from the caching issue, there was another problem with the automation process: passing the website URL from copy and pasted embed code (which we can’t change from a user’s web page) into PHP.
Initially, we tried to get this URL via referrers in PHP, which failed horribly in a couple of versions of Safari.
Side note: the reason behind this last struggle was that we used server-side rendering for React components and styling tags to avoid burdening users. There’s really no way of knowing for sure whether the request is done by iframe or directly while on the server side.
And We Did It!
In the end, all of these changes were implemented by a small group of five people during our HackWeek.
There are tons of things a team could improve on in an automation like this and they mostly involve using the power of machine learning. For example, instead of fine-tuning the approach like we did, you could train a machine learning model for better detection of important properties related to your product or project automation.
Nevertheless, our team at JotForm was happy with what we managed to achieve by simply using the techniques above.