Rapid Business Apps Development Series – Part 2 (Expression Blend)
Purpose: The purpose of this series is to illustrate how to rapidly develop business applications integrated with Microsoft Dynamics AX 2012. In this document we'll focus primarily on User Interface, other documents in this series will cover Web Services and Communication Infrastructure, and more.
Challenge: Often times companies in the world have professional Designers or people who have design skills in staff. These people know how to create a truly amazing User Experiences embracing Windows 8 UX principles and they are usually familiar with some graphical design software packages. The question is how *I* as a Designer can quickly design a modern Windows 8 business app. Say one of typical Manufacturing industry workloads could be for Production Manager who wants to review the list of Production orders to be aware of what's going on while he/she is on the go (away from the desk) inside or outside of facility and potentially take some actions.
Solution: Microsoft has a variety of Windows 8 Apps integrated with Microsoft Dynamics AX 2012 available in the Store, as well as published a detailed guidance on how to develop secure mobile apps integrated with Microsoft Dynamics AX 2012. In this document I'll look at things from designer perspective who is likely familiar with some graphical design software packages, has knowledge of HTML5/CSS and experience in web design using Expression Blend. In this walkthrough I'll try to be one of those web designers who wants to design a concept modern Windows 8 App for Production Manager described above within less than 1 hour. All I need in this particular case is a PC running Windows 8.1 and Blend for Visual Studio 2013 installed on it. You can visually build engaging and sophisticated user interfaces for Windows 8 apps by using the accurate design surface and tools in Blend for Visual Studio.
Please note that in this document I assume that the reader has web design skills, has a desire to quickly design modern Windows 8 App for business and will be ready to collaborate with a developer in order to add appropriate business logic when bringing the app to life.
Please find more info about Blend for Visual Studio 2013 here: http://msdn.microsoft.com/en-us/library/jj171012.aspx
Important: Please note that as I Designer I'll be able to design my app by using Blend for Visual Studio 2013 graphical user interface and only minimally touching HTML/CSS code, and I'll collaborate with developer when it comes to displaying dynamic data or there's a need to work on some complex aspects of User Interface which would require coding.
Walkthrough
Let's begin!
In the previous walkthrough (Part 1) we have defined a business scenario for Production Manager App.
Now since we know what we want to build, we can discuss what we will be using to do that
All I'll be given is Windows 8.1 PC and Blend for Visual Studio 2013 installed.
The idea is to utilize Windows 8 Hub App standard Visual Studio 2013 template and rich visual tools of Blend which will give me a jump start for Production Manager App design. Please note that most of the work I'll do below will be template transformation using rich graphical user interface of Blend. Let's see how far we can get with standard Visual Studio Hub App template and Blend for Visual Studio 2013. My goal is still to do this within less than 1 hour
Let's get to work now
First step will be to create a Project based on Visual Studio 2013 Hub App template
Visual Studio 2013 – Hub App
Hub App template will give me a desired pages structure, navigation and more right away
Let's review what we've already got
For example, this is how our future Overview screen looks like in Blend graphical user interface. Web designers will certainly find themselves at home using Blend user interface.
Overview screen (All production orders)
In Blend you will have access to all possible types of standard HTML and Windows 8 specific WinJS controls
Windows App Controls
HTML Elements
Please note that in Blend graphical user interface you can use drag-n-drop, copy and paste, selections of valid values/properties from drop-down lists and other techniques you are used to in graphical design software packages, as well as to have access to HTML/CSS code if necessary
Our first step will be to change the title of our app
In order to do that I will simply double click on text "BlendApp1" on the canvas and change it to "Manufacturing Companion". Please note that underlying HTML code will automatically be changed to which you can immediately observe in hub.html displayed in lower pane of the screen
Now our Overview screen will look like this
Next I'd like to change a color schema
In order to do that I'll simply right click on ui-dark.css reference and then change it to ui-light.css using drop-down selection on Edit Style Reference form
Now we got a light color schema which looks better to me
Next I'll make our app look friendlier and more professional by adding a background image
Here's the image I want to use for my Production Manager App
I put the file into images folder and add it to the project
Now all I need to do is to change Hero image "background-image: url" property in CSS Properties pane on the right hand side
Now our app looks even better
Please note that our Overview screen currently looks busy and truly all we need for our app is Section 3. That's why on the next step we're going to simply our template. In order to simplify a template you may want to get rid of some UI elements at all by deleting them, or you may want to comment some UI element out instead. Please note that you can install an add-in for Blend which will allow you to comment/uncomment HTML code using Blend graphical user interface and without touching the code itself. For example, you can use BlendShortcuts add-in available on CodePlex for this purpose: http://blendshortcuts.codeplex.com/
In my case I'll comment out appropriate HTML manually using HTML editor in Blend. Please note that Blend conveniently highlights appropriate block of HTML when you select particular UI element on the canvas which makes Designer life much easier.
Now Overview page looks like what we need
Next we'll further modify Hub page
This time we will copy and paste styles from Section to Hub to make it look like we need
Please note that you can copy styles from one CSS document to another, and/or introduce new styles as appropriate using copy and paste technique in Blend
This is how Overview screen looks like right now
Just for the reference I'll provide HTML/CSS source code for Hub and Section pages, so we will be able to get an idea about changes we introduced in code
Let's look at how the source code looked like Before changes
Hub.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hubPage</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.2.0/css/ui-light.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
<link href="/css/default.css" rel="stylesheet" />
<link href="/pages/hub/hub.css" rel="stylesheet" />
<script src="/js/data.js"></script>
<script src="/pages/hub/hub.js"></script>
</head>
<body>
<div class="hubpage fragment">
<header aria-label="Header content" role="banner">
<button data-win-control="WinJS.UI.BackButton"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Manufacturing Companion</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<!-- Customize the Hub control by modifying the HubSection controls here. -->
<div class="hub" data-win-control="WinJS.UI.Hub">
<div class="hero" data-win-control="WinJS.UI.HubSection">
</div>
<!--
<div class="section1" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section1'} }">
<img src="/images/gray.png" width="420" height="280" />
<div class="subtext win-type-x-large" data-win-res="{ textContent: 'Section1Subtext' }"></div>
<div class="win-type-medium" data-win-res="{ textContent: 'DescriptionText' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section1Description' }"></span>
<span data-win-res="{ textContent: 'Section1Description' }"></span>
<span data-win-res="{ textContent: 'Section1Description' }"></span>
</div>
</div>
<div class="section2" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section2'} }">
<div class="item-title win-type-medium" data-win-res="{ textContent: 'Section2ItemTitle' }"></div>
<div class="article-header win-type-x-large" data-win-res="{ textContent: 'Section2Subtext' }"></div>
<div class="win-type-xx-small" data-win-res="{ textContent: 'Section2ItemSubTitle' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
</div>
</div>
-->
<div class="section3" data-win-control="WinJS.UI.HubSection" data-win-res="{ winControl: {'header': 'Section3'} }"
data-win-options="{ onheaderinvoked: select('.pagecontrol').winControl.section3HeaderNavigate }">
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<img src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="win-type-medium" data-win-bind="textContent: title"></div>
<div class="win-type-small" data-win-bind="textContent: description"></div>
</div>
<div class="itemslist win-selectionstylefilled" data-win-control="WinJS.UI.ListView" data-win-options="{
layout: {type: WinJS.UI.GridLayout},
selectionMode: 'none',
itemTemplate: select('.section3 .itemTemplate'),
itemDataSource: select('.pagecontrol').winControl.section3DataSource,
oniteminvoked: select('.pagecontrol').winControl.section3ItemNavigate
}">
</div>
</div>
<!--
<div class="section4" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section4'} }">
<div class="top-image-row">
<img src="/images/gray.png" />
</div>
<div class="sub-image-row">
<img src="/images/gray.png" />
<img src="/images/gray.png" />
<img src="/images/gray.png" />
</div>
<div class="win-type-medium" data-win-res="{ textContent: 'DescriptionText' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section4Description' }"></span>
<span data-win-res="{ textContent: 'Section4Description' }"></span>
</div>
</div>
-->
</div>
</section>
</div>
</body>
</html>
|
Hub.css
.hubpage header[role=banner] {
position: relative;
z-index: 2;
}
.hubpage section[role=main] {
-ms-grid-row: 1;
-ms-grid-row-span: 2;
z-index: 1;
}
.hubpage .hub .win-hub-surface {
height: 100%;
}
.hubpage .hub .hero {
-ms-high-contrast-adjust: none;
background-image: url(/images/gears.jpg);
background-size: cover;
margin-left: -80px;
margin-right: 80px;
padding: 0;
width: 780px;
}
.hubpage .hub .hero:-ms-lang(ar, dv, fa, he, ku-Arab, pa-Arab, prs, ps, sd-Arab, syr, ug, ur, qps-plocm) {
margin-left: 80px;
margin-right: -80px;
}
.hubpage .hub .hero .win-hub-section-header {
display: none;
}
.hubpage .hub .section1 {
width: 420px;
}
.hubpage .hub .section1 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section1 .subtext {
margin-bottom: 7px;
margin-top: 9px;
}
.hubpage .hub .section2 {
width: 440px;
}
.hubpage .hub .section2 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section2 .item-title {
margin-top: 4px;
margin-bottom: 10px;
}
.hubpage .hub .section2 .article-header {
margin-bottom: 15px;
}
.hubpage .hub .section3 {
}
.hubpage .hub .section3 .itemslist {
height: 100%;
margin-left: -10px;
margin-right: -10px;
margin-top: -5px;
}
.hubpage .hub .section3 .win-container {
margin-bottom: 36px;
margin-left: 10px;
margin-right: 10px;
}
.hubpage .hub .section3 .win-item {
height: 229px;
width: 310px;
}
.hubpage .hub .section3 .win-item img {
height: 150px;
margin-bottom: 10px;
width: 310px;
}
.hubpage .hub .section4 {
width: 400px;
}
.hubpage .hub .section4 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section4 .top-image-row {
height: 260px;
margin-bottom: 10px;
width: 400px;
}
.hubpage .hub .section4 .top-image-row img {
height: 100%;
width: 100%;
}
.hubpage .hub .section4 .sub-image-row {
margin-bottom: 20px;
display: -ms-flexbox;
-ms-flex-flow: row nowrap;
-ms-flex-pack: justify;
}
.hubpage .hub .section4 .sub-image-row img {
height: 95px;
width: 130px;
}
|
Section.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>sectionPage</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.2.0/css/ui-light.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
<link href="/css/default.css" rel="stylesheet" />
<link href="/pages/section/section.css" rel="stylesheet" />
<script src="/js/data.js"></script>
<script src="/pages/section/section.js"></script>
</head>
<body>
<!-- These templates are used to display each item in the ListView declared below. -->
<!--
<div class="headertemplate" data-win-control="WinJS.Binding.Template">
<h2 class="group-subtitle" data-win-res="{ 'innerHTML': 'SectionSubtitle' }"></h2>
<img class="group-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<h4 class="group-description" data-win-bind="innerHTML: description"></h4>
</div>
-->
<div class="itemtemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-info">
<h4 class="item-title" data-win-bind="textContent: title"></h4>
<h6 class="win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
<h4 class="item-description" data-win-bind="textContent: description"></h4>
</div>
</div>
</div>
<!-- The content that will be loaded and displayed. -->
<div class="sectionpage fragment">
<header aria-label="Header content" role="banner">
<button data-win-control="WinJS.UI.BackButton"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle"></span>
</h1>
</header>
<section aria-label="Main content" role="main">
<div class="itemslist win-selectionstylefilled" aria-label="List of this section's items" data-win-control="WinJS.UI.ListView" data-win-options="{
selectionMode: 'none',
layout: {type: WinJS.UI.GridLayout, groupHeaderPosition: 'left'},
currentItem: {type: WinJS.UI.ObjectType.item, index: 0, hasFocus: true},
itemDataSource: select('.pagecontrol').winControl.itemDataSource,
itemTemplate: select('.itemtemplate'),
oniteminvoked: select('.pagecontrol').winControl.itemInvoked
}">
<!--
groupDataSource: select('.pagecontrol').winControl.groupDataSource,
groupHeaderTemplate: select('.headertemplate'),
-->
</div>
</section>
</div>
</body>
</html>
|
Section.css
.sectionpage section[role=main] {
-ms-grid-row: 1;
-ms-grid-row-span: 2;
}
.sectionpage .itemslist {
height: 100%;
position: relative;
width: 100%;
z-index: 0;
}
/* This selector is used to prevent ui-dark/light.css from overwriting changes
to .win-surface. */
.sectionpage .itemslist .win-horizontal.win-viewport .win-surface {
margin-bottom: 35px;
margin-left: 50px;
margin-right: 50px;
margin-top: 128px;
}
.sectionpage .itemslist .win-groupheader {
-ms-grid-columns: 1fr;
-ms-grid-rows: auto auto 1fr;
display: -ms-grid;
height: 100%;
margin-left: 0;
margin-right: 40px;
padding: 0;
width: 480px;
}
.sectionpage .itemslist .win-groupheader:-ms-lang(ar, dv, fa, he, ku-Arab, pa-Arab, prs, ps, sd-Arab, syr, ug, ur, qps-plocm) {
margin-left: 40px;
margin-right: 0;
}
.sectionpage .itemslist .win-groupheader .group-subtitle {
-ms-grid-row: 1;
margin-bottom: 14px;
margin-top: 6px;
max-height: 48pt;
overflow: hidden;
}
.sectionpage .itemslist .win-groupheader .group-image {
-ms-grid-row: 2;
background-color: rgba(147, 149, 152, 1);
height: 240px;
margin: 0;
margin-bottom: 20px;
width: 480px;
}
.sectionpage .itemslist .win-groupheader .group-description {
-ms-grid-row: 3;
margin-bottom: 55px;
overflow: hidden;
}
.sectionpage .itemslist .win-container {
margin-bottom: 11px;
margin-left: 33px;
margin-right: 33px;
margin-top: 5px;
}
.sectionpage .itemslist .item {
-ms-grid-columns: 110px 10px 1fr;
-ms-grid-rows: 1fr;
display: -ms-grid;
height: 110px;
padding: 7px;
width: 480px;
}
.sectionpage .itemslist .item .item-info {
-ms-grid-column: 3;
}
.sectionpage .itemslist .item .item-info .item-title {
margin-top: 4px;
max-height: 20px;
overflow: hidden;
}
.sectionpage .itemslist .item .item-info .item-description {
max-height: 60px;
overflow: hidden;
}
|
And this is how the source code looks like After changes
Hub.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>hubPage</title>
<!-- WinJS references -->
<link href="//Microsoft.WinJS.2.0/css/ui-light.css" rel="stylesheet" />
<script src="//Microsoft.WinJS.2.0/js/base.js"></script>
<script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
<link href="/css/default.css" rel="stylesheet" />
<link href="/pages/hub/hub.css" rel="stylesheet" />
<script src="/js/data.js"></script>
<script src="/pages/hub/hub.js"></script>
</head>
<body>
<div class="hubpage fragment">
<header aria-label="Header content" role="banner">
<button data-win-control="WinJS.UI.BackButton"></button>
<h1 class="titlearea win-type-ellipsis">
<span class="pagetitle">Manufacturing Companion</span>
</h1>
</header>
<section aria-label="Main content" role="main">
<!-- Customize the Hub control by modifying the HubSection controls here. -->
<div class="hub" data-win-control="WinJS.UI.Hub">
<div class="hero" data-win-control="WinJS.UI.HubSection">
</div>
<!--
<div class="section1" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section1'} }">
<img src="/images/gray.png" width="420" height="280" />
<div class="subtext win-type-x-large" data-win-res="{ textContent: 'Section1Subtext' }"></div>
<div class="win-type-medium" data-win-res="{ textContent: 'DescriptionText' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section1Description' }"></span>
<span data-win-res="{ textContent: 'Section1Description' }"></span>
<span data-win-res="{ textContent: 'Section1Description' }"></span>
</div>
</div>
<div class="section2" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section2'} }">
<div class="item-title win-type-medium" data-win-res="{ textContent: 'Section2ItemTitle' }"></div>
<div class="article-header win-type-x-large" data-win-res="{ textContent: 'Section2Subtext' }"></div>
<div class="win-type-xx-small" data-win-res="{ textContent: 'Section2ItemSubTitle' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
<span data-win-res="{ textContent: 'Section2Description' }"></span>
</div>
</div>
-->
<div class="section3" data-win-control="WinJS.UI.HubSection" data-win-res="{ winControl: {'header': 'Section3'} }"
data-win-options="{ onheaderinvoked: select('.pagecontrol').winControl.section3HeaderNavigate }">
<!--
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<img src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="win-type-medium" data-win-bind="textContent: title"></div>
<div class="win-type-small" data-win-bind="textContent: description"></div>
</div>
-->
<div class="itemTemplate" data-win-control="WinJS.Binding.Template">
<div class="item">
<img class="item-image" src="#" data-win-bind="src: backgroundImage; alt: title" />
<div class="item-info">
<h4 class="item-title" data-win-bind="textContent: title"></h4>
<h6 class="win-type-ellipsis" data-win-bind="textContent: subtitle"></h6>
<h4 class="item-description" data-win-bind="textContent: description"></h4>
</div>
</div>
</div>
<div class="itemslist win-selectionstylefilled" data-win-control="WinJS.UI.ListView" data-win-options="{
layout: {type: WinJS.UI.GridLayout},
selectionMode: 'none',
itemTemplate: select('.section3 .itemTemplate'),
itemDataSource: select('.pagecontrol').winControl.section3ItemDataSource,
oniteminvoked: select('.pagecontrol').winControl.section3ItemNavigate
}">
<!--
itemDataSource: select('.pagecontrol').winControl.section3DataSource
-->
</div>
</div>
<!--
<div class="section4" data-win-control="WinJS.UI.HubSection" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'Section4'} }">
<div class="top-image-row">
<img src="/images/gray.png" />
</div>
<div class="sub-image-row">
<img src="/images/gray.png" />
<img src="/images/gray.png" />
<img src="/images/gray.png" />
</div>
<div class="win-type-medium" data-win-res="{ textContent: 'DescriptionText' }"></div>
<div class="win-type-small">
<span data-win-res="{ textContent: 'Section4Description' }"></span>
<span data-win-res="{ textContent: 'Section4Description' }"></span>
</div>
</div>
-->
</div>
</section>
</div>
</body>
</html>
|
Hub.css
.hubpage header[role=banner] {
position: relative;
z-index: 2;
}
.hubpage section[role=main] {
-ms-grid-row: 1;
-ms-grid-row-span: 2;
z-index: 1;
}
.hubpage .hub .win-hub-surface {
height: 100%;
}
.hubpage .hub .hero {
-ms-high-contrast-adjust: none;
background-image: url(/images/gears.jpg);
background-size: cover;
margin-left: -80px;
margin-right: 80px;
padding: 0;
width: 780px;
}
.hubpage .hub .hero:-ms-lang(ar, dv, fa, he, ku-Arab, pa-Arab, prs, ps, sd-Arab, syr, ug, ur, qps-plocm) {
margin-left: 80px;
margin-right: -80px;
}
.hubpage .hub .hero .win-hub-section-header {
display: none;
}
.hubpage .hub .section1 {
width: 420px;
}
.hubpage .hub .section1 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section1 .subtext {
margin-bottom: 7px;
margin-top: 9px;
}
.hubpage .hub .section2 {
width: 440px;
}
.hubpage .hub .section2 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section2 .item-title {
margin-top: 4px;
margin-bottom: 10px;
}
.hubpage .hub .section2 .article-header {
margin-bottom: 15px;
}
.hubpage .hub .section3 {
}
.hubpage .hub .section3 .itemslist {
height: 100%;
margin-left: -10px;
margin-right: -10px;
margin-top: -5px;
}
.hubpage .hub .section3 .itemslist .win-container {
margin-bottom: 11px;
margin-left: 33px;
margin-right: 33px;
margin-top: 5px;
}
.hubpage .hub .section3 .itemslist .item {
-ms-grid-columns: 110px 10px 1fr;
-ms-grid-rows: 1fr;
display: -ms-grid;
height: 110px;
padding: 7px;
width: 480px;
}
.hubpage .hub .section3 .itemslist .item .item-info {
-ms-grid-column: 3;
}
.hubpage .hub .section3 .itemslist .item .item-info .item-title {
margin-top: 4px;
max-height: 20px;
overflow: hidden;
}
.sectionpage .itemslist .item .item-info .item-description {
max-height: 60px;
overflow: hidden;
}
/*
.hubpage .hub .section3 .win-container {
margin-bottom: 36px;
margin-left: 10px;
margin-right: 10px;
}
.hubpage .hub .section3 .win-item {
height: 229px;
width: 310px;
}
.hubpage .hub .section3 .win-item img {
height: 150px;
margin-bottom: 10px;
width: 310px;
}
*/
.hubpage .hub .section4 {
width: 400px;
}
.hubpage .hub .section4 .win-hub-section-content {
overflow-y: hidden;
}
.hubpage .hub .section4 .top-image-row {
height: 260px;
margin-bottom: 10px;
width: 400px;
}
.hubpage .hub .section4 .top-image-row img {
height: 100%;
width: 100%;
}
.hubpage .hub .section4 .sub-image-row {
margin-bottom: 20px;
display: -ms-flexbox;
-ms-flex-flow: row nowrap;
-ms-flex-pack: justify;
}
.hubpage .hub .section4 .sub-image-row img {
height: 95px;
width: 130px;
}
|
For the next step it would be nice to introduce some meaningful demo data. As a Designer my preference would be to create a file with demo data and then give it to developer to embed this into the app. In this case I can fully control how demo data will look like and I'll not need to touch the code. This will be easy to do using JSON file format
JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write. It is easy for machines to parse and generate. Please find more info about JSON here: http://www.json.org/
I'll then create data.json file
And put my demo data in there in key-value pairs in correspondence with JSON format
Here's the full listing of my demo data file (data.json)
[{ "groupkey": "current", "grouptitle": "Current orders", "groupsubtitle": "Current orders", "groupbackgroundImage": "../../images/gear.png", "description": "Group description", "title": "Production order 101", "subtitle": "Production order 101", "description": "Item description", "content": "Item content", "backgroundImage": "../../images/gear.png" },
{ "groupkey": "current", "grouptitle": "Current orders", "groupsubtitle": "Current orders", "groupbackgroundImage": "../../images/gear.png", "description": "Group description", "title": "Production order 102", "subtitle": "Production order 102", "description": "Item description", "content": "Item content", "backgroundImage": "../../images/gear.png" },
{ "groupkey": "delayed", "grouptitle": "Delayed orders", "groupsubtitle": "Delayed orders", "groupbackgroundImage": "../../images/gear.png", "description": "Group description", "title": "Production order 201", "subtitle": "Production order 201", "description": "Item description", "content": "Item content", "backgroundImage": "../../images/gear.png" },
{ "groupkey": "delayed", "grouptitle": "Delayed orders", "groupsubtitle": "Delayed orders", "groupbackgroundImage": "../../images/gear.png", "description": "Group description", "title": "Production order 202", "subtitle": "Production order 202", "description": "Item description", "content": "Item content", "backgroundImage": "../../images/gear.png" }]
|
After that I'll add this file to the project, so my developer can use it
You also noticed that I put "backgroundImage" URL for the product being produced in Production order. So I'll also need to make sure this image is available at the path specified in demo data file. For this purpose I'll add gear image file to images folder
And subsequently I'll add this file to the project
Now my developer can pick this file up and display the demo data appropriately
The only preparation work we have to do before my developer can programmatically parse demo data file out will be to change data.json file Property Package Action = Content
Now my developer will do the following modifications to data.js file in order to display demo data properly. I'll provide source code below just for your information
Data.js
(function () {
"use strict";
var demoItems = new WinJS.Binding.List();
//var list = new WinJS.Binding.List();
var list = generateDemoData();
var groupedItems = list.createGrouped(
function groupKeySelector(item) { return item.group.key; },
function groupDataSelector(item) { return item.group; }
);
/*
// TODO: Replace the data with your real data.
// You can add data from asynchronous sources whenever it becomes available.
generateSampleData().forEach(function (item) {
list.push(item);
});
*/
WinJS.Namespace.define("Data", {
items: groupedItems,
groups: groupedItems.groups,
getItemReference: getItemReference,
getItemsFromGroup: getItemsFromGroup,
resolveGroupReference: resolveGroupReference,
resolveItemReference: resolveItemReference
});
// Get a reference for an item, using the group key and item title as a
// unique reference to the item that can be easily serialized.
function getItemReference(item) {
return [item.group.key, item.title];
}
// This function returns a WinJS.Binding.List containing only the items
// that belong to the provided group.
function getItemsFromGroup(group) {
return list.createFiltered(function (item) { return item.group.key === group.key; });
}
// Get the unique group corresponding to the provided group key.
function resolveGroupReference(key) {
return groupedItems.groups.getItemFromKey(key).data;
}
// Get a unique item from the provided string array, which should contain a
// group key and an item title.
function resolveItemReference(reference) {
for (var i = 0; i < groupedItems.length; i++) {
var item = groupedItems.getAt(i);
if (item.group.key === reference[0] && item.title === reference[1]) {
return item;
}
}
}
// Returns an array of sample data that can be added to the application's
// data list.
function generateSampleData() {
var itemContent = "Item Content";
var itemDescription = "Item Description";
var groupDescription = "Group Description";
// These three strings encode placeholder images. You will want to set the
// backgroundImage property in your real data to be URLs to images.
var darkGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY3B0cPoPAANMAcOba1BlAAAAAElFTkSuQmCC";
var lightGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY7h4+cp/AAhpA3h+ANDKAAAAAElFTkSuQmCC";
var mediumGray = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXY5g8dcZ/AAY/AsAlWFQ+AAAAAElFTkSuQmCC";
// Each of these sample groups must have a unique key to be displayed
// separately.
var sampleGroups = [
{ key: "group1", title: "Group Title: 1", subtitle: "Group Subtitle: 1", backgroundImage: darkGray, description: groupDescription },
{ key: "group2", title: "Group Title: 2", subtitle: "Group Subtitle: 2", backgroundImage: lightGray, description: groupDescription },
{ key: "group3", title: "Group Title: 3", subtitle: "Group Subtitle: 3", backgroundImage: mediumGray, description: groupDescription },
{ key: "group4", title: "Group Title: 4", subtitle: "Group Subtitle: 4", backgroundImage: lightGray, description: groupDescription },
{ key: "group5", title: "Group Title: 5", subtitle: "Group Subtitle: 5", backgroundImage: mediumGray, description: groupDescription },
{ key: "group6", title: "Group Title: 6", subtitle: "Group Subtitle: 6", backgroundImage: darkGray, description: groupDescription }
];
// Each of these sample items should have a reference to a particular
// group.
var sampleItems = [
{ group: sampleGroups[0], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[0], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[0], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[0], title: "Item Title: 4", subtitle: "Item Subtitle: 4", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[0], title: "Item Title: 5", subtitle: "Item Subtitle: 5", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[1], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[1], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[1], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[2], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[2], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[2], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[2], title: "Item Title: 4", subtitle: "Item Subtitle: 4", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[2], title: "Item Title: 5", subtitle: "Item Subtitle: 5", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[2], title: "Item Title: 6", subtitle: "Item Subtitle: 6", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[2], title: "Item Title: 7", subtitle: "Item Subtitle: 7", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[3], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[3], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[3], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[3], title: "Item Title: 4", subtitle: "Item Subtitle: 4", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[3], title: "Item Title: 5", subtitle: "Item Subtitle: 5", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[3], title: "Item Title: 6", subtitle: "Item Subtitle: 6", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[4], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[4], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[4], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[4], title: "Item Title: 4", subtitle: "Item Subtitle: 4", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[5], title: "Item Title: 1", subtitle: "Item Subtitle: 1", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[5], title: "Item Title: 2", subtitle: "Item Subtitle: 2", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[5], title: "Item Title: 3", subtitle: "Item Subtitle: 3", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[5], title: "Item Title: 4", subtitle: "Item Subtitle: 4", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[5], title: "Item Title: 5", subtitle: "Item Subtitle: 5", description: itemDescription, content: itemContent, backgroundImage: lightGray },
{ group: sampleGroups[5], title: "Item Title: 6", subtitle: "Item Subtitle: 6", description: itemDescription, content: itemContent, backgroundImage: mediumGray },
{ group: sampleGroups[5], title: "Item Title: 7", subtitle: "Item Subtitle: 7", description: itemDescription, content: itemContent, backgroundImage: darkGray },
{ group: sampleGroups[5], title: "Item Title: 8", subtitle: "Item Subtitle: 8", description: itemDescription, content: itemContent, backgroundImage: lightGray }
];
return sampleItems;
}
function objectFindByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
function generateDemoData() {
WinJS.xhr({ url: "data/data.json" }).then(
function complete(r) {
var orderList = JSON.parse(r.responseText);
var demoGroups = [];
for (var i = 0; i < orderList.length; i++) {
var demoGroup = objectFindByKey(demoGroups, 'key', orderList[i].groupkey);
if (demoGroup == null) {
demoGroup = { key: orderList[i].groupkey, title: orderList[i].grouptitle, subtitle: orderList[i].groupsubtitle, backgroundImage: orderList[i].groupbackgroundImage };
demoGroups.push(demoGroup);
}
demoItems.push({ group: demoGroup, title: orderList[i].title, subtitle: orderList[i].subtitle, description: orderList[i].description, content: orderList[i].content, backgroundImage: orderList[i].backgroundImage });
}
},
function error(er)
{
//TODO:
});
return demoItems;
}
})();
|
Please note that in data.json file I provided info about group particular item belongs to with the info about this item, that's why I needed to dynamically compose the list of groups for reuse. In order to simplify this task I introduced objectFindByKey JavaScript function. Ultimately I used WinJS.xhr (XML HTTP Request) to read the info from the file just like I'd read this through a Web Services
Alternatively we could use Windows.Storage.StorageFile.getFileFromApplicationUriAsync method to access the file, and then Windows.Storage.FileIO.readTextAsync method to parse out info which contains a single JSON object definition, or Windows.Storage.FileIO.readLinesAsync method to parse out info which may contains one JSON object per line (for example, group versus item)
Before we'll take a look at the result I'll also need to ask my developer to make a necessary adjustment in Hub.js to work with groups data properly
Hub.js
(function () {
"use strict";
var nav = WinJS.Navigation;
var session = WinJS.Application.sessionState;
var util = WinJS.Utilities;
// Get the groups used by the data-bound sections of the Hub.
/*
var section3Group = Data.resolveGroupReference("group4");
var section3Items = Data.getItemsFromGroup(section3Group);
*/
var section3Groups = Data.groups;
var section3Items = Data.items;
WinJS.UI.Pages.define("/pages/hub/hub.html", {
processed: function (element) {
return WinJS.Resources.processAll(element);
},
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
ready: function (element, options) {
var hub = element.querySelector(".hub").winControl;
hub.onheaderinvoked = function (args) {
args.detail.section.onheaderinvoked(args);
};
hub.onloadingstatechanged = function (args) {
if (args.srcElement === hub.element && args.detail.loadingState === "complete") {
hub.onloadingstatechanged = null;
hub.element.focus();
}
}
// TODO: Initialize the page here.
},
navigateToGroup: function (key) {
nav.navigate("/pages/section/section.html", { groupKey: key });
},
/*
section3DataSource: section3Items.dataSource,
*/
section3GroupDataSource: section3Groups.dataSource,
section3ItemDataSource: section3Items.dataSource,
/*
section3HeaderNavigate: util.markSupportedForProcessing(function (args) {
nav.navigate("/pages/section/section.html", { title: args.detail.section.header, groupKey: section3Group.key });
}),
*/
section3ItemNavigate: util.markSupportedForProcessing(function (args) {
var item = Data.getItemReference(section3Items.getAt(args.detail.itemIndex));
nav.navigate("/pages/item/item.html", { item: item });
}),
unload: function () {
// TODO: Respond to navigations away from this page.
},
updateLayout: function (element) {
/// <param name="element" domElement="true" />
// TODO: Respond to changes in layout.
},
});
})();
|
Now we can take a look at the result
Please note that the challenge when using dynamic demo data (for example, from the file) is that you will not see the data at design time, but only at run time. So it would make sense to use static demo data for design and then switch to dynamic demo data at the end
Next I'll also change the title of the section from "Section 3" to "Production order" to better reflect a nature of the data
In this case section title is defined in resources.resjson file which you can find in strings/en-US folder
as displayed below
You can open this file in Blend or simply in Notepad in order to make necessary changes
Please find more info about how to load string resources in Windows 8 Apps here: http://msdn.microsoft.com/en-us/library/windows/apps/hh465248.aspx
Now Overview screen will look like the following at design time
And like this at run time. As you can see our dynamic demo data gets properly displayed on Overview screen
For Item screen we're going to create design completely from scratch
For this purpose we will use a rich capabilities in Blend which allows you to work with Html attributes (for example, display properties, Grid columns, h2 textContent, etc.), CSS attributes (for example, create CSS classes, etc.), manipulate with image using drag-n-drop technique and more, more, more. I just listed some of the features I used when I was designing Item screen using graphical user interface in Blend
Please note how easy it is to design a page when you have full visibility of all UI elements at real-time when designing and also a full control over all possible properties for these UI elements
I'll just provide couple of examples
This is how you can very quickly "drag-n-drop" Item page structure which consists of <article>, number of <div>'s, <image>'s and <h2> tags. In particular I want to highlight how to define design grid structure for the <article>. Please note how I specified "display: -ms-grid;" property for <article> tag in CSS Properties pane on the right hand side
For newly created UI elements I also wanted to introduce a dedicated CSS classes which is very easy to do by right-clicking on appropriate UI element and selecting "Add New Class" function
And, of course, it is very intuitive to work with HTML Attributes/properties in Blend. For example, this is how I defined labels for sections headers (textContent = Overview)
As the result you can very quickly and precisely design Item screen like this
Using Blend Designers can create amazing User Experiences to truly delight people
In conclusion I'll provide an example of modern Concept Windows 8 App built which was built to make Driver's life easier. Imagine that your company manages a fleet of vehicles and assigns delivery tasks to drivers. The typical workload for Driver role would be to review the list of Delivery routes for the day, get info about all the stops on the way and delivery addresses, obtain optimal driving directions using Bing Maps, etc. This concept app is called Driver Companion and it was demonstrated at Convergence 2014 event. I hope this app will be a good inspiration for you to go ahead and build your own beautiful modern Windows 8 apps for business.
Driver Companion - Welcome
Driver Companion – My schedule
Driver Companion – Bing Maps
Summary: This document describes how use Blend for Visual Studio 2013 to very quickly design a modern Windows 8 App which implements a real-world Manufacturing industry scenario for Production Manager role. I also provided an example of the app for Driver role which was built using rapid business app development technique and the guidance published by Microsoft on how to build Secure Mobile Apps for Microsoft Dynamics AX 2012.
Please find more info about how to build Secure Mobile Apps for Microsoft Dynamics AX 2012 in White Paper here: http://www.microsoft.com/en-us/download/details.aspx?id=38413
Tags: Dynamics ERP, Apps, Windows 8.1, Blend for Visual Studio 2013, HTML5, CSS, Hub App, User Interface, Manufacturing, Production Manager.
Note: This document is intended for information purposes only, presented as it is with no warranties from the author. This document may be updated with more content to better outline the concepts and describe the examples.