Categories / Shop Center

How We Built a Photoshop Extension with HTML, CSS, and JS.

Creative Market March 22, 2024 · 8 min read

Earlier this week, we released the Creative Market Photoshop Extension – a new way to buy, download, and install awesome design resources without leaving Photoshop! Gerren shared a few high-level design considerations from the process of planning the app; now let’s focus on the nerd guts of it.
The Creative Market Photoshop Extension is a Backbone.js web app that lives inside of Photoshop. We can update it without you having to install a new update. How’s that work? Read on!

Architecture

Creative Suite Extensions are really AIR applications that link a few special libraries from Adobe’s Creative Suite SDK.
We developed the initial prototype as a traditional Flex application, entirely using Flex UI controls. Quickly we realized we wanted something more flexible–a platform that would let us iterate like we iterate on the web. The rigidity of the code combined with the pains of recompiling and issuing updates for the smallest of changes just wasn’t going to work.
At that point, we chose to build the core of the application as a web app that lives on our servers that gets loaded into a mx:HTML control (Webkit).

Advantages

  • Developing with HTML/CSS/JS is fast.
  • Rich open-source ecosystem. Using and contributing to OS projects like jQuery, Handlebars.js, Backbone.js, Backbone.Validation, RequireJS, and Almond makes our lives easier.
  • Share components made for the web. Components like the credit card widget built by Levi for the web purchase process can be dropped in without modification.

Certain functions like downloading & installing products, checking for updates, and general interactions with the filesystem still happen in AS3, but are invoked via Javascript–explained more later.

Native Feel

An experience that felt like a webpage wasn’t an option–we did everything we could to make it snappy & feel like it was a part of Photoshop.

HTML / Asset Caching

The mx:HTML control is great for online use, but there’s little to no control of cache use—which makes offline usage nearly impossible. If you’re disconnected from the Internet, the window will hang until the connection becomes available or it times out (even if the page exists in cache).
We developed a custom OfflineHTML control that extends mx:HTML. It parses HTML and CSS stylesheets, picks out all the static resources (like images and javascript files), downloads them, and rewrites all references to the local copies. That way, when the user opens up the extension again, everything is immediately available and served off disk–even if disconnected from the internet.

(If there were a good way to proxy requests within the HTML control, we would have just used that… but there’s not. Sad panda.)

Live Re-skinning

We generate & apply CSS stylesheets on-the-fly when the extension receives reskin events that contain current UI colors. To generate the stylesheets and add them to the DOM, we use jquery-stylist.

Note: On Photoshop CS5.0, the rendered brightness is off, so we have to lighten every color channel by 0x08 to get it to match Photoshop’s UI.

Image Preloading

When the application’s compiled, we generate a “manifest.json†file that lists all image assets used. As the app starts, we prefetch each in the list so that when the user gets to a view that needs an image… it’s available.

Communication

JS ⇔ AS3

Getting the Javascript web application to talk to the Flex container (and vice versa) happens through a sandbox bridge. It allows you to safely isolate the two domains and treat objects and methods on the other side as if they’re not from another language. With that comes a few caveats:

  • Objects cannot contain functions. You can pass function references alone to the other side and invoke them, however.
  • Object prototypes will not match. If passing objects, it’s best to serialize them then deserialize on the other end. In AS3, objects coming from JS are represented as __HTMLScriptObject and __HTMLScriptArray types. These types are undocumented and wreak havoc with JSON.encode. They do not always behave.

AS3 ⇒ Photoshop

Controlling Photoshop is easy with JSX scripting. With the Scripting Listener plugin installed, Photoshop logs all actions performed as code to “ScriptingListenerJS.log”. If taken, wrapped in a function, and dropped in your project’s *.jsx file, the code can be executed from AS3 using evalScript:

var result:SyncResult = CSXSInterface.instance.evalScript('jsxFunction');

If needed, you can pass along arguments:

CSXSInterface.instance.evalScript('jsxFunction', 'Yo!');

However, in CS4 products, only one argument is supported. In CS5+, passing strings with certain characters like quotation marks and backslashes in them will cause a PlugPlugRequestFailed result–for no valid reason.
The solution is to serialize and encode the arguments (hexadecimal) in AS3 then decode them in the JSX script. This will prevent failures from mysteriously-forbidden characters and bypass CS4’s limit of only one argument.

Front-end Structure

Backbone.js

worked wonderfully for defining views tied to data models (and keeping the two decoupled). The UI is made up of many views within views.

Fundamental Views

  • View: The view that all other views are derived from. Contains methods like: “activate”, “deactivate”, “repaint”, “reflow”, and methods for setting up form validation via Backbone.Validation.
  • Page: An extensible view for all pages (Home, Account, Product Detail, etc).
  • PageStack: A container for Page views, controlled by push and pop methods. It manages the visibility of its children so that the last item in the stack is the only one visible. Also, it sets the “previousPage” property on children which is useful for displaying a “Back” button in each.
  • Dialog: An extensible view for all modal dialogs. Dialogs are opened by pages: page.openDialog(dialogView);
  • Root: The main view of the application, containing the footer tab strip and two PageStacks: one for the Browse tab and one for the Account tab.

Event Bus

Models and views are connected to a global event bus. Both can declare an event namespace that will cause all emitted events to be prefixed when sent to the global bus. For example, with our Session model, the namespace is “session”. Other models and views can listen for its events easily: “session:change:id”.

Session Management

When a user signs in or out, all views with user state must be updated to reflect the change. Basic controls listen for “session:change” events and update accordingly.
Pages require slightly more care. For instance, once a user has signed out, it’s important to remove the “Account” page. Rather than doing this manually and having handlers strewn about the application, each subclass of Page simply declares whether it needs a session or not in order to exist:

false View is cleared when the user signs in.
true View is cleared when the user signs out.
null “Don’t care”. Nothing is done in either case.

PageStack views listen for session changes and selectively remove child pages that don’t match their defined requirement.

Build Tooling

The conventional method of compiling extensions involves Flash Builder with the Extension Builder plugin. It makes getting started easy, but long term, it becomes difficult to work with as a project’s needs diverge from the basics.
We automated it all–compiling, packaging, application signing, and deploys–using node.js. This is cool for two reasons: (1) use any IDE you want, and (2) it’s fast!
All project settings live in a single “package.json” file. The properties get synthesized to various XML & MXI files needed by the Flex compiler and UCF tool.

Compilation

When compiling a release, we compile two separate versions: one for CS5 and one for CS6. These are both packaged up as a single ZXP installer.

Deploys

To deploy, we generate changelogs, automatically tag the version on Github, generate “update.xml” (along with a JSON representation), and push the files to S3. It’s all scripted–there’s nothing manual to do with sending out new versions.
In the next few days, we’ll be open sourcing the tool for all of this 🙂 The tool is open-source and available at: https://github.com/creativemarket/csxs

Conclusion

Adobe provides a pretty cool platform that makes it quick to develop for their products. The difficulties lie with scattered & incomplete documentation and platform bugs that are out of your control as a 3rd party developer. Sadly there’s a “fend for yourself” developer ecosystem right now. I’d love to see that change.
Our Photoshop Extension is still in its infancy–there’s still so much room to grow. As we dream, plan, and develop new features and tighter integration, I hope to share what we learn.

Upcoming API

Are you a developer looking to integrate Creative Market content and inline-purchasing in your own website or app? Apply for an API key today!

Lettering Worksheets
Getting started with hand lettering?
Free lettering worksheets

Download these worksheets and start practicing with simple instructions and tracing exercises.

Download now!
About the Author
Author
Creative Market

Making beautiful design simple & accessible to all.

View More Posts
Go to My Shop
Related Articles