The above is a live demo of the widget. Feel free to interact with it for a little bit, but don't get too distracted. There's an article to read!
Alright, the best place to start when designing a widget of this magnitude is the actual design. I used to use tools like Photoshop, Adobe XD, and even paper and pencil to mock up user interfaces. While those have some value, when designing for the web, today's web browsers already have built into them some rather amazing Developer Tools. So what happens now is:
- Create a .html file on my local computer with a bunch of divs.
- Point my web browser at the .html file.
- Open Developer Tools and pick an element out and style it in the browser.
- Once satisfied with the changes, go back to the .html file and apply those styles in a 'style' tag.
- Reload the page to make sure I didn't forget to copy any styles to the file.
- Repeat steps 3-5 until the design is mostly done.
- Load the .html file in multiple desktop browsers (e.g. Firefox, Edge, and Chrome) and fix any remaining issues.
- Test out as much keyboard and mouse interaction as is possible at this point (e.g. 'hover' and 'focus').
For this particular widget, there were five major areas to design:
- History navigation tools (fixed size)
- Current path broken down by path segment (scrollable)
- A customizable toolbar (scrollable)
- The main display area for folder and file items (scrollable)
- A status bar/information/feedback area (fixed size)
In this case, you get to see the final product and intuitively know it is going to work well on all devices BUT imagine for a moment that you had not seen the widget before. It takes time to figure out what to do and can take days to plan out what the final user interface might look like. It's also important to remain semi-fluid when designing because the early design phase only accounts for 85% to 90% of the final widget's actual design.
As there are going to be various adjustments made as the widget is built, what I like to do is create outer and inner wrapper 'div's for almost everything. Using wrapper 'div's up front allows for using the browser to rapidly target CSS changes without having to mess with HTML too much. Let's see what that looks like in practical terms by using the actual design HTML that was used for this widget:
If we load that into a web browser, it looks like:
That's pretty ugly looking but the reason is due to not having any CSS styles yet. It is just HTML layout at this point. The next step is to fire up Developer Tools in the web browser, apply a few styles to one of the outermost 'div's to add a border, and then copy those styles back into the HTML once satisfied with the result. The resulting HTML now looks like:
Which now looks like:
Okay...this is going to take a while. Styling up the entire widget in this case, including designing icons, took about 40 hours. Here's a rough timelapse of the process (click the image to view the whole animation):
However, I don't know exactly how long it took to get the design done. This sort of thing takes as long as it takes. But if I had to guess, each step of the design process took an average of 20-ish minutes to complete in real time.
Some portions of the design obviously took longer than others such as doing research on how to hide a horizontal overflow scrollbar for the path segments or containing flex items to a scrolling region or finding existing icons for the toolbar or even creating icons from scratch. Icon design and the numerous complexities that arise, especially when making icons that are smaller than 32x32 pixels, is its own topic for another day. A single icon for a specialized situation can even take an entire week to create.
During the design process, I made sure to test various extremes:
- What does it look like if there is a really long status bar item?
- What happens if there are 50+ path segments?
- What happens if the widget is smaller than the toolbar?
- Does the widget always fill its space?
- What happens if there are 1,000+ items in the main area?
- No items in the main area?
- And so on.
Toward the end of the animation is a keyboard navigation test. I set the background color to an ugly red so I could actually see where the keyboard focus currently was. Obviously, that hideous red color isn't in the final widget. The goal was to make sure tab key navigation on a keyboard going through the widget would make sense. It actually takes a while to figure out good tab key navigation options for a widget like this and experience has taught me that it is best to do it up-front rather than to try to bolt it on later.
One side-note: You may notice an extra wrapper 'div' with a class of 'fe_fileexplorer_dropzone' and are wondering what that is for. That was a very early attempt to plan ahead for future drag-and-drop operations. Specifically, dragging and dropping files from the desktop to start uploading files. The original plan was to use the dropzone with jQuery File Upload like what was used for the jQuery Fancy File Uploader widget. However, once I got to actually writing the upload portion in the actual widget, I ended up NOT using jQuery File Upload for various reasons. As a result, the dropzone div still exists in the final widget but is actually unused. Always plan ahead a little but not care too much if there is an extra leftover wrapper div here or there later on. There are much bigger fish to fry when building a widget like this.
Interactivity Planning Phase
So let's move onto the next step and start planning the interactivity bits. The five major components interact with each other either through direct function calls or indirectly through the custom events system. An example of these interactions is:
- Double-click/tap on a folder.
- A SetPath() call is made to load a new path.
- The current path segment bar is updated to reflect the new path by rebuilding path segments.
- Existing folder/file items are removed from the view.
- If the folder is already mapped, those items are loaded. Otherwise a loading screen is displayed.
- The history stack is updated to point at the new path.
- The history tools are notified that navigation took place so they can update their visual appearance.
- The toolbar tools are notified to update so they can enable/disable themselves accordingly.
- The status bar handler is notified to update what it is displaying.
- A refresh of the current folder is triggered so that a backend server can load an updated item list.
As shown above, changing folders requires all five major areas to be updated in some fashion. When two components interact with each other, the complexity of the code increases exponentially. When complexity increases, the important thing to do is to pace oneself and move at a continual, steady pace knowing it will take a while to complete but it will eventually get done. Keeping a list of bite-sized tasks helps too where no single task takes longer than a day or two to complete. Constant little victories helps to keep morale high and slowly adds up to a completed project.
Not operating under a deadline is ideal too - the stress of a deadline can lead to early coding mistakes that can be costly or even impossible to correct later. Not having a deadline allows for sufficient time to do the necessary research to decide on the best approach to solving various problems that will inevitably arise. Note that a no-deadline approach only works for highly self-motivated individuals.
Since the history navigation tools were just mentioned and while I'm thinking about it: If there is going to be "undo/redo" or "history navigation" in a piece of software, it is the single most important thing to get right up-front because everything else about the software will revolve around some portion of the undo stack/history tracker. Attempting to bolt an undo/redo stack onto a piece of software after the fact is usually extremely difficult if not impossible.
In general, there will be a pointer to a specific undo/history item and all of the rest of the code in the software will look at that one item. For this particular widget, I keep a 'currfolder' pointer to the current folder and all of the rest of the code looks at 'currfolder' to know what folder is currently being viewed by the user.
A Brief Detour: Closures
The code above places a function into the global namespace on the window. Referencing the function or global is easy: Simply call TheTestFunction() which is an alias for window.TheTestFunction(). Well, that's not so bad. But what if you have 100 functions like this? 1,000? If you control every aspect of the current document, you can avoid things like name collisions, but imagine if there are 50 different pieces of software from 15 different domains all competing in the global namespace? And what happens if the web browser itself starts using those names too? It can become a mess really fast.
By the way, there's nothing wrong with using the global namespace for small applications. It's there to be used. Encapsulation inside a closure is really only required when building a library/widget that will be used elsewhere (i.e. what this post is talking about) or security and/or potential name conflicts are of concern. Other than those things, as long as the application works and doesn't have security vulnerabilities, then it's totally fine to use the global namespace. Anywho...back to closures.
The syntax is weird to look at. It basically says to create an anonymous function with zero parameters and then immediately execute it. Inside the function is the original function that gets defined as well but it is local to the anonymous function. The window object remains unaffected. This allows for lots of functions and variables and code to be fully encapsulated in a single closure. Of course, this creates a new problem: How do we get functions to be accessible to the outside world? The answer is to selectively export to the window object.
If everything above makes some sense, then we are ready to move onto looking at a more involved class.
A Real Class: DebounceAttributes
You may have noticed in the live demo from earlier that there was a slight delay between scrolling and when thumbnail images started loading into the view. That intentional delay is the DebounceAttributes class at work. The widget waits until after scrolling stops for a moment and then starts loading the thumbnails. Using a class here is quite useful because the Start() function can be made very efficient: If the interval timer already exists, the function ends up doing nothing when the 'scroll' event fires the callback on the class instance.
Every time the interval fires, the attributes are compared to their previous values. When they are the exact same for a certain, specified number of times, the interval stops and the callback function runs.
There is an interesting object in the DebounceAttributes class: '$this.settings'. This variable makes a public merged settings object with the defaults object and the options object. The approach normalizes an object so that all keys at least exist in the final object and also makes it easier to identify what all of the possible options are at a glance.
There is one private function that is defined with a 'var' instead of a more traditional 'function MyFunctionName()' approach. Technically, it doesn't really matter. I prefer assignment like this in case I later decide I want a specific function to be public instead of private. The 'var' vs. '$this.' prefix decides whether the function is private or public.
There are also a number of public functions but the last one is interesting: $this.Destroy(). The Destroy() function immediately cleans up any running bits and then destroys itself. The idea here is to convince the browser during its next garbage collection cycle that the object is no longer in use and to free up the associated RAM. The downside is that Destroy() functions can be tricky to get right AND can be several screens long for large, complex widgets.
Note: When referencing a public function from inside a function, it is important to use '$this.FunctionName(...)' instead of 'this.FunctionName(...)' because by the time the function runs, 'this' may be in a completely different context. The private '$this' variable keeps track of the original 'this' context from when the class was instantiated.
For anyone curious about the first line of code in DebounceAttributes or TheTestFunction() from the previous section, it verifies that it is: Called with the 'new' keyword so it won't affect the global namespace and, slightly more advanced, makes the class not inheritable on the prototype. There is still a way to get the function to do the wrong thing via call()/apply() and forcing an instance of itself to run in its own context, but the point is to not wind up dumping garbage into the global namespace and to also explicitly deny class inheritance.
Private Custom Events
In the past, event handling in web browsers was limited to one callback per event type (onclick/onmousemove/etc.) on each DOM node. When a little library called jQuery came along, it also brought the ability to easily apply multiple callbacks to a single DOM node event type. For that and many, many other reasons including call chaining, a powerful DOM selection engine, and plugins, jQuery became extremely popular. Eventually, all major web browsers adopted a similar model with addEventListener() and removeEventListener() functions along with many of the other features found in jQuery, which made things better for everyone.
When making multiple classes, an event model allows the classes to remain loosely coupled with each other instead of going down a class inheritance path. This allows a class to be aware that something might be interested in knowing about a specific event happening without having to have precise knowledge about those somethings.
There are two approaches to custom events: Public via the DOM and private via a custom implementation. Public custom events can be useful but anyone can trigger them and requires a DOM node to use them, and callbacks are limited to receiving an Event object instance as a single parameter.
Private custom events, on the other hand, allow for greater control over what can trigger them, there is no risk of events having name collisions, they do not require a DOM node to function, can run callbacks with as many parameters as desired, and precisely control what 'this' should be in the callback. Here is what I use in my classes:
This approach allows various interested parties to register for specific events. When those events fire, callbacks receive sensible parameter lists, and 'this' references the object instance. Dispatching an event from within the class is easy:
Then it becomes a matter of documenting the parameters for the callback function for each emitted event. But I tend to write fairly extensive documentation for my software.
For this widget, it needed to know in some places if a specific event had at least one handler before doing something like marking the current folder as "Busy" or adding a tool to the toolbar. If there were no listeners for a 'delete' event, the current folder would effectively "freeze" whenever the user pressed 'Delete'. The hasEventListener() function lets the code detect if there is at least one listener for the specific event before proceeding to enable the current folder's busy state.
The widget also needed to allow asynchronous operations to fully handle the dispatched event exactly one time. This was accomplished by using an internal variable inside a callback function (aka a closure) kind of like this:
Renaming a folder or file, for example, dispatches the 'rename' event to all registered callbacks with a callback function (renamecallback()) as the first parameter. The first callback to call the renamecallback() callback is the only one that is accepted and processed by using a private boolean variable called 'calledback' to track if the renamecallback() function has been called before, which allows any additional callbacks to renamecallback() to be ignored. (Does your brain hurt from reading that? Imagine how many times I had to proofread it.)
For most use-cases of the widget, there will only be at most one registered listener, but this approach avoids potentially unfortunate situations AND allows for complex scenarios of multiple resource providers being attached to the same widget - only the provider who is associated with an item would pick up and handle the request for that item while the rest of the listeners would ignore the event.
Creating DOM Nodes
In a complex widget, several DOM nodes will eventually need to be created. For this, I use a simple but effective CreateNode() function in the main closure:
The CreateNode() function is used to quickly generate private 'elems' arrays like this one:
The PopupMenu class creates a mini popup-menu for the Recent Locations and path segment areas of the widget. I like to attach elements from the deepest leaf nodes to the parent nodes and finally attach the top level node to the parent DOM element. Building the nodes separately from the live DOM won't trigger a reflow, which is an expensive operation. Also, building from the deepest children up to the parent helps to avoid missing various nodes during the attachChild() phase.
When creating a DOM node, the type of the node used will dictate how aggressively styling has to be applied. For instance, using a 'button' for a button makes sense but buttons are difficult to style properly. Using a real button means fighting against both a user's CSS on the page as well as web browser defaults. However, browsers also include a lot of nice built-in functionality for standard elements (i.e. various events for mouse, keyboard, and touch like click handling, native screen reader support, and more).
The tradeoffs are really dependent on mood and fit. If something is very clearly a button, make it a 'button' element or clearly an input field, make it an 'input' field. If it is not as obvious, then decide on how much effort needs to be expended to shoehorn an existing element in and what the likelihood is that someone else's CSS styles will mess up the UI. Which brings us to the topic, which is browser testing.
An example of this is the 'button' element. Firefox tends to show a dotted outline around focused button elements and Google Chrome tends to show a blue glow around buttons. Both issues can be overridden with a little CSS, but without cross-browser testing, the widget would look fine in one browser and a bit strange in another instead of presenting a consistent user experience across all browsers. This, of course, doesn't even cover a situation where someone globally styles all button elements on their webpage to do something weird like rotate them 5 degrees.
Besides styles, one of the most important areas of cross-browser testing is dealing with obvious hacks. As an example, I spent a rather considerable amount of time figuring out how to get right-click clipboard cut, copy, and paste operations to work equally well across all three major desktop browsers for the widget. Firefox was the most permissive - allowing cut/copy/paste events pretty much everywhere (yay!). But then I discovered that Chrome was the most restrictive and only allows those events in editable elements that have focus (boo!). Edge only blocked events until a paste happened in a editable element somewhere on the page and then stopped blocking them altogether (probably an unintentional bug, but also: boo!).
I tried various things, including a mildly comical but totally disastrous attempt to use "contenteditable". The final, working solution involved setting up a hidden-ish textarea that overlays the main area but only "shows" on right-click and temporarily forces keyboard focus into it. The textarea, when "visible," is fully transparent and all text in the textarea is rendered invisible. Unfortunately, Edge made life difficult by still showing a blinking keyboard caret on the screen and Chrome liked to show selection areas.
The solution to both problems was to shrink the font to 1px and enlarge the textarea to a few pixels outside the top of the widget's main area where various stuff could "show" to its heart's content but the "overflow: hidden" on the parent caused it to magically disappear. Is this hack awesome? Yes. Could it break in the future? Absolutely.
In short, that was/is all one giant hack to get the right-click menu to use the browser's built-in clipboard handlers. Bouncing between three different browsers was the only way to see if it was working properly everywhere.
Various other areas that required significant cross-browser testing included: The horizontal/vertical scrolling path segment bar, drag-and-drop (this was a mess all on its own), hardware capture of the back/forward buttons on the mouse (that still doesn't work quite right), keyboard navigation (mostly to check CSS styles), keyboard shortcuts, selection box variable scroll speed (click to draw a selection box around items but go outside the main area of the widget), upload cancelling, and plenty of other things I've forgotten about already.
This widget, in particular, weighs in at 225KB+ unminified, ungzipped. It takes a couple of moments to transfer that file to a remote host for every little change, so developing it locally saved me quite a bit of time but did exclude broader browser testing until closer to the end of the first dev cycle. I had my hands full anyway with just the major desktop browsers.
Handling Keyboard, Mouse, and Touch
Keyboards make it easier to type in words and sentences and apply modifiers to other device actions (e.g. Ctrl + click). They aren't super great at navigating great distances in a document even with dedicated keys for the purpose such as Page Up and Page Down and are even worse at horizontal scrolling. Also, when navigating with a keyboard, many things can be considered "modal", which adds an interesting challenge.
Mice make it easier to scroll vertically great distances as well as accurately place a cursor at a specific position on the screen. They are extremely cumbersome to use for any kind of data entry but make up for that by being able to rapidly change focus to different areas of a user interface. Most mice have three buttons (left, right, middle click) but some mice also have more buttons than that (e.g. back and forward). Users who can use both a keyboard + mouse are always more efficient at navigating a user interface than those who can only use a single input method for whatever reason.
Touchscreens are relatively new to web development. They've technically always been around but were always kind of clunky and not widely used until several advancements were made to the technology and smartphones subsequently brought them to the forefront of web application design. A touchscreen is somewhat like a mouse, but far less accurate. Mobile web browsers attempt to simulate mouse events, which can go a long way to making mouse-driven interfaces work okay on touch devices. Users need extra space around elements and/or the elements themselves to be larger than a finger to be comfortable to use. Both vertical and horizontal scrolling are equally easy to do using a touchscreen but vertical scrolling is still strongly preferred because horizontal swiping may already be used for something else (e.g. opening a menu or changing views). Multi-touch events are also possible but may not be intuitive to the user. Text entry is usually accomplished with a virtual keyboard that will resize and scroll the browser.
- Keyboard: Arrow keys without modifiers to navigate and select one item. Also supports Ctrl + arrow keys to navigate without selecting anything and then Ctrl + spacebar to select/deselect individual items. Shift + arrow keys to select from an anchor point. Ctrl + A to select all. Type in a filename to go directly to and select the filename. Optional toolbar icon to enable/disable checkboxes for spacebar multiselect.
- Mouse: Scroll through the items list using the scrollwheel. Click + drag in an open space to select multiple items using a selection box. Click on a single item to select it. Optional toolbar icon to enable/disable checkboxes for mouse-only multiselect. The mouse handlers in this widget also support keyboard modifiers (Ctrl and Shift) for multiple item selection.
- Touch: Scroll through the items naturally with touch + drag. Tap to select an item. Tap a checkbox to start a multiselect operation. Double-tap in open space to deselect all. Triple-tap in open space to select all.
That example covers just one device interaction experience with the widget.
The Americans with Disabilities Act (ADA) basically says that those with physical disabilities should have equal access to the same resources that those who are not disabled have and then follows up with a hefty fine schedule for active non-compliance (up to $75,000 USD for the first violation and up to $150,000 for subsequent violations). The definition of a disability is fairly broad, but for websites, this mostly means being able to navigate through a whole webpage with a single device.
While enforcement of ADA against website operators has been fairly sporadic, it's generally pretty rude to exclude people...and both you and I are just one minor freak accident from losing the mobility of a hand or arm. And we'll probably start losing our eyesight as we age too and need additional help from various assistive technologies. I once worked in the same office with someone who was nearly blind and they navigated the web with a specialized device that blew up the portion of the screen they were looking at so that only a couple of very large words could be seen at a time (approximately 1000% zoom - Firefox maxes out at 300% zoom).
Enter the concept of accessible software. On the surface, we want to make sure our widgets reach the broadest possible audience of users. The reality is that, unless we already use assistive technologies like NV Access because we need to, we aren't really prepared for the culture shock that goes with those technologies.
Screen readers are the primary thought behind most accessibility efforts but that only addresses one people group - those with vision impairment. How vision impaired users use screen readers is more akin to reading and understanding the raw HTML in View Source at 4x speed all day, every day. That's not something the vast majority of web developers are used to.
Another thing to do is make sure every operation has multiple methods for accomplishing the same task. Some users will find it physically easier to use one method over another even if a particular method is less efficient. For example, a user might prefer using the Paste toolbar option in this widget on a desktop browser because it makes more sense to them and/or a browser plugin makes it easier to paste specific content while the vast majority of users will either right-click + Paste or use Ctrl + V on the keyboard as shortcuts in desktop browsers.
It's actually possible to, without proper direction, make an experience worse for users who use assistive technologies by improperly implementing ARIA attributes. What needs to happen is have an actual user of relevant technologies supply critical, continuous feedback on whether things are better or worse as adjustments are made to a widget or interface.
Enabling Multilingual Support
A good first implementation is to allow for language packs to be created. A language pack provides a set of direct string mappings from the source language to the target language. For this widget, I'm using a semi-rudimentary system via the options object to the FileExplorer class called 'langmap' and a function called Translate():
Translate() is a really simple function that gets called anywhere in the code that uses a string in English that will be emitted to the DOM. If it finds a matching string in langmap, it will use that string instead of the English language string. A translator making a language pack just needs to look for Translate calls in the code and create suitable mapping strings in an object.
But let's say you want to translate a dynamically generated string such as, "Selected 5 out of 10 items." We could do something along the lines of, "Selected %u out of %u items." with a classic sprintf()-style string handler. However, that forces the order of the 5 before the 10. In some languages, it might be more correct to present to the reader the 10 before the 5. For example, a literal translation from another language back into rough English might produce, "Items total 10 with 5 selected." A better solution is to use a formatted string with positional numerical tokens:
The FormatStr() function takes the output of Translate() and replaces tokens in the string with dynamic values. There is still some room for language-specific oddities to crop up such as comma vs. period when displaying large numbers, calendar dates, etc. However, Translate() and FormatStr() covers the vast majority of translation issues and also makes it easier to write widget code in the native language of the author.
A more traditional method is to use a placeholder value like "selected_items_count" and then map that string to a language string. The downside is that every language then requires a language pack and is more tedious to design the widget in the first place. Both approaches have pros and cons.
There are other considerations beyond string mappings and dynamic string generation when integrating multilingual support. One of those is left-to-right (LTR) vs. right-to-left (RTL) reading order. Many Middle Eastern languages (e.g. Arabic, Hebrew, Urdu), read and expect right-to-left user interfaces pretty much exclusively. A few other languages also used to be fairly RTL-heavy, but many people who read/write those languages have become used to seeing and reading in either direction these days, so it doesn't matter as much and, in some cases, LTR is preferred. In RTL mode, most things are flipped horizontally but there can be a mixture of RTL + LTR. Browsers can do most of the heavy lifting here with a few well-placed 'dir' attribute indicators. It helps to have someone who reads and speaks the language to provide good insight. RTL support can cause a variety of layout issues, so be prepared to possibly rewrite some code.
Another multilingual consideration has significant impact on the actual design of the widget itself: Words have different lengths in different languages and some words don't have a good, direct translation and may require additional words to clarify meaning. In general, assume that a single string will probably fit fine on one line and could wrap but that there will be display issues if two separate strings are placed onto the same line even in different columns (odd wrapping or cutoffs resulting in a variety of alignment issues). This doesn't mean you can't put multiple strings onto a single line, just be aware that multilingual-related issues with line length might crop up when doing so.
One more multilingual consideration is not related to words but icons. An icon with a word or even a letter in it is a translator's nightmare but even fully graphical icons may have a sufficiently different meaning in different cultures. For example, I once had a conversation with someone in the United Kingdom (UK) about the use of a padlock as a security icon. Their own cultural worldview was that padlocks offered little to no actual security and so the padlock icon did not equate to security in their mind based on their own local culture's perspective. This shows that even a well-recognized, purely graphical icon may not convey the intended or same meaning across cultures even within the same language group.
This is a huge topic that is virtually inexhaustible but those are some key highlight areas. In my opinion, as long as support exists in a widget to build a language pack that enables the widget to show 95% to 98% of all strings in a different language, then that's an excellent first step. The Translate() and FormatStr() functions also make it fairly easy to adopt such an approach.
Closures do a lot to secure a widget's internals from outside actors. Anything inside a closure is directly inaccessible to outside actors.
In my experience, the average website loads code from about 15 different domains. Anything other than that which is served directly from your own domain and code that you wrote yourself (or have independently vetted) is not secure. Period.
When building widgets like this one that will have access to sensitive server-side stuff such as file data and folders, it is also a good idea to take advantage of 'isTrusted' in keyboard, mouse, and touch event handlers. For instance, an attacker who gains access to the page could, for example, send simulated 'Ctrl + A' and 'Delete' keystrokes to the widget to automatically select all of items and then delete them. Fortunately, the browser will set 'isTrusted' to false for simulated events and true for real keyboard/mouse/touch events (with some exceptions for some browser extensions that the user specifically installed).
These are some additional tips that didn't really fit in anywhere else.
Don't be afraid to refactor anything at any point. That includes right up to release. For example, I wound up refactoring the status bar progress area almost last-minute because I realized that copy operations could potentially take quite a while. It hadn't come up during testing because I only simulated a 250ms delay and was primarily focused on getting clipboard copy/paste to work properly. I ended up grabbing most of the code for the file upload status bar and making it more generic and reusable. It took a few hours to refactor the code and then test to verify that I didn't break the uploader in the process of refactoring. Then I was able to reuse the refactored code to provide progress feedback during long copy operations.
Don't do a MVP if it can be avoided. The MVP, or Minimally Viable Product, can work fine for a complete application that receives regular, frequent updates. However, widgets and libraries, in my opinion, really need to be feature-complete and fully tested before going out the door. Nothing's worse than finding a widget or library that looks good on the surface but doesn't actually function properly, wasting lots of time shoehorning it into a solution.
Write your own great documentation after finishing the software but before releasing it. I've caught countless coding mistakes/errors over the years by doing this. I'll look at a piece of code while documenting it and say, "Huh. That's weird." And then realize it's a bug and then go and fix it and then come back and finish writing the documentation for it. It's that last extra pass over the code that really solidifies the software long before it enters the user's hands. Plus I get good documentation I can point users at, which automatically eliminates 95% of all of the support issues that arise.
Export enough useful functions/events. If I think my users might want to do something specific even if I don't initially plan on using that particular feature myself, I'll still export a potentially useful function or emit a custom event for it. Doing so saves me from dealing with a feature request from a user later on. I also find myself irregularly using the extra features myself.