Powerful user interactions
Imagine the following -
You can do all the following operations using the same experience
- Select and load a data file
- Choose a location where to save the data file
- Choose master data for creation or alteration
- Configure user preferences
- Look for and enable new features in the application
- View 'advanced' and hence 'hidden' information
- Search and open a report
- Navigate to additional information via a related report
- Choose data for entry
Well, this needn't be imagined. This is what TallyPrime provides.
A seamless experience where users can perform all these diverse operations without having to 'learn' each of these patterns separately is delightful. The convenience of just typing and getting what you want is a joy in itself. Users can achieve an unprecedented speed of working in such an environment, as they don't have to learn each interaction separately. The system behaves consistently with the user’s expectations and is super smooth, even with all the diversity each of these functional areas brings. Most of these areas have immense diversity and scale, ranging from types of masters, count of masters, types of reports, modules enabled, geography of business, versions of data files, user rights, binary choice, multi-choice and free text configurations, simple and more advanced configurations, frequent and non-frequent configurations and and so on.
When all of the above aspects come together, the end task gets done much faster, leaving the user delighted and accomplished! The application works as a user's superpower in getting the work done.
A functionality-heavy application that is used both regularly and for long hours needs to minimize the cognitive load on the user. This is what we set out to solve a while back!
How to create this experience, and why is it challenging?
The sheer diversity of use cases and the exceptions in each of the areas listed above is a great challenge. Imagining a single experience for these is a herculean task in itself, let alone building one! User interaction, functional use cases, user behaviors across segments, and user roles create a mind-boggling diversity. Add to it the dimension of future extensibility, and we have a super exciting challenge to solve.
Even if we take for granted (for a moment) that a solution is designed and available to solve the functional and experience requirements, engineering aspects make it a non-trivial activity. Such rich functionality can't be built and maintained by a single team. Ensuring consistency is a challenge when different teams develop different functionalities.
Further, with a high volume of code come the chances of introducing bugs and drifts. With such scale and diversity come the aspects of performance as well, and maintainability of the code - remember we are in it for the long haul!
Finding Nemo, we mean finding solution 😊!
After studying diversity, building blocks, user interactions, and tasks to be done, we narrowed down the 'selection list' as one of the crucial artifacts to take on the task. Internally known as 'Table,’ it has been used for decades by millions of users. As such, we did not have any issues to solve; we only wanted to take the experience to a whole new level by tapping into the vast potential of a selection list. We evaluated our entire product and crafted the desired experience that we wanted to create with the selection list. Furthermore, our application programmers must also find it easy to handle diversity and achieve desired quality. It would be excruciating to code such diverse requirements in 1000s of places. It required a step back and revamp of how our platform interprets a Table. In essence, we set out to better something which was already accepted and loved by millions of our customers.
Build entire stack from scratch
For the rest of the article to make sense, it is important to call out a few key elements.
We have built the entire stack from scratch, which means we have our own DB, and UI engine and nothing is used off the shelf (except the C++ language 😊).
The product interface is written in TDL (Tally Definition Language), which has been developed in-house. This means all elements such as buttons, menus, selection lists are implemented by our own UI engine, and we have the control over their behaviors.
Selection list: World's take vs. Ours!
A selection list is a collection of values that allows users to select a value in a field. This is how the world sees a selection list, often worded as Drop-down list, list box, table, picklist etc. It is used to control the user’s input to a field, instead of free text, the user can ‘select’ from a set of predefined values. Now the moment you see a list, you naturally expect some kind of searching within the list, which we get in most of the applications, but we felt that there is so much untapped potential in a ‘selection’ list.
At Tally, our table has always been quite powerful. We use it to select a value from several thousands of possible options. With a reducing search, multi-column search, and highlighting the typed characters, searching and selecting is already very fast and has helped achieve our ‘super-fast data entry’ objective. However, we knew we could do much more than this, allowing us to reach the scale we want to!
Solution
Small, independent behaviors
As part of the revamp, we quickly concluded that we would need small and independent building blocks or behaviors that we needed to design. After carefully studying various aspects, such as user interaction, behavior when the user presses enter, select-ability of the list element and so on, we finalized what various behaviors would exist. Some of them are -
- Configuration
- Action
- Label
- On The fly
- ….
We can understand some of these behaviors in a little more detail before proceeding -
Configuration
This can be visualized as a preference or a setting. For example, whether the user wants to use the application in greyscale or full-color mode is a binary configuration. Choosing among different predefined styles of display of master information is a multi-choice configuration and port number to listen on is a free-text configuration.
Action
We define action as a predefined set of options that allow the user to change how the current view appears. For example, choosing to show or hide advanced options in a set of configurations is an action. Similarly, viewing inactive masters in a list is also an action in the table.
Label
These are 'headers' that allow for better classification of information in a Table and simplify data scanning. These typically have no action associated other than 'expanding' and 'collapsing' to make a list smaller/bigger.
On the fly
We have enhanced the table to accept dynamic values from user, different from values already present in the table. This is achieved through ‘on the fly’ element behavior.
Thus, now an element in table could behave in one of the predefined manners. The job of the application programmer is now to only map the behaviors to one of these! That too becomes a super simple job because of the extensive use of templates.
Object behavior list
enum eTableElementBehaviorType { OBT_CONFIG,
OBT_ACTION,
OBT_ON_THE_FLY,
OBT_LABEL,
OBT_DEFAULT,
….
};
More on the behaviors
Along with the effect of what happens when a user 'acts' on an element, there are more characteristics to a behavior which we were able to embed owing to this approach.
Configuration behavior
As we saw above, configurations let the user set their preferences and come in all sorts of diversity with respect to possible choices, data types, and how the selection is made. Introducing configurations as a list in Table has given users the ability to search and configure. This allows for unprecedented speed, with the same configs now being possible with 10x fewer keystrokes as compared to existing product.
For OBT_CONFIG, we have a very nuanced characteristic that aids intuitive usage but is very easy to miss as it is deeply embedded! Let’s look at it with some more details.
- The 'labels' in table are initially closed.
- On typing, the element becomes visible.
- Upon acting on this configuration, ALL configurations of this group/family become visible, while other groups remain closed!
- All of this behavior is abstracted again, based on OBT_CONFIG!
Label behavior
For OBT_LABEL, we have various behaviors built in. These include
- Auto Styling (bold)
- Clear the text in field
- Auto action behavior (enter will toggle visibility of child elements)!
Processing of object types
EditField::AcceptField
{
switch (((TableNode *)GetObjBehaviorType())
{
case OBT_CONFIG:
{
/*Place Cursor at Start Position*/
vEditInfo->uCurPtr = vString;
vEditInfo->uPos = 0;
/* Set Text in Replacable mode */
vEditInfo->uKeyPressed = 0;
/* If Parent is Not exploded - Explode */ ModifyExplodeStateOfSelectedNode(vEditInfo->uTableInfo);
return KEY_NOACTION;
}
case OBT_LABEL :
{
/* Label filed Clear Text for Edit filed*/
pField->CopyString (_TEXT(""));
return ETB_EXPLODE;
}
}
Obsessed with NFRs
As mentioned above, we are quite finicky about non-functional aspects as well. Since now we have got a glimpse of how we engineered few aspects of our 'table', let us look at how this approach allows us to meet our goals of NFRs. This section talks about some of them (not exhaustive!).
Simplicity - the most simple experience, yet!
The experience is very intuitive and hence simple to use. Simple, uncluttered visual layout, very few key keystrokes to operate, and applicability of interaction patterns across the product create a simple user experience.
Speed - get work done, faster!
User can get the work completed extremely fast as the information is available in a more accessible manner, diversity can be explored from a common place, and action can be taken in place. In one of our internal demos, the audience had difficulty believing the product is being operated live due to the speed with which folder navigation and file selection was done! The same benefits are available in application configurations, master operations, transactions, and many more areas.
Faster development cycle, fewer bugs
For all the N features, a particular behavior has been coded only once, implying no testing is required for all those N features! This reduces the overall development cycle and also reduces the number of bugs.
Consistency - guaranteed!
All the properties of a behavior (as seen in the code above) are defined once; hence all such items inheriting the behavior have the same properties even though used in N totally different features! - thus guaranteeing consistency and hence easy learnability.
Extensibility - add new behaviors in future!
Just like we built the behaviors of Configuration, Label, Action etc. in the future it becomes super easy to add a new behavior. Who knows some day we will add a OBT_ANIMATE and introduce an animation behavior to an item in the list!
Easier to work with
We have spent dedicated time in keeping the TDL APIs simple to understand and implement. For example, one simply has to write 'Behave As : Label' in the object. This makes the overall process of the development of different layers quite smooth and fun.
[Object: Cfg Settings Login] Name : @@CfgSettingsLogin Behave As : Label ...
We've built independent components and behaviors that are simple and serve a single purpose. However, when combined with other such behaviors, complex and incredibly rich experiences emerge, thus playing a small but crucial role in achieving our vision!
Powerful product enabled by technology
At Tally, we build technologies that enable us to build 10x better products. TallyPrime is a functionally rich product. It caters to a diverse set of audience (2M+ businesses), in a diverse set of geographies(100+ countries), and a diverse set of industries, all from a single application with a single code base!
Such a powerful product can be powered only by equally deep and powerful engineering. Speed of the user while operating the product and consistency of experience are crucial in a product of this nature. This, however, is super difficult to achieve given the sheer diversity the product caters to. The team at Tally is obsessed with Non Functional attributes of the product. Some of these are speed, simplicity, reliability and consistency. This, after all, has played a key role in getting the product to where it is today.
Hopefully, you enjoyed reading this, as much as we enjoyed solving the problem and writing this article!
Stay tuned for the next one in series!
Read More: