Building great digital products involves several teams that focus on different things. Teams can range from product design over content strategy to UX design, and at some point, the vision of the new shiny product ideas reaches the development team, which then has the honor to build the dream product everybody has in mind.
Personally, I’ve been involved in the startup industry building awesome products for seven years now, and I truly believe in the fact that well-defined structures play a significant role in successful product development. The first thing coming to my mind is always content structure. It doesn’t matter what the product you’re building is about, a well-defined content structure and a proper content model will most likely help all people being involved understanding what they’re dealing with.
Defined terms will build the base for conversations which contributes to avoid misunderstandings. And not to forget the most important part – structured data comes in a reusable format and may be even available via a public API. That’s a huge win!
Unfortunately, it’s usually only developers that will make use of this structured data. And this got me thinking. What if we could connect more people to this data?
Giving the design team real data
Front-end developers know these situations, which happen once in awhile when collaborating with design teams: content far from realistic built the base for product designs and screens. Not everybody is aware of people that don’t have a beautiful, short name like “Jane Doe”, and this unawareness can lead to this one moment when a design just can’t work with production data. What follows then is another design iteration, which then costs time and money. I’ve been there – I’ve done that!
But what if we could give the design teams a way to deal with real data?
“Sketch-Contentful” – sync your Sketch file with real data
My personal website is built using Contentful, and I’ve got a designer friend who creates screens for me when my nerd design skills are not able to produce something decent-looking. So what has to be done to make it possible for Sketch to deal with data coming from a JSON API?
It should be possible to fetch data for particular layers of a Sketch document from a given space in Contentful. To achieve this, Sketch needs to make HTTP requests against the Content Delivery API. Therefore, storing an API access token and a space ID to identify the data on a per document basis becomes necessary.
This can’t be too hard, can it?
Getting used to the Sketch development workflow
As this was my first Sketch plugin, the first thing I did was to head over to the Sketch developers page and started reading the documentation. Sketch plugins are written in CocoaScript, which claims to be a variation of JavaScript. I write JavaScript for a living, so this sounded perfect for me.
I quickly discovered the first hurdle, which is the setup of a development environment. Sketch provides a custom script editor which works fine for simple scripts and plugins, but it is surely not the environment I prefer to write complex code in.
To install a Sketch plugin, you can either double-click a .sketchplugin
file (which is actually a folder), or you place a new folder inside of Sketch’s plugin directory that is deeply buried in the ~/Library/Application Support
folder. The Library
folder is a place I don’t like to work in, so the first thing I did was to set a symbolic link to a directory where I usually store all my code related projects.
1 2 3 | # create SymLink to be able # in different directory then ~/Library/Application Support/... ln -s ~/Projects/sketch-contentful/Contentful.sketchplugin ~/Library/Application\ Support/com.bohemiancoding.sketch3/Plugins/Contentful.sketchplugin |
A Sketch plugin has to follow a particular folder structure so that Sketch knows how to deal with it.
1 2 3 4 5 6 | Contentful.sketchplugin Contents/ Sketch/ manifest.json Resources/ icon.png |
The most important file in a Sketch plugin is the manifest.json
file. It is the general configuration file for the plugin. It defines the version, author and which commands should be available. After symlinking the project and providing a valid manifest.json
my new plugin appeared already in the plugins dialog of Sketch. First mission accomplished!
There was only one problem at this stage. Sketch didn’t seem to take file changes into account immediately. To test my changes I always had to restart Sketch to run the latest code. Later I found out that this can be solved by enabling a “refresh” mode. The explanation how to do this is, unfortunately, a bit hidden in the Sketch documentation under Scripting Preferences.
1 2 | # make Sketch constantly reload the plugin files
defaults write ~/Library/Preferences/com.bohemiancoding.sketch3.plist AlwaysReloadScript -bool YES |
After setting the symbolic link and enabling the refresh mode, I was ready to get my hands dirty on writing some code.
To debug the scripts I used the log
function provided by Sketch, which writes to my system logs. I found myself using a mixture of a tail
command running in my terminal and a the Console.app app provided by OSX.
1 2 | # monitor logs
tail -f /var/log/system.log | grep Sketch |
Adding commands to the Sketch plugin menu
For the needs of my Contentful plugin, I decided to implement two different commands, the configuration of which is pretty straightforward. I simply added new settings objects to the commands
property in the manifest.json
file, and I was ready to go.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | { // ... // ... "commands" : [ { "name": "Open settings", "identifier": "com.contentful.settings-contentful", "script": "lib/settings.js", "handler": "openSettings", "isRoot" : false }, { "name": "Fetch data", "identifier": "com.contentful.fetch-contentful", "script": "lib/fetch.js", "handler": "populateData", "isRoot" : false } ], // ... // ... } |
Each command has to define a file and a particular function that should be executed whenever the user triggers the command. For example, to display the settings dialog, the function openSettings
in the file lib/settings.js
will be executed. The falsy isRoot
property leads to the commands being displayed inside of the plugin menu and not at the plugin menu root level, which is fine for me as they’re grouped together under the Contentful label.
The defined command handlers will be executed with a current context object. This object holds references to other useful objects like the current document or the current command, which will come in handy for later usage.
1 2 3 | function openSettings(context) { // ... } |
Implementation of a settings dialog
Showing a settings window including two input fields to give the possibility to store data was way harder than I expected. Displaying a typical OSX dialog from a Sketch plugin means entering the “Cocoa world”, and as my Cocoa experience is limited, this became tricky. I didn’t find any examples in the Sketch docs on how to achieve something like this and digged the AppKit documentation and scanned several other Sketch plugins on GitHub.
I ended up copying and modifying the code of a plugin called Sketch-Constraints and made my way through it with the good old trial and error approach. In case you’re interested in the implementation just have a look at the createSettingsFunction
of the Contentful Sketch plugin.
Thanks to the power of open source I succeeded! ❤️
The next step was to store the configuration data I received from the settings dialog. To achieve this there are the functions setValue_forKey_onDocument_forPluginIdentifier
and valueForKey_onDocument_forPluginIdentifier
defined in the context.command
object. I found these on sketchplugin.com and am still looking for some actual documentation around these. Sketchplugin.com seems to be very active, so I think it’s a good place to ask for help.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // set the data for the current document context.command.setValue_forKey_onDocument_forPluginIdentifier( inputs[0].stringValue(), 'spaceId', docData, 'contentful' ); // get the data for the current document var spaceId = context.command.valueForKey_onDocument_forPluginIdentifier( 'spaceId', docData, 'contentful' ); |
Analyzing all included layers
So at this point I was able to store settings – the next step was to analyze the document and to find out which layers should be connected with data that is stored in Contentful. I decided to follow a layer name convention to connect the Sketch document with the data in Contentful.
1 2 3 | Author ${entry:fjsdaklfsjf8239qr} // a Group |__ Title ${fields:title} // a TextLayer |__ Description ${fields:description} // a TextLayer |
An SVG group should represent a content entry and define the entry id. Inside of this group text layers would be able to connect to a given data property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var fieldLayers = page.children().forEach(function(child) { var connectedEntry = child.name().match(/{entry:(.+?)}/); // is this layer referring to a Contentful entry if (connectedEntry) { // fetch the data for that entry var data = fetchJSON( 'https://cdn.contentful.com/spaces/' + spaceId + '/entries?access_token=' + cdaToken + '&sys.id=' + connectedEntry[ 1 ] ).items[0]; child.children().forEach(function(layer) { var connectedField = layer.name().match(/{fields:(.+?)}/); // is this layer referring to a field of a Contentful entry if (connectedField) { // apply data to the specific layer } }); } }); |
I didn’t hit any problems analyzing the document, and the implementation of this went relatively well.
Making a request from within Sketch
The missing part was then to make an actual HTTP request, and I have to admit that I struggled massively with this. Again I just ended up copying a snippet from an existing plugin. There doesn’t seem to be a way to make a request with plain JavaScript in Sketch plugins (if you know how, please tell me!). The code I ended up using was a mixture of Objective-C and JavaScript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * Fetch JSON from a given URL */ function fetchJSON(url) { var request = NSMutableURLRequest.new(); [request setHTTPMethod:@"GET"]; [request setURL:[NSURL URLWithString:url]]; var error = NSError.new(); var responseCode = null; var oResponseData = [NSURLConnection sendSynchronousRequest:request returningResponse:responseCode error:error]; var dataString = [[NSString alloc] initWithData:oResponseData encoding:NSUTF8StringEncoding]; return JSON.parse(dataString); } |
Writing a Sketch plugin – way harder than expected
I have to say that writing a simple Sketch plugin that fetches some JSON data took me way longer than I thought. The current plugin implementation works fine for the simple use case of a string replacement. Dealing with relations and images is an entirely different story.
The hardest part was this mixed Cocoascript environment, because it was hard for me to grasp. The Sketch documentation only links to a GitHub repository of Cocoascript, where it states that it’s “JavaScript + the Cocoa frameworks, and then ObjC brackets show up to party as well.” This statement sounds neither good nor bad, but I want to point out that Cocoascript unifies three different technologies. I’m fine with the JavaScript part but have limited knowledge about Cocoa or Objective-C. This was a massive slow-down for my productivity.
Developer experience and documentation matters
While writing this plugin I discovered (once more) that developer experience is really important when offering a software product for developers. Surely plugins are not Sketch’s primary business, but they’re part of its ecosystem. Good documentation, getting-started guides and tons of useful examples matter a lot.
And I don’t want to blame Sketch here – I only want to point out that writing good docs is hard, and keeping them up to date is even harder. Providing a good developer experience is not something that one can do on the side.
I prepared a short video where I actually show the plugin in action. As you can see the configuration is pretty straightforward, and overall it's easy to use.
It’s tough, but there’s a lot of potential
Nevertheless, I think the idea of bringing real data into the design space has a lot of value. For me, this plugin was just a proof of concept, but I’ll try to keep track of it and maybe push it a bit further. What I’d like to see are people that have ideas and wishes on how to connect content management with Sketch. And if you’re a developer and want to help to improve this idea I’d really happy to collaborate on that, so open an issue or submit a PR to the project on Github!