Ask most people how they feel about browser-based apps and they'll tell you that there's a tradeoff between the convenience the cloud offers, and the power they get from the desktop versions of the same programs. Even the browser versions of Microsoft Office applications do not have the full feature sets of Microsoft's desktop versions of the same. Our goal was to eliminate this tradeoff and we've succeeded.
We started with three design goals:
- SmartDraw had to have the full feature set and be file compatible with SmartDraw for Windows.
- It had to be fast enough to be fun to use.
Off-the shelf frameworks and components can save time and effort if you want to develop a quick in-house line of business app, but they are absolutely not the way to go if your goal is to develop a reliable, scalable, maintainable commercial application. In fact, this may be the reason why so many cloud apps have a "lightweight" reputation.
So we wrote some code! In fact, a lot of code. We built our own object libraries which are:
- easy to maintain,
- under our control, and
- quick and scalable.
It took more time but SmartDraw is our bread and butter. Quick and dirty was not going to cut it.
No matter what language or platform you choose in writing a commercial application, the basic steps are the same.
- Decide what your app will do and what interface you want. This was easy for us since we had our Windows desktop application as a template.
- Propose an internal design to achieve this. Break it down into functional components that will become the building blocks of the app. This was also pretty easy because we had the Windows product as a guide. We would need a memory block manager to handle undo and redo effortlessly, a graphics interface, a way of presenting and interacting with a user interface, a file format, a text editor, a graphic editor, a business logic manager and so on.
- Propose the software architecture. This is where we specify the object design, object hierarchy and data that are maintained.
- Look to see if there are existing libraries that can be used for one or more of these building blocks. If so, carefully evaluate them and if they do the job by all means use them. Otherwise write your own.
- Write code in a disciplined and maintainable way. Code has to be readable and understandable by everyone on the team — including anyone that might join the team in the future. Code must also follow standards that the team agrees to.
The diagram below shows a simplified view of the SmartDraw architecture.
We made the some important design decisions early on:
- We would use SVG as our graphics engine because of its performance and superior quality when compared to Canvas. SVG is also quite scalable for printing and export.
- We would design a memory block manager that manages all of the objects that make up the composition of a document as discrete blocks, and also manages undo and the writing of the file to the server in a very efficient way.
- We would use Web Sockets to save the data and to communicate with the server.
- Our file format would be the very compact binary (but xml-like) format we use for our Windows product.
The List Manager manages the shapes on a page. The Business Manager applies the business logic that controls the behavior of specific diagrams like flowcharts or floor plans.
The User Interface
This is the sort of thing a framework is supposed to do for you: create and handle a simple control like this for the text font. The control shows the font of the currently selected shape or text, and allows you to change it.
We looked at a wide range of such frameworks, including Dojo and React before setting on Angular.js. As we began development we quickly realized three things:
- Angular wouldn't scale to the extent our sophisticated app required, and it obfuscated what was going on.
The Graphics Interface
Another choice we made was to use SVG to display graphics instead of Canvas. From a performance point of view this was an easy choice. SVG is a vector graphic format that gives you resolution independent output. However it does present some difficulties:
- There is no open source rich text editor for SVG. We would have to write one.
- We weren't satisfied with the existing graphics libraries for SVG. Raphael, for example, was designed to use either Canvas or SVG, and to also work with older browsers. This gave it a "lowest common denominator" feature set. We wanted the advanced effects of a pure SVG library and so we wrote our own.
If an error occurs during an operation, the previous good state is restored, maintaining the integrity of the model.
An undo operation exchanges only the modified blocks from the previous state with the current state. Redo does the reverse. Undo states maintain only the changes to the document model. This is very efficient both in speed and memory usage.
The Block Manager also makes it possible to write only the modified objects to the server after each operation. A change will often only write a few bytes back to the server. This combined with the use of web sockets to send the data makes saving the document after each change very fast.
Libraries We Did Use
After much prototyping and research we used just a handful of third party components. These include:
- jQuery — to get references to UI elements,
- Fileparser.js — to read and write the binary file format,
- Hammer.js — to handle both mouse and touch events, and
- Svg.js — to provide the lowest level interface to SVG markup.
We used these sparingly, often with our own modifications to make them more robust. We load them all from our own site so that our app is not dependent someone else's site being available, or on updates which we don't control. (Relying on external loading of components can lead to serious problems, such as the recent NPM Kik module debacle.)
It took us a year or more to write these libraries. With these in place we set about writing the app.
- Adding members to an object on the fly,
- Never actually formally defining an object, and
- Assigning variables as hard coded constants (like align=16 instead of align=SDJS.ListManager.TextFlags.AlignLeft).
These are all recipes for unreliable code that is impossible to maintain three years out when the author has moved on or forgotten what he or she did.
Here are just a few of the constructs we established to write disciplined code:
- Created a consistent name space for all objects. For example, all of the ListManager objects began with SDJS.ListManager. The UI objects began with SDUI. And so on.
- Defined constructors for all of the objects we used. For example:
If we needed to add a new member to the object, we added it to the definition, never to an instance on the fly. We also froze objects so this wasn't possible. Constants were also formally defined:
- Created an inherited object model for shapes. The list manager manages a list of shapes that together make up the drawing. Shapes are all inherited versions of a basic drawing object. For example:
The base object has prototypes for ALL methods used by the derived shapes, but implements very few of them. There are derived objects for shape, simple line, and polygon line and so on. For example:
These objects either implement their own methods, or inherit them from their base class. Again all methods appear in the
class, even if they do nothing, so that it represents the union of all methods for all objects.
The emergence of TypeScript and Ecmascript 6 make it easier to write this kind of code. Writing a serious application requires disciplined coding.
We made many optimizations to ensure snappy performance including simple things like:
Pre-calculating array lengths so we never write:
Instead we write :
Looking up constants:
If a comparison is made in a loop with SDJS.ListManager.TextFlags.AlignLeft, it takes time within each loop to find this value. Instead we assign:
before the loop so this is done just once.
One of our design goals was to import Visio® files in an editable form. To do this we had to extend the model of SmartDraw for Cloud and Windows to accommodate the Visio file format. These included multiple pages per document and support for new types of curves including NURBs, Splines and more.
The inherited object structure we implemented for the list manager objects we described earlier made this much simpler to implement. We just added or overrode the methods we already had for other polygon lines, and so on. This applied to our 20-year-old windows model too.
The HTML 5 stack can support desktop quality apps. What's important is applying the same
techniques that make desktop apps work well to the web platform. The language in which the app
is written is much less important than applying the design and discipline a large app requires.
It's also important to use well-designed components that you control and understand,
even if it means writing them yourself. Slapping together a grab bag of open source
code will make your app as reliable as the worst component you use. It can also make
it much larger and significantly slower than it need be. And if you do use a third
party library — load a stable version from our own site!
develop commercial-quality applications, and more traditional app developers
increase dramatically and the trade-off between desktop and cloud will fade away.