Skip to main content Skip to footer

Reducing Code Size Using Wijmo

The new year has arrived, and one of my resolutions is to get a little bit leaner by cutting down on my code size! Trimming down code size in your app can be tough, but we're going to guide you through some ways in which Wijmo can help you burn off some of those SLOC (source lines of code). Before we jump in, let's loosen up and stretch our minds by talking about code size itself. If you'd like, you can skip right to the code example.
_All of the code for this sample is also available on GitHub._

Why worry about code size?

There are several very important and high-impact reasons to lose code size, which we'll break down into three key categories: maintainability, efficiency, and performance. These categories are listed in no particular order, and they're all equally significant to your app's overall quality.

Maintainability

The amount of code in an app is inversely proportional to its maintainability.

When considering code size, remember that it not only affects the end-user, but also the developer and their experience working with the app's source code. Of course, the nature of some applications requires them to have a large codebase, but in general, the amount of code in an app is inversely proportional to its maintainability. (This is, of course, excluding code comments. which almost always positively impact maintainability and readability.) The explanation for this trend is simple, and it comprises the "Occam's razor of software design": too many code points to unused or unnecessary code creates confusion for developers. For most developers, just mentioning this issue conjures up frustrating memories. We've all experienced slowdowns resulting from trying to understand code written with obscure methods or unnecessary references. Even worse, most developers are familiar with the frustration of interpreting cumbersome code, only to find that it's not even used in the application containing it! While cutting down on code size can't eliminate this design flaw on its own, it's still a good place to begin if you want to:

  • Make your code more maintainable for future developers;
  • Easier to read for your team members; or
  • More tailored to you for your own development purposes.

Efficiency

Keeping code light and lean means fewer teams are needed to manage it.

Code size doesn't just affect the maintainability of your program on an individual or team level for developers. It also affects productivity and workflow at the enterprise level. I'll refer to this property as "efficiency." Even if our workplaces don't directly utilize the old adage, most of us have felt the pressure it implies: "time is money." So how does code size affect the time that developers spend working on an application? I already mentioned one impact: The amount of code in an app is inversely proportional to its maintainability. The more time developers spend sifting through code, the less time they spend adding new features, debugging issues, or brainstorming new projects to work on. Here's another way code size affects efficiency: Compilation time for a given application will almost always increase as the SLOC increase. These time expenditures add up, and they can cause slowdowns in the overall workflow. Increased code size also adds time to the deploy process for any app, and that adds up. Lastly, a large codebase may lead to over-organization of business structure. Keeping code light and lean means fewer teams are needed to manage it. Following that theme, fewer development teams means less time spent coordinating between them. Ultimately, efficiency benefits from a smaller codebase for all of these reasons. Aside from time cost, one other efficiency factor tied to code size is storage size. Intuitively, apps with more code take up more space. Whether you're building a desktop app for employees at your company or a web app for the rest of the world, it never hurts to use less server or local hard drive storage if you can.

Performance

Even subsecond reductions in loading time can retain a measurable percentage of users.

The single most important reason to shed code size (especially in a web app) is to improve performance. While maintainability and efficiency are internal issues affected by code size, performance directly impacts the end-user. If you still need a reason to lose some code size, this is it. In any application, the size of its codebase hints at its performance, but web apps are really the only instance where code size directly impacts performance. This direct relationship is derived from a simple source. Any client accessing a web app has to load all of that app's assets from a server (disregarding caching), and that includes the app's codebase, at least the front-end portion of it. Being mindful of the page-load impact is important for 2 reasons:

  1. Accessibility: Over 60% of internet users worldwide are accessing the internet via 2G data connections. That's a connection with speeds on the order of kilobits per second (Kb/s)—think 56K modems. Even if you're building a web app for internal (company) use, it's good practice to design for the worst case scenario. For example, say one day your company's internet service provider is having unexpected connection speed issues. Could employees still load your app, or would it take tens of minutes? These are just some considerations to make when thinking about page load times.
  2. User behavior: users on the web are much quicker to abandon an application due to its load time than on the desktop. Recent Google studies have shown that even subsecond reductions in loading time can retain a measurable percentage of users. Conversely, small increases in loading time can drive users away! The gist is: web users are more flighty than desktop or even mobile app users. Keeping their attention by quickly delivering content is vital.

So, with this very simplistic view of web app performance in mind, how can we reduce page load times? Some developers leverage caching to reduce page load time after an initial visit. Most web servers compress server files before sending them to clients by default. Others address the problem at its root and reduce the size of their codebase as much as possible. While most web apps use a combination of approaches to increase performance, let's focus how reducing code size can have a positive impact... and how Wijmo can help.

How can Wijmo help?

While adding Wijmo to your app does mean loading another library, it's likely more efficient and performant than using in-house controls that have not undergone years of optimization.

On the surface, using Wijmo might seem like an increase of an app's code size, but in reality, Wijmo can decrease the size of your codebase in multiple ways. Let's examine a few of them based on the properties we already talked about:

  • Maintainability: Wijmo abstracts all of the methods involved in displaying and customizing UI controls. With one of the largest sets of web UI controls available, Wijmo eliminates the need to maintain the code for most of a web application's UI in-house. Since UI code is typically highly reusable, referencing it from a separately maintained module can make code much easier to understand. Wijmo also boasts one of the most complete API references in the web control field. Being able to quickly and easily locate API documentation reduces the learning curve for new developers.
  • Efficiency: Similarly, because Wijmo provides a comprehensive set of UI controls, development teams can focus on writing application-specific logic. Rather than spending hours of development time writing a complex chart control or specialized combo box, teams can use Wijmo controls and focus on how their app needs to use these controls. Incorporating Wijmo can save hours upon hours of development time and increase efficiency. This increase in efficiency comes at a low cost to storage space. Wijmo's libraries are minified and modular, and you can only load the ones you need for your project.
  • Performance: While Wijmo's development team adds new features frequently, they also focus on optimizing existing controls with each iteration. This means that Wijmo's UI controls are written using the most concise code. While adding Wijmo to your app does mean loading another library, it's likely more efficient and performant than using in-house controls that have not undergone years of optimization. Wijmo UI controls also integrate with one another using reusable, abstract functions. As you use more UI controls in your application, you compound the performance increase when using Wijmo.

Overall, Wijmo diminishes the code size's negative effects by handling the UI branch of development. Wijmo abstracts UI control code so that you can easily reuse it. This reusable code is offered up in compact modules that you can selectively load into your application, a modular approach that increases the maintainability, efficiency, and performance of your application and its development workflow. Lastly, Wijmo is designed with large datasets in mind. Our UI controls offer impressive post-page load performance when working with these large groups of data. Now let's see an example of how Wijmo can help reduce code size.

The Periodic Table Sunburst Chart Example

A few weeks ago, I wrote another post detailing how to use Wijmo's new Sunburst Chart control to create a new representation of the periodic table of the elements. As the keen reader may have observed, that post used quite a bit of unnecessary code. I was deliberately less concise than I could have been so I could demonstrate how Wijmo's built-in utilities can tremendously reduce code size. This example will be especially useful for the web app developer who's already using Wijmo UI controls, but who's looking to optimize their app without adding more libraries.

Structuring Data Using CollectionView

In the original Periodic Table Sunburst Chart sample, the data is prepared by manually crawling the JSON source, analyzing each node, and sanitizing it for display on the chart. The data was ultimately passed to the chart as a hierarchical array. This method of data preparation involved several computationally expensive and verbose array-related functions:

// Define constants that we need to properly group elements const metalTypes = 'Alkali Metal|Alkaline Earth Metal|Transition Metal|Lanthanide|Actinide|Metal'.split('|'); const nonmetalTypes = 'Noble Gas|Halogen|Nonmetal'.split('|'); const otherTypes = 'Metalloid|Transactinide'.split('|');

// These are all ordered by the type lists above // The metal and nonmetal descriptions have one extra item for the "Others" category const metalTypeDescriptions = 'Shiny,Soft,Highly Reactive,Low Melting Point|Ductile,Malleable,Low Density,High Melting Point|High Melting Point,High Density|Soluble,Highly Reactive|Radioactive,Paramagnetic|Brittle,Poor Metals,Low Melting Point'.split('|'); const nonMetalTypeDescriptions = 'Toxic,Highly Reactive,Poor Conductors|Colorless,Odorless,Low Chemical Reactivity|Volatile,Low Elasticity,Good Insulators'.split('|'); const otherTypeDescriptions = 'Metallic looking solids,Semiconductors|Radioactive,Synthetic Elements'.split('|');

/** * Loads the data to display in the Sunburst chart and formats it for delivery * * @param {dataSourceLoadedCallback} callback */ var getChartDataSource = function (callback) { let groupsCollection = []; // declare an empty array to add the finished groups to - this is what we will ultimately send as a payload

let metals = new Group('Metals');
// Add all of the metals subGroups
for (let i = 0; i < metalTypes.length; i++) {
    if (metalTypes[i] === 'Metal') {
        metals.subGroups.push(new SubGroup('Other Metals'));
    } else {
        metals.subGroups.push(new SubGroup(metalTypes[i] + ((metalTypes[i].slice(-1) === 's') ? 'es' : 's')));
    }
    metals.subGroups[i].characteristics = metalTypeDescriptions[i];
}

let nonmetals = new Group('Nonmetals');
// Add all of the nonmetal subGroups
for (let i = 0; i < nonmetalTypes.length; i++) {
    if (nonmetalTypes[i] === 'Nonmetal') {
        nonmetals.subGroups.push(new SubGroup('Other Nonmetals'));
    } else {
        nonmetals.subGroups.push(new SubGroup(nonmetalTypes[i] + ((nonmetalTypes[i].slice(-1) === 's') ? 'es' : 's')));
    }
    nonmetals.subGroups[i].characteristics = nonMetalTypeDescriptions[i];
}

let others = new Group('Others');
// Add all of the other subGroups
for (let i = 0; i < otherTypes.length; i++) {
    others.subGroups.push(new SubGroup(otherTypes[i] + ((otherTypes[i].slice(-1) === 's') ? 'es' : 's')));
    others.subGroups[i].characteristics = otherTypeDescriptions[i];
}

// Retrieve an array listing of all elements
// TODO: Build in true async functionality so that we can pause execution
// until the JSON is loaded and parsed
getObjectFromJson('data/elements.json', function (jsonObj) {
    let jsonElementArray = jsonObj['periodic-table-elements'];
    // Loop through all of the elements from the JSON and turn them into Element objects
    // then sort them into their groups based on type
    for (let i = 0; i < jsonElementArray.length; i++) {
        // Make a new Element object for us to work with
        let currentElement = new Element(jsonElementArray[i]);

        // Declare empty currentGroup and currentSubGroup variables then set them based on
        // the element type
        let currentGroup, currentSubGroup;
        if (metalTypes.indexOf(currentElement.type) !== -1) { // it is a metal!
            currentGroup = metals;
            if (currentElement.type === 'Metal') { // these belong in the "Others" category
                currentSubGroup = metals.subGroups[metals.subGroups.length - 1]; // Others will always be the last SubGroup in the array
            }
        } else if (nonmetalTypes.indexOf(currentElement.type) !== -1) { // it is a nonmetal!
            currentGroup = nonmetals;
            if (currentElement.type === 'Nonmetal') { // these belong in the "Others" category
                currentSubGroup = nonmetals.subGroups[nonmetals.subGroups.length - 1]; // Others will always be the last SubGroup in the array
            }
        } else { // it is...something else
            currentGroup = others;
        }
        // If the SubGroup wasn't defined above then we need to use the array's find method in conjunction with
        // our custom function to locate the SubGroup that matches the element's type
        if (typeof (currentSubGroup) === 'undefined') currentSubGroup = currentGroup.subGroups.find(subGroupMatchesElementType, currentElement);
        currentSubGroup.elements.push(currentElement);
    }

    // Add our constructed groups to the master collection
    groupsCollection.push(metals);
    groupsCollection.push(nonmetals);
    groupsCollection.push(others);

    // Deliver the collection payload to the callback
    callback(groupsCollection);
});

}

/** * A function designed to be used with the array.find() method to search for a SubGroup that matches a given element * * @param {Object} subGroup the SubGroup object to compare an element's type to * @returns {boolean} true for a match and false for no match */ function subGroupMatchesElementType(subGroup) { return subGroup.subGroupName === this.type + ((this.type.slice(-1) === 's') ? 'es' : 's'); }

This code clearly isn't the prettiest, and it takes up a good amount of code real estate (around 50 SLOC). Much of this code is comprised of logic for iterating through arrays and picking out elements based on the raw JSON data associated with them. There's also a significant amount of processing that must be done to the JSON before loading so that it can be piped into the Sunburst Chart. Wijmo provides an extremely handy utility that can make data sorting and preparation processes like this one much easier. This utility is the CollectionView object. The CollectionView object contains several built-in functions and properties for storing, filtering, and sorting collections of objects. One of its most useful functions is sorting hierarchical data one level at a time using the PropertyGroupDescription helper. This helper sorts objects into sub-collections based on one of their specific properties. Taking advantage of these utilities lets us transform the code above into logic that's much easier to read and also drops several SLOC:

// The file path for the element JSON data var ELEMENT_DATA_FILE_PATH = 'data/elements.json';

// Arrays named by "Group" containing all of the possible "Subgroup" types var METAL_TYPES = 'Alkali Metal|Alkaline Earth Metal|Transition Metal|Lanthanide|Actinide|Metal'.split('|'); var NON_METAL_TYPES = 'Noble Gas|Halogen|Nonmetal'.split('|'); var OTHER_TYPES = 'Metalloid|Transactinide'.split('|');

// Separate out the titles that will be on the chart as constants so that they can be // easily changed as options later var METALS_TITLE = "Metals"; var NON_METALS_TITLE = "Nonmetals"; var OTHERS_TITLE = "Others";

WijmoPeriodicSunburst.ElementDataLoader = { generateElementCollectionView: function (callback) { WijmoPeriodicSunburst.XhrFileLoader.readPlaintextContents(ELEMENT_DATA_FILE_PATH, function (e) { // parse the loaded JSON into a variable var rawElementData = JSON.parse(this.responseText);

        // flatten the resulting raw element data array by removing the ID and "un-nesting" the properties object
        var elementData = rawElementData['periodic-table-elements'].map(function (item) {
            item.properties.value = 1;
            return item.properties;
        });

        // initialize a new object from the Wijmo CollectionView function using our "cleansed" array
        var elementCv = new wijmo.collections.CollectionView(elementData);
        // Do the first tier of grouping
        // We'll take advantage of the wijmo.collections.PropertyGroupDescription object to sort elements
        // in the collection view based on which constant array contains their type
        elementCv.groupDescriptions.push(new wijmo.collections.PropertyGroupDescription('type', function (item, prop) {
            if (METAL_TYPES.includes(item[prop])) {                    
                return METALS_TITLE;
            } else if (NON\_METAL\_TYPES.includes(item[prop])) {
                return NON\_METALS\_TITLE;
            } else {
                return OTHERS_TITLE;
            }
        }));

        // Do the second tier of grouping
        // The only consideration we have to make here is that we don't want to duplicate group names. So if
        // we find another "Metal" or "Nonmetal", we need to prefix it with "Other." Finally, we just want to
        // go ahead and add the appropriate plural ending to make things sound more natural
        elementCv.groupDescriptions.push(new wijmo.collections.PropertyGroupDescription('type', function (item, prop) {
            if (item[prop] === METAL\_TYPES[METAL\_TYPES.length - 1] || item[prop] === NON\_METAL\_TYPES[NON\_METAL\_TYPES.length - 1]) {
                return 'Other ' + item[prop] + (item[prop].endsWith('s') ? 'es' : 's');
            } else {
                return item[prop] + (item[prop].endsWith('s') ? 'es' : 's');
            }
        }));

        // Descriptions of the different subcategories ordered by the type lists above
        // The metal and nonmetal descriptions have one extra item for the "Others" category
        var METAL_DESCRIPTIONS = 'Shiny,Soft,Highly Reactive,Low Melting Point|Ductile,Malleable,Low Density,High Melting Point|Brittle,Poor Metals,Low Melting Point|High Melting Point,High Density|Soluble,Highly Reactive|Radioactive,Paramagnetic'.split('|');
        var NON\_METAL\_DESCRIPTIONS = 'Volatile,Low Elasticity,Good Insulators|Colorless,Odorless,Low Chemical Reactivity|Toxic,Highly Reactive,Poor Conductors'.split('|');
        var OTHER_DESCRIPTIONS = 'Metallic looking solids,Semiconductors|Radioactive,Synthetic Elements'.split('|');
        var DESCRIPTION\_COLLECTION = [NON\_METAL\_DESCRIPTIONS, METAL\_DESCRIPTIONS, OTHER_DESCRIPTIONS]; // create an array containing all of the element description arrays

        // Assign a new object property to each "subgroup" Object in the CollectionView based on the arrays above
        // This property will be stored in the CollectionView items and can be recalled later for display on the chart
        for (var i = 0; i < elementCv.groups.length; i++) {
            for (var j = 0; j < elementCv.groups[i].groups.length; j++) {
                elementCv.groups[i].groups[j].elementProperties = DESCRIPTION_COLLECTION[i][j];
            }
        }

        callback(elementCv);
    });
}

};

Weighing it at just over 30 SLOC, this reworked code is almost 40% lighter than the original sample. Not only this, but the sorting and array manipulation logic is all handled behind the scenes in a Wijmo module. This makes the code much more manageable, and it also increases workflow efficiency by implementing a deeper separation of concerns in your application's development team. On this small sample scale, using CollectionView saves some time and a few kilobytes of bandwidth. On a larger scale, implementing CollectionView when working with collection sorting, filtering, or grouping can significantly impact the overall size and performance of a web app using Wijmo.

Getting Chart Objects Using the hitTest Method

Another unnecessarily lengthy implementation in the original periodic table sample is in the logic used to display element information. Primarily contained in the ViewAdapter.js file, these functions map the label of a clicked chart item to its object representation. While this implementation also uses the hitTest method, it only extracts the data label from the returned HitTestInfo object:

/** * Gets an object associated with a panel on the Sunburst chart by looking up the data label/name for that panel * * @param {string} chartItemName the data label/name of a chart panel * @param {Object} chartCollectionView the chart's collectionView object * @returns {Object} a Group, SubGroup or Element object found by looking up the chartItemName */ var getObjectFromChartName = function (chartItemName, chartCollectionView) { let chartGroups = chartCollectionView.items; // grab the array of Group objects from the chart return findObjectWithMatchingName(chartGroups, chartItemName); };

/** * An internal function that recursively searches through the chart's object collection for an object with a name that matches the search term * * @param {Array} haystack an array of Objects to search through. In this case, this should be the root items array from a the Sunburst chart's collectionView * @param {string} needle a search term to look for in the object collection. This should be a panel data label/name from the chart * @returns {Object} a Group, SubGroup or Element if the lookup succeeds */ function findObjectWithMatchingName(haystack, needle) { // We should be entering this method with the haystack being an array of Groups, SubGroups, or Elements // We have to loop through the array and, depending on the type of object, check to see if it's what // we're looking for for (let i = 0; i < haystack.length; i++) { let currentObject = haystack[i]; if (currentObject instanceof Group) { // we have a Group if (currentObject.groupName === needle) { return currentObject; } else { let subSearch = findObjectWithMatchingName(currentObject.subGroups, needle); if (typeof (subSearch) !== 'undefined') { return subSearch; } } } else if (currentObject instanceof SubGroup) { // we have a SubGroup if (currentObject.subGroupName === needle) { return currentObject; } else { let subSearch = findObjectWithMatchingName(currentObject.elements, needle); if (typeof (subSearch) !== 'undefined') { return subSearch; } } } else { // must be an Element if (currentObject.eleSymbol === needle) return currentObject; } } }

Not only does this function take up a few (non-trivial) SLOC, but it also introduces some very rigid restrictions to the application. This code certainly can't be reused anywhere as it relies on the very specific data structures employed for the periodic table Sunburst Chart. In the revised sample, this file and the code contained within are removed altogether. Because we are now using a CollectionView to initialize the Sunburst Chart, the hitTest method gives us access directly to the clicked object, not just its data label.

    // Perform a hit test to get a clicked panel's name then use it to set up the info panel via the ViewAdapter
    let ht = mySunburst.hitTest(e.pageX, e.pageY);
    myPropTile.showInfoPanel(getObjectFromChartName(ht.name, mySunburst.collectionView));

becomes

    // Perform a hit test to get a clicked panel's name then use it to set up the info panel via the ViewAdapter
    var ht = mySunburst.hitTest(e.pageX, e.pageY);
    myPropTile.showInfoPanel(ht.item);

This is impossible in the original example because the chart's data source is simply a hierarchical array which eventually leads to objects. In the revised sample, the data source is a proper object collection represented using Wijmo's CollectionView. Putting the inherent subjectiveness aside, this code is much easier to read and it lets us avoid having to write and maintain our own array manipulation and lookup code. As previously discussed, this saves time and increases performance and efficiency.

How Much Did Wijmo Optimize?

Before jumping right into statistics, it's worth noting that the above two changes also allow the app to live free of the OOP-type data structure we forced on it in the original sample. The Group.js, Subgroup.js, and Element.js files are all gone, reducing the application size and simplifying it greatly. Here is the JavaScript file structure of our app before using the Wijmo utilities: - scripts -- app.js -- DataSourceAdapter.js -- Element.js -- Group.js -- JsonDataLoader.js -- PropertiesTile.js -- shim.js -- SubGroup.js -- ViewAdapter.js (9 files) And here is the file structure afterwards: - scripts -- app.js -- ElementDataLoader.js -- PropertiesTile.js -- XhrFileLoader.js (4 files) So by implementing Wijmo, we can reduce the number of JavaScript files needed to build out our application. This doesn't mean much directly in JavaScript, but it does indicate that we've reduced the number of concerns we need to focus on by passing off some of the tough work to Wijmo. But what about what's in these files? Did we actually reduce the size of our application? The answer is an overwhelming yes! By implementing built-in Wijmo methods (which we already had access to in the previous sample), the application shed over 100 SLOC and is now 6 KB smaller! Wait, only 6 KB? This might seem like an insignificant amount of code size to fret about, especially if you're only considering performance. But think about it this way: imagine that someone is loading those 6 KB over a 56K modem or (as over 60% of the world does) a 2G mobile connection. Those 6 KB could then result in up to 200 ms of additional loading time, which could be the difference between users leaving and staying. Most importantly, this reduction in code size is what we've observed for only a small sample. You can expect this number to grow with the overall size of an app, so implementing these code-saving Wijmo utilities could shave seconds off of loading time for a full-featured web app. This not only improves the user experience, but it could also be the determining factor in whether your app sinks or swims. The last consideration is just how much simpler the code becomes when we use the Wijmo utilities. It's more readable and more debuggable, and it only does what it truly needs to do. Why handle data organization for our chart when Wijmo can do it for us? Why map data labels to data objects when Wijmo can help us out by fetching an object instead? By passing off responsibility where we can, our application becomes something that developers enjoy working on rather than a burden. Most importantly, it leaves the development team open to add new user-facing features rather than devote time to writing under-the-hood logic from scratch. All in all, Wijmo takes on the burden of tedious data processing and interaction tasks to help us lose some code size and become more efficient developers.

View Sample

Try Wijmo

MESCIUS inc.

comments powered by Disqus