{"id":67909,"date":"2019-07-09T04:19:01","date_gmt":"2019-07-09T11:19:01","guid":{"rendered":"http:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/"},"modified":"2019-07-09T04:19:01","modified_gmt":"2019-07-09T11:19:01","slug":"how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2","status":"publish","type":"post","link":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/","title":{"rendered":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)"},"content":{"rendered":"<link rel=\"canonical\" href=\"https:\/\/www.smashingmagazine.com\/2019\/07\/build-sketch-plugin-javascript-html-css-part-2\/\"><title>How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)<\/title><\/p>\n<article>\n<header>\n<h1>How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)<\/h1>\n<address>Matt Curtis<\/address>\n<p>                  <time datetime=\"2019-07-08T12:30:59+02:00\">2019-07-08T12:30:59+02:00<\/time><time datetime=\"2019-07-09T11:06:16+00:00\">2019-07-09T11:06:16+00:00<\/time><\/header>\n<p>As mentioned in <a href=\"https:\/\/www.smashingmagazine.com\/2019\/07\/build-sketch-plugin-javascript-html-css-part-1\/\">part 1<\/a>, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well. To profit from it the most, you will need to have at least some basic experience writing JavaScript (and, optionally, HTML\/CSS).<\/p>\n<p>In the previous part of this tutorial, we learned about the basic files that make up a plugin, and how to create the plugin\u2019s user interface. In this second and final part, we\u2019ll learn how to connect the user interface to the core plugin code and how to implement the plugin\u2019s main features. Last but not least, we\u2019ll also learn how to optimize the code and the way the plugin works.<\/p>\n<h3>Building The Plugin\u2019s User Interface: Making Our Web Interface And The Sketch Plugin Code \u201cTalk\u201d To Each Other<\/h3>\n<p>The next thing we need to do is to set up communication between our web interface and the Sketch plugin.<\/p>\n<p>We need to be able to send a message from our web interface to the Sketch plugin when the \u201cApply\u201d button in our web interface is clicked. This message needs to tell us about what settings the user has input \u2014 like the number of steps, rotation amount, the number of duplicates to create, and so on.<\/p>\n<p><code>WKWebView<\/code> makes this task a little bit easier for us: we can send messages to our Sketch plugin from our web interface\u2019s JavaScript code by using the <code>window.webkit.messageHandlers<\/code> API.<\/p>\n<p>On our Sketch code\u2019s side, we can use another method, <code>addScriptMessageHandler:name:<\/code> (or <code>addScriptMessageHandler_name<\/code>) to register a message handler that will be called whenever it receives a message sent from our plugin web interface.<\/p>\n<p>Let\u2019s start by making sure we can receive messages from our web UI. Head over to our <code>ui.js<\/code> file\u2019s <code>createWebView<\/code> function, and add the following:<\/p>\n<div>\n<pre><code>function createWebView(pageURL){\n        const webView = WKWebView.alloc().init();\n\n        \/\/        Set handler for messages from script\n\n        const userContentController = webView.configuration().userContentController();\n        \n        const ourMessageHandler = ...\n\n        userContentController.addScriptMessageHandler_name(\n                ourMessageHandler, \"sketchPlugin\"\n        );\n\n        \/\/        Load page into web view\n\n        webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());\n\n        return webView;\n};\n<\/code><\/pre>\n<\/div>\n<p>Here we use the web view\u2019s <a href=\"https:\/\/developer.apple.com\/documentation\/webkit\/wkusercontentcontroller?language=objc\"><code>userContentController<\/code><\/a> property to add a message handler we\u2019ve named \u201csketchPlugin\u201d.  This \u201cuser content controller\u201d is the bridge that ensures messages get across from our web view.<\/p>\n<div data-component=\"FeaturePanel\" data-audience=\"non-subscriber\" data-remove=\"true\"><\/div>\n<p>You might have noticed something odd about the above code: the object we\u2019re adding as the message handler, <code>ourMessageHandler<\/code>, doesn\u2019t exist yet! Unfortunately, we can\u2019t just use a regular JavaScript object or function as the handler, as this method expects a certain kind of native object.<\/p>\n<p>Luckily for us, we can get around this limitation by using <a href=\"https:\/\/github.com\/matt-curtis\/MochaJSDelegate\"><code>MochaJSDelegate<\/code><\/a>, a mini-library I wrote that makes it possible to create the kind of native object we need using regular ol\u2019 JavaScript. You\u2019ll need to manually <a href=\"https:\/\/github.com\/matt-curtis\/MochaJSDelegate\/blob\/master\/MochaJSDelegate.require.js\">download<\/a> and save it in your plugin bundle under <code>Sketch\/MochaJSDelegate.js<\/code>.<\/p>\n<p>In order to use it, we\u2019ll need to first import it into <code>ui.js<\/code>. Add the following at the top of the file:<\/p>\n<pre><code>const MochaJSDelegate = require(\".\/MochaJSDelegate\");\n<\/code><\/pre>\n<p>Now we can use <code>MochaJSDelegate<\/code> to create the type of message handler <code>addScriptMessageHandler:name:<\/code> is expecting:<\/p>\n<div>\n<pre><code>function createWebView(pageURL){\n        const webView = WKWebView.alloc().init();\n\n        \/\/        Set handler for messages from script\n\n        const userContentController = webView.configuration().userContentController();\n\n        const scriptMessageHandler = new MochaJSDelegate({\n                \"userContentController:didReceiveScriptMessage:\": (_, wkMessage) => {\n                        \/* handle message here *\/\n                }\n        }).getClassInstance();\n\n        userContentController.addScriptMessageHandler_name(\n                scriptMessageHandler, \"sketchPlugin\"\n        );\n\n        \/\/        Load page into web view\n\n        webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());\n\n        return webView;\n};\n<\/code><\/pre>\n<\/div>\n<p>The code we just added creates the native object we need. It also defines a method on that object named  <code>userContentController:didReceiveScriptMessage:<\/code> \u2014 this method is then called with the message we want as the second argument. Since we\u2019re not actually sending any messages yet, we\u2019ll have to come back here at a later time and add some code to actually parse and handle the messages we receive.<\/p>\n<p>Next, we need to add some code to our web interface to send us those messages. Head over to <code>\/Resources\/web-ui\/script.js<\/code>. You\u2019ll find I\u2019ve already written most of the code that handles retrieving the values of the HTML <code><inputs \/><\/code> the user will enter their options into.<\/p>\n<p>What\u2019s still left for us to do is to add the code that actually sends the values over to our Sketch code:<\/p>\n<p>Find the <code>apply<\/code> function and add the following to the end of it:<\/p>\n<div>\n<pre><code>\/\/        Send user inputs to sketch plugin\n\nwindow.webkit.messageHandlers.sketchPlugin.postMessage(JSON.stringify({\n        stepCount, startingOptions, stepOptions\n}));\n<\/code><\/pre>\n<\/div>\n<p>Here we use <code>window.webkit.messageHandlers<\/code> API we mentioned earlier to access the message handler we registered above as <code>sketchPlugin<\/code>. Then send a message to it with a JSON string containing the user\u2019s inputs.<\/p>\n<p>Let\u2019s make sure everything is set up properly. Head back to <code>\/Sketch\/ui.js<\/code>. In order to make sure we\u2019re getting messages as we expect, we\u2019ll modify the method we defined earlier so that it displays a dialog when we get a message:<\/p>\n<div>\n<pre><code>function createWebView(pageURL){\n        \/\/ ...\n\n        const scriptMessageHandler = new MochaJSDelegate({\n                \"userContentController:didReceiveScriptMessage:\": (_, wkMessage) => {\n                        const UI = require(\"sketch\/ui\");\n                        \n                        UI.alert(\"Hey, a message!\", wkMessage.body());\n                }\n        }).getClassInstance();\n\n        userContentController.addScriptMessageHandler_name(\n                scriptMessageHandler, \"sketchPlugin\"\n        );\n\n        \/\/ ...\n};\n<\/code><\/pre>\n<\/div>\n<p>Now run the plugin (you may need to first close any existing Mosaic window you have opened), enter some values, then click \u201cApply\u201d. You should see an alert like the one below \u2014 this means everything is wired up correctly and our message went through successfully! If not, go back over the previous steps and ensure that everything was done as described.<\/p>\n<figure><a href=\"https:\/\/i0.wp.com\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png?ssl=1\"><\/p>\n<p>    <img data-recalc-dims=\"1\" decoding=\"async\" srcset=\"http:\/\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/web-ui-config-message.png 400w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_800\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png 800w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1200\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png 1200w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_1600\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png 1600w,\n\t\t\t        https:\/\/res.cloudinary.com\/indysigner\/image\/fetch\/f_auto,q_auto\/w_2000\/https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png 2000w\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/web-ui-config-message.png?w=900\" sizes=\"100vw\" alt=\"Image showing the dialog you should see after clicking the \u2018apply\u2019 button in the plugin\u2019s UI.\"><\/a><figcaption>\n      The dialog you should see appear once you click Apply. (<a href=\"https:\/\/cloud.netlifyusercontent.com\/assets\/344dbf88-fdf9-42bb-adb4-46f01eedd629\/7ebcc999-8917-48f3-91e3-1e333b8ad8d9\/web-ui-config-message.png\">Large preview<\/a>)<br \/>\n    <\/figcaption><\/figure>\n<p>Now that we\u2019re able to send messages from our interface to our plugin, we can move on to writing the code that actually does something useful with that information: generating our layer mosaics.<\/p>\n<div><\/div>\n<h3>Generating The Layer Mosaics<\/h3>\n<p>Let\u2019s take stock of what\u2019s necessary in order to make this happen. Simplifying things a bit, what our code needs to do is:<\/p>\n<ol>\n<li>Find the current document.<\/li>\n<li>Find the current document\u2019s selected layer.<\/li>\n<li>Duplicate the selected layer (we\u2019ll call it the <em>template<\/em> layer) x number of times.<\/li>\n<li>For each duplicate, tweak its position, rotation, opacity, etc., by the specific values (amounts) set by the user.<\/li>\n<\/ol>\n<p>Now that we\u2019ve got a reasonable plan, let\u2019s continue writing. Sticking with our pattern of modularizing our code, let\u2019s create a new file, <code>mosaic.js<\/code> in the <code>Sketch\/<\/code> folder, and add to it the following code:<\/p>\n<pre><code>function mosaic(options){\n\n};\n\nmodule.export = mosaic;\n<\/code><\/pre>\n<p>We\u2019ll use this function as the only export of this module since it makes for a simpler API to use once we import it \u2014 we can just call <code>mosaic()<\/code> with whatever options we get from the web interface.<\/p>\n<p>The first two steps we need to take are getting the current document, and then its selected layer. The <a href=\"http:\/\/link\/\">Sketch API<\/a> has a built-in library for document manipulation which we can get access to by importing the <code>sketch\/dom<\/code> module. We only need the <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#document\"><code>Document<\/code><\/a> object right now, so we\u2019ll pull it out explicitly. At the top of the file, add:<\/p>\n<pre><code>const { Document } = require(\"sketch\/dom\");\n<\/code><\/pre>\n<p>The <code>Document<\/code> object has a method specifically for accessing the current document we can use, called <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#access-the-selected-document\"><code>getSelectedDocument()<\/code><\/a>. Once we have the current document instance, we can access whatever layers the user has selected via the document\u2019s <a href=\"https:\/\/developer.sketch.com\/reference\/api\/#selection\"><code>selectedLayers<\/code><\/a> property. In our case, though, we only care about single-layer selections, so we\u2019ll only grab the first layer the user has selected:<\/p>\n<div>\n<pre><code>function mosaic(options){\n        const document = Document.getSelectedDocument();\n        const selectedLayer = document.selectedLayers.layers[0];\n};\n\nmodule.export = mosaic;\n<\/code><\/pre>\n<\/div>\n<p><strong>Note:<\/strong> <em>You might have been expecting <code>selectedLayers<\/code> itself to be an array, but it\u2019s not. Instead, it\u2019s an instance of the <a href=\"http:\/\/selectedlayers\/\"><code>Selection<\/code><\/a> class. There\u2019s a reason for this: the <code>Selection<\/code> class contains a bunch of useful helper methods for manipulating the selection like <a href=\"https:\/\/developer.sketch.com\/reference\/api\/#selection\">clear, map, reduce, and forEach<\/a>. It exposes the actual layer array via the <code>layer<\/code> property.<\/em><\/p>\n<p>Let\u2019s also add some warning feedback in case the user forgets to open a document or to select something:<\/p>\n<div>\n<pre><code>const UI = require(\"sketch\/ui\");\n\nfunction mosaic(options){\n        const document = Document.getSelectedDocument();\n\n        \/\/        Safety check:\n\n        if(!document){\n                UI.alert(\"Mosaic\", \"?? Please select\/focus a document.\");\n\n                return;\n        }\n\n        \/\/        Safety check:\n\n        const selectedLayer = document.selectedLayers.layers[0];\n\n        if(!selectedLayer){\n                UI.alert(\"Mosaic\", \"?? Please select a layer to duplicate.\");\n                \n                return;\n        }\n};\n\nmodule.export = mosaic;\n<\/code><\/pre>\n<\/div>\n<p>Now that we\u2019ve written the code for steps 1 and 2 (finding the current document and selected layer), we need to address steps 3 and 4:<\/p>\n<ul>\n<li>Duplicate the template layer x number of times.<\/li>\n<li>For each duplicate, tweak its position, rotation, opacity, etc., by the specific values set by the user.<\/li>\n<\/ul>\n<p>Let\u2019s start by pulling all the relevant information we need out of <code>options<\/code>: the number of times to duplicate, starting options, and step options. We can once again use <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Destructuring_assignment\">destructuring<\/a> (like we did earlier with <code>Document<\/code>) to pull those properties out of <code>options<\/code>:<\/p>\n<div>\n<pre><code>function mosaic(options) {\n        \/\/        ...\n        \n        \/\/        Destructure options:\n\n        var { stepCount, startingOptions, stepOptions } = options;\n}\n<\/code><\/pre>\n<\/div>\n<p>Next, let\u2019s sanitize our inputs and ensure that step count is always at least 1:<\/p>\n<div>\n<pre><code>function mosaic(options) {\n        \/\/        ...\n        \n        \/\/        Destructure options:\n        \n        var { stepCount, startingOptions, stepOptions } = options;\n        \n        stepCount = Math.max(1, stepCount);\n}\n<\/code><\/pre>\n<\/div>\n<p>Now we need to make sure that the template layer\u2019s opacity, rotation, etc., all match up with the user\u2019s desired starting values. Since applying the user\u2019s options to a layer is going to be something we\u2019ll do a lot of, we\u2019ll move this work into its own method:<\/p>\n<div>\n<pre><code>function configureLayer(layer, options, shouldAdjustSpacing){\n        const { opacity, rotation, direction, spacing } = options;\n\n        layer.style.opacity = opacity \/ 100;\n        layer.transform.rotation = rotation;\n\n        if(shouldAdjustSpacing){\n                const directionAsRadians = direction * (Math.PI \/ 180);\n                const vector = {\n                        x: Math.cos(directionAsRadians),\n                        y: Math.sin(directionAsRadians)\n                };\n\n                layer.frame.x += vector.x * spacing;\n                layer.frame.y += vector.y * spacing;\n        }\n};\n<\/code><\/pre>\n<\/div>\n<p>And because spacing only needs to be applied in-between the duplicates and not the template layer, we\u2019ve added a specific flag, <code>shouldAdjustSpacing<\/code>, that we can set to <code>true<\/code> or <code>false<\/code> depending on whether we\u2019re applying options to a template layer or not. That way we can ensure that rotation and opacity will be applied to the template, but not spacing.<\/p>\n<p>Back in the <code>mosaic<\/code> method, let\u2019s now ensure that the starting options are applied to the template layer:<\/p>\n<pre><code>function mosaic(options){\n        \/\/        ...\n        \n        \/\/        Configure template layer\n\n        var layer = group.layers[0];\n        \n        configureLayer(layer, startingOptions, false);\n}\n<\/code><\/pre>\n<p>Next, we need to create our duplicates. First, let\u2019s create a variable that we can use to track what the options for the current duplicate are:<\/p>\n<pre><code>function mosaic(options){\n        \/\/        ...\n\n        var currentOptions; \/\/ ...\n}\n<\/code><\/pre>\n<p>Since we already applied the starting options to the template layer, we need to take those options we just applied and add the relative values of <code>stepOptions<\/code> in order to get the options to apply to the next layer. Since we\u2019ll also be doing this several more times in our loop, we\u2019ll also move this work into a specific method, <code>stepOptionsBy<\/code>:<\/p>\n<pre><code>function stepOptionsBy(start, step){\n        const newOptions = {};\n        \n        for(let key in start){\n                newOptions[key] = start[key] + step[key];\n        }\n\n        return newOptions;\n};\n<\/code><\/pre>\n<p>After that, we need to write a loop that duplicates the previous layer, applies the current options to it, then offsets (or \u201csteps\u201d) the current options in order to get the options for the next duplicate:<\/p>\n<div>\n<pre><code>function mosaic(options) {\n        \/\/        ...\n        \n        var currentOptions = stepOptionsBy(startingOptions, stepOptions);\n\n        for(let i = 0; i <\/code><\/pre>\n<\/div>\n<p>All done \u2014 we\u2019ve successfully written the core of what our plugin is supposed to do! Now, we need to wire things up so that when the user actually clicks the \u201cApply\u201d button our mosaic code is invoked.<\/p>\n<p>Let\u2019s head back to <code>ui.js<\/code> and adjust our message handling code. What we\u2019ll need to do is parse the JSON string of options we\u2019re getting so that they\u2019re turned into an object we can actually use. Once we have these options, we can then call the <code>mosaic<\/code> function with them.<\/p>\n<p>First, parsing. We\u2019ll need to update our message handling function to parse the JSON message we get:<\/p>\n<div>\n<pre><code>function createWebView(pageURL){\n        \/\/        ...\n        \n        const scriptMessageHandler = new MochaJSDelegate({\n                \"userContentController:didReceiveScriptMessage:\": (_, wkMessage) => {\n                        const message = JSON.parse(wkMessage.body());\n                }\n        });\n}\n<\/code><\/pre>\n<\/div>\n<p>Next, we\u2019ll need to pass this over to our <code>mosaic<\/code> function. However, this isn\u2019t really something our code in <code>ui.js<\/code> should be doing \u2014 it\u2019s supposed to be primarily concerned with what\u2019s necessary to display interface-related things on screen \u2014  not creating mosaics itself. To keep these responsibilities separate, we\u2019ll add a second argument to <code>createWebView<\/code> that takes a function, and we\u2019ll call that function whenever we receive options from the web interface.<\/p>\n<p>Let\u2019s name this argument <code>onApplyMessage<\/code>:<\/p>\n<div>\n<pre><code>function createWebView(pageURL, onApplyMessage){\n        \/\/ ...\n        \n        const scriptMessageHandler = new MochaJSDelegate({\n                \"userContentController:didReceiveScriptMessage:\": (_, wkMessage) => {\n                        const message = JSON.parse(wkMessage.body());\n                        \n                        onApplyMessage(message);\n                }\n        });\n}\n<\/code><\/pre>\n<\/div>\n<p>We\u2019ll also need to modify our exported method, <code>loadAndShow<\/code>, to take this <code>onApplyMessage<\/code> argument as well and pass it off to <code>createWebView<\/code>:<\/p>\n<pre><code>function loadAndShow(baseURL, onApplyMessage){\n        \/\/        ...\n        \n        const webView = createWebView(pageURL, onApplyMessage);\n}\n<\/code><\/pre>\n<p>Finally, head over to <code>main.js<\/code>. We now need to import our <code>mosaic<\/code> function, and call it with the options we receive from the plugin\u2019s user interface:<\/p>\n<pre><code>const mosaic = require(\".\/mosaic\");\n\nfunction onRun(context){\n        UI.loadAndShow(context.scriptURL, options => {\n                mosaic(options);\n        });\n};\n<\/code><\/pre>\n<p>We\u2019re almost done!<\/p>\n<p>However, if we ran our code now and clicked the \u201cApply\u201d button in the plugin interface, nothing would happen. Why? The reason is due to how Sketch scripts are run: by default, they \u201clive\u201d only until the bottom of your script is reached, after which Sketch destroys it and frees up whatever resources it was using.<\/p>\n<p>This is a problem for us since it means that anything we need to have asynchronously happen (in this case, that\u2019s after the bottom of our code is reached), like receiving messages, cannot, because our script has been destroyed. This means we wouldn\u2019t get any of our messages from the web interface since we\u2019re not around to receive and respond to them!<\/p>\n<div><\/div>\n<p>There\u2019s a way to signal to Sketch that we need our script to stay alive beyond this point, using <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#async\"><code>Fibers<\/code><\/a>. By creating a Fiber, we tell Sketch that something asynchronous is happening and that it needs to keep our script around. Sketch will then only destroy our script when absolutely necessary (like the user closing Sketch, or when the Mosaic plugin needs to be updated):<\/p>\n<pre><code>\/\/        ...\n\nconst Async = require(\"sketch\/async\");\n\nvar fiber;\n\nfunction onRun(context){\n    if(!fiber){\n        fiber = Async.createFiber();\n        fiber.onCleanup(() => {\n            UI.cleanup();\n        });\n    }\n    \n    UI.loadAndShow(context.scriptURL, options => {\n        mosaic(options);\n    });\n};\n<\/code><\/pre>\n<p>Voil\u00e0! Let\u2019s try out our plugin now. With a layer selected in Sketch, enter some settings, then click apply:<\/p>\n<figure><figcaption>Let\u2019s try out our plugin now \u2014 with a layer selected in Sketch, enter some settings, then click \u201cApply.\u201d<\/figcaption><\/figure>\n<h3>Final Improvements<\/h3>\n<p>Now that we\u2019ve got the majority of our plugin\u2019s functionality implemented, we can try to \u201czoom out\u201d a bit and take a look at the big picture.<\/p>\n<h4>Improving The User\u2019s Experience<\/h4>\n<p>If you\u2019ve played around with the plugin in its current state, you might\u2019ve noticed that one of the biggest points of friction appears when you try to edit a Mosaic. Once you create one, you have to hit undo, adjust the options, then click \u2018Apply\u2019 (or press Enter). It also makes it harder to edit a Mosaic after you\u2019ve left your document and returned to it later, since your undo\/redo history will have been wiped out, leaving you to manually delete the duplicate layers yourself.<\/p>\n<p>In a more ideal flow, the user could just select a Mosaic group, adjust options and watch the Mosaic update until they get the exact arrangement they\u2019re looking for. To implement this, we have two problems to solve:<\/p>\n<ol>\n<li>First, we\u2019ll need a way to group the duplicates that make up a Mosaic together. Sketch provides the concept of Groups, which we can use to solve this problem.<\/li>\n<li>Second, we\u2019ll need a way to tell the difference between a normal, user-created group and a Mosaic group. Sketch\u2019s API also gives us a way to store information on any given layer, which we can use as a way tag and later identify a group as one of our \u2018special\u2019 Mosaic groups.<\/li>\n<\/ol>\n<p>Let\u2019s revisit the logic we wrote in the previous section to address this. Our original code follows the following steps:<\/p>\n<ol>\n<li>Find the current document.<\/li>\n<li>Find the current document\u2019s selected layer.<\/li>\n<li>Duplicate the selected layer (we\u2019ll call it the <em>template<\/em> layer) x number of times.<\/li>\n<li>For each duplicate, tweak its position, rotation, opacity, etc., by the specific values (amounts) set by the user.<\/li>\n<\/ol>\n<p>In order to make our new user flow possible, we need to change these steps to:<\/p>\n<ol>\n<li>Grab the current document.<\/li>\n<li>Grab the current document\u2019s selected layer.<\/li>\n<li>Determine whether the selected layer is a Mosaic group or not.\n<ul>\n<li>If it\u2019s some other layer, use it as the template layer and go to step 4.<\/li>\n<li>If it <em>is<\/em> a Mosaic group, consider the first layer in it as the template layer, and go to step 5.<\/li>\n<\/ul>\n<\/li>\n<li>Wrap the template layer inside a group, and mark that group as a Mosaic group.<\/li>\n<li>Remove all layers from inside the group except the template layer.<\/li>\n<li>Duplicate the template layer x number of times.<\/li>\n<li>For each duplicate, tweak its position, rotation, opacity, etc., by the specific values set by the user.<\/li>\n<\/ol>\n<p>We\u2019ve got three new steps. For the first new step, step 3, we\u2019ll create a function named <code>findOrMakeSpecialGroupIfNeeded<\/code> that will look at the layer passed to it to determine whether or not it\u2019s a Mosaic group. If it is, we\u2019ll just return it. Since the user could potentially select a sublayer nested deep in a Mosaic group, we\u2019ll also need to check the parents of the selected layer to tell if they\u2019re one of our Mosaic groups as well:<\/p>\n<div>\n<pre><code>function findOrMakeSpecialGroupIfNeeded(layer){\n        \/\/        Loop up through the parent hierarchy, looking for a special group\n\n        var layerToCheck = layer;\n\n        while(layerToCheck){\n                if(\/* TODO: is mosaic layer? *\/){\n                        return layerToCheck;\n                }\n\n                layerToCheck = layerToCheck.parent;\n        }\n};\n<\/code><\/pre>\n<\/div>\n<p>If we weren\u2019t able to find a Mosaic group we\u2019ll simply wrap the layer we were passed inside a <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#create-a-new-group\"><code>Group<\/code><\/a>, then tag it as a Mosaic group.<\/p>\n<p>Back at the top of the file, we\u2019ll need to pull out the Group class now too:<\/p>\n<pre><code>const { Document, Group } = require(\"sketch\/dom\");\n<\/code><\/pre>\n<div>\n<pre><code>function findOrMakeSpecialGroupIfNeeded(layer){\n        \/\/        Loop up through the parent hierarchy, looking for a special group\n\n        var layerToCheck = layer;\n\n        while(layerToCheck){\n                if(\/* TODO: is mosaic layer? *\/){\n                        return layerToCheck;\n                }\n\n                layerToCheck = layerToCheck.parent;\n        }\n        \n        \/\/        Group\n\n        const destinationParent = layer.parent;\n        const group = new Group({\n                name: \"Mosaic Group\",\n                layers: [ layer ],\n                parent: destinationParent\n        });\n        \n        \/* TODO: mark group as mosaic layer *\/\n        \n        return group;\n};\n<\/code><\/pre>\n<\/div>\n<p>Now we need to fill in the gaps (todo\u2019s). To begin with, we need a means of identifying whether or not a group is one of the special groups belonging to us or not. Here, the <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#settings\"><code>Settings<\/code><\/a> module of the Sketch library comes to our rescue. We can use it to store custom information on a particular layer, and also to read it back.<\/p>\n<p>Once we import the module at the top of the file:<\/p>\n<pre><code>const Settings = require(\"sketch\/settings\");\n<\/code><\/pre>\n<p>We can then use two key methods it provides, <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#set-a-layer-setting\"><code>setLayerSettingForKey<\/code><\/a> and <a href=\"https:\/\/developer.sketchapp.com\/reference\/api\/#get-a-layer-setting\"><code>layerSettingForKey<\/code><\/a>, to set and read data off a layer:<\/p>\n<div>\n<pre><code>function findOrMakeSpecialGroupIfNeeded(layer){\n        const isSpecialGroupKey = \"is-mosaic-group\";\n\n        \/\/        Loop up through the parent hierarchy, looking for a special group\n\n        var layerToCheck = layer;\n\n        while(layerToCheck){\n                let isSpecialGroup = Settings.layerSettingForKey(layerToCheck, isSpecialGroupKey);\n\n                if(isSpecialGroup) return layerToCheck;\n\n                layerToCheck = layerToCheck.parent;\n        }\n        \n        \/\/        Group\n\n        const destinationParent = layer.parent;\n\n       layer.remove(); \/\/ explicitly remove layer from it\u2019s existing parent before adding it to group\n\n        const group = new Group({\n                name: \"Mosaic Group\",\n                layers: [ layer ],\n                parent: destinationParent\n        });\n        \n        Settings.setLayerSettingForKey(group, isSpecialGroupKey, true);\n\n        return group;\n};\n<\/code><\/pre>\n<\/div>\n<p>Now that we\u2019ve got a method that handles wrapping a layer in a mosaic group (or, if already a mosaic group, just returns it) we can now plug it into our main <code>mosaic<\/code> method just after our safety checks:<\/p>\n<div>\n<pre><code>function mosaic(options){\n        \/\/       ... safety checks ...\n\n        \/\/        Group selection if needed:\n\n        const group = findOrMakeSpecialGroupIfNeeded(selectedLayer);\n}\n<\/code><\/pre>\n<\/div>\n<p>Next we\u2019ll add a loop to remove all layers from the group except the template layer (which is the first):<\/p>\n<div>\n<pre><code>function mosaic(options) {\n        \/\/        ...\n        \n        \/\/        Remove all layers except the first:\n        \n        while(group.layers.length > 1){\n                group.layers[group.layers.length - 1].remove();\n        }\n}\n<\/code><\/pre>\n<\/div>\n<p>Lastly, we\u2019ll make sure that the group\u2019s size is fitted to its new contents since the user might have originally selected a layer nested within the old group (a layer that we might have removed).<\/p>\n<p>We\u2019ll also need to make sure to set the current selection to our mosaic group itself. This will ensure that if the user is making a bunch of rapid changes to the same mosaic group it won\u2019t become deselected. After the code we already wrote to duplicate a layer, add:<\/p>\n<pre><code>function mosaic(options) {\n        \/\/        ...\n        \n        \/\/        Fit group to duplicates\n\n        group.adjustToFit();\n\n        \/\/        Set selection to the group\n\n        document.selectedLayers.clear();\n        group.selected = true;\n}\n<\/code><\/pre>\n<p>Try out the plugin again. You should find that editing a mosaic is much smoother now!<\/p>\n<h4>Improving The Interface<\/h4>\n<p>One other thing you might notice is the lack of synchronization between the display window and the interface inside it, in terms of them both becoming visible at the same time. This is due to the fact that when we display the window, the web interface isn\u2019t guaranteed to have finished loading, so sometimes it\u2019ll \u201cpop\u201d or \u201cflash in\u201d afterwards.<\/p>\n<p>One way to fix this is by listening for when the web interface has finished loading, and only then show our window. There is a method, <a href=\"https:\/\/developer.apple.com\/documentation\/webkit\/wknavigationdelegate\/1455629-webview?language=objc\"><code>webView:didFinishNavigation:<\/code><\/a>, that WKWebView will call when the current page has finished loading. We can use it to get exactly the notification we\u2019re looking for.<\/p>\n<p>Back in <code>ui.js<\/code>, we\u2019ll extend the <code>MochaJSDelegate<\/code> instance we created to implement this method, which will in turn call the <code>onLoadFinish<\/code> argument we\u2019ll pass to <code>createWebView<\/code>:<\/p>\n<div>\n<pre><code>function createWebView(pageURL, onApplyMessage, onLoadFinish){\n        const webView = WKWebView.alloc().init();\n\n        \/\/        Create delegate\n\n        const delegate = new MochaJSDelegate({\n                \"webView:didFinishNavigation:\": (webView, navigation) => {\n                        onLoadFinish();\n                },\n                \"userContentController:didReceiveScriptMessage:\": (_, wkMessage) => {\n                        const message = JSON.parse(wkMessage.body());\n                        \n                        onApplyMessage(message);\n                }\n        }).getClassInstance();\n\n        \/\/        Set load complete handler\n\n        webView.navigationDelegate = delegate;\n\n        \/\/        Set handler for messages from script\n\n        const userContentController = webView.configuration().userContentController();\n\n        userContentController.addScriptMessageHandler_name(delegate, \"sketchPlugin\");\n\n        \/\/        Load page into web view\n\n        webView.loadFileURL_allowingReadAccessToURL(pageURL, pageURL.URLByDeletingLastPathComponent());\n\n        return webView;\n};\n<\/code><\/pre>\n<\/div>\n<p>And back in the <code>loadAndShow<\/code> method, we\u2019ll adjust it so that it only shows the window once the web view has loaded:<\/p>\n<div>\n<pre><code>function loadAndShow(baseURL, onApplyMessage){\n        \/\/        ...\n\n        const window = createWindow();\n        const webView = createWebView(pageURL, onApplyMessage, () => {\n                showWindow(window);\n        });\n\n        window.contentView = webView;\n\n        _window = window;\n};\n<\/code><\/pre>\n<\/div>\n<p>Bingo! Now our window displays only when the web view has finished loading, avoiding that annoying visual flicker.<\/p>\n<h3>Conclusion<\/h3>\n<p>Congratulations, you\u2019ve built your first Sketch plugin! ?<\/p>\n<p>If you\u2019d like to install and play around with Mosaic, you can <a href=\"https:\/\/github.com\/matt-curtis\/mosaic\">download the complete plugin<\/a> from GitHub. And before you go, here are a few resources that might be handy during the rest of your journey:<\/p>\n<ul>\n<li><a href=\"https:\/\/developer.sketchapp.com\/\">developer.sketchapp.com<\/a><br \/>\nThe official resource regarding Sketch plugin development. Contains several useful guides, as well as an API reference for the Sketch JavaScript library.<\/li>\n<li><a href=\"https:\/\/sketchplugins.com\/\">sketchplugins.com<\/a><br \/>\nA fantastic and helpful community of Sketch plugin developers. Great for getting all your burning questions answered.<\/li>\n<li><a href=\"https:\/\/github.com\/sketchplugins\/plugin-directory\">github.com\/sketchplugins\/plugin-directory<\/a><br \/>\nOfficial, central GitHub repository of Sketch plugins. You can submit your plugins here and share them with the rest of the Sketch community!<\/li>\n<\/ul>\n<div>\n  <img data-recalc-dims=\"1\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/logo-red-5.png?w=900\" alt=\"Smashing Editorial\"><span>(mb, yk, il)<\/span>\n<\/div>\n<\/article>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00 As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code&#8230;<a class=\"moretag\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/\"> Read the full article&#8230;<\/a><\/p>\n","protected":false},"author":4,"featured_media":67910,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[371],"tags":[],"class_list":["post-67909","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-user-experience"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.0.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Guest Contribution\"\/>\n\t<meta name=\"keywords\" content=\"user experience\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.0.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta property=\"og:description\" content=\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-07-09T11:19:01+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-07-09T11:19:01+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies\" \/>\n\t\t<meta name=\"twitter:description\" content=\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.\" \/>\n\t\t<script type=\"application\/ld+json\" class=\"aioseo-schema\">\n\t\t\t{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#article\",\"name\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies\",\"headline\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)\",\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/07\\\/web-ui-config-message.png?fit=400%2C192&ssl=1\",\"width\":400,\"height\":192},\"datePublished\":\"2019-07-09T04:19:01-07:00\",\"dateModified\":\"2019-07-09T04:19:01-07:00\",\"inLanguage\":\"en-US\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#webpage\"},\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#webpage\"},\"articleSection\":\"User Experience\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#breadcrumblist\",\"itemListElement\":[{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"position\":2,\"name\":\"User Experience\",\"item\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/\",\"nextItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#listItem\",\"name\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)\"},\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog#listItem\",\"name\":\"Home\"}},{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#listItem\",\"position\":3,\"name\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)\",\"previousItem\":{\"@type\":\"ListItem\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/category\\\/user-experience\\\/#listItem\",\"name\":\"User Experience\"}}]},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/\",\"name\":\"Guest Contribution\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#authorImage\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g\",\"width\":96,\"height\":96,\"caption\":\"Guest Contribution\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#webpage\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/\",\"name\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies\",\"description\":\"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.\",\"inLanguage\":\"en-US\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#breadcrumblist\"},\"author\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"creator\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/author\\\/guestcontribution\\\/#author\"},\"image\":{\"@type\":\"ImageObject\",\"url\":\"https:\\\/\\\/i0.wp.com\\\/www.taterboy.com\\\/blog\\\/wp-content\\\/uploads\\\/2019\\\/07\\\/web-ui-config-message.png?fit=400%2C192&ssl=1\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#mainImage\",\"width\":400,\"height\":192},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/2019\\\/07\\\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\\\/#mainImage\"},\"datePublished\":\"2019-07-09T04:19:01-07:00\",\"dateModified\":\"2019-07-09T04:19:01-07:00\"},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/\",\"name\":\"Design for Immersive Technologies\",\"description\":\"User Experience for Games, Virtual Reality (VR) and Augmented\\\/Mixed Reality (AR\\\/MR)\",\"inLanguage\":\"en-US\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.taterboy.com\\\/blog\\\/#organization\"}}]}\n\t\t<\/script>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies","description":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.","canonical_url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/","robots":"max-image-preview:large","keywords":"user experience","webmasterTools":{"miscellaneous":""},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#article","name":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies","headline":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)","author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/web-ui-config-message.png?fit=400%2C192&ssl=1","width":400,"height":192},"datePublished":"2019-07-09T04:19:01-07:00","dateModified":"2019-07-09T04:19:01-07:00","inLanguage":"en-US","mainEntityOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#webpage"},"isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#webpage"},"articleSection":"User Experience"},{"@type":"BreadcrumbList","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#breadcrumblist","itemListElement":[{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","position":1,"name":"Home","item":"https:\/\/www.taterboy.com\/blog","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","position":2,"name":"User Experience","item":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/","nextItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#listItem","name":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)"},"previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog#listItem","name":"Home"}},{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#listItem","position":3,"name":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)","previousItem":{"@type":"ListItem","@id":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/#listItem","name":"User Experience"}}]},{"@type":"Organization","@id":"https:\/\/www.taterboy.com\/blog\/#organization","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","url":"https:\/\/www.taterboy.com\/blog\/"},{"@type":"Person","@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author","url":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/","name":"Guest Contribution","image":{"@type":"ImageObject","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#authorImage","url":"https:\/\/secure.gravatar.com\/avatar\/d2aad73eb0f48c67b141e9fc978a0e498969791ed623346e361d02d19775b0bb?s=96&d=mm&r=g","width":96,"height":96,"caption":"Guest Contribution"}},{"@type":"WebPage","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#webpage","url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/","name":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies","description":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.","inLanguage":"en-US","isPartOf":{"@id":"https:\/\/www.taterboy.com\/blog\/#website"},"breadcrumb":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#breadcrumblist"},"author":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"creator":{"@id":"https:\/\/www.taterboy.com\/blog\/author\/guestcontribution\/#author"},"image":{"@type":"ImageObject","url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/web-ui-config-message.png?fit=400%2C192&ssl=1","@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#mainImage","width":400,"height":192},"primaryImageOfPage":{"@id":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/#mainImage"},"datePublished":"2019-07-09T04:19:01-07:00","dateModified":"2019-07-09T04:19:01-07:00"},{"@type":"WebSite","@id":"https:\/\/www.taterboy.com\/blog\/#website","url":"https:\/\/www.taterboy.com\/blog\/","name":"Design for Immersive Technologies","description":"User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","inLanguage":"en-US","publisher":{"@id":"https:\/\/www.taterboy.com\/blog\/#organization"}}]},"og:locale":"en_US","og:site_name":"Design for Immersive Technologies | User Experience for Games, Virtual Reality (VR) and Augmented\/Mixed Reality (AR\/MR)","og:type":"article","og:title":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies","og:description":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well.","og:url":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/","article:published_time":"2019-07-09T11:19:01+00:00","article:modified_time":"2019-07-09T11:19:01+00:00","twitter:card":"summary","twitter:title":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) | Design for Immersive Technologies","twitter:description":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2) Matt Curtis 2019-07-08T12:30:59+02:002019-07-09T11:06:16+00:00As mentioned in part 1, this tutorial is intended for people who know and use the Sketch app and are not afraid of dabbling with code as well."},"aioseo_meta_data":{"post_id":"67909","title":null,"description":null,"keywords":null,"keyphrases":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_custom_url":null,"og_image_custom_fields":null,"og_custom_image_width":null,"og_custom_image_height":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"location":null,"local_seo":null,"created":"2021-02-07 19:02:25","updated":"2026-09-01 14:58:32","focus_keyword":null,"additional_keywords":null,"truseo_locale":null,"primary_term":null,"og_image_url":null,"og_image_width":null,"og_image_height":null,"twitter_image_url":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"Article","isEnabled":true},"graphs":[]},"limit_modified_date":false,"ai":null,"breadcrumb_settings":null,"seo_analyzer_scan_date":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/\" title=\"User Experience\">User Experience<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">&raquo;<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tHow To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.taterboy.com\/blog"},{"label":"User Experience","link":"https:\/\/www.taterboy.com\/blog\/category\/user-experience\/"},{"label":"How To Build A Sketch Plugin With JavaScript, HTML And CSS (Part 2)","link":"https:\/\/www.taterboy.com\/blog\/2019\/07\/how-to-build-a-sketch-plugin-with-javascript-html-and-css-part-2\/"}],"jetpack_publicize_connections":[],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p8wzr5-hFj","jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.taterboy.com\/blog\/wp-content\/uploads\/2019\/07\/web-ui-config-message.png?fit=400%2C192&ssl=1","_links":{"self":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/67909","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/comments?post=67909"}],"version-history":[{"count":0,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/posts\/67909\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media\/67910"}],"wp:attachment":[{"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/media?parent=67909"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/categories?post=67909"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.taterboy.com\/blog\/wp-json\/wp\/v2\/tags?post=67909"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}