David J McClelland

Experiences at the Intersection of Programming & Design

Archive for the ‘Practice’ Category

Adding truncation feature to Axis Tick Labels in chart widget extension required the addition of dynamic properties.

Solution Phase One:

Use D3 to set titles on tick labels exceeding a user-configurable length:

if (thisWidget.truncateX !== 0) {
    d3.selectAll(widgetContainerId + '-chart .nv-x .nv-axis .tick text')
        .each(function (i) {
            var tick = d3.select(this);
            var fullText = tick.text();
            var truncText = fullText;
            // standard truncation with ellipses
            if (fullText.length > thisWidget.truncateX) {
                truncText = fullText.substring(0, thisWidget.truncateX) + '...';
            }
            // preceding truncation with ellipses
            if (thisWidget.truncateX < 0){
                truncText = '...' + fullText.substring(Math.abs(thisWidget.truncateX), fullText.length);
            }
            tick.text(truncText);
            tick
                .append("svg:title")
                .text(fullText)
    });
}

I Considered using an algorithm based on available space but this was complicated by text tilt option and the fact that it would have to calculate every label individually. Instead I added a property for X and Y axis truncation to the widget IDE. Note that using a negative number performs truncation at the start of the label string instead of the end. This is handy when labels suffer from an overuse of Hungarian notation.

Negative value leaves n characters at the end of the label and places a preceding ellipses

Standard truncation replaces limits to the first n characters and replaces the rest with an ellipses

I Used the standard Thingworx platform Tooltip library

…with the Widget chart extension to apply a tooltip to truncated text label’s title.

// qtip jquery extension over titles in d3 axis
$('title').each(function () { // Grab all elements with a title element,and set "this"
    $(this).parent().qtip({
        id : thisWidget.jqElementId,
        content: {
            text: $(this).text()
        },
        position: {
            target: 'mouse',
            adjust: {
                x: 10,
                y: 10
            }
        },
        style: {
            classes: 'widget-barChart-tooltipStyle' + customTooltipStyleName,
            widget: false, // Use the jQuery UI widget classes
            def: false // Remove the default styling (usually a good idea, see below)
        }
    });
    // remove title element
    $(this).remove();
});

I Removed the titles when I noticed browser tooltips poking through since titles are divs and not attributes in D3.

-12 truncation result

12 truncation result

 

I Used Qtip class name attribute to style tooltips according to widget property

This was the most confusing aspect of integrating the tooltips. Qtip lets you add a classname to the Qtip element that it puts on the Body DOM so you can create a style and have it apply to it (in style object in snippet above). Sounds simple! But QTip applies its own default class names to each tooltip on its own, and sometimes they superseded my custom classes. In the snippet above, classes is where a classname is defined, and widget and def are false to prevent the other classnames from being added by Qtip. The additional customToooltipStyleName is appended when the widget has a custom style applied to it. In this way, that classname matches the specific widget id, making it apply the custom style to that widget alone.

Default platform tooltip style

custom widget tooltip style

custom widget tooltip result

 

Solution Phase Two:

When multiple services provide data to Chart Widget, which one provides the label text?

I had sidestepped this issue in the original chart widget design by leaving out X-Axis Tick Labels. This was intentional: we released an MVP and wanted feedback from customers about what was important to them to include in a more elaborate solution. I added labels soon after release, but they only worked on single-service bound charts. Adding truncation to labels forced the issue that this effort should not be wasted on a feature that didn’t really work in every case.

Generate a service picker

Adding a property to the widget IDE to list off bound services seemed like it would be trivial, but this was not the case. There is no ready-made property that will display a drop-down list of data sources currently bound to a widget. There is no ready way to persist this information in the widget entity outside of the internal bindings definition, which is used by the Connections tab in the IDE and by Runtime to retrieve the data and update the widget display. This is not directly exposed in the widget API.

So, how to roll our own Bound Service Picker? I looked at how Collections widget used JSON to create and persist bindings and update the IDE properties from a service definition. I took this concept a significant step further by hiding the underlying JSON and using it to create a dropdown picker.

this.afterAddBindingSource = function (bindingInfo) { ...
this.afterRemoveBindingSource = function (bindingInfo) {...

These functions were the key to managing a list of binding: Every time anything is bound to the widget or unbound, one of these functions is called. The bindingInfo provides the name of the binding target property, and this enables selectively adding and removing dataSource names to a hidden string property that is constructed as a serialized array. The length of the array determines whether the DataSource picker is displayed in the IDE (since it is only required when 2 or more services are bound). The array also updates the selectOptions of the DataSource picker, which are the selections it displays in the dropdown.

Number of bound dataSources is reflected in properties. Each datasource has different labels

Short Labels from DataSource1

Long labels from DataSource2

Generate a Field picker

The same technique was used to populate a Field Picker. The contents of the field picker only needed to be retrieved when the first data source was bound, because every service bound to the Chart Widget has to share a common Data Shape. And the selectOptions of the Field Picker are the Field Definitions from the service Data Shape.

var dataShape = thisWidget.getInfotableMetadataForProperty(property) || {};

This function returns the fieldnames of a service when it is bound if it is called from within afterAddBindingSource() as value names. These are put in an array used to populate a Field Names dropdown property in the Widget IDE and serialized and put in a hidden string property for persistence.

The selected item in the pickers is persisted in the widget entity model and restored when the Mashup containing the widget Extension is reopened in Mashup Builder. If that were not the case, the selections can be monitored and set to hidden string properties as well. I set it up that way and then verified this additional step wasn’t needed.

Every Data Source has the same DataShape which in the example consists of these two field names.

Managing the Data Source and Field Name pickers

    /**
     * Invoked by the platform whenever the user binds a data source to a property on this widget.
     * @param bindingInfo <Object>        An object containing the newly created binding's properties.
     */

    this.afterAddBindingSource = function (bindingInfo) {
        var thisWidget = this;
        var property = bindingInfo.targetProperty;
        var properties = thisWidget.allWidgetProperties().properties;
var seriesNum = thisWidget.getProperty('NumberOfSeries');
        if( seriesNum === dataSources.length && seriesNum < thisWidget.MAX_SERIES){
            thisWidget.setProperty('NumberOfSeries', thisWidget.getProperty('NumberOfSeries') + 1);
        }
        // minimum binding requirement is met
        if (property.indexOf('Data') > -1) {
            this.jqElement.find(".configuration-warning").remove();
        }
        //adding multiple data services to populate chart - only need one dataShape - they all must be same
        if (property.indexOf('DataSource') > -1) {
            thisWidget.setProperty('SingleDataSource', false);
            var dataShape = thisWidget.getInfotableMetadataForProperty(property) || {};

            // build up an array of datashape names and types to allow user to pick dataSource to 
            // provide x-axis label field
            dataSourceList.push({'value': property, 'text' : property});
            // also provide a simple list of data sources to runtime to match them to the dataSource 
            //x-axis label field
            dataSources.push(property);

            // build field names list if it is not already defined
            if(String(thisWidget.getProperty('_dataShape')).length === 0) {
                for (var key in dataShape) {
                    // check also if property is not inherited from prototype
                    if (dataShape.hasOwnProperty(key)) {
                        var value = dataShape[key];
                        multiServiceDataShape.push({'value': value.name, 'text': value.name});
                        dataShapeList.push(value.name);
                    }
                }
            }

            properties['X-AxisLabelField']['selectOptions'] = multiServiceDataShape;
            properties['X-AxisLabelField']['isEditable'] = true;
            properties['X-AxisLabelField']['isVisible'] = true;

            thisWidget.setProperty('_boundDataSources', dataSources.join(','));
            thisWidget.setProperty('_dataShape', dataShapeList.join(','));

            properties['X-AxisLabelDataSource']['selectOptions'] = dataSourceList;
            properties['X-AxisLabelDataSource']['isEditable'] = true;
            properties['X-AxisLabelDataSource']['isVisible'] = true;

            this.setSeriesProperties(this.getProperty('NumberOfSeries'),
            this.getProperty('SingleDataSource'));
            this.updatedProperties();
        }
    };

    /**
     * Invoked by the platform whenever the user unbinds a data source to a property on this widget.
     * @param bindingInfo <Object>        An object containing the newly removed binding's properties.
     */

    this.afterRemoveBindingSource = function (bindingInfo) {
        var thisWidget = this;
        var property = bindingInfo.targetProperty;
        var properties = thisWidget.allWidgetProperties().properties;

        // remove the datasource that was unbound
        for(var i = 0; i < dataSourceList.length; i++){
            if(dataSourceList[i].value === property){
                dataSourceList.splice(i, 1);
            }
        }

        // dataSources is not in same order as dataSourceList so splice separately
        for(var i = 0; i < dataSources.length; i++){
            if(dataSources[i] === property){
                dataSources.splice(i, 1);
            }
        }

        thisWidget.setProperty('_boundDataSources', dataSources.join(','));

        if (property.indexOf('DataSource') > -1) {

            // the X-AxisLabelField doesn't provide any functionality when there is one binding
            if (dataSources.length <= 1){
                properties['X-AxisLabelField']['isVisible'] = false;
                properties['X-AxisLabelDataSource']['isVisible'] = false;
                this.updatedProperties();
            }
        }

        //all data bindings were removed - sound the alarm!
        if (dataSources.length === 0 && property.indexOf('Data') > -1) {
            this.addNoBindingWarning();
        }

        this.updatedProperties();
    };

nvd3 Charts Knowledge Base

D3.svg

Flaky Features

Feature

Chart Type

syntax

Problem

Axis Visibility Line Chart
.showYAxis(true)
Toggles Axis Tick Labels Function should be .showYAxisLabels
Line with Focus Chart Toggles Axis Ticks and Labels should be separate functions

Helpful nvd3 Resources

Issue

Chart Type

Link

Note

Tic Misalignment with dates Line Stack Overflow For round dates
Tic Misalignment with times Line  same link, further down  Time Series with .useInteractiveGuideline
Styling Chart Elements Any Github Resizing changes colors
Drawing Lifecycle Any nvd3.org
Time Series example Line GitHub Good Starter
Time Series Done Right Line jsFiddle Very Good starter
Another Time Series Example Line nvd3
D3 Formatting Github
D3 Time Formats Bostock You can’t guess them all
Angular Directives All Github Lots of option settings – Angular not required
Responsive  All Stack Overflow d3.js
Responsive All Github d3.js oriented too
Reverse X and Y All Timely Portfolio Lots of other examples
Continuous Flow Time Plunker
Click Events Any Plunker
Stack Overflow

D3 Techniques

Issue

Link

Note

Assign multiple attributes efficiently Stack Overflow For round dates
Groups and Transforms Stack Overflow
Cool examples rCharts

 

  • 0 Comments
  • Filed under: Practice
  • JavaScript D3-Based Data Visualization Libraries

    charts

    D3.js is the leading drawing library for creating svg-based scalar images in browsers because it is powerful and fast. The corollary that great power comes with great responsibility holds true: D3 is said to be very difficult to learn and use well, and rewards those who can specialize in developing with it. Writing out svg seems even harder/more special then D3, but I am considering any library that uses SVG really.

    Fortunately, many charting libraries have sprung up for those who can’t specialize in D3 or what it does, but need graphs and charts in browsers -like me. Probably too many, each with its own not-quite-generic approach to a subset of chart functionality it’s creator had in mind.

    There are quite a few comparison pages offering a list of the libraries with overviews and checklists of features. These are useful at eliminating some, such as non-open source packages, canvas-only libraries etc. Many comparison sites simply list the popular choices but lack any depth beyond a blurb and URL. I was unsatisfied and started my own rating table.

    My 5-6 libraries that supported all popular chart types:

    C3 Chartist Dygraph Dimple NVD3 Rickshaw
    Project
    Health

    Low/

    5K Stars

    High/

    7.5K

    High/

    1.7K

    Low/

    1.5K

    High/

    4.6K

    dead
    D3
    X X X X
    relative
    speed
    12 1.4 5.7 2.5
    strength

    stable/

    finished

    canvas/svg hybrid

    animation,

    interactions,

    responsive

    speed
    weakness
    slow

    fussy CSS

    No legend

    No tooltips

    No Touch

    unwanted sorting,

    fussy css

    D3 pokes through dead

    I tested speed by loading 30 charts displaying a single series of 40 values as a line chart in a single browser window in IE 10. Whatever each library decided to enable as defaults was accepted and displayed. Tooltips and axis overlay effects did not appear to tip the scale: the fastest and slowest library had the same features. Animation transitions were turned off/avoided.

     

    Conclusion

    NVD3 for speed, C3 for features and documentation. Dimple could be great for interactivity and animation, but strangely, it sorted by y values.

    I hear a lot about High Charts and Chart Fusion, both commercial products. They were slow and dated, but offer support and many templates. Google charts is an API-driven service which is too restrictive for use in an application.

  • 0 Comments
  • Filed under: Practice
  • How to Clear Input Fields in Thingworx Composer

    thingworx-ptc-logo

    There are several ways to clear input fields:

    One: Using Widget ResetToDefaultValue Property

    resetDefault1

    The arrow pointing to ResetToDefaultValue is filled because it is bound to an event.

    When to use:

    • Use if there is a single input field
    • Also use when there are other fields that should not be reset at the same time, or by the same trigger

    Two: Using Panel ResetInputsToDefaultValue Property

    resetDefault2

    When to use:

    • If there are multiple, related input fields

    Three: Using Mashup ResetInputsToDefaultValue Property

    resetDefault3

    When to use:

    • If there are multiple related input fields in multiple panels, or even if some inputs are not in panels.

    Events that usually trigger this:

    • SetProperties: it is the Composer equivalent to a form submit – after you submit a value, resetting the input signals that it is ready to take a new value
    • Reset Button click
    resetDefault4

    The click event of setProps button is wired to SetProperties. The ServiceInvokeCompleted event of SetProperties is wired to ResetToDefaultValue on the textInput Widget NumericEntry-21. A reset button could be wired directly to ResetToDefaultValue, or to the ResetInputsToDefaultValue handler of the panel or widget.

     

  • 0 Comments
  • Filed under: Practice
  • Intellij IdeaCygWingitorange@2x

    I didn’t find much existing basic documentation on how to get started with scripting in earnest, so I decided to fill the gap with my own experience. I had never been an avid user of command windows or terminals or scripting, and I admit to complaining that Cygwin is be a confusing heap. But that hasn’t stopped me from realizing their benefits, and I am ready to give them the respect they deserve since they have saved me time and drudgery.

    Solving Script Amnesia

    A successful script automates some tedious activity on the OS and is soon forgotten. That’s my problem: when I need to write or edit a script its hard to pick up where I left off or remember arcane syntax that I don’t use anywhere else. I don’t write scripts all day, so I often don’t remember where I put it, what environment I edited in, let alone some arcane syntax and other context information.

    I should have realized a long time ago that if a script is worth writing, it is worth safeguarding and properly maintaining like any source code. But script editing happens in spurts as a means to an end – it’s hard to justify taking the time to build up an infrastructure and become more proficient. This post documents how I set up a script repository and IDE using Git, Bitbucket, IntelliJ, Cygwin and some assorted environmental variables. So this documents my effort to get a a scripting environment set up that I can get familiar with and rely on. I’ll know it’s successful if I start writing more scripts because I feel more confident that I will get a longer return from the effort.

    Before I set up my scripting environment I asked my self some questions to help determine what tools and techniques I would need, and I encourage you to do answer these as well:

    Scripting Environment Questions:

    • Portability: How portable does my script need to be?
      • What OS(s) will it run on? My case: Win/Mac/Linux
      • What user(s) will use it? Me/Coworkers/My boss/Continuous Integration
    • IDE? Can I edit my scripts in the same IDE I use for everything else? Yes (detailed follow)
    • Extensibility: If my script can’t do everything natively, is there a strategy to add functionality? (Yes, details follow)
      • For example, if your script needs to do a REST call, do you: script a browser? import a cURL library?
    • Conventions: Is there a convention I can follow to put my scripts where both the OS and I can find them without tweaking? (Yes, requires some care)
      • For example: home/user/bin

    My answers lead me to a create a setup that prioritizes flexibility across OS and users and tries to follow general conventions. So, it should work for anybody, but you can sacrifice some of the generalization for your own convenience if you want to.

    Portability

    Once you combine Windows with another OS you are probably going to run into Cygwin as your go-to solution because you can write bash scripts that can run on Windows, MacOS and Linux. It is still very possible/maybe necessary to write Windows-only scripts in Cygwin. All my scripts are .sh except some narrow Windows cases.

    Portability also pertains to editing and running scripts on multiple machines in multiple user accounts. It means creating a project definition that can be put under version control to allow me to edit and run scripts on any machine as any user. To do this I followed conventions regarding where to house scripts (see Conventions below) and set my cygwin user folder as the project root and committed this to BitBucket using Git.

    IDE Support

    Maybe it should have been obvious, but it took me awhile to decide that I should strive to use my regular editor to edit scripts. I use IntelliJ, and I found a plugin called BashSupport that supports syntax highlighting, rename refactoring, documentation lookup, inspections, quickfixes and such. I can also run scripts from run configurations within IntelliJ- not sure if that is due to BashSupport or not honestly. Before that I used Notepad++, or simply Notepad. Another big advantage of using an IDE: it creates a project definition so you can reopen the project and see your scripts! Again, should have thought of this earlier, but my scripts were all small and scattered and this seemed like a heavyweight tank cracking a nut.

    Extensibility

    Cygwin has cultivated a large library of plugins that scratch the itch of various developer needs. It is likely to cover any requirement I will have. Install Cygwin to C:\ and don’t install any optional components. Save the installer (setup-x86_64.exe in my case) in the cygwin folder inside a folder you create called installer. Cygwin will create a cache of the plugins library in there. You will need the installer if you ever decide to install a plugin, so its a good idea to keep it with the project folder instead of losing in in the downloads folder like I have done.

    cygwinAlwaysRunAsAdmin

    Tip: In Cygwin Shortcut select Preferences: Shortcut Pane: Click Run as Administrator

    Conventions

    Cygwin tries to follow conventions whenever possible. It puts a directory structure that mimics Linux root, but it is located inside the Cygwin installation folder. Instead of accepting the default “Program files” location, install cygwin at C:\ to avoid spaces in any paths. My user dir is therefore: C:\cygwin64\home\[windows username]\bin. I added bin to .bashrc so I can run scripts from anywhere in Cygwin.

    Running Scripts

    From Intellij: As I mentioned, you can run shell scripts from within Intellij using run configurations. This seems like a lot of work after getting past the cool idea factor. It doesn’t open up any debugging options as far as I can tell. It’s a good idea to check the “Show this page” box to fill in any necessary arguments before running a script.

    cygwinRunConfiguration

    Intellij Script Run Configuration

    scriptRunInIDE

    Script output in Run Output of Intellij

    From Cygwin: cd bin, then ./script.sh to execute. Or add bin to .bashrc and execute script.sh from any location. Add Cygwin to context menu in Windows.

    From Git Bash: Open it from your bin directory to run a script on a Windows dev box lacking Cygwin. Otherwise useless because it is not extensible and conventional.

    You can run Cygwin in the IntelliJ terminal or in a terminal emulator like Conemu as well – Google for details.

    Polymer Data Models/Templates in Custom Elements

    A custom element can contain it’s own data source and be its own data model, as this example shows. The script provides a JavaScript object “salutations” containing a list of 4 objects with properties “what” and “who”.

    The inner template sets a repeat property that processes each “who/what” object “s” in salutations. The contents of the template is a mix of html and mustachioed object notations that results in a repeater containing labels and text inputs as shown in the inset from the resulting page.

    Source Code Explainer

    customElementDataModelTemplate

    My Feature Branch

    Polymer.org Example

    Live example

  • 0 Comments
  • Filed under: Practice, Tools
  • Google Maps: Set Zoom to Fit All Markers

    mapMarkerSeries

    Zoom to fit a group of Markers

    JsFiddle is a great tool to use for experiments with Google Maps. If you want to see the map displayed in the output frame, there are a few tweaks you need to know about

    Note: Using Google Maps API V3

    map options can be minimal:

    var mapOptions = {
    mapTypeId: google.maps.MapTypeId.ROADMAP
    };

    Use bounds global to hold outermost marker coordinates:

    var bounds = new google.maps.LatLngBounds();

    Put locations in an array “locationArray”. Iterate through them as you create markers so that each coordinate can be compared to bounds via bounds.extend in case it increases the size. Map.fitBounds(bounds) will set the zoom.

    var coord;
    for (coord in locationArray) {
    var newMarker = new google.maps.Marker({
    position: locationArray[coord],
    map: map,
    draggable: false
    });
    bounds.extend(locationArray[coord]);
    markers.push(newMarker);
    }
    map.fitBounds(bounds);
    }

    Example:

    Google Maps in JSFiddle

    google-maps-icon

    JsFiddle is a great tool to use for experiments with Google Maps. If you want to see the map displayed in the output frame, there are a few tweaks you need to know about, which I will cover here and share in a working example. I started with an example from the excellent developer resources Google provides.

    Note: Using Google Maps API V3

    Tweak One: The Google API Library include:

    The include must be modified from the following to work in JSFiddle:

    https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=true

    &dummy=.js is a workaround JSFiddle uses to recognize that this is a JavaScript library when it doesn’t end with a .js suffix – paste this in instead of the default URI:

    https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=true&dummy=.js

    Tweak Two: The Google examples provide a initialization call that does not work in JSFiddle:

    google.maps.event.addDomListener(window, 'load', initialize);

    replace it with this:

    initialize();

    (Not Really a Tweak) You must have the CSS to see the map container div:

    The html and body entries are required, the map div can be set to pixel values if you want.

    html { height: 100% }
    body { height: 100%; margin: 0; padding: 0 }
    #map-canvas{
    width:100%;
    height:100%;

    }

    Example

    below requires your permission to display your current location in the map. Location information does not go to this website, it goes directly to Google Maps.

    Taming Localization Resources

    rosetta_ston_19491_lg

    I have been working in the “legacy” portion of an application lately. It is state-of-the-art Java Struts from 2000. In their zeal to separate logic and presentation, Sun made it fairly difficult to trace out the origins of specific elements on a web page back to code in the project.

    Its not that hard to find all the localization bundles that need updating using a search tool, but testing that everything got translated and put in place is going to be tough for QA and bug fixing.

    Problem: .properties files are used for localization strings but also for lots of other things in Java. How do I find just the localization ones? Nobody thought to include a marker comment like #l18n in files that require translation.

    Solution: manually check the files and update a perl script with filenames and directory names to skip. Later, write a perl script to add the comment marker to each valid string resource .property file.

    Problem: I found all the localization strings, now how do I know which ones have to be updated? How many revisions do I have to go back to get all the changes needed without paying for translating strings that were already done last time?

    Solution: If someone had thought to add a comment to the Perl script that gathers the properties deltas with the SVN revision, I would have just done a diff to that – easy. I made that my final step. But what I did was check the translated files for revision numbers and then compare them to the revision numbers of the English source. This allowed me to account for  potential changes that may have been added when the last batch of resources were still being translated.

    Key Learning: The automation of the Perl script to gather the properties and then run a diff back to an earlier commit was a timesaver, but not a replacement for manual inspection and checking of the resources. I found some properties that had never been translated and never came up as a bug in the software. I avoided getting system properties needlessly translated.

  • 0 Comments
  • Filed under: Practice
  • Microsoft WebMatrix Review

    webMatrix

    How do you respond to Free and Open Source software eating your market?

    One way is to create a user-friendly package of bite-size meals for novices to follow like a breadcrumb trail back to your nest. Include the better FOSS add-ons and make it go down easier with a polished toolset. In other words, spend money seducing developers with the aspect FOSS is weakest at- the developer experience.

    I stumbled over WebMatrix while attempting to create an API site to help with some technical editing I am doing on a book. I started out using another Microsoft tool called Web Platform Installer 4.5. WPI is a utility for managing the task of installing various parts of the Microsoft solution stack along with some FOSS that it plays with. In my opinion it suffers from the Dictionary Paradox: I don’t know what the things are it lists as I browse it: conversely, don’t know what the things are that I will need to build a particular solution. Somehow it installed WebMatrix, but I didn’t know what it was or that I had it. Here’s a synopsis. I discovered that it had installed WebMatrix after I found it via another search and started installing it.

    The most powerful aspect of WebMatrix it combines many activities that normally straddle several separate applications into a unified experience that focuses on learning and accomplishment. Instead of hunting down IIS manager, IP addresses, port numbers, default browsers, database manager, tables, Javascript libraries, Java versions, firewall settings, .NET runtimes etc. you create a simple Hello World. MySQL, IIS and a stripped-down IDE are just right for getting from 0 to trouble in an afternoon – I highly recommend it. Especially to Java tool developers.

  • 0 Comments
  • Filed under: Practice
  • Categories





    Finger Lakes, NY

    United States of America

    Subscribe via Email

    Enter your email address to subscribe to this blog and receive notifications of new posts by email.