Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
HTML5 Data and Services Cookbook

You're reading from   HTML5 Data and Services Cookbook Take the fast track to the rapidly growing world of HTML5 data and services with this brilliantly practical cookbook. Whether building websites or web applications, this is the handbook you need to master HTML5.

Arrow left icon
Product type Paperback
Published in Sep 2013
Publisher Packt
ISBN-13 9781783559282
Length 480 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Toc

Table of Contents (21) Chapters Close

HTML5 Data and Services Cookbook
Credits
About the Authors
About the Reviewers
www.PacktPub.com
Preface
1. Display of Textual Data 2. Display of Graphical Data FREE CHAPTER 3. Animated Data Display 4. Using HTML5 Input Components 5. Custom Input Components 6. Data Validation 7. Data Serialization 8. Communicating with Servers 9. Client-side Templates 10. Data Binding Frameworks 11. Data Storage 12. Multimedia Installing Node.js and Using npm Community and Resources Index

Displaying metric and imperial measurements


Websites that deal with calculations and measurements often need to solve the problem of using both metric and imperial units of measurement. This recipe will demonstrate a data-driven approach to dealing with unit conversions. As this is an HTML5 book, the solution will be implemented on the client side rather than on the server side.

We're going to implement a client-side, "ideal weight" calculator that supports metric and imperial measurements. This time, we're going to create a more general and elegant data-driven solution that utilizes modern HTML5 capabilities, such as data attributes. The goal is to abstract away the messy and error-prone conversion as much as possible.

Getting ready

The formula for calculating the body mass index (BMI) is as follows:

BMI = (Weight in kilograms / (height in meters x height in meters))

We're going to use BMI = 22 to calculate the "ideal weight".

How to do it...

  1. Create the following HTML page:

    <!DOCTYPE HTML>
    <html>
        <head>
            <title>BMI Units</title>
        </head>
        <body>
            <label>Unit system</label>
            <select id="unit">
                <option selected value="height=m,cm 0;weight=kg 1;distance=km 1">Metric</option>
                <option value="height=ft,inch 0;weight=lbs 0;distance=mi 1">Imperial</option>
            </select><br>
    
            <label>Height</label>
            <span data-measurement="height" id="height">
                <input data-value-display type="text" id="height" class="calc">
                <span data-unit-display></span>
                <input data-value-display type="text" id="height" class="calc">
                <span data-unit-display></span>
            </span>
            <br>
            <label>Ideal Weight</label>
            <span data-measurement="weight" id="weight">
                <span data-value-display type="text">0</span>
                <span data-unit-display></span>
            </span> <br>
            
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
            <script type="text/javascript" src="unitval.js"></script>
            <script type="text/javascript" src="example.js"></script>
            </script>
        </body>
    </html>

    This page looks very much like the regular page we would make for a BMI-based ideal weight calculator. The main differences are as follows:

    • We have an imperial/metric selection input

    • We also have additional custom data attributes to give special meanings to the HTML fields

    • We use data-measurement to denote the kind of measurement that the element will display (for example, either the weight or the height)

    • We use data-display-unit and data-display-value to denote fields that display unit strings and values of the measurement respectively

  2. Create a file named example.js with the following code:

    (function() {
        // Setup unitval
        $.unitval({
            weight: {
                "lbs": 0.453592, // kg
                "kg" : 1 // kg
            },
            height: {
                "ft"  : 0.3048, // m
                "inch": 0.0254, // m
                "m"   : 1, // m
                "cm"  : 0.01, // m
            }
        });
        $("#unit").change(function() {
            var measurementUnits = $(this).val().split(';').map(function(u) {
                var type_unitround = u.split('='),
                    unitround = type_unitround[1].split(' ');
                return {
                    type: type_unitround[0],
                    units: unitround[0].split(','),
                    round: unitround[1]
                };
            });
            // Setup units for measurements.
            $('body').unitval(measurementUnits);
        });
    
        $("#unit").trigger("change");
    
        $('#height').on('keyup change',function() {
            var height = $('#height').unitval(), bmi = 22;
            var idealWeight = bmi * height * height;
            $("#weight").unitval(idealWeight);
        });
    
    }

    The first part of the code configures a jQuery plugin called unitval, with the conversion factors for the measurements and units that we are going to use (weight and height).

    The second part sets the measurement units for the document by reading the specification from the select field. It specifies an array of measurements, each having the following:

    • A type string, for example "height"

    • A list of units, for example ["ft", "inch"]

    • The number of decimals to use for the last unit

    The third part is a regular calculator that is written almost exactly as it would be written if there were no unit conversions. The only exception is that values are taken from the elements that have the data-measurement attribute using the jQuery plugin named $.unitval.

  3. We're going to write a generic unit converter. It will need two functions: one that will convert user-displayed (input) data to standard international (SI) measurement units, and another to convert it back from SI units to user-friendly display units. Our converter will support using multiple units at the same time. When converting from input, the first argument is the measurement type (for example, distance), the second is an array of value-unit pairs (for example, [[5, 'km'], [300,'m']]), a single pair (for example [5,'km']), or simply the value (for example 5).

  4. If the second parameter is a simple value, we're going to accept a third one containing the unit (for example 'km'). The output is always a simple SI value.

    When converting a value to the desired output units, we specify the units as an array, for example, as either ['km', 'm'] or as a single unit. We also specify rounding decimals for the last unit. Our output is an array of converted values.

    Conversion is done using the values in the Factors object. This object contains a property for every measurement name that we're going to use. Each such property is an object with the available units for that measurement as properties, and their SI factors as values. Look in the following in example.js for an example.

  5. The source code of the jQuery plugin, unitval.js, is as follows:

    (function() {
        var Factors = {};
        var Convert = window.Convert = {
            fromInput: function(measurement, valunits, unit) {
                valunits = unit ? [[valunits, unit]] // 3 arguments
                    : valunits instanceof Array && valunits[0] instanceof Array ? valunits  
                    : [valunits]; // [val, unit] array
                
                var sivalues = valunits.map(function(valunit) { // convert each to SI
                    return valunit[0] * Factors[measurement][valunit[1]];
                });
                // sivalues.sum():
                return sivalues.reduce(function(a, e) { return a + e; });
            },
            toOutput: function(measurement, val, units, round) {
                units = units instanceof Array ? units : [units];
                var reduced = val;
                return units.map(function(unit, index) {
                    var isLast = index == units.length - 1,
                        factor = Factors[measurement][unit];
                    var showValue = reduced / factor;
                    if (isLast && (typeof(round) != 'undefined'))
                        showValue = showValue.toFixed(round) - 0;
                    else if (!isLast) showValue = Math.floor(showValue);
                    reduced -= showValue * factor;
                    return showValue;
                });
            }
        };
        $.unitval = function(fac) {
            Factors = fac;
        }
        // Uses .val() in input/textarea and .text() in other fields.
        var uval = function() {
            return ['input','textarea'].indexOf(this[0].tagName.toLowerCase()) < 0 ?
                    this.text.apply(this, arguments) : this.val.apply(this, arguments);
        }
  6. Our generic convertor is useful, but not very convenient or user friendly; we still have to do all the conversions manually. To avoid this, we're going to put data attributes on our elements, denoting the measurements that they display. Inside them, we're going to put separate elements for displaying the value(s) and unit(s). When we set the measurement units, the function setMeasurementUnits will set them on every element that has this data attribute. Furthermore, it will also adjust the inner value and unit elements accordingly:

    // Sets the measurement units within a specific element.
    // @param measurements An array in the format [{type:"measurement", units: ["unit", ...], round:N}]
    // for example [{type:"height", units:["ft","inch"], round:0}]
        var setMeasurementUnits = function(measurements) {
            var $this = this;
            measurements.forEach(function(measurement) {
                var holders = $this.find('[data-measurement="'+measurement.type+'"]');
                var unconverted = holders.map(function() { return $(this).unitval(); })
                holders.attr('data-round', measurement.round);
                holders.find('[data-value-display]').each(function(index) {
                    if (index < measurement.units.length)    
                        $(this).show().attr('data-unit', measurement.units[index]);
                    else $(this).hide();
                });
                holders.find('[data-unit-display]').each(function(index) {
                    if (index < measurement.units.length)    
                        $(this).show().html(measurement.units[index]);
                    else $(this).hide();
                });
    
                holders.each(function(index) { $(this).unitval(unconverted[index]); });
            });
        };
  7. As every element knows its measurement and units, we can now simply put SI values inside them and have them display converted values. To do this, we'll write unitval. It allows us to set and get "united" values, or set unit options on elements that have the data-measurement property:

        $.fn.unitval = function(value) {
            if (value instanceof Array) {
                setMeasurementUnits.apply(this, arguments);
            }
            else if (typeof(value) == 'undefined') {
                // Read value from element
                var first       = this.eq(0),
                    measurement = first.attr('data-measurement'),
                    displays    = first.find('[data-value-display]:visible'),
                    // Get units of visible holders.
                    valunits = displays.toArray().map(function(el) {
                        return [uval.call($(el)), $(el).attr('data-unit')] });
                // Convert them from input
                return Convert.fromInput(measurement, valunits);
            }
            else if (!isNaN(value)) {
                // Write value to elements
                this.each(function() {
                    var measurement   = $(this).attr('data-measurement'),
                        round         = $(this).attr('data-round'),
                        displays      = $(this).find('[data-value-display]:visible'),
                        units         = displays.map(function() {
                            return $(this).attr('data-unit'); }).toArray();
      var values = Convert.toOutput(measurement, value, units, round);
                    displays.each(function(index) { uval.call($(this), values[index]); });
                });
            }
        }
    }());

This plugin will be explained in the next section.

How it works...

HTML elements have no notion of measurement units. To support unit conversion, we added our own data attributes. These allow us to give a special meaning to certain elements—the specifics of which are then decided by our own code.

Our convention is that an element with a data-measurement attribute will be used to display values and units for the specified measurement. For example, a field with the data-measurement="weight" attribute will be used to display weight.

This element contains two types of subelements. The first type has a data-display-value attribute, and displays the value of the measurement (always a number). The second type has a data-display-unit attribute, and displays the unit of the measurement (for example, "kg"). For measurements expressed in multiple units (for example, height can be expressed in the form of "5 ft 3 inch"), we can use multiple fields of both types.

When we change our unit system, setMeasurementUnits adds additional data attributes to the following elements:

  • data-round attributes are attached to data-measurement elements

  • data-unit attributes containing the appropriate unit is added to the data-display-value elements

  • data-display-unit elements are filled with the appropriate units

As a result, $.unitval() knows both the values and units displayed on every measurement element on our page. The function reads and converts the measurement to SI before returning it. We do all our calculations using the SI units. Finally, when calling $.unitval(si_value), our value is automatically converted to the appropriate units before display.

This system minimizes the amount of error-prone unit conversion code by recognizing that conversions are only really needed when reading user input and displaying output. Additionally, the data-driven approach allows us to omit conversions entirely from our code and focus on our application logic.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image