Download
Download Sample Code [ZIP 379KB]
Abstract
This document is intended to provide developers with an accelerated path in the development of prototype applications. The product concepts and visual designs should also provide inspiration for ideas for similar applications.
The accompanying sample code focuses on demonstrating the following Windows* 8 features within an education application.
- Semantic Zoom
- App Bar
- Swipe Select
- Share
Semantic Zoom
Semantic Zoom is a touch-optimized technique used by Windows* Store apps for presenting and navigating large sets of related data or content within a single view (such as a photo album, app list, or address book).
Semantic Zoom uses two distinct modes of classification (or zoom levels) for organizing and presenting the content: one low-level (or zoomed in) mode that is typically used to display items in a flat, all-up structure and another, high-level (or zoomed out) mode that displays items in groups and enables a user to quickly navigate and browse through the content.
The Semantic Zoom interaction is performed with the pinch and stretch gestures (moving the fingers farther apart zooms in and moving them closer together zooms out), or by holding the Ctrl key down while scrolling the mouse scroll wheel, or by holding the Ctrl key down (with the Shift key, if no numeric keypad is available) and pressing the plus (+) or minus (-) key.
Creating a custom SemantiZoom control
To provide this zooming functionality, the SemanticZoom control uses two other controls: one to provide the zoomed-in view and one to provide the zoomed-out view. These controls can be any two controls that implement the IZoomableView interface.
Note: ListView is the only Windows Library for JavaScript* control that implements this interface.
WinJS.UI.IZoomableView interface: Supports Semantic Zoom functionality by exposing a control as either the zoomed-in or the zoomedout view of the Semantic Zoom control.
To implement IZoomableView interface we need to implement the following methods:
beginZoom: Initiates Semantic Zoom on the custom control.
configureForZoom: Initializes the semantic zoom state for the custom control. This method takes the following four parameters:
- sZoomedOut
- Type: Variant
- True if this is the zoomed-out view; otherwise false.
- isCurrentView
- Type: Variant
- True if this is the current view; otherwise false.
- triggerZoom
- Type: Variant
- The function that manages semantic zoom behavior. Triggers a zoom in or zoom out if the control is the visible control.
- prefetchedPages
- Type: Variant
- The number of pages of content to pre-fetch for zooming.
- This value is dependent on the size of the semantic zoom container. More content can be displayed based on the zoom factor and the size of the container.
endZoom: Terminates Semantic Zoom on the zoomed in or zoomed out child of the custom control. This method takes one parameter
- isCurrentView
- Type: Variant
- True if the control is the visible control; otherwise false.
getCurrentItem: Retrieves the current item of the zoomed-in or zoomed-out child of the custom control.
getPanAxis: Retrieves the panning axis of the zoomed-in or zoomed-out child of the custom control.
handlePointer: Manages pointer input for the custom control. This method takes one parameter:
- pointerId
- Type: Variant
- The ID of the pointer
positionItem: Positions the specified item within the viewport of the child control when panning or zooming begins. This method takes two parameters:
- item
- Type: Variant
- The object to position within the viewport of the child control.
- item can be a number, a string, or an object with any number of properties.
- position
- Type: Variant
- An object that contains the position data of the item relative to the child control.
position must be an object with four number properties: left, top, width, and height.
These values specify a rectangle that is typically the bounding box of the current item, though the details are up to the control. The units of the position must be in pixels. And the coordinates must be relative to the top-left of the control viewport (which should occupy the same area as the Semantic Zoom viewport), except when in RTL mode. In RTL mode, return coordinates relative to the top-right off the control viewport.
The rectangle is transformed from the coordinate system of one control to another.
setCurrentItem: Selects the item closest to the specified screen coordinates. This method takes two parameters:
x
Type: Variant
The x-coordinate in the device-independent pixels (DIPs) relative to the upper-left corner of the SemanticZoom viewport.
y
Type: Variant
The y-coordinate in the DIPs relative to the upper-left corner of the SemanticZoom viewport.
For this particular sample code we are not using ListView; instead, we are creating our own custom semantic zoom control. We start with implementing IZoomableView interface.
We start by describing the scenario, i.e., exactly what is going to be implemented. For example: We are going to be creating a control that has two states for the zoomedIn and zoomedOut views. The control shows a historic timeline from year 1400 to 1550. In the zoomedOut view, the timeline is shown with data sparsely populated. When the user zooms in with the appropriate gesture, the timeline is expanded with more detail.
To do this we implement the ZoomableView class that implements the IZoomableView interface. The customSemanticZoom.js file looks like this:
// implementing Izoomable interface var ZoomableView = WinJS.Class.define(function (timeline) { // Constructor this._timeline = timeline; }, { // Public methods getPanAxis: function () { return this._timeline._getPanAxis(); }, configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom, prefetchedPages) { this._timeline._configureForZoom(isZoomedOut, isCurrentView, triggerZoom, prefetchedPages); }, setCurrentItem: function (x, y) { this._timeline._setCurrentItem(x, y); }, getCurrentItem: function () { return this._timeline._getCurrentItem(); }, beginZoom: function () { this._timeline._beginZoom(); }, positionItem: function (/*@override*/item, position) { return this._timeline._positionItem(item, position); }, endZoom: function (isCurrentView) { this._timeline._endZoom(isCurrentView); }, handlePointer: function (pointerId) { this._timeline._handlePointer(pointerId); } });
In the code above we defined a class called
ZoomableView that implements the IZoomableView interface.
Now we define the actual control under the namespace CustomControls. In method WinJS.Class.define we pass the following parameters
Constructor method that creates the zoomedIn and zoomedOut views for semantic zoom control
Instance members that are a set of properties and methods made available on the type.
In the constructor method we create the element _viewport that will contain the semantic zoom controls. Inside _viewport we create another element, _canvas, which will display the zoomedIn or zoomedOut control elements inside it. Here we use a variable called _ initialView of type boolean to indicate whether the current view is zoomedIn or zoomedOut. This value is set in the html where we add the semantic zoom control to the html body. For the zoomedIn view, the value of _initialView will be true, and for the zoomedOut view it will be false.
In this sample we are using baked-in values of images and their positioning for both zoomedIn and zoomedOut views. For the zoomedIn view we are using zoomedInPointsArray that contains the names of the images that need to be positioned at different points on the history timeline scale. itemHeightArray and itemPositionArray contain the heights and pixel positions of the images, respectively. Once the item is created, we add the click event handler to the item so that when users click on any point on the history timeline, it will navigate to the detail page. Once the item is created, we append it to _canvas.
We use the same approach to create the zoomedOut view as well. In the zoomedOut view for click event, we invoke the _triggerZoom() method in order to move into zoomedIn view.
Now we define the methods that we implemented in the IZoomableView interface. But before that, we define a property that returns the ZoomableView instance. Continuing in the customSemanticZoom.js file:
// Define custom control for semantic zoom WinJS.Namespace.define("CustomControls", { Timeline: WinJS.Class.define(function (element, options) { this._element = element; if (options) { if (typeof options.initialView === "boolean") { this._initialView = options.initialView; } } this._viewport = document.createElement("div"); this._viewport.className = "viewportStyle"; this._element.appendChild(this._viewport); this._canvas = document.createElement("div"); this._canvas.className = "canvasStyle"; this._viewport.appendChild(this._canvas); var viewportWidth = this._element.offsetWidth; var viewportHeight = this._element.offsetHeight; var that = this; // If current view is initial view then create ZoomedIn view if (this._initialView) { this._element.className = "timeline-zoomed-in"; var zoomedInPointsArray = ["timeline-1.png", "timeline-2.png", "timeline-3.png", "timeline-4.png", "timeline-5.png"] var itemHeightArray = [537, 800, 537, 777, 833]; var itemPositionArray = [470, 220, -110, -95, -200]; // Create items for the zoomed in view for (var i = 0; i <= zoomedInPointsArray.length - 1; i++) { var item = document.createElement("div"); item.className = "zoomedInItem"; item.style.backgroundImage = "url(/images/timeline/" + zoomedInPointsArray[i] + ")"; item.style.marginLeft = itemPositionArray[i] + "px"; item.style.height = itemHeightArray[i] + "px"; // Add click event handler to navigate from page item.addEventListener("click", function () { WinJS.Navigation.navigate("/pages/detail/detail.html"); }, false); this._canvas.appendChild(item); } // Create bottom timeline scale var timelineScale = document.createElement("div"); timelineScale.className = "scaleStyle"; this._canvas.appendChild(timelineScale); } // Create zoomedOut view else { this._element.className = "timeline-zoomed-out"; var zoomedOutPointsArray = ["sezo-1.png", "sezo-2.png", "sezo-3.png", "sezo-4.png", "sezo-5.png"] var itemWidthArray = [280, 280, 280, 267, 226]; // Create items for the zoomed out view for (var i = 0; i <= zoomedOutPointsArray.length - 1; i++) { var item = document.createElement("div"); item.className = "zoomedOutItem"; item.style.backgroundImage = "url(/images/timeline/" + zoomedOutPointsArray[i] + ")"; item.style.width = itemWidthArray[i] + "px"; // Add click event handler to trigger zoom item.addEventListener("click", function () { if (that._isZoomedOut) { that._triggerZoom(); } }, false); this._canvas.appendChild(item); } } this._element.winControl = this; }, { // Public properties zoomableView: { get: function () { if (!this._zoomableView) { this._zoomableView = new ZoomableView(this); } return this._zoomableView; } }, // Private properties _getPanAxis: function () { return "horizontal"; }, _configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom, prefectchedPages) { this._isZoomedOut = isZoomedOut; this._triggerZoom = triggerZoom; }, _setCurrentItem: function (x, y) { // Here set the position and focus of the current element }, _beginZoom: function () { // Hide the scrollbar and extend the content beyond the viewport var scrollOffset = -this._viewport.scrollLeft; this._viewport.style.overflowX = "visible"; this._canvas.style.left = scrollOffset + "px"; this._viewport.style.overflowY = "visible"; }, _getCurrentItem: function () { // Get the element with focus var focusedElement = document.activeElement; focusedElement = this._canvas.firstElementChild; // Get the corresponding item for the element var /*@override*/item = 1; // Get the position of the element with focus var pos = { left: focusedElement.offsetLeft + parseInt(this._canvas.style.left, 10), top: focusedElement.offsetTop, width: focusedElement.offsetWidth, height: focusedElement.offsetHeight }; return WinJS.Promise.wrap({ item: item, position: pos }); }, _positionItem: function (/*@override*/item, position) { // Get the corresponding item for the element var year = Math.max(this._start, Math.min(this._end, item)), element = this._canvas.children[item]; //Ensure the element ends up within the viewport var viewportWidth = this._viewport.offsetWidth, offset = Math.max(0, Math.min(viewportWidth - element.offsetWidth, position.left)); var scrollPosition = element.offsetLeft - offset; // Ensure the scroll position is valid var adjustedScrollPosition = Math.max(0, Math.min(this._canvas.offsetWidth - viewportWidth, scrollPosition)); // Since a zoom is in progress, adjust the div position this._canvas.style.left = -adjustedScrollPosition + "px"; element.focus(); // Return the adjustment that will be needed to align the item return WinJS.Promise.wrap({ x: adjustedScrollPosition - scrollPosition, y: 0 }); }, _endZoom: function (isCurrentView, setFocus) { // Crop the content again and re-enable the scrollbar var scrollOffset = parseInt(this._canvas.style.left, 10); this._viewport.style.overflowX = "auto"; this._canvas.style.left = "0px"; this._viewport.style.overflowY = "hidden"; this._viewport.scrollLeft = -scrollOffset; }, _handlePointer: function (pointerId) { // Let the viewport handle panning gestures this._viewport.msSetPointerCapture(pointerId); } }) }) }
On the html page we add the SemanticZoom control and set its control property zoomFactor and initiallyZoomedOut properties.
The zoomFactor property gets or sets a value that specifies how much scaling the cross-fade animation performs when the SemanticZoom transitions between views. The initiallyZoomedOut property gets or sets a value that indicates whether the control is zoomed out.
In our sample we set the zoomFactor to 0.5 and initiallyZoomedOut to false so that when the page loads, it is in the zoomedIn view.
Under semantic zoom control we add zoomedIn and zoomedOut views so that we can switch from one view to another. In our JavaScript code we define a namespace CustomControls that contains the Timeline control. Our zoomedIn and zoomedOut views will be of type Timeline control. The value of the initialView property of the Timeline control should be true for zoomedIn view and false for zoomedOut view.
Add the custom semantic zoom control to the timeline.html page:
<div id="sezoDiv" data-win-control="WinJS.UI.SemanticZoom" data-win-options="{ zoomFactor: 0.5, initiallyZoomedOut: false }"> <div id="ZoomedInDiv" data-win-control="CustomControls.Timeline" data-win-options="{initialView: true }"> </div> <div id="ZoomedOutDiv" data-win-control="CustomControls.Timeline" data-win-options="{initialView: false }"> </div> </div>
We want the timeline to show as a single straight line with images for events at certain points of time. The following CSS sets up the timeline. Add the following CSS style to the timeline.css file:
.timeline-fragment { height: 100%; width: 100%; } section { height: 100%; width: 100%; } span { margin-left: 70px; } .win-semanticzoom { height: 520px; } .timeline-zoomed-in { color: WhiteSmoke; height: 100%; width: 100%; } .timeline-zoomed-out { margin-left: 50px; } .viewportStyle { position: absolute; overflow-x: auto; overflow-y: hidden; height: 100%; } .canvasStyle { position: relative; overflow: hidden; height: 100%; } .zoomedInItem, .zoomedOutItem { width: 215px; height: 537px; position: relative; overflow: hidden; float: left; background-position: center; background-repeat: no-repeat; } .scaleStyle { background-image: url(/images/timeline/timeline-meter-bottom.png); background-repeat: no-repeat; position: absolute; bottom: 0px; width: 1366px; height: 109px; }
App bar
The next feature we want to demonstrate is the app bar, which represents an application toolbar for displaying commands in Windows 8 apps.
In this sample we are using a custom app bar. This is how we add the custom app bar to detail.html page:
<!-- BEGINTEMPLATE: Template code for AppBar --> <div id="customAppBar" data-win-control="WinJS.UI.AppBar" data-win-options="{layout:'custom',placement:'bottom'}"> <div id="leftButtonsContainer"> <div id="addNotes"></div> <div id="addImages"></div> </div> <div id="bookmark"></div> </div> <!-- ENDTEMPLATE →>
The layout property of the app bar control gets or sets the layout of the app bar contents and/or sets a value that specifies whether the app bar appears at the top or bottom of the main view. Under the app bar control we added three elements that are custom app bar buttons. Two buttons must be positioned on the left side of the app bar, and they are wrapped inside the leftButtonsContainer div.
In this sample, app bar and app bars buttons have custom background images. We set the background images and button positions in the CSS for the App bar.
App bar styles in detail.css:
#customAppBar { background-image: url(/images/app-bar-2.png); height: 100px; } #leftButtonsContainer { width: 150px; float: left; margin-left: 30px; } #addNotes { height: 54px; width: 54px; background-image: url(/images/icon-add-note.png); margin-top: 10px; float: left; } #addImages { height: 54px; width: 54px; background-image: url(/images/icon-add-pic.png); float: right; margin-top: 10px; } #bookmark { height: 54px; width: 54px; background-image: url(/images/icon-bookmark.png); float: right; margin-right: 30px; margin-top: 12px; }
Swipe Select
In this scenario when an app bar button is clicked, a flyout (popup) pops up. The flyout contains a list view with four images in it. Users can select any of the four images by using swipe select, which is a feature implemented by list view.
WinJS.UI.ListView displays data items in a customizable list or grid. The ListView control has a property called swipeBehavior. ListView.swipeBehavior gets or sets how the ListView reacts to the swipe gesture. The swipe gesture can select the swiped items or have no effect on the current selection.
In the following code we add a flyout and a ListView within it in the detail.html file:
<!--Add image flyout--> <div id="addImageFlyout" class="addImageFlyout" data-win-control="WinJS.UI.Flyout"> <!--Template for the listView within the flyout--> <div id="listViewItemTemplate" data-win-control="WinJS.Binding.Template"> <div class="listViewItem"> <img src="#" class="listViewItemTemplate-Image" data-win-bind="src: picture" /> </div> </div> <!--End listView template--> <!--ListView --> <div id="listView" data-win-control="WinJS.UI.ListView" data-win-options="{ itemDataSource : Data.itemList.dataSource, itemTemplate: select('#listViewItemTemplate'), selectionMode: 'single', tapBehavior: 'none', swipeBehavior: 'select', layout: { type: WinJS.UI.GridLayout } }"> </div> <!--ListView end--> </div> <!--Flyout end-->
WinJS.UI.Flyout displays lightweight UI that is either information or requires user interaction. Unlike a dialog, a Flyout can be light dismissed by clicking or tapping off of it.
To enable swipe select, set the swipeBehavior property of list view to “select” and the selectionMode property should not be “none.” Instead, it should either be “single” or “multi.”
Notice that we are setting itemDataSource of the list view to Data.itemList.dataSour. ListView.itemDataSource property gets or sets the data source that provides the ListView with items. To show the images in list view, we need to create a binding list that contains the source of the images that we will display. WinJS.Binding.List object represents a list of objects that can be accessed by an index or by a string key. Provides methods to search, sort, filter, and manipulate the data.
This is how we create the binding list for the list view in data.js file:
// Create an array of images that will appear in the listview inside addImage flyout var myDataImages = new WinJS.Binding.List([ { picture: "images/icon-add-pic.png" }, { picture: "images/icon-add-pic.png" }, { picture: "images/icon-add-pic.png" }, { picture: "images/icon-add-pic.png" }, ]); // Create a namespace to make the data publicly // accessible. var publicMembers = { itemList: myDataImages }; WinJS.Namespace.define("Data", publicMembers);
Now we add a click event handler for the “Add Image” button on the app bar, which brings up the flyout that contains the list view. Once the flyout is displayed, we call the forceLayout method on the listView control to make sure all the images are visible inside the ListView because while flyout is hiding the ListView control inside it, the flyout is also hidden. ListView.forceLayout method forces the ListView to update its layout. Use this function when making the ListView visible again after its style.display property had been set to "none."
Add the event handler to the detail.jsl file:
var addImageButton = document.getElementById("addImages"); var listView = element.querySelector("#listView").winControl; addImageButton.addEventListener("click", function (e) { // On "Add Image" button click show the flyout with image thumb nails in list view var addImageFlyout = document.getElementById("addImageFlyout"); var anchor = document.getElementsByTagName("body"); addImageFlyout.winControl.show(anchor[0], "", "left"); // Set the position of the flyout addImageFlyout.style.bottom = "100px"; addImageFlyout.style.left = "130px"; listView.forceLayout(); });
Add the appropriate style to the flyout and listView controls in the detail.css file:
.addImageFlyout { background-image: url(/images/flyout-add-image.png); height: 492px; width: 488px; background-repeat:no-repeat; } /* Template for items in the ListView */ .listViewItem { width: 150px; height: 150px; } listViewItemTemplate-Image { width: 140px; height: 140px; margin: 5px; } /* CSS applied to the ListView */ #listView{ width: 450px; height: 400px; border: solid 2px rgba(0, 0, 0, 0.13); }
Share
To set up your application as a share source app, you first need to get the instance of the DataTransferManager class that’s been assigned to the current window.
Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView() returns the DataTransferManager object associated with the current window.
This class supports a datarequested event, which is fired when a user presses the Share charm. Your app needs to listen for this event to know when the user wants to share data from your app. To do this, add the event handler onShareRequested to the datarequested event.
In the onShareRequested handler we create an html fragment string shareHtml. Then we pass the string to the Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(shareHtml) method, which returns a string representing the formatted HTML. This method takes a string that represents HTML content and adds the necessary headers to ensure it is formatted correctly for share and clipboard operations.
In the next step we add the html content to the data package. To share images, we create a random access stream around the image uri by calling the Windows.Storage.Streams.RandomAccessStreamReference.createFromUr(uri) method . We need to set the share email title as well.
When unloading the page, we must disable the share by setting the ondatarequested event handler to null.
In the share.js file:
/* Methods */ var Enable = function () { var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.ondatarequested = onShareRequested; }; var Disable = function () { var dataTransferManager = Windows.ApplicationModel.DataTransfer.DataTransferManager.getForCurrentView(); dataTransferManager.ondatarequested = null; }; /* Private methods */ function onShareRequested(e) { var request = e.request; // Construct the html fragment that will be shared var description = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."; description = description.length > 250 ? description.substring(0, 200) + '...' : description; var shareHtml = '<div style="width:100%; overflow:hidden; display:table;">' + '<div style="padding:0 25px 0 0; width:35%; display:table-cell; vertical-align:top; ;">' + '<img src="ms-appx://' + "/images/icon-add-pic.png" + '" style="border:1px solid #ccc;"/></div>' + '<p>' + description + '</p>' + '</div>'; // Format the html fragment var obj = Windows.ApplicationModel.DataTransfer.HtmlFormatHelper.createHtmlFormat(shareHtml); //Adds HTML content to the DataPackage. request.data.setHtmlFormat(obj); // If there are images in the html fragment then we will need to create the random access stream around specified uri var streamRef = Windows.Storage.Streams.RandomAccessStreamReference.createFromUri(new Windows.Foundation.Uri("ms-appx://" + "/images/icon-add-pic.png")); request.data.resourceMap["ms-appx://" + "/images/icon-add-pic.png"] = streamRef; // Set the email title request.data.properties.title = "The New World (Litware)"; // required }
About Ratio
Ratio is a leading multi-screen agency that partners with global brands to create seamless experiences across all platforms. We deliver multi-screen apps that provide consistent and optimized user experiences across the web, mobile, tablet, Connected TV, and most recently the console ecosystem specifically using our CypressX product which allows media brands to launch differentiated apps quickly on the Xbox LIVE platform. Ratio’s specialized team combines product strategy with compelling design and deep technical expertise to deliver award-winning applications for our clients that include AT&T, Condé Nast, Intel, Meredith, Microsoft, NASDAQ and Time Warner. Founded in 2001, Ratio is privately held and headquartered in Seattle, WA. To learn more about Ratio, visit http://www.WeAreRatio.com or follow the company on Twitter @teamratio.
Intel and the Intel logo are trademarks of Intel Corporation in the U.S. and/or other countries.
Copyright © 2013 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.