Objective
As designers, 'forging' data is just part of the job, whether its lorem-ipsem (please stop doing that!) or somewhat realistic data coming from the existing application. But wouldn't it be better and more realistic to use production or development level APIs usually available to the software team directly within the designs?
Content informs design decisions – and helps you convey your purpose
Data are relentless – so digital products must be designed for robustness
It's fun 🎉 – seeing your design evolve with meaningful data is motivating and rewarding
Existing Solutions
- Pre-filled JSON files - weak alternative since it provides persistency however still forged in a way.
- Sketch-Data-Faker - DataProvider that provides a large amount of fake data from Faker.js, same problem ... its forged / fake.
- Data-Populator - Probably the most updated and best existing solution using 'handlebars' and quite comprehensive, however personally i've found it not very persistent for my use-case ... also woulda been nice to have this support native sketch DataProvider
Architecture
Dependencies
1. Sketch
2. NPM / NodeJS
3. SKPM (Sketch Plug-in Manager)
4. CocoaScript (JS -> Objective-C Wrapper)
Sketch's DataProvider Handlers
So each type of Data Model has its set of handlers or functions which:
1. Request data from an api endpoint
2. Manipulate the data-set if required
3. Fill it in the appropriate text layer or text override
For this example, let's assume we have this model:
Application
And it has several properties:
Name
Response Time
Error Rate
Note that this is specific the 'Application Performance Monitoring' industry
Let's first create the boilerplate with the help of skpm:
skpm create my-data-provider-plugin
Update the manifest.json
to make sure its a dataprovider plugin (Full manifest shown for reference)
{
"version": "1.0.0",
"compatibleVersion": 52,
"bundleVersion": 1,
"icon": "icon.png",
"author": "ShyGuy",
"description": "Data provider plugin to bring consistency to designs ;)",
"suppliesData": true,
"commands": [
{
"script": "index.js",
"handlers": {
"actions": {
"Startup": "onStartup",
"Shutdown": "onShutdown",
"SupplyApplications": "onSupplyApplications"
}
}
}
]
}
Now for each 'command' we need a equal handler in index.js
as follows:
export function onStartup () {
dataMapping.forEach(d => {
DataSupplier.registerDataSupplier('public.text', d.displayName, d.handler)
})
}
export function onShutdown () {
// Deregister the plugin
DataSupplier.deregisterDataSuppliers()
}
export function onSupplyApplications (context) {
let dataKey = context.data.key;
let wrappedItems = util.toArray(context.data.items).map(sketch.fromNative);
UI.message('Fetching application names from API');
setValuesToLoading(wrappedItems);
getApplications()
.then(apps => {
const minApps = getMinArray(apps).map(app => app.name);
wrappedItems.forEach((wi, index) => {
DataSupplier.supplyDataAtIndex(dataKey, getTruncatedText(wi, minApps[index]), index)
})
})
.then(() => { UI.message('Success!') })
.catch(err => { UI.message(err) })
}
Let's also take a look at dataMapping
which is essentially telling Sketch to register these functions as data suppliers:
export const dataMapping = [
// use '_' for nested data menu in sketch
{
displayName: 'application_application-name',
handler: 'SupplyApplications'
}
];
And finally, the getApplications()
api call function:
const { getApiUrl } = require('../api.js');
export async function getApplications () {
const api = getApiUrl('applications', null);
return await fetch(api.url, {
headers: api.headers
}).then(response => {
return response.json()._value.slice(0,500);
}).catch(error => {
console.log(error)
});
}
What does this look like in sketch?
And then this type of behavior can be used for almost any endpoint and data-set!
For overrides or directly on any text layer
Truncation and Number Formatter
Will cover soon!