Continued from #2: First build and run

It's been a couple weeks since my last post, but I haven't been idle during that time–my Cordova efforts have been channeled into an MSDN Magazine article on Cordova, and now that I have that draft done I can share more of the insights here, meaning that I can ramble on with more details than I can fit into an article! ūüôā

First of all, I modified my markup to be a little more generic and flexible for the app. One change is to drop the map display from the app altogether in favor of a simple field for the location. I did this because the map element took up a lot of space that I didn't have on small phones, and also created more complexity for layout. In the end, the map really isn't important to the app, and by making it a text field the user can also edit the location information if they want.

Another change is to add an explicit Share button because platforms other than Windows don't have the swipe-in-from-right-edge gesture to get to it, and don't have a system-level sharing feature either. (That's something we'll deal with later on.) I could have made use a WinJS app bar for this, but I decided to keep it simple and just create a div with a button.

I also just made all the content areas <div> elements instead of using <header> and <section> and so forth. Here's the new markup:

<body>
    <div class="mainContainer">
        <div class="title mainChild">
            <h1 class="titlearea ">Here My Am!</h1>
        </div>
        <div id="locationSection" class="mainChild subsection">
            <h2 class="section-title">Here:</h2>
            <div id="locationControls">
                <input id="txtLocation" type="text" placeholder="tap to edit" />
                <button id="btnLocate" class="iconGlobe"></button>
            </div>
        </div>
        <div id="photoSection" class="mainChild subsection">
            <h2 id="photoHeading" class="section-title">My:</h2>
            <div id="photo">
                <img id="photoImg" src="#" draggable="false"
                    alt="Tap to capture image from camera" />
            </div>
        </div>
        <div id="shareSection" class="mainChild">
            <button id="btnShare" class="shareButton" disabled>Share</button> 
        </div>
    </div>
</body>

I should note that one of the complications that came up in migrating the Windows Here My Am! app is that the map I originally used was either displayed in an iframe with an ms-appx-web:// src or an x-ms-webview element, both of which are specific to Windows. I had to do this because the map area used remote code for Bing Maps. Now Cordova apps on all platform other than windows automatically run inside a webview, which means I could have just references the Bing Maps script directly, but that would (a) not work if the device is offline, and (b) wouldn't work on Windows because you can't load script into the default local context in which index.html runs. Upon further investigation, there really isn't a workaround for this at present: your root page in a Cordova app on Windows runs in the local context, but on other platforms runs in a web context. It's a difference that you just have to live with for now, and in my case I just dropped the map feature.

My next step is to style the markup it so it looks like the following:

HereMyAm_Cordova_3-1

The big trick here is to understand the impact of Cordova's nature on your HTML/CSS work. On Windows 8/8.1 and Windows Phone 8.1, a Cordova apps runs as a native JavaScript app; on all other platforms it creates a native (non-JS) wrapper around a webview. Whatever the case, the engine that renders your HTML, CSS, and JavaScript is related to the browser of the underlying platform. On Windows it's IE (10 or 11, depending on the OS); on Android it's the Android browser, (Chrome-based, of the version that matches the OS), and on iOS it's iOS Safari (7 or 8).

Which means: a Cordova's app CSS must work in each target platform's webview, just like web applications must work in different browsers by employing the appropriate prefixed and non-prefixed styles. In short, everything you know about responsive web design applies with Cordova.

As noted in the last post, I discovered this right away because the CSS grid upon which I rely heavily on Windows isn't implemented on these other platforms. And that's not all–there are many variances and differences. But thankfully there's the site caniuse.com that will tell you what parts of HTML5 and CSS are and aren't available on which platfoms, and whether or not they need prefixing. Super helpful! I suggest you visit that site and get familiar with it.

What I found was very helpful at this stage (aside from going to a CSS framework which would relieve me from the details), was to first create a Windows app project with my HTML, and then open it in Blend for Visual Studio and do my Windows-based styling. In this case I needed to use flexbox for the layout, knowing that I couldn't use the grid. Blend gave me an easy way to work out things like margins and media queries in a very friendly environment, setting the baseline styles for the app.

Along the way I also decided that given the small size of many phone screens and the fact that I eliminated the map section, it didn't make sense to support a landscape orientation. We'll return later on how to enforce that in a Cordova app; at this point it just meant that I didn't need to worry about landscape orientations at all.

With my baseline CSS from Blend, I then dropped the HTML and CSS onto my web server; you can see it all at http://www.kraigbrockschmidt.com/src/heremyam/index.html (and it has some JS in there too now). What this allowed me to do is check the styling in a variety of browsers, and then use the browser's developer tools to find the necessary styling for that browser.

On my Windows machine I opened the site in both IE and Chrome; in the latter I worked out the webkit styles for flexbox. Once I did that and updates index.css on the web host, I then opened the same page on my Android tablet to verify the layout. Then I switched to my MacBook (which I acquired to build for iOS) and checked things out in Safari, and then also opened the site on my iPad. I opened the site on my Windows Phone too. (And, I might add, that creating a short URI saves a lot of trouble entering it into a mobile browser.)

I liked using the browsers here for three reasons. First, the developer tools let you change the live CSS and see the result, just like Blend does for Windows. Second, the mobile browsers especially are very clearly linked to the webview that a Cordova app will use. Third, a browser is easily resizable, allowing you to test and style for smaller screen sizes. For example, here are the IE, Chrome, and Safari browsers sized down to a minimum, with styles for <= 499px widths and <= 480px heights, which reduce font sizes and tighten up margins:

HereMyAm_Cordova_3-2

Here's the resulting CSS. Note that use of flex: 1 1 0px; and -webkit-flex: 1 1 0px; to expand certain elements to full available space (the location input control horizontally, the image area vertically). The #btnLocation::before rule is what sets the globe icon in the locate button, and other styles on the button make it circular.

@-ms-viewport{width:auto!important}

/* 
    Notes:
    Windows and Windows Phone (through 8.1) use -ms prefixed styles.
    Android and iOS use -webkit prefixed styles.

    There are a few general design breakpoints for layout:
    Heights: 480 and 768 (actually 767, see below)
    Widths: 480, which accomodates the 500-pixel width of Windows split-screen views.
    
    If you use device-[height | width] then there are more variations. Using
    [min | max]-[width | height] you get automatic scaling for CSS, which reduces
    the breakpoints. This has the effect that some device sizes don't translate
    exactly: the iPad's 768 dimension comes through as 767.78 so the breakpoint
    has to be 767 as CSS media queries operate on integer comparisons.    	
*/

/* 
    This app will focus on being portrait-only to simplify the layout
	requirements. The only adjustments we'll make, then, are for 
	various screen sizes and not for orientation.
*/

.mainContainer {
    /* Windows */
	display: -ms-flexbox;
	-ms-flex-flow: column;	

    /* Android, iOS */
    display: -webkit-flex;
    -webkit-flex-flow: column;
	
	height: 100%;	
}

/* Override of WinJS */
h3 {
    font-weight: 300;
}

.mainChild {
    margin-left: 20px;
    margin-right: 20px;	
}

.title {
	margin-top: 10px;	
}


.subsection {
	display:-ms-flexbox;		
    -ms-flex-flow: column;	

    display: -webkit-flex;
    -webkit-flex-flow: column;		
	
	margin-top: 15px;	
}

.section-title {
	/* height: 40px; */
}

#locationControls {
    /* Use a row flexbox here to allow the input control to fill the space */
	display:-ms-flexbox;		
    -ms-flex-flow: row;	

    display: -webkit-flex;
    -webkit-flex-flow: row;		
}

#txtLocation {
	flex: 1 1 0px;  /* Expand to fill available space */    
    -webkit-box-flex: 1 1 0px;   
    -webkit-flex: 1 1 0px;
}

#btnLocate {
    margin-left: 15px;

    /* Make it circular */
	min-width: 40px;
    width: 40px;
    border-radius: 20px;
}

#btnLocate::before {
    /* WinJS references ../fonts/Symbols.ttf from its stylesheets */
    font-family: "Symbols";
    font-weight: normal;
    font-size: 10pt;    
    content: "E12B"; /* globe */  
}

#photoSection {	
	flex: 1 1 0px;        
    -webkit-flex: 1 1 0px;    
    min-height: 100px;
}

#photo {	
	border: solid 1px gray;	
	width: 100%;    
	height: calc(100% - 40px); /* 40px is height of heading */
    margin-top: 6px;
    margin-bottom: 10px;

    /* Make sure the image is centered inside this area */
    display: -webkit-box;
    display: -ms-flexbox;    
    display: flex;
    justify-content: center;
    align-items: center;

    /* iOS */
    display: -webkit-flex;
    -webkit-flex: 1 1 0px;
    -webkit-align-items: center;
    -webkit-justify-content: center;
}

#shareSection {
    display: -webkit-box;
    display: -ms-flexbox;
    display: -webkit-flex;
    display: flex;
	-ms-flex-pack: center;	
    -ms-flex-item-align: center;
    justify-content: center;
    align-items: center;
    -webkit-justify-content: center;
    -webkit-align-items: center;

	margin-bottom: 15px;
}

.shareButton {
    margin-left: 8px;
    margin-right: 8px;
}

/* Adjustments for narrower/shorter screens */
@media screen and (orientation: portrait) and (max-width: 499px),
       screen and (orientation: portrait) and (max-height: 480px)
 {	
    h2 {
        font-size: 14pt;
    }

    button, input {
        font-size: 10pt;        
    }
}


/* Tighten up margins for short screens */
@media screen and (orientation: portrait) and (max-height: 480px) {
    .title {
        margin-top: 5px;
    }

    .mainChild {
        margin-left: 10px;
        margin-right: 10px;
    }

    .subsection {
        margin-top: 10px;
    }

    button, input {
        min-height: 15px;
    }

    #photoSection {
        margin-top: 5px;
    }

    #photo {
        margin-bottom: 5px;
    }

    #shareSection {
        margin-bottom: 5px;
    }
}

 

It's important to note–and you'll see this if you run in the Ripple emulator–that the sizes reported to CSS are scaled sizes, especially where max-height, max-width, and so forth are concerned. I would suggest sticking with these instead of trying to get device pixels, because doing that would only increase the complexity of your CSS for no benefit.

 

 


In the Here My Am! app of my book, one problem I didn’t get around to solving was scaling of captured images to fit the display area. Typically this isn’t a problem when you capture an image large enough for that area, but when you have a low-res camera (or a high-res camera set to a low res output), or when you change view states, the image can become distorted.

As I’m working through the book now¬†for a¬† second edition, I fixed this particular problem in Chapter 2’s version of the app, and fortunately¬†the solution¬†turns out to be pretty simple. The trick is to first wrap the img element in a div where that div contains a 1×1 CSS grid. The img element, as a child, is then centered within that cell. So in default.html I replaced the <img> element line with these:

<divid=”photo”class=”graphic”>
<imgid=”photoImg”src=”#”alt=”Tap to capture image from camera”role=”img”/>
</div>

And added these bits to default.css:

#photo {
    display: -ms-grid;
    -ms-grid-columns: 1fr;
    -ms-grid-rows: 1fr;
}

#photoImg {
    -ms-grid-column-align: center;
    -ms-grid-row-align: center;
}

Within photoImg, then, we can style that element to be 100% width or 100% height depending on whether the picture’s aspect ration is greater than or smaller than the parent div, photo. The original image size can be easily obtained from StorageFile.properties.getImagePropertiesAsync. So in this added function, imgElement is photoImg, parentDiv is photo, and file is the captured StorageFile object:

function scaleImageToFit(imgElement, parentDiv, file) {
    file.properties.getImagePropertiesAsync().done(function (props) {
        var scaleToWidth = (props.width / props.height > parentDiv.clientWidth / parentDiv.clientHeight);
¬†¬†¬†¬†¬†¬†¬† imgElement.style.width = scaleToWidth ? “100%” : “”;
¬†¬†¬†¬†¬†¬†¬† imgElement.style.height = scaleToWidth ? “” : “100%”;
}, function (e) {
¬†¬†¬†¬†¬†¬†¬† console.log(“getImageProperties error: “ + e.message);
    });
}

I then call this function from within the completed handler of captureUI.captureFileAsync, after setting the img.src URI. I also have a window.onresize handler to call this again with the most recently captured file, which handles scaling the image again for new view states.

With this approach, specifically centering the image in the parent div’s one grid cell, we get automatic letterboxing around the image.

I believe it would also be possible to solve this with a WinJS FlexBox control, which effectively does the same thing. I don’t think it would mean any less code, however. The solution here, of course, depends on¬†the WinRT APIs and having a StorageFile object. For use with¬†pure HTML/CSS, you can create a new Image and assign its src, and once loaded that object’s width and height properties will be available. When you have those values, you can then use the same calculation as above to scale the image without changing the aspect ratio.

 

 


When implementing Accessibility support in an app, it's very important to be sensitive to the contrast in images. As I wrote in Chapter 17 of my book:

Technically speaking, high contrast is defined by the W3C as a minimum luminosity ratio of 4.5 to 1. A full explanation including how to measure this ratio can be found on http://www.w3.org/TR/WCAG20-TECHS/G18.html. A Contrast Analyzer (from the Paciello Group) is also available to check your images (some of mine in Here My Am! failed the test). Do note, however, that creating high contrast graphics isn’t required for non-informational content such as logos and decorative graphics. At the same time, full-color graphics might look out of place in a high contrast mode, so be sure to evaluate your entire end-to-end user experience under such conditions.

The contrast ratio is what you need when you want to make high-contrast graphics that are still in color–this is what I had to do with the Here My Am! graphics. The original ones I received from Microsoft Press, which I use for "standard" contrast, look like this:

widetile.scale-100_contrast-standard

Running the two colors (#FFFFFF and #00B294) here through the contrast analyzer, I'm told that it's ratio is only 2.69:1. So in a graphics editor (I use the free Paint.NET), I tweaked the Value portion of the HSV to proportionally darken the teal color down to #005C36 which gives an 8.13:1 ratio:

widetile.scale-100_contrast-high

This is well beyond the 4.5:1 ratio, and passes all the test of the Contrast Analyzer. (I could have used #00663B which is a touch brighter and passes at 7.09:1). I use this as the high contrast image (color).

Then I needed to make black on white and white on black variants. The easy way to do this in Paint.NET was to first convert to grayscale, then use the Levels adjustment. For black on white, I converted the original (standard contrast) image to grayscale and inverted the colors:

widetile.scale-100_gray1

Then I used the Adjustments > Levels command to set the input levels at 0 and 128 with outputs at 0, 1.0, and 255:

levels

 

This created black on white (light border added):

widetile.scale-100_BonW

After this, another Adjustments > Invert Colors command produced the white on black:

widetile.scale-100_WonB


Thanks to M. Dirksma on the Building Windows Store Apps with HTML5/JS Forum for this one. It applies to the first edition of Programming Windows 8 Apps in HTML, CSS, and JavaScript.

When running versions of Here My Am! that use an iframe element to host the Bing Maps web control (Chapters 2-7; in Chapter 8 this is replaced with the Bing Maps SDK control), there is a possibility that the iframe isn’t instantiated before the app attempts to set the geolocation. This can throw an exception.

The solution to this–a good practice for any situation where you might depend on iframe content being loaded, is to place that dependent code inside a handler for the iframe’s load event. M. Dirksma’s solution is as follows:

document.getElementById("map").addEventListener("load", function () {
    var gl = new Windows.Devices.Geolocation.Geolocator();
    gl.getGeopositionAsync().done(function (position) {
        lastPosition = { latitude: position.coordinate.latitude,
            longitude: position.coordinate.longitude };
        callFrameScript(document.frames["map"], "pinLocation",
            [position.coordinate.latitude, position.coordinate.longitude]);
    }, function (error) { console.log("Unable to get location."); })
    ;
}, false);

This should prevent the error and avoid any odd timing issues.