Quantcast
Channel: Jive Syndication Feed

Mobile Dev Course W3U3 Rewrite - Intro

$
0
0

tl;dr - the Github repo "w3u3_redonebasic" is a simple re-write of one of the open.sap.com mobile course sample SAPUI5 apps to fix some fundamental issues.

 

 

In the current open.sap.com course Introduction to Mobile Solution Development there are a number of SAPUI5 based example apps that are used to illustrate various concepts and provide learning and exercise materials. Unfortunately, these apps don't particularly show good techniques; in fact I'd go so far as to say that some of the approaches used are simply not appropriate:

 

  • they ignore fundamental SAPUI5 mechanisms such as automatic module loading
  • libraries are included that aren't necessary and may cause issues
  • OData and JSON models are confused and abused

 

I would class these as "must-change". There is an urgency of scale at work here as much as anything else; there are over 28,000 registered participants on this course, and it would make me happy to think that there's a way to get them back on the right path, SAPUI5-wise.

 

There are of course other aspects that are less "incorrect" with the app but neverthless perhaps better done a different way. I would class these as "nice-to-have". Examples are:

 

  • views are built in JavaScript instead of declaratively in XML*
  • general app organisation could be improved; there is an Application.js-based 'best practice' approach in the publically available documentation but this has not been followed (there is also a Component-based approach*)

 

*both these things will become more important over time, starting very soon!

 

So I've picked a first app - the "MyFirstEnterpriseReadyWebApp" in Week 3 Unit 3 (W3U3) - and re-written it. I have addressed the "must-change" aspects, but left (for now) the "nice-to-have" aspects.

 

I stuck to the following principles:

 

  • not to deviate from the general architectural approach too much (i.e. remain with MVC and have the same views and progression through them)
  • not to introduce any new functionality or styling (save for moving from sap_mvi to sap_bluecrystal)
  • to keep the app code and structure feel as close to the original as possible

 

These principles are so that any course participant who has already looked at the original app will feel at home and be able to more easily recognise the improvements.

 

I've pushed my new "Redone, Basic" version of the W3U3 app to Github so the code is available for everyone to study and try out, but also over the course of the next few posts I'll highlight some of the changes and describe the differences and the fixes, and the reasons why. Until then, have a look at the repo "w3u3_redonebasic" and see what you think.

 

Here are the follow on posts (links inserted here as I write them) dealing with the detail of the rewrite:

 

Mobile Dev Course W3U3 Rewrite - Index and Structure

Mobile Dev Course W3U3 Rewrite - App and Login

Mobile Dev Course W3U3 Rewrite - ProductList, ProductDetail and SupplierDetail

Mobile Dev Course W3U3 Rewrite - XML Views - An Intro

Mobile Dev Course W3U3 Rewrite - XML Views - An Analysis

 

Share & enjoy

dj


Mobile Dev Course W3U3 Rewrite - Index & Structure

$
0
0

I rewrote the mobile dev course sample app from W3U3. This post explains what I changed in the index.html file, and why. It also takes a look at the general app structure of directories and files.

 

First, I'll take the lines of the new version of index.html chunk-by-chunk, with comments.

 

index.html

 

<!DOCTYPE HTML><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>    <title>W3U3 Redone Basic</title>

 

There's an important meta tag that I added, the X-UA-Compatible one. This is to give IE the best chance of running SAPUI5 properly. Without this there could be rendering issues in IE. (Of course, the alternative is to stop using IE altogether, but that's a different debate!)

 

 

    <script src="https://sapui5.hana.ondemand.com/sdk/resources/sap-ui-core.js"        type="text/javascript"        id="sap-ui-bootstrap"        data-sap-ui-libs="sap.m"        data-sap-ui-xx-bindingSyntax="complex"        data-sap-ui-theme="sap_bluecrystal">    </script>

 

Here in the bootstrap tag I'm specifying the complex binding syntax, which I'll be using later on (in the ProductDetail view, to fix a problem with the product image URL). I'm also specifying the Blue Crystal theme (sap_bluecrystal), rather than the Mobile Visual Identity theme (sap_mvi).

 

 

    <script>        jQuery.sap.log.setLevel(jQuery.sap.log.LogLevel.INFO);        jQuery.sap.registerModulePath("com.opensap", "./myapp/");        sap.ui.jsview("idAppView", "com.opensap.App").placeAt("root");    </script>

 

This is where you'll see the biggest change in this file. The open.sap.com course version has a ton of <script> tags (12, to be precise) to load every single file in the app, including some that aren't even necessary. This is simply ignoring the automatic module loading mechanism that is built into SAPUI5. The mechanism not only allows you to avoid a sea of <script> tags, it also allows you to organise your app's resources in a clean and efficient way, refer to them semantically rather than physically, and have load-on-demand features too.

 

Here, we're saying "modules that begin with 'com.opensap' can be found in the 'myapp' directory below where we are". And then we use that module loading system directly by asking for the instantiation of the "com.opensap.App" view (giving it an id of "idAppView") before having it rendered in the main <div> tag (see below).

 

Also note the use of the jQuery.sap.log.* functions for console logging. This abstracts the logging mechanism so you don't have to think about whether console.log works in a particular browser properly (yes IE, I'm looking at you again).

 

 

  </head>  <body class="sapUiBody">    <div id="root"></div>  </body></html>

 

Instantiating the view, which has an associated controller with an onInit function, is also a better way, or at least a more SAPUI5-way, to kick off processing, rather than have a function referred to in the onload attribute of the <body> tag, as the open.sap.com course version of the app does. So this is how we're doing it here. The sap.ui.jsview call causes that view's controller's onInit function to run, rather than having an onload="initializeModel()" in the <body> tag.

 

 

Files and app organisation

 

This post is also probably the best place to cover the app's organization and files, so I'll do that here as well. First, I'll show you what the original open.sap.com version looks like, then I'll show you what this new version looks like.

 

Original version

originalstructure.PNG

Here we see first of all that the views and controllers are split into separate directories. This isn't wrong, it just feels a little odd. So in the new version I've put the view/controller pairs together in a "myapp" directory.

 

More disconcerting is the model/modeljs file. The fact that the file is in a directory called "model" suggests that it has something to do with data. But when we look into the file there is some data-relevant stuff (creating a JSON model and setting it on the core) but there's also some view instantiation and placement for rendering. This is not ideal. It's not a problem that the model file is a script (when you're building JSON-based model data manually it's often useful to be able to construct properties and values dynamically), but I do have a problem with the mix of concerns.

 

There's often a js directory when the app requires 3rd party libraries that have functionality that SAPUI5 does not provide. These three files (Base64.js, datajs-1.1.1.js and SMPCloudHTTPClient.js) do not fall into this category, and don't belong here. In fact, this whole directory and its contents is not required:

 

  • Base64.js is used in the original version to encode a Basic Authentication username / password combination. As you'll see, this is very manual and not necessary.

 

  • The datajs-1.1.1.js library is a specific version of the OData library. SAPUI5 speaks OData natively and does not need an extra library; indeed, the inclusion of a specific version like this may clash with the one that SAPUI5 supplies and uses internally.

 

  • The SMPCloudHTTPClient.js here is used to create a new client object that includes the Application Connection ID (APPCID) header on requests to the SMP server. As you'll see in an upcoming post that looks more closely at the use and abuse of the OData modelling in the app, you'll see that this is also not necessary.

 

 

Rewritten version

newstructure.PNG

 

As you can see, the rewritten version is smaller, doesn't have extraneous and unnecessary libraries and has the view and controller pairs in one directory ("myapp", referred to in the module loading mechanism in the index.html file earlier).

 

It also has a 'real' model file that just has data in it - app.json. This data is just information about the relationship with the app backend on the SMP server and is very similar to the intention of the original version.

 

So, that's it for the index and app organisation. Have a look for yourself at the original and new versions of the index.html and model files, and compare them alongside this description here.

 

See the end of the initial post "Mobile Dev Course W3U3 Rewrite - Intro" for links to all the parts in this series.

 

Until next time, share & enjoy!

dj

Mobile Dev Course W3U3 Rewrite - App & Login

$
0
0

I rewrote the mobile dev course sample app from W3U3. This post explains what I changed in the App and Login views / controllers. See the links at the bottom of the opening post to get to explanations for the other areas.

 

App

In the index.html, we instantiated the App view, which is a JavaScript view. The view has a corresponding controller and they look like this.

 

App.view.js

 

sap.ui.jsview("com.opensap.App", {    getControllerName: function() {        return "com.opensap.App";    },    createContent: function(oController) {        var oApp = new sap.m.App("idApp", {            pages: [                sap.ui.jsview("Login", "com.opensap.Login"),                sap.ui.jsview("ProductList", "com.opensap.ProductList"),                sap.ui.jsview("ProductDetail", "com.opensap.ProductDetail"),                sap.ui.jsview("SupplierDetail", "com.opensap.SupplierDetail")            ]        });        return oApp;    }
});

 

This is actually not too far from the original version. However, it is much shorter, as it takes advantage of the pages aggregation property of the App control, and sticks the views straight in there. This is much quicker and neater than the slightly pedestrian way it is done in the original version. Also, there is no need to navigate explicitly to Login (this.app.to("Login")) as the first control in the aggregation will be the default anyway.

 

App.controller.js

 

sap.ui.controller("com.opensap.App", {    onInit: function() {        sap.ui.getCore().setModel(new sap.ui.model.json.JSONModel("model/app.json"), "app");    }
});


The App controller is even smaller, and uses the onInit event to create the JSON model that will hold the data about the application connection (this was mentioned in the Index & Structure post).

 

Note that rather than having one single model in the app that holds all sorts of unrelated data, as it is done in the original version (there's a single JSON model for everything, and that's it), I am using setModel's optional second parameter, to specify a name ("app") for the model. This way it becomes a "named model" and is not the default (where no name is specified). You'll see later that references to properties in named models are prefixed with the name and a ">" symbol, like this: "{app>/ES1Root}".

 

The original App controller had empty onInit, onBeforeShow and navButtonTap events, which I have of course left out here (I'm guessing they might have come from a controller template and left in there).

 

Login

So the App view is used as a container, that has navigation capabilities (it actually inherits from NavContainer); it doesn't have any direct visible elements of its own. Instead, the "pages" aggregation is what holds the content entities, and the first one in there is the one that's shown by default. In this case it's Login.

 

The Login view and its corresponding controller are somewhat more involved, so let's take a look at the rewritten version step by step.

 

Login.view.js

 

The view itself is fairly self explanatory and doesn't differ too much from the original. There are however a couple of things I want to point out before moving on to the controller.

 

sap.ui.jsview("com.opensap.Login", {    getControllerName: function() {        return "com.opensap.Login";    },    createContent: function(oController) {        return new sap.m.Page({            title: "Login",            showNavButton: false,            footer: new sap.m.Bar({                contentMiddle: [                    new sap.m.Button({                        text: "Login",                        press: [oController.loginPress, oController]                    }),                ]            }),            content: [                new sap.m.List({                    items: [                        new sap.m.InputListItem({                            label: "Username",                            content: new sap.m.Input({ value: "{app>/Username}" })                        }),                        new sap.m.InputListItem({                            label: "Password",                            content: new sap.m.Input({ value: "{app>/Password}", type: sap.m.InputType.Password })                        })                    ]                })            ]        });    }
});

 

First is the use of the press event on the Button control. The tap event (used in the original version of the app) is deprecated. You will see that throughout the app I've replaced the use of 'tap' with 'press'.

 

Also, note how the handler is specified for the press event in the construction of the Button: [fnListenerFunction, oListenerObject] (and it's the same in the original). This form allows you to specify, as the second oListenerObject parameter, the context which 'this' will have in the fnListenerFunction handler. In other words, doing it this way will mean that when you refer to 'this' in your handler, it will do what you probably expect and refer to the controller.

 

Then we have the construction of the values for the Input controls. Because I loaded the data about the application connection into a named model (to keep that separate from the main business data) I have to prefix the model properties with "app>" as mentioned above.

 

 

Login.controller.js

 

So now we'll have a look at the Login controller, and if you compare this new version with the original, you'll see that there are a number of differences.

 

sap.ui.controller("com.opensap.Login", {    oSMPModel: null,

 

As described in the course, this app needs to create a connection with the SMP server. The API with which to do that is OData-based - an OData service at the address

 

https://smp-<your-id>trial.hanatrial.ondemand.com/odata/applications/latest/<your-app-name>/

 

and as you saw in this unit (W3U3) we need to perform an OData "create" operation on the Connections collection to create a new Connection entity. So to do this, I'm using a model to represent the OData service, and I'm storing it singularly in the controller -- we don't need to set the model anywhere on the control tree, the create operation is just to make the connection and get the application connection ID (APPCID).

 

 

    loginPress: function(oEvent) {        var oAppData = sap.ui.getCore().getModel("app").getData();        if (!this.oSMPModel) {            this.oSMPModel = new sap.ui.model.odata.ODataModel(                oAppData.BaseURL + "/odata/applications/latest/" + oAppData.AppName            );        }

 

When the login button is pressed we use the application data (from model/app.json, stored in the named "app" model) to construct the URL of the SMP connections OData service and create an OData model based on that.



        this.oSMPModel.create('/Connections', { DeviceType: "Android" }, null,             jQuery.proxy(function(mResult) {                localStorage['APPCID'] = mResult.ApplicationConnectionId;                this.showProducts(mResult.ApplicationConnectionId);            }, this),            jQuery.proxy(function(oError) {                jQuery.sap.log.error("Connection creation failed");                // Bypass if we already have an id                if (/an application connection with the same id already exists/.test(oError.response.body)) {                    jQuery.sap.log.info("Bypassing failure: already have a connection");                    this.showProducts(localStorage['APPCID']);                }            }, this)        );    },

 

Now we have this SMP model, performing the OData create operation (an HTTP POST request), sending the appropriate entity payload, is as simple as

 

this.oSMPModel.create('/Connections', { DeviceType: 'Android'}, ...)

 

That's it. We just catch the APPCID from the result object and here we're storing it in localStorage on the browser. This is a small workaround to the problem with the original app where you had to delete the connection from the SMP Admin console each time. The failure case being handled here is where we are told that an application connection already exists ... if that's the case then we just grab what we have in localStorage and use that.

 

Unlike the original app version, we're not interested in actually storing any results so there's no need to add it to the model. By the way, if you look at how the APPCID is added to the model in the original app version, there's a pattern used which goes generally like this:

 

  1. var oData = sap.ui.getCore().getModel().getData();
  2. oData.someNewProperty = "value";
  3. sap.ui.getCore().getModel().setData(oData);

 

If you find yourself doing this, take a look at the optional second bMerge parameter of setData. It uses jQuery.extend() and it might be what you're looking for - it will allow you to simply do this:

 

  1. sap.ui.getCore().getModel().setData({someNewProperty: "value"}, true);

 

Anyway, we get the APPCID back from the SMP's OData service and then call showProducts (below) to actually start bringing in the business data and showing it.

 

 

    showProducts: function(sAPPCID) {        var oAppData = sap.ui.getCore().getModel("app").getData();        var oModel = new sap.ui.model.odata.ODataModel(            oAppData.BaseURL + "/" + oAppData.AppName,            { 'X-SUP-APPCID': sAPPCID }        );        sap.ui.getCore().setModel(oModel);        var oApp = this.getView().getParent();        oApp.to("ProductList");    }
});

 

The showProducts function creates a new model. Yes, another one. This time, it's a model for the business data, available at the OData service that was described in the course and is proxied behind the SMP service. So first we use the application data in the "app" model to construct the proxy URL, which will be something like this:

 

https://smp-<your-id>trial.hanatrial.ondemand.com/<your-app-name>/

 

But then notice that we don't do anything manually, unlike the original app. We don't specify the HTTP method (GET) and we don't make any explicit calls (like OData.read). We just create a new OData model, specifying the service URL, and an additional object containing custom headers that we want sent on every call. The header we want is of course the X-SUP-APPCID so that's what we specify. From them on we just let the model do the work for us.

 

What we certainly don't do here, which was done in the original app, is call OData.read (which, incidentally, doesn't store the returned data in the model), and then manually shovel the raw JSON (the OData comes back as a JSON representation) into a single, central JSON model. There's no need, and this is really mixing up different mechanisms: OData and its corresponding model, JSON and its corresponding model, and their respective ways of working.

 

So you'll see, there are no explicit calls (HTTP requests) made for the business data. And you'll see that this hold true throughout the app (e.g. also later when we navigate from the ProductDetail view to the SupplierDetail view, following a navigation property). And remember, as described in the Index & Structure post in this series, there is no explicit external OData library (the original app had brought in datajs-1.1.1.js as a 3rd party library) - the SAPUI5 framework takes care of this for you.

 

Ok, well that's it for this post.

 

See the end of the initial post "Mobile Dev Course W3U3 Rewrite - Intro" for links to all the parts in this series.

 

Share & enjoy!

 

CodeTalk: SAPUI5 and SAP Fiori

$
0
0

Yesterday I was honoured to be a guest on Ian Thain 's CodeTalk series of video interviews. The subject was SAPUI5 and SAP Fiori, and the published interview is split over two videos on YouTube. Here's what we covered, based on the questions asked.

 

Video Playlist: https://www.youtube.com/playlist?list=PLfctWmgNyOIcae85Ytr6b_J1jgcDb4-JL

 

Part 1

 


In this part, we discuss SAPUI5 and SAP Fiori in general, and talk about the relation between these two things, major features in SAPUI5, including the automatic module loading system, the data model mechanisms, and in particular OData. We also talk about the architecture and startup of a very simple app.

 

Questions covered:


  • What is SAPUI5, where is it positioned, and where is it being used right now?
  • What does SAPUI5 consist of?
  • What does SAPUI5 look like?
  • Can you give me an overview of some of the major features of SAPUI5?
  • Can you tell me more about OData and SAPUI5?
  • What is Fiori?
  • What does a basic app look like?

 

Part 2

 

 

In this part we dig a little deeper, and talk about what a more complex app looks like. There's an example custom Fiori app, built using the Component concept and there's an 11 minute screencast that walks through that app, the controls used, and then looks under the hood to see how it's put together (bootstrap, parameters, Component and ComponentContainer, index.html, Component.js, views (JavaScript & XML) and controllers, View containing the SplitApp control, custom utility functions, internationalisation, folder structure, and more).

 

Further questions covered:

 

  • What does a more complete app look like?
  • What skills do I need?
  • What are the next steps?

 

If you just want a future reference to the screencast, it's available separately here too: https://www.youtube.com/watch?v=tfOO4szA2Bg

 

Share and enjoy!

Custom Sorting and Grouping

$
0
0

Summary: Learn how to control the order of groups in a sorted list. You don't do it directly with the grouper function, you do it with the sorter function.

 

One of the features of the app that the participants build in the CD168 sessions at SAP TechEd Amsterdam is a list of sales orders that can be grouped according to status or price (the screenshot shows the orders grouped by price).

 

groupbyprice.PNGThis is achieved by specifying a value for the vGroup parameter on the Sorter, as documented in the sap.ui.model.Sorter API reference:

 

Configure grouping of the content, can either be true to enable grouping based on the raw model property value, or a function which calculates the group value out of the context (e.g. oContext.getProperty("date").getYear() for year grouping). The control needs to implement the grouping behaviour for the aggregation which you want to group.

 

So what this means is that you either specify a boolean true value, or the name of a function.

 

- use a boolean true to have the entries grouped "naturally" by their value: useful and useable where you have texts that will be the same for some entries

 

- specify the name of a function that will do some custom grouping: useful where you have numeric values that you might want to group into sizes or ranges

 

Here in the example in the screenshot on the left, we're using a custom grouper function to arrange the sales orders into value groups (less than EUR 5000, less than EUR 10,000 and more than EUR 10,000).

 

 

 

 

Group Order Challenge

 

But what if you wanted to influence not only the sort but also the order of the groups themselves? Specifically in this screenshot example, what if we wanted to have the "< 5000 EUR" group appear first, then the "> 10,000 EUR" group and finally the "< 10,000 EUR" group? (This is a somewhat contrived example but you get the idea). This very question is one I was asking myself while preparing for the CD168 session, and also one I was asked by an attendee.

 

To understand how to do it, you have to understand that the relationship between the sorter and the grouper can be seen as a "master / slave" relationship. This is in fact reflected in how you specify the grouper - as a subordinate of the master.

 

The sorter drives everything, and the grouper just gets a chance to come along for the ride.

 

So to answer the question, and to illustrate it in code step by step, I've put together an example. It takes a simple list of numbers 1 to 30 and displays them in a list, and groups them into three size categories. You can specify in which order the groups appear, but the key mechanism to achieve this, as you'll see, is actually in the sorter.

 

 

 

Simple and Complex Sorting

 

To understand further, you have to remember that there's a simple sorter specification and a more complex one. Using a simple sorter is often the case, and you'd specify it like this:

 

new sap.m.List("list", {    items: {        path: '/records',        template: new sap.m.StandardListItem({            title: '{amount}'        }),        sorter: new sap.ui.model.Sorter("amount") // <---    }
})

 

This is nice and simple and sorts based on the value of the amount property, default ascending.

 

The complex sorter is where you can specify your own custom sorting logic, and you do that by creating an instance of a Sorter and then specifying your custom logic for the fnCompare function.

 

We'll be using the sorter with its own custom sorting logic.

 

 

 

Step By Step

So here's the example, described step by step. It's also available as a Gist on Github: Custom Sorter and Grouper in SAPUI5 and exposed in a runtime context using the bl.ocks.org facility: http://bl.ocks.org/qmacro/7702371

 

As the source code is available in the Gist, I won't bother showing you the HTML and SAPUI5 bootstrap, I'll just explain the main code base.

 

 

        var sSM = 10;  // < 10  Small        var sML = 15;  // < 15  Medium                                  //    15+ Large

 

Here we just specify the boundary values for chunking our items up into groups. Anything less than 10 is "Small", less than 15 is "Medium", otherwise it's "Large". I've deliberately chosen groupings that are not of equal size (the range is 1-30) just for a better visual example effect.

 

 

 

        // Generate the list of numbers and assign to a model        var aValues = [];        for (var i = 0; i < 30; i++) aValues.push(i);        sap.ui.getCore().setModel(            new sap.ui.model.json.JSONModel({                records: aValues.map(function(v) { return { value: v }; })            })        );

 

So we generate a list of numbers (I was really missing Python's xrange here, apropo of nothing!) and add it as a model to the core.

 

 

 

        // Sort order and title texts of the S/M/L groups        var mGroupInfo = {            S: { order: 2, text: "Small"},            M: { order: 1, text: "Medium"},            L: { order: 3, text: "Large"}        }

 

Here I've created a map object that specifies the order in which the Small, Medium and Large groups should appear in the list (Medium first, then Small, then Large). The texts are what should be displayed in the group subheader/dividers in the list display.

 

 

 

        // Returns to what group (S/M/L) a value belongs        var fGroup = function(v) {            return v < sSM ? "S" : v < sML ? "M" : "L";        }

 

This is just a helper function to return which size category (S, M or L) a given value belongs to.

 

 

 

        // Grouper function to be supplied as 3rd parm to Sorter        // Note that it uses the mGroupInfo, as does the Sorter        var fGrouper = function(oContext) {            var v = oContext.getProperty("value");            var group = fGroup(v);            return { key: group, text: mGroupInfo[group].text };        }

 

Here's our custom Grouper function that will be supplied as the third parameter to the Sorter. It pulls the value of the property from the context object it receives, uses the fGroup function (above) to determine the size category, and then returns what a group function should return - an object with key and text properties that are then used in the display of the bound items.

 

 

        // The Sorter, with a custom compare function, and the Grouper        var oSorter = new sap.ui.model.Sorter("value", null, fGrouper);        oSorter.fnCompare = function(a, b) {            // Determine the group and group order            var agroup = mGroupInfo[fGroup(a)].order;            var bgroup = mGroupInfo[fGroup(b)].order;            // Return sort result, by group ...            if (agroup < bgroup) return -1;            if (agroup > bgroup) return  1;             // ... and then within group (when relevant)            if (a < b) return -1;            if (a == b) return 0;            if (a > b) return  1;        }

 

Here's our custom Sorter. We create one as normal, specifying the fact that we want the "value" property to be the basis of our sorting. The 'null' is specified in the ascending/descending position (default is ascending), and then we specify our Grouper function. Remember, the grouper just hitches a ride on the sorter.

 

Because we want to influence the sort order of the groups as well as the order of the items within each group, we have to determine to what group each of the two values to be compared belong. If the groups are different, we just return the sort result (-1 or 1) at the group level. But if the two values are in the same group then we have to make sure that the sort result is returned for the items themselves.

 

 

        // Simple List in a Page        new sap.m.App({            pages: [                new sap.m.Page({                    title: "Sorted Groupings",                    content: [                        new sap.m.List("list", {                            items: {                                path: '/records',                                template: new sap.m.StandardListItem({                                    title: '{value}'                                }),                                sorter: oSorter                            }                        })                    ]                })            ]        }).placeAt("content");

 

And that's pretty much it. Once we've done the hard work of writing our custom sorting logic, and shared the group determination between the Sorter and the Grouper (DRY!) we can just specify the custom Sorter in our binding of the items.

 

And presto! We have what we want - a sorted list of items, grouped, and those groups also in an order that we specify.

 

sortedgroupings.PNG

 

 

Post Script

 

There was a comment on this post which was very interesting and described a situation where you want to sort, and group, based on different properties. This is also possible. To achieve sorting on one property and grouping based on another, you have to recall that you can pass either a single Sorter object or an array of them, in the binding.

 

So let's say you have an array of records in your data model, and these records have a "beerName" and a "beerType" property. You want to group by beerType, and within beerType you want the actual beerNames sorted.

 

In this case, you could have two Sorters: One for the beerType, with a Grouper function, and another for the beerName. Like this:

 

        var fGrouper = function(oContext) {            var sType = oContext.getProperty("beerType") || "Undefined";            return { key: sType, value: sType }        }        new sap.m.App({            pages: [                new sap.m.Page({                    title: "Craft Beer",                    content: [                        new sap.m.List("list", {                            items: {                                path: '/',                                template: new sap.m.StandardListItem({                                    title: "{beerName}",                                    description: "{beerType}"                                }),                                sorter: [                                    new sap.ui.model.Sorter("beerType", null, fGrouper),                                    new sap.ui.model.Sorter("beerName", null, null)                                ]                            }                        })                    ]                })            ]        }).placeAt("content");

I've put a complete example together for this, and it's in the sapui5bin Github repo here:

 

sapui5bin/SortingAndGrouping/TwoProperties.html at master · qmacro/sapui5bin · GitHub

 

And while we're on the subject of code examples, there's a complete example for the main theme of this post here:

 

sapui5bin/SortingAndGrouping/SingleProperty.html at master · qmacro/sapui5bin · GitHub

    

Share & enjoy!

Mobile Dev Course W3U3 Rewrite - XML Views - An Analysis

$
0
0

I rewrote the mobile dev course sample app from W3U3. Then I created a new branch 'xmlviews' in the repo on Github and rebuilt the views in XML. I then took a first look at XML views in general. Now this post looks at the specific XML views that I built in the W3U3 rewrite. See the links at the bottom of the opening post of this series to get to explanations for the other areas.

 

We know, from the other posts in this series, that there are a number of views. Let's just take them one by one. If you want an introduction to XML views, please refer to the previous post Mobile Dev Course W3U3 Rewrite - XML Views - An Intro. I won't cover the basics here.

 

 

App View

 

The App view contains an App control (sap.m.App) which contains, in the pages aggregation, the rest of the views - the ones that are visible. This is what the App view looks like in XML.

 

<?xml version="1.0" encoding="UTF-8" ?><core:View controllerName="com.opensap.App" xmlns:core="sap.ui.core"    xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">    <App id="app">        <mvc:XMLView viewName="com.opensap.Login" id="Login" />        <mvc:XMLView viewName="com.opensap.ProductList" id="ProductList" />        <mvc:XMLView viewName="com.opensap.ProductDetail" id="ProductDetail" />        <mvc:XMLView viewName="com.opensap.SupplierDetail" id="SupplierDetail" />    </App></core:View>

We're aggregating four views in the App control (introduced by the <App> tag). Because the pages aggregation is the default, we don't have to wrap the child views in a <pages> ... </pages> element. Views and the MVC concept belong in the sap.ui.core library, hence the xmlns:core namespace prefix usage.

 

 

Login View

 

The Login view contains, within a Page control, a user and password field, and a login button in the bar at the bottom. This is what the XML view looks like.

 

<?xml version="1.0" encoding="UTF-8" ?><core:View controllerName="com.opensap.Login" xmlns:core="sap.ui.core"    xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">    <Page        title="Login"        showNavButton="false">        <footer>            <Bar>                <contentMiddle>                    <Button                        text="Login"                        press="loginPress" />                </contentMiddle>            </Bar>        </footer>        <List>            <InputListItem label="Username">                <Input value="{app>/Username}" />            </InputListItem>            <InputListItem label="Password">                <Input value="{app>/Password}" type="Password" />            </InputListItem>        </List>    </Page></core:View>

You can see that the Page control is the 'root' control here, and there are a couple of properties set (title and showNavButton) along with the footer aggregation and the main content. Note that as this is not JavaScript, values that you think might appear "bare" are still specified as strings - showNavButton="false" is a good example of this.

 

The Page's footer aggregation expects a Bar control, and that's what we have here. In turn, the Bar control has three aggregations that have different horizontal positions, currently left, middle and right. We're using the contentMiddle aggregation to contain the Button control. Note that the Button control's press handler "loginPress" is specified simply; by default the controller object is passed as the context for "this". You don't need to try and engineer something that you might have seen in JavaScript, like this:

 

new sap.m.Button({     text: "Login",     press: [oController.loginPress, oController]
}),

... it's done automatically for you.

 

Note also that we can use data binding syntax in the XML element attributes just like we'd expect to be able to, for example value="{app>/Username}".

 

 

ProductList View

 

In the ProductList view, the products in the ProductCollection are displayed. There's a couple of things that are worth highlighting in this view. First, let's have a look at the whole thing.

 

<?xml version="1.0" encoding="UTF-8" ?><core:View controllerName="com.opensap.ProductList" xmlns:core="sap.ui.core"    xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">    <Page        title="Products">        <List            headerText="Product Overview"            items="{                path: '/ProductCollection'            }">            <StandardListItem                title="{Name}"                description="{Description}"                type="Navigation"                press="handleProductListItemPress" />        </List>    </Page></core:View>

The List control is aggregating the items in the ProductCollection in the data model. Note how the aggregation is specified in the items attribute - it's pretty much the same syntax as you'd have in JavaScript, here with the 'path' parameter. The only difference is that it's specified as an object inside a string, rather than an object directly:

 

items="{                path: '/ProductCollection'            }"

So remember get your quoting (single, double) right.

 

And then we have the template, the "stamp" which we use to produce a nice visible instantiation of each of the entries in the ProductCollection. This is specifiied in the default aggregation 'items', which, as it's default, I've omitted here.

 

 

ProductDetail View

 

By now I'm sure you're starting to see the pattern, and also the benefit of writing views in XML. It just makes a lot of sense, at least to me. It's cleaner, it makes you focus purely on the controls, and also by inference causes you to properly separate your view and controller concerns. You don't even have the option, let alone the temptation, to write event handling code in here.

 

So here's the ProductDetail view.

 

<?xml version="1.0" encoding="UTF-8" ?><core:View controllerName="com.opensap.ProductDetail" xmlns:core="sap.ui.core"    xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">    <Page        title="{Name}"        showNavButton="true"        navButtonPress="handleNavButtonPress">        <List>            <DisplayListItem label="Name" value="{Name}" />            <DisplayListItem label="Description" value="{Description}" />            <DisplayListItem label="Price" value="{Price} {CurrencyCode}" />            <DisplayListItem                label="Supplier"                value="{SupplierName}"                type="Navigation"                press="handleSupplierPress" />        </List>        <VBox alignItems="Center">            <Image                src="{app>/ES1Root}{ProductPicUrl}"                decorative="true"                densityAware="false" />        </VBox>    </Page></core:View>

 

We're not aggregating any array of data from the model here, we're just presenting four DisplayListItem controls one after the other in the List. Below that we have a centrally aligned image that shows the product picture.

 

 

SupplierDetail View

 

And finally we have the SupplierDetail view.

 

<?xml version="1.0" encoding="UTF-8" ?><core:View controllerName="com.opensap.SupplierDetail" xmlns:core="sap.ui.core"    xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc">    <Page        id="Supplier"        title="{CompanyName}"        showNavButton="true"        navButtonPress="handleNavButtonPress">        <List>            <DisplayListItem label="Company Name" value="{CompanyName}" />            <DisplayListItem label="Web Address" value="{WebAddress}" />            <DisplayListItem label="Phone Number" value="{PhoneNumber}" />        </List>    </Page></core:View>

Again, nothing really special, or specially complicated, here. Just like the other views (apart from the "root" App view), this has a Page as its outermost control. Here again we have just simple, clean declarations of what should appear, control-wise.

 

 

Conclusion

 

So there you have it. For me, starting to write views in XML was a revelation. The structure and the definitions seem to more easily flow, so much so, in fact, that in a last-minute addition to the DemoJam lineup at the annual SAP UK & Ireland User Group Conference in Birmingham last week, I took part, and for my DemoJam session I stood up and build an SAP Fiori-like UI live on stage. Using XML views.

 

This brings to an end the series that started out as an itch I wanted to scratch: To improve the quality of the SAPUI5 application code that was presented in the OpenSAP course "Introduction To Mobile Solution Development". There are now 6 posts in the series, including this one:

 

Mobile Dev Course W3U3 Rewrite - Intro

Mobile Dev Course W3U3 Rewrite - Index and Structure

Mobile Dev Course W3U3 Rewrite - App and Login

Mobile Dev Course W3U3 Rewrite - ProductList, ProductDetail and SupplierDetail

Mobile Dev Course W3U3 Rewrite - XML Views - An Intro

Mobile Dev Course W3U3 Rewrite - XML Views - An Analysis

 

I hope you found it useful and interesting, and as always,

 

Share and enjoy!

dj

UI5 XML Views - Another Example

$
0
0

I've been diving into UI5 XML views and sharing the love recently - see Mobile Dev Course W3U3 Rewrite - XML Views - An Intro (as part of Mobile Dev Course W3U3 Rewrite - Intro), and the XML view based templates and snippets in my SublimeUI5 Package for the "developer's choice" Sublime Text editor.

 

Recently John Patterson supplied a JSBin example of an OData sourced table with a filter on dates, in answer to Using Table filter when a formatter function is used.

 

This was a very nice example but I thought it would be an interesting exercise to convert it to XML, for a number of reasons:

 

  • XML views are important (in the context of learning about and extending Fiori apps)
  • it would be a good test of my latest #SublimeUI5 snippet "index - Mobile - Single Page - MVC" (indexmspmvc) (Note 1)
  • all of the XML view work I've done so far has involved controls predominantly from the sap.m library (as they're also what the Fiori apps are built with) so I wantd to try using non-sap.m controls (Note 2)

 

So I did, and have made it available in the sapui5bin repo on Github here:

 

sapui5bin/SinglePageExamples/ODataDateTableFilter.html at master · qmacro/sapui5bin · GitHub

 

Open up this link in a separate window to view it and read the rest of the post.

 

I'll cover the "single page MVC" concept in another post; for now, here are a few notes to help you navigate:

 

  • the XML view is defined in a script tag with the id "view1" and brought to life with sap.ui.xmlview({ viewContent:jQuery('#view1').html() })
  • I've specified the XML namespace declarations (xmlns) for the relevant libraries, having the most common controls' namespace (sap.ui.table) as the default
  • I've used the extended binding syntax for the Table's "rows" aggregation, to include the OData select parameters
  • I've declared the date formatting 'dateShort' (for the third column) in a namespace (util.formatting) with jQuery.sap.declare
  • We have a local controller (which doesn't actually have anything to do)

 

The one thing I'm not entirely convinced about is having to set the filterType property on the BirthDate column "procedurally" (in JavaScript); perhaps I'll get round to looking into how to do this particular bit a different way at some stage.

 

Anyway, I thought this might be more useful insight into XML views and single page MVC examples.

 

Share & enjoy!

dj

 

 

 

Note 1: This "single page MVC" idea is something I've wanted to put together and share for a while; it's easy to write a single page demo UI5 app but not so easy to do that and involve the MVC concept as well - in a single file ... until now.

 

Note 2: The SAP Fiori Wave 1 apps have views that are written declaratively in HTML; the SAP Fiori Wave 2 and 3 apps have views written in XML, using the sap.m controls, with a smattering of sap.ui.layout controls too

XML Views and Resource Bundle Declarations

$
0
0

Just a quick post on the train on the way down to London this morning.

 

The other day, Andreas Kunz pointed to an overview of the MVC options which contains very detailed information - an interesting and recommended read. One of the things that piqued my interest was the ability, in XML views, to specify a resource bundle (for internationalisation) declaratively, using a couple of attributes of the root View element. This I thought was rather neat.

 

So further to my recent explorations and posts on XML views ...

 

Mobile Dev Course W3U3 Rewrite - XML Views - An Intro

Mobile Dev Course W3U3 Rewrite - XML Views - An Analysis

UI5 XML Views - Another Example

 

... I thought I'd put together a little runnable app and make it available on sapui5bin, to demonstrate it. The result is XMLResourceBundleDeclaration, which is an index file, instantiating an XML view that has the resourceBundle declaration in it; this points to the resourceBundle.properties file in the i18n folder where you might expect to find it in other apps too.

 

The runnable is here: https://github.com/qmacro/sapui5bin/tree/master/XMLResourceBundleDeclaration

 

Screen Shot 2014-01-28 at 08.08.17.png

 

Share and enjoy!


Public SAP Mentor Monday 24 Mar 2014: UI5 with Andreas Kunz

$
0
0

On Monday 24 March 2014 we will have a public SAP Mentor Monday session on the subject of UI5.

 

For those of you who don't know, a public SAP Mentor Monday is an hour-long webinar format where everyone is invited and the subject is a specific topic. The subject of this public SAP Mentor Monday is UI5. That is to say, SAPUI5 and OpenUI5 - the licenced and open source versions both. UI5 is the toolkit that is powering the UI/UX revolution at SAP, and we have a special guest that will join us from the UI5 team in Walldorf - Andreas Kunz.

 

These are exciting times for SAP, and for me there's no place nearer the epicentre of the visible renewal than UI5.

 

Join the webinar to hear about and discuss UI5, with folk who share your interest. I'll be hosting it, and if you want to submit questions in advance, you can do so using this form.

 

Hope to see you there!

 

NOTE: Time to be confirmed, but is usually around 2000CET.

 

Link to SAP Connect Session: https://sap.na.pgiconnect.com/sapmm +1-866-312-7353

Participant Passcode:  378 224 4518

Country        Number  

US and Canada       1-866-312-7353

US and Canada       1-720-897-6637

US and Canada       1-646-434-0499

US and Canada       1-484-427-2544

Argentina     0800 444 1292 

Australia, Melbourne        +61 3 8687 0624

Australia, Sydney   +61 2 9009 0688

Australia       1 800 651 017  

Austria, Vienna       +43 1 2530 21750

Austria           0 800 006 088  

Bahrain, Manama  +973 1619 9392

Bahrain         8000 4811

Belgium, Brussels   +32 2 404 0657

Belgium         0800 39675

Botswana     002 698 003 001 802 

Brazil, Porto Alegre           +55 51 4063 8328

Brazil, Rio de Janeiro         +55 21 4063 5267

Brazil, Sao Paulo     +55 11 3163 0498

Bulgaria, Sofia         +359 2 491 7542

Bulgaria         00 800 118 4451

Canada, Montreal  +1 514 669 5883

Canada, Toronto    +1 416 915 3225

Canada          1 877 252 4916

Chile, Santiago        +56 2 599 4973

Chile   123 0020 6704 

China, Beijing          +86 10 5904 5002

China, Northern Region   10 800 650 0630

China, Southern Region   10 800 265 2601

China +400 120 0519 

Colombia      01 800 518 1236

Croatia          0800 222 228   

Cyprus, Nicosia       +357 2200 7933

Cyprus           800 964 63

Czech Republic, Prague    +420 228 882 890

Czech Republic        800 701 387      

Denmark, Copenhagen    +45 32 71 16 49

Denmark      80 701 624

Dominican Republic          1 888 751 4814

Estonia, Tallinn        +372 622 6444 

Estonia          8000 111 358   

Finland, Helsinki     +358 9 2310 1631

Finland          0 800 770 120  

France, Paris            +33 1 70 70 17 77

France           0800 946 522   

France           0811 657 737   

Germany, Frankfurt          +49 69 2222 10764    

Germany, Munich  +49 89 7104 24682    

Germany      0800 588 9331 

Greece, Athens       +30 21 1181 3805

Greece           00800 128 573 

Hong Kong   +852 3051 2732

Hong Kong   800 905 843      

Hungary, Budapest            +36 1 778 9215

Iceland          800 9901

India, Bangalore     +91 80 6127 5055

India, Delhi   +91 11 6641 1356

India, Mumbai        +91 22 6150 1743

India   000 800 1007 702

Indonesia      001 803 657 916

Ireland, Dublin        +353 1 247 6192

Ireland           1 800 937 869  

Ireland           1890 907 125   

Israel, Tel Aviv         +972 3 763 0750

Israel  1809 212 927   

Italy, Milan   +39 02 3600 9839

Italy, Rome   +39 06 4523 6623

Italy    800 145 988      

Japan, Osaka           +81 6 4560 2101

Japan, Tokyo           +81 3 4560 1261

Japan 0120 639 800   

Jordan           800 22813

Kazakhstan  8800 333 4239 

Latvia, Riga   +371 6778 2556

Latvia 8000 4247

Lithuania, Vilnius    +370 5205 5165

Lithuania       8800 31308

Luxembourg            +352 2487 1454

Luxembourg            800 27071

Malaysia,Kuala Lumpur    +60 3 7723 7221

Malaysia       1 800 806 547  

Malta 800 62208

Mauritius      802 033 0006   

Mexico, Mexico City          +52 55 1207 7362

Mexico          001 800 514 8609

Netherlands, Amsterdam            +31 20 716 8291

Netherlands 0800 265 8462 

New Zealand, Auckland   +64 9 929 1760

New Zealand           0800 885 018   

Norway, Oslo          +47 21 50 27 61

Norway         800 510 67

Oman            800 73655

Pakistan        008 009 004 4138

Panama         00 800 226 9817

Peru   0800 54 762      

Philippines    1 800 1651 0726

Poland, Warsaw     +48 22 212 0699

Poland           00 800 121 3995

Portugal, Lisbon     +351 21 781 0275

Portugal        800 784 425      

Puerto Rico  1 855 693 8763

Romania, Bucharest          +40 21 529 3917

Romania       0800 895 807   

Russia, Moscow      +7 495 213 17 63

Russia            810 800 2106 2012    

Saudi Arabia            800 844 4276   

Singapore     +65 6654 9828 

Singapore     800 186 5015   

Slovakia, Bratislava            +421 2 3300 2610

Slovakia        0800 001 825   

Slovenia, Ljubljana            +386 1 888 8261

Slovenia        0800 80923

South Africa,Johannesberg         +27 11 019 7009

South Africa 0800 984 011   

South Korea, Seoul            +82 2 3483 1901

South Korea 007 986 517 503

Spain, Barcelona    +34 93 800 0782

Spain, Madrid         +34 91 769 9443

Spain  800 600 279      

Sweden, Stockholm          +46 8 5033 6514

Sweden         0200 883 436   

Switzerland, Geneva         +41 22 592 7995

Switzerland, Zurich           +41 43 456 9248

Switzerland  0800 740 352   

Taiwan, Taipei         +886 2 2656 7307

Taiwan          00 806 651 935

Thailand        001 800 658 151

Turkey           00800 448 825 462    

UAE    8000 444 1726 

UK, Belfast    +44 28 9595 0013

UK, Edinburgh         +44 13 1460 1125

UK, London  +44 20 3364 5639

UK, Reading +44 11 8990 3053

UK       0800 368 0635 

UK       0845 351 2778 

Ukraine         0800 500 254   

Uruguay        0004 019 0509 

Venezuela    0 800 100 8510

Vietnam        120 651 66

Mocking up the Payroll Control Center Fiori App

$
0
0

Following on from a great debate about Fiori and Freeori that stemmed from a post by John Appleby there were some comments about HCM app renewals. Latterly John Moypointed out a post "Improve payroll data validation with SAP Payroll control center add-on" where some very Fiori-like UIs were being shown.

 

Coffee Time

 

I thought it would be a nice little coffee-time exercise to try and reproduce one of the Fiori app pages shown in the screenshots in that post:

 

http://scn.sap.com/servlet/JiveServlet/downloadImage/38-101981-386999/620-326/Pic+1.png

So I did, and as I did it I recorded it to share. I thought I'd write a few notes here on what was covered, and there's a link to the video and the code at the end.

 

Developer tools

 


  • With that editor I'm using the SublimeUI5 package which gives me UI5 flavoured snippets and templates.

 

  • Specifically I started out with the "indexmspmvc" snippet (Index Mobile Single-Page MVC) which gives me everything I need to build MVC-based examples with XML views, controllers, and more ... all in a single page, a single file. Not recommended for productive use, but extremely useful for testing and demos.

 

In-line XML views

 

The XML views in this single-page MVC are defined in a special script tag

 

<script id="view1" type="sapui5/xmlview">    <mvc:View        controllerName="local.controller"        xmlns:mvc="sap.ui.core.mvc"        xmlns="sap.m">        ${6:<!-- Add your XML-based controls here -->}    </mvc:View></script>

and then picked up in the view instantiation with like this:

 

var oView = sap.ui.xmlview({    viewContent: jQuery('#view1').html()
})

Controls Used

 

This is a Fiori UI, so the controls used are from the sap.m library.

 

 

  • That Page's content is a single control, an IconTabBar.


 

 

  • For the info and infoState properties of the StandardTile I'm using a couple of custom formatters.


Video

 

 

 

Code

 

I have of course made the code available, in the sapui5bin repo on Github:

 

https://github.com/qmacro/sapui5bin/blob/master/SinglePageExamples/PayrollControlCenterMockup.html

 

Share and enjoy!

Reaching Out

$
0
0

As a technology company SAP is over 4 decades old. Over that time it's innovated at a tremendous pace, and along the way it has abstracted, invented and reinvented technologies like no other company I know. In parallel with this, there's been an incredible growth in community, business and technical. In this post I want to focus on the technical.

 

The oft unspoken status quo with the SAP technical community is that the members operate within a bubble. It's a very large and comfortable bubble that powers and is powered by the activity within; folks like you and me learning, arguing, corresponding and building within communities like this one - the SAP Community Network. We have SAP TechEd, which is now called d-code. We have SAP Inside Tracks. We have InnoJams, DemoJams and University Alliance events too. Every one of these events, and event types, are great and should continue. But there's a disconnect that I feel is moving closer to the surface, becoming more obvious. This disconnect is that this bubble, this membrane that sustains us, is in many areas non-permeable.

 

There are folks who operate on both sides of that bubble's surface. Folks that attend technology conferences that are not SAP related. Folks that are involved in developer communities that have their roots outside the SAP developer ecosphere. Folks that write on topics that are not directly related to SAP technologies (but with a short leap of imagination surely are). But these folks are the exception.

 

SAP's progress in innovation has been slowly turning the company's technology inside out. Moving from the proprietary to the de facto to the standard. Embracing what's out there, what's outside the bubble. HTTP. REST-informed approaches to integration. OData. JavaScript and browser-centric applications. Yes, in this last example I'm thinking of SAP's UI5. In particular I'm thinking about what SAP are doing with OpenUI5 - open sourcing the very toolkit that powers SAP's future direction of UI development. With that activity, SAP and the UI5 teams are reaching out to the wider developer ecospheres, to the developer communities beyond our bubble. If nothing else, we need these non-SAP developers to join with us to build out the next decade.

 

I try to play my part, and have done for a while. I've spoken at OSCON, JabberConf, FOSDEM and other conferences over the years, and attended others such as Strata and Google I/O too. I've been an active participant in various non-SAP tech communities in areas such as Perl, XMPP and Google technologies. This is not about me though, it's about us, the SAP developer community as a whole. What can we do to burst the bubble, to help our ecosphere and encourage SAP to continue its journey outwards? One example that's close to my heart is to encourage quality Q&A on the subject of UI5 on the Stack Overflow site. But that's just one example.

 

How can we reach out to the wider developer ecosphere? If we do it, and do it with the right intentions, everybody wins.

Small steps: OpenUI5 toolkit now in jsbin.com

$
0
0

In our continued efforts to spread the word of SAPUI5 in general and OpenUI5 in particular, we try to make small steps forward.

 

Here's some quick news about a small step forward with respect to example and demonstration code snippets: jsbin.com now supports the automatic insertion of the OpenUI5 bootstrap. Select the "Add library" menu option, choose the OpenUI5 entry:

 

jsbin1.jpg

 

and lo, the bootstrap script tag is inserted, ready for you to go:

 

jsbin2.jpg

Have a go for yourself!

 

Reaching out and bringing the SAP and non-SAP developer communities closer, one small step at a time.

 

And if you're interested in how this came about, see this pull request on Github: https://github.com/jsbin/jsbin/pull/1220

 

Share & enjoy!

SAP Developer Advisory Board - Your Input!

$
0
0

Gregor Wolf and I are attending the SAP Developer Advisory Board meeting on Tue 22 Apr 2014.

 

We'd like to ask you, as members of the general SAP Developer Community, for your thoughts. There isn't a particular agenda or categorisation we'd like to impose here, we just want to make it open enough for you to write what you think.

 

Here's a form where you can add your thoughts

 

We can't of course guarantee that there will be enough time to air all the questions but we'll do our best!

 

Thank you.

Understanding SAP Fiori Webinar - The Director's Cut

$
0
0

Yesterday Brenton OCallaghan and I hosted a public webinar "Understanding SAP Fiori", which was well attended and also a lot of fun to do. There was never going to be enough time to cover all the stuff we wanted to, so today we sat down together and followed up on what we covered in the webinar.

 

We recorded it as a Google+ Hangout and published it on YouTube.

 

Here are some of the things that we covered:

 

  • SAP Fiori transactional app design
  • master detail patterns with responsive containers
  • the structure of a transactional app
  • components, with routing and the visible control
  • the responsive container and its aggregations
  • semantic colours for Buttons and other controls
  • responsive design and the demandPopin property
  • the SplitApp modes
  • examining the S2 and S3 views
  • extension points

 

plus a small update to the SAP Fiori App Analysis app - select an app from the list to get a popup with more information, including a link to the official SAP docu for the selected app. (For more background on this app, see another short video "The SAP Fiori App Analysis application" also on YouTube.)

 

 

It was a fun 30+ minutes, we hope you enjoy it too!

 

 

Bluefin Solutions - Understanding SAP Fiori Webinar Followup

Mobile Dev Course W3U3 Rewrite - ProductList, ProductDetail & SupplierDetail

$
0
0

I rewrote the mobile dev course sample app from W3U3. This post explains what I changed in the ProductList, ProductDetail and SupplierDetail views / controllers. See the links at the bottom of the opening post to get to explanations for the other areas.

 

ProductList

 

If you remember back to the Login controller (described in the previous post in this series) we arrive at the ProductList view after successfully logging in, creating the OData model for the business available at the OData service, and performing a move from the Login page to this ProductList page with oApp.to("ProductList"), the navigation mechanism that is available in the App control, inherited from NavContainer.

 

ProductList.view.js

 

Here's what the ProductList view looks like.

 

sap.ui.jsview("com.opensap.ProductList", {    getControllerName: function() {        return "com.opensap.ProductList";    },    createContent: function(oController) {        return new sap.m.Page("ProductPage", {            title: "Products",            content: [                new sap.m.List({                    headerText: "Product Overview",                    items: {                        path: "/ProductCollection",                        template: new sap.m.StandardListItem({                            title: "{Name}",                            description: "{Description}",                            type: sap.m.ListType.Navigation,                            press: [oController.handleProductListItemPress, oController]                        })                    }                })            ]        });    }
});

 

Like the previous views, this isn't actually much different from the original version. I've left out stuff that wasn't needed, and in particular the icon property of each StandardListItem was pointing at the wrong model property name, resulting in no icon being shown in the list. I've removed the icon* properties as well as a couple of list properties (inset and type).

 

What I have done, though, mostly for fun, is to write the createContent function as a single statement. This in contrast to the multiple statements in the original, but perhaps more interestingly, the whole thing looks more declarative than imperative. This will come into play when we eventually look at declarative views in XML, which are actually my prefererence, and arguably the neatest and least amount of typing ... which might surprise you. Anyway, more on that another time.

 

ProductList.controller.js

The ProductList controller is very simple; all it has to do is handle the press of the StandardListItem (see the press event specification in the view above).

 

sap.ui.controller("com.opensap.ProductList", {    handleProductListItemPress: function(oEvent) {        this.getView().getParent().to("ProductDetail", {            context: oEvent.getSource().getBindingContext()        });    }
});

 

Again, I've left out the empty boilerplate code from the original, and am just doing what's required, nothing more: getting the binding context of the source of the event (the particular StandardListItem that was pressed), and passing that in the navigation to the ProductDetail page.

 

Note that I've been sort of interchanging the word page and view here and earlier. This is in relation to the App control, which has a 'pages' aggregation from the NavContainer control. As the documentation states, you don't have to put Page controls into this pages aggregation, you can put other controls that have a fullscreen semantic, and one of those possible controls is a View.

 

 

ProductDetail

 

So we've navigated from the ProductList to the ProductDetail by selecting an item in the List control, and having that item's binding context (related to the OData model) passed to us. Here's what the view looks like.

 

sap.ui.jsview("com.opensap.ProductDetail", {    getControllerName: function() {        return "com.opensap.ProductDetail";    },    onBeforeShow: function(oEvent) {        if (oEvent.data.context) {            this.setBindingContext(oEvent.data.context);        }    },

 

So in the ProductDetail view, where we want to simply show more detail about that particular Product entity, we first make sure that the passed context is bound (to the view).


 

  createContent: function(oController) {        return new sap.m.Page({            title: "{Name}",            showNavButton: true,            navButtonPress: [oController.handleNavButtonPress, oController],            content: [                new sap.m.List({                    items: [                        new sap.m.DisplayListItem({                            label: "Name",                            value: "{Name}"                        }),                        new sap.m.DisplayListItem({                            label: "Description",                            value: "{Description}"                        }),                        new sap.m.DisplayListItem({                            label: "Price",                            value: "{Price} {CurrencyCode}"                        }),                        new sap.m.StandardListItem({                            title: "Supplier",                            description: "{SupplierName}",                            type: sap.m.ListType.Navigation,                            press: [oController.handleSupplierPress, oController]                        })                    ]                }),                new sap.m.VBox({                    alignItems: sap.m.FlexAlignItems.Center,                    items: [                        new sap.m.Image({                            src: "{app>/ES1Root}{ProductPicUrl}",                            decorative: true,                            densityAware: false                        })                    ]                })            ]        });    }
});

 

Once that's done, all we have to do is fill out the createContent function, which again is very similar to the original. Note that here I'm using two model properties together for the value of the "Price" item to show a currency value and code.

 

In the original version, there was some custom data attached to the Supplier item - specifically the SupplierId property from the Product. This was used, in the controller, to manually (and somewhat "bluntly") construct an OData Entity URL for subsequent (manual) retrieval. Of couse, you might have guessed by now what I'm going to say. Not necessary at all. More on this shortly. But it's worth pointing out that the attaching of the custom data is quite a useful and widely available facility in general. It's widely available because it's part of the Element class, from which, ultimately, all controls inherit. So you can attach custom data in name/value pairs to any control you wish, more or less.

 

Finally, let's have a quick look at that VBox control containing the product image. I took a lead from the original app and decided to prefix the relative URL (which is what is contained in the ProductPicUrl property) with the generic (non-SMP-proxied) 'sapes1' URL base. And to achieve this prefixing I just concatenated a couple of model properties - one from the named "app" model (the ES1Root) and the other being the actual image relative URL.


Ok, let's have a look at the rewritten controller. 

 

ProductDetail.controller.js

 

sap.ui.controller("com.opensap.ProductDetail", {    handleNavButtonPress: function(oEvent) {        this.getView().getParent().back();    },    handleSupplierPress: function(oEvent) {        this.getView().getParent().to("SupplierDetail", {            context: oEvent.getSource().getBindingContext()        });    }
});

 

As well as the back navigation, we have the handling of the press of the Supplier item in the ProductDetail view. This should take us to the SupplierDetail view to show us more information about the supplier.

 

So before we think about how we make this work, let's pause for a second and consider the business data that we're consuming through the OData service.

 

OData Model and Service

 

We have, in the OData service originating at https://sapes1.sapdevcenter.com/sap/opu/odata/sap/ZGWSAMPLE_SRV/, a number of EntitySets, or 'collections', including the BusinessPartnerCollection and the ProductCollection - both of which have entities that we're interested in for our app. We start out with the ProductCollection, display a list, pick a specific product for more detail, and then go to the supplier for that product. If you look at the OData metadata for this service, you'll see that in the definition of the Product entity, there's a navigation property that will take us directly from the product entity to the related business partner entity. How useful is that? Yes, very! So let's use it.

 

navprop.jpg

Before we look at how we use it, let's review how the original app was doing things here to go from the selected product detail to the supplier. In the supplierTap function of the original ProductDetail controller, the OData.read function was called explicitly (ouch), on a manually constructed OData URL (ouch), which abruptly jumped straight to the BusinessPartnerCollection, ignoring this navigation feature (double-ouch). The supplier's ID (which had been squirrelled away in the custom data as mentioned earlier) was specified directly, as a key predicate, and a JSON representation was requested:

OData.read("https://sapes1.devcenter.com/sap/opu/odata/sap/ZGWSAMPLE_SRV/BusinessPartnerCollection('" + supplierId + "')?$format=json", ...)

 

Yes, you can guess the next bit :-) The JSON data was passed directly to the next view, bypassing any semblance of OData model usage. Ouch. I guess this also bypasses the SMP URL rewriting security and should have really been the SMP-based URL. And ouch.

 

So how did we do it here? Well, just by passing the context of the selected product, as usual. Just like we did when we went from the ProductList view to the ProductDetail view. And then following on from that in the SupplierDetail view with a reference to the relative 'Supplier' entity.


 

SupplierDetail

 

SupplierDetail.view.js

 

Ok, so here's the view.

 

sap.ui.jsview("com.opensap.SupplierDetail", {    getControllerName: function() {        return "com.opensap.SupplierDetail";    },    onBeforeShow: function(oEvent) {        if (oEvent.data.context) {            this.setBindingContext(oEvent.data.context);        }    },   createContent: function(oController) {        var oPage = new sap.m.Page({            title: "{CompanyName}",            showNavButton: true,            navButtonPress: [oController.handleNavButtonPress, oController],            content: [                new sap.m.List({                    items: [                        new sap.m.DisplayListItem({                            label: "Company Name",                            value: "{CompanyName}"                        }),                        new sap.m.DisplayListItem({                            label: "Web Address",                            value: "{WebAddress}"                        }),                        new sap.m.DisplayListItem({                            label: "Phone Number",                            value: "{PhoneNumber}"                        })                    ]                })            ]        });        oPage.bindElement("Supplier");        return oPage;    }
});

 

This view looks pretty normal and doesn't differ much from the original. We have the onBeforeShow and the createContent. But the key line is this:

 

oPage.bindElement("Supplier")

 

At the point that this is invoked, there's already the binding context that refers to the specific product previously chosen, say, like this:

 

https://sapes1.sapdevcenter.com/sap/opu/odata/sap/ZGWSAMPLE_SRV/ProductCollection('HT-1007')

 

(I'm using the 'sapes1' link rather than the SMP-rewritten one here so you can navigate them from here and have a look manually if you want.)

 

Following the navigation property mentioned earlier, to the supplier (the entity in the BusinessPartnerCollection) is simply a matter, OData-wise, of extending the path to navigate to the supplier, like this:

 

https://sapes1.sapdevcenter.com/sap/opu/odata/sap/ZGWSAMPLE_SRV/ProductCollection('HT-1007')/Supplier

 

So in OData terms, we're navigating. And in path terms, we're going to a relative "Supplier", which is exactly what we're doing with the oPage.bindElement("Supplier"). The bindElement mechanism, when called on an entity in an OData model, triggers an automatic OData "read" operation, i.e. an HTTP GET request, and updates the model.  Bingo!

 

Looking at the Network tab of Chrome Developer Tools, this is what we see happens:

calls.jpg

 

The first call (ProductCollection?$skip...) was for the initial binding to "/ProductCollection" in the ProductList view. Then a product HT-1007 was selected, the App navigated to the ProductDetail view, and then the supplier item was pressed. And when the bindElement in the SupplierDetail view was called, this triggered the last call in the screenshot - to "Supplier", relative to ProductCollection('HT-1007').

 

All automatic and comfortable!

 

 

SupplierDetail.controller.js

 

sap.ui.controller("com.opensap.SupplierDetail", {    handleNavButtonPress: function(oEvent) {        this.getView().getParent().back();    }
})

 

Let's finish off with a quick look at the corresponding controller for this view. It doesn't have much work to do - just navigate back when the nav button is pressed. And it's very similar to the original.

 

So there we have it. Embrace SAPUI5 and its myriad features (automatic module loading, well thought out controls, OData models, and more) and have fun building apps.

 

That's draws this series to an end. Thanks for reading. The link to the Github repo where the rewritten app can be found is in the original post in this series, and also here: https://github.com/qmacro/w3u3_redonebasic

 

Share & enjoy!







Latest Images