openSAP Developing Web Apps with SAPUI5 Week 4 Unit 1 00:00:10 Hello and welcome to the last week for the openSAP course "Developing Web Apps with SAPUI5".My name is Janina, and our unit today is called "Navigation and Routing Concepts". 00:00:22 But first, let's have a look at what topics are covered within this week.We will start with navigation and routing concepts, learn how to update and manipulate data, 00:00:35 how to write QUnit tests and integration tests with OPA, and as a last topic, we will learn how to create custom controls.Furthermore, there will be a dedicated wrap-up unit. 00:00:46 So in the last week, called "Master SAPUI5", we enhance the application and learn how to test it.Today we will focus on routing and navigation concepts. 00:00:57 As you might have noticed, we are already using routing within our application.So let's have a deeper look into it. 00:01:09 If we run our application now, and, for example, select an item here, 00:01:19 we already have a navigation in progress.And if we have a look at the hash and go to the 00:01:29 here we see that we now have "ProductSet" and the number of the item.If we change this to something invalid, we see a different view is now displayed. 00:01:45 As you can see, SAPUI5 offers a hash-based navigation, which allows you to build one-page applications where the navigation is done by changing the hash, 00:01:55 so the browser does not have to reload the page.Instead there is a callback to which the app and especially the affected view can react. 00:02:04 A string, that is the hash, is used, parsed, and matched against patterns which will then inform the handlers.Let's have a look at an Explored sample to understand the pattern matching. 00:02:17 If you go to the SAPUI5 Explored application and search for "pattern", just select the "Route" sample, and then you see there's a sample called "Route Patterns". 00:02:31 If we select it, we see the hash here, which is empty right now, and we can enter a new hash.There's also a help, so for example, we enter this one and we set the hash, and we see this pattern is matched. 00:02:54 A pattern usually consists of mandatory and optional parameters.So in this case, we have to have "product" at the beginning, then "MandatoryProductId", 00:03:07 then "detail", and then a mandatory "DetailId".So because we now have "Product/5/Detail/3", this pattern is matched. 00:03:19 You can also have optional parameters, like the "OptionalProductId" here, and you can also have query strings. 00:03:29 Usually you also have a catch-all pattern to catch anything that has not been caught in one of the existing patterns.So if we type something here, like "foo", and click "Set Hash", 00:03:44 we now see that the last pattern that catches everything has been matched.Keep in mind that the order of the pattern is relevant 00:03:53 because if you had the catch-all route at the start, the other patterns would no longer match.Okay, so let's see how routing in SAPUI5 works. 00:04:10 A route is used to notify your application that the hash has changed to a certain value.When a route matches the hash, it provides the data passed over the hash to a defined handler. 00:04:21 The pattern of the route is matched against the hash.If the pattern matches, your application will also get the parameters. 1 00:04:29 When defining routes, as I already said, the order is important: Only the first route that matches is informed.All routes defined after the first route that was hit are ignored. 00:04:40 If you define a catch-all route, always define it as the last route.Single-page applications based on SAPUI5 can use a "router" to dispatch hash-based URLs to one or more views of the app. 00:04:56 The router is shown here at the bottom of the graphic.In SAPUI5, we can simply add a routing section to our existing sap.ui5 section in the descriptor file to configure the router. 00:05:11 There are three properties that can be used to configure the routing of our application: So let's go back to our application and see what's already been done here. 00:05:23 We have to open the manifest.json, and we switch to the code editor – you can also use the descriptor editor, but right now I'll explain how to do it in the code editor. 00:05:36 If you scroll to the end, there's a section called "routing".This section contains three important paths: 00:05:47 First is the "config" – this section contains the global router configuration and default values that apply for all routes and targets.As we are doing a mobile application, we are using "sap.m.routing.Router". 00:06:03 The default view type is XML.If you had another view type, you could also override it for each route, or you could change the general view type here. 00:06:14 The "viewPath" where everything is located is "opensap.manageproducts.view".Our main "controlId" is our "app", and the aggregation where the page should be added later is "pages". 00:06:31 Then there is a pretty new configuration path called "bypassed", which I will explain to you in detail a little later.So there we want to display a "notFound" target. 00:06:44 It's like a catch-all route, and if nothing is matched, this target is displayed.There's also the asynchronous feature, which you can turn on so that the views are loaded asynchronously, because the default is "false". 00:07:02 The next large section is the "routes" section.Here you define a route, and each route defines a name, a pattern, as we already saw in the Explored application, 00:07:15 and one or more targets to navigate to when the route has been matched.So if we start our application without a hash, then we want to display the "worklist" target. 00:07:27 And in this case, where we have a product set with a special product ID, we want to display the "object" target, which then matches the "object" view here. 00:07:41 So each route references one or more targets from here, and each target defines a view to be displayed.You can also define the view level, which might be interesting for animating your forward and back navigation. 00:08:08 Okay.Let's go to the Component.js. 00:08:19 Within the component, we have overridden the init function of the base component.Here we initialize the router, 00:08:32 and we get a reference here, and then we call the initialize.This is pretty important because otherwise the router is not instantiated automatically, 00:08:43 and the configuration would not be loaded in the descriptor.But if we do this, the routing events and also our configuration within the descriptor, 00:08:52 which was the manifest.json, are now automatically enabled within the app.So let's have a look at what the routing does for 00:09:05 So we have our router here.Within the manifest.json, we have our routing configuration, which contains all our routes and targets. 00:09:15 A route can have one or more targets, and whenever the hash is matched against the pattern of a route, 00:09:23 the corresponding target is displayed or navigated to.So in our UI, we then see the corresponding view. 00:09:31 You can also attach to a "pattern matched event" for the target and the route, so these events are fired when they are either displayed or matched. 00:09:45 You can also manipulate the hash reform the router, and the router automatically listens to hash changes within the URL.Let's go back to the manifest.json. 00:10:02 As we can see, we have two routes: the empty one, and the "ProductSet".And we have four targets: "worklist", "object", "objectNotFound", and "notFound". 00:10:19 If we go back to our application, we now see here the product: "notFound" view.And if we change the hash to something completely invalid, 00:10:33 now the "notFound" target is displayed, which was defined within the "bypassed", because we've now added a hash that doesn't match a pattern, so the "notFound" view which was defined in the "bypassed" is displayed. 00:10:50 Okay, if we navigate back now, here we have a product, and if we have a look at the hash, we see here "ProductSet" and the product ID. 00:11:02 So this is what we defined here within this pattern.Okay. 00:11:14 Now let's have a look at the object controller, so we go to "controller" and then "Object.controller.js".And within the init function here, we attach to the pattern matched event I mentioned before. 00:11:36 So whenever the "object" route got matched, we execute this "_onObjectMatched" function.With this functionality, we then do something. 00:11:52 If we go to this, we see within the "_onObjectMatched" function, we now change the binding context to the selected item on the list so that the right information is displayed. 00:12:13 This also enables deep linking, meaning that if we are on the application and we now reload, we come with the deep link right into the product ID.So by changing the binding context and attaching to the pattern match, this now works. 00:12:31 If we didn't do this, we would only see an empty page here.As I've already shown you, if we enter something here that doesn't exist, 00:12:46 then we display the product: "notFound" view, and we do this without changing the hash, so if we look at the hash, it's still the same. 00:12:56 This is also done within the "_onBindingChange" method, so if we search for this here, so we check whether the product we want to display is available – so we just check here – 00:13:17 so if it wasn't available, we'd get our targets and we'd display the "objectNotFound" target, and the "objectNotFound" target is what we see here. 00:13:29 So you can use targets to display a view without changing the hash as well.We also saw that when you enter something that is not valid, not defined as a pattern here, 00:13:55 then the "bypassed" target is matched and automatically displayed, and it's the same as before, so if we enter something totally invalid, 00:14:05 then we see the "notFound" view, but as you can see here, the hash has not changed, so this is also done with a target.So this is what the "bypassed" property automatically does for us. 00:14:20 You could also define multiple targets here, but in our case, one is enough.I think next we also want to be able to add data within our application. 00:14:38 This will be done in the next unit, but in this unit, we will do some basic routing stuff.We will define a new route and target within the manifest.json, 00:14:52 we will create the corresponding view and controller, and we will also register to the route pattern matched event. 00:15:00 As a first step, let's add an "add" button to our worklist view, so we just go back to the worklist, so just go here with an empty hash.I forgot to mention that in UI5 the hash starts after this hash and slash symbol, so now it's like an empty hash. 00:15:24 Here we have our worklist view, and we want to add an "add" button here.As I want to avoid typos, I prepared the code and I'm just going to copy it in here, 00:15:38 but don't worry, you have an exercise document where you can copy the code and see all the implementation later on, so you don't have to type it if you don't want to. 00:15:50 So we now go to the Worklist.view.xml.It's within the header toolbar, which is within the table. 00:16:02 After the search field, we add this button, so it has an ID and we have an add icon, and we also register the "onAdd" method when the "press" event is fired. Okay. 00:16:23 So now we also have to add this "onAdd" function to the Worklist.controller.js, otherwise it would not work.And I also have this one here. 00:16:39 We scroll down until the event handlers start, and we enter it here.So it's an event handler, and whenever the "press" event is fired when you press the button, we call this method, 00:17:01 and inside we get our router, and then we want to navigate to the "add", which we still have to define.So we will have to define a route called "add", but before we do this, I hope that we will see the button if we now reload the application. 00:17:23 Now you see a "+" button here, which is the "add" button, and if we press it, the event is fired, but nothing happens as we have not yet defined the pattern. 00:17:37 So this is what we'll do in the next step.So let's go to the manifest.json, 00:17:46 and first we have to define one route – I'm just going to copy it again – so we add it here, and it's the same as the routes above so we have a pattern. 00:18:02 We will give this a new pattern, so every time we have "AddProduct" here, we want this route to be matched.We give it the name "add", and we want to display the target "add". 00:18:15 We also have to define this target, so I'm just going to copy this, and we have to scroll down to the "targets" section and add this one here, 00:18:31 so it has the name "add".And we want to display the view "Add", which is not there yet, and we also have the ID and the view level. 00:18:46 Now we will define the view and the controller, so we go to the "view" folder, we create a new file, and we name it "Add.view.xml". 00:19:04 There we just define a pretty basic view.So we have the controller we mentioned here, which we still have to define, 00:19:18 and we just have the semantic namespace because we want to add a semantic full-screen page, and this page has an id="page", it has a title, we have to add this title to the i18n file. 00:19:32 We want to show a navigation button, and whenever this button is pressed, we want to execute the method "onNavBack". 00:19:44 So now we will also add a new controller..."Add.controller.js"... 00:19:56 and the controller looks a bit big now, but actually it's not doing very much, so I'm just going to save this.So we get the history, which we will need as I'm going to explain to you in a minute, 00:20:14 we just extend the base controller, we have the "onInit" function, and within this, we register to the add route matched. 00:20:22 So we say "this.getRouter" and "getRoute("add")", and whenever this route is hit, we want to attach to it.And then this "_onRouteMatched" is executed. 00:20:36 And within this function, you would then do whatever you want to do when this route is matched, but we're not doing anything at all right now, so this will be done in the next unit. 00:20:49 In the controller, we also have the back navigation.It's pretty similar to what we also have in the object controller, 00:21:00 so whenever we click the back navigation, which is the "Back" button here in the top left-hand corner, we check to see whether there is already a browser history, and if there is, we just go one back. 00:21:21 If there is no browser history – so if we had come into the app with a deep link – we would just navigate to the main page, which is the worklist, with a forward history instead of back navigation. 00:21:41 So this is what is done here, and it's also similar in the object controller.In the Add.view.xml, we mentioned the "addPageTitle", which isn't there yet so I'm going to define this as well. 00:22:02 So we go to the i18n file and I just go to the end, where I add it.And if I did everything right now, it should work, so let's test it. 00:22:18 We run the application again.Here we have our main page – the "Products" view – and if we now click the "+" button, 00:22:30 we now navigate automatically to our new view, which is called "Add" but the title is "New Product".And if we open the debugger, I'll just show you what really happens inside. 00:22:47 So we go into Sources and we use Ctrl + O to search for the Add.controller.js file.Let's make this a bit bigger because otherwise we're not able to see anything. 00:23:10 So here we see the code we just copied, and we're just going to set a breakpoint here in the "onInit", and also in the "_onRouteMatched", and in the "onNavBack" to show you what's happening. 00:23:27 So if we now reload the page, we first stop at the initialization of the controller. 00:23:38 Here we register to the "add route matched".And if we now continue, this route has already been hit because we didn't change the hash, we reloaded it. 00:23:51 So here we have the hash, which is the pattern we defined in the manifest.json for the add route.So it's matched, and here we could now do whatever we want, but we'll do that in the next unit. 00:24:06 And if we continue and press the "Back" button, we see we're here in the "onNavBack" method because this is the one we defined in our back navigation button. 00:24:19 Let's just step over here to show you that we have the history but it's empty – you can see here it's "undefined".Because it's undefined, we now navigate back to the worklist. 00:24:36 That's everything that happens here now.That's it for the routing and navigation concept. 00:24:43 In this unit, you learned about the router, routes and their patterns, targets, and how navigation in single-page apps works in SAPUI5. 00:24:53 We also enhanced the routing of our application with the new route, and in the next unit, you will add more functionality to the new add view. Week 4Unit 2 00:00:10 Hello and welcome to Week 4, Unit 2 of our openSAP course "Developing Web Apps with SAPUI5".My name is Thomas, and in this unit we will update and manipulate data. 00:00:24 Let's first have a look at what we want to achieve.This is the application we have been working on, and in the previous exercise we added an "add" button. 00:00:35 When this button is pressed, a new view should open up.In this new view, there is a bunch of input fields grouped together in a form, and then the user can enter data. 00:00:51 Once he has entered the data, he presses the Save button.On saving the product, the data is transmitted to the server, persisted there, 00:01:02 and after a successful completion of this process, we will show the detail view for the new product, together with a little message toast indicating the successful creation. 00:01:15 So that's the user perspective of what we want to achieve – let's have a look at what we need to do before we really start the coding.On opening the add view, the routing will throw an event that the route pattern has matched. 00:01:33 This is received by the add controller.Then the add controller will call "metadataLoaded" of the OData model. 00:01:42 With this we wait until the metadata is loaded because we need it for the next step.The next step is to create an entry in the OData model. 00:01:53 When creating the entry, you need to know the metadata.Once the new, empty entry has been created, we have a binding path, 00:02:04 and with this we will establish a binding of the form in the view against the OData model.By the way, we are using a smart form. 00:02:14 This is not a usual form, but it brings some intelligence.It reads annotations of the OData service, and with the help of that, offers additional features. 00:02:26 So the binding is established, and as the user enters data, the data will be written back to the model.Last but not least, when the user presses the Save button, the add controller calls the submit function on the OData model. 00:02:45 By doing so, the OData model creates the corresponding HTTP requests that are sent to the server.Once the server completes the action, HTTP responses are sent back. 00:02:58 Now the OData model knows that it was successfully completed, which is indicated to the controller via a success event.Once the controller gets the success event, it finishes the process and navigates to the product view. 00:03:18 So much for that as an overview – now we can start the coding.First of all, we need a bunch of new texts. 00:03:35 There's really not much to say about that. They have all been nicely prepared, so I just paste those texts inside.The next one is a bit more curious. 00:03:52 I open the manifest.json, I switch to the code editor, and I go to the "models" section.Here I see two models: one is for internationalization, and one is for the data model, which is an unnamed model. 00:04:13 And here are the settings.So you have to know that by default the OData model operates in a one-way binding mode. 00:04:22 This means that whenever the data is changed in the model, it is populated with a view, but if you change the data in the view, it's not written back, so that's a one-way binding. 00:04:33 You cannot change the model with that.But, of course, we want to change the model now, so we need to establish a two-way binding. 00:04:43 We do so by setting the "defaultBindingMode" to "TwoWay".Save the file... okay. 00:05:00 Then we continue.I open the Add.controller.js, where most of the work will be done now, 00:05:11 and here you see the "_onRouteMatched" function, which we did already in the last unit.So far, there's just a comment here. 00:05:18 I remove the comment, and I bring in this coding.We access the model, and on the model, we call the function "metadataLoaded". 00:05:30 This function is a promise.A promise is something a bit more difficult and advanced. 00:05:36 Basically, it abstracts from asynchronous or synchronous processing, because we don't currently know whether the metadata has been loaded or will be loaded in the future. 00:05:45 We don't know, but it would change the way we But this promise just says that whenever this has happened, we then do something else. 00:05:57 And this something else is another function "_onMetadataLoaded", which I will implement here.So the whole concept of promises is a bit more challenging. 00:06:11 You will also find instructions in the exercises and more guidance on the Internet – take your time to understand this part.I must stress, however, that this is an important part and you should learn it. 00:06:24 All right, then we continue with the implementation.So when the metadata has been loaded, I first create a bunch of properties. 00:06:37 So actually the OData service we are working with has eight mandatory fields which need to be supplied on creation.And I don't want the user to enter all of them, so I default many of those properties. 00:06:49 For example, I set the currency code to "EUR".And, most importantly, I generate a product ID on the client. 00:06:57 So in this example, the ID generation is on the client.Basically, I generate a random number, and with that, get some ID. 00:07:10 Next we are ready to call the "createEntry" function on the OData model.When calling this function, you need to supply a path inside which the new object should be created. 00:07:28 I say: Please put the new object to the product And I hand over those properties defined before. 00:07:35 With that, a new entry will be created, it will be a product, and those properties will be defaulted to these values.As a result, I get a context. 00:07:48 This is a binding context path pointing to the new object.And this makes it very convenient for me... to bind our view exactly against this path. 00:08:03 This is what is happening here.I access the view, I call "setBindingContext", and I pass the context from the resulting call before. 00:08:13 With that, the view is bound against the new object.Let's clean up this empty space, this looks all nice and neat. 00:08:23 And with that, the controller looks good for now.And we continue to the view. 00:08:33 This is the add view – it's pretty empty so far.And I'll put some content inside. 00:08:41 The controller I'm putting in the content is a smart form I supply an ID and some basic properties, but that's not so important for now. 00:08:52 The smart form is a new package that we're not using yet, so I need to define an additional namespace.It's in the sap.ui.comp library, and there it's "smartform". 00:09:11 All right.Then we continue with the implementation of the smart form. 00:09:17 Actually, this is a hierarchical control which comprises groups, so I'll put a group inside the smart form, again with an ID and a label. 00:09:31 And last but not least, inside this single group that we have, I put group elements: four group elements, each of which contains a smart field. 00:09:47 Again, the smart field comes from a new namespace – we need to handle this..."smartfield", all right. 00:10:04 Now observe that we're not giving much information – we're just giving an ID and a data binding to the property.We're not specifying the label, and we're not specifying whether this is a mandatory field – we're just saying: smartfield, the data is here. 00:10:25 So with that, I'm finished for now, so let's see if this all runs well.I relaunch the application, 00:10:38 I press the "add" button, and here we go. 00:10:46 We see four input fields, and we see labels.We also see that some of the input fields have a mandatory indication with this little asterisk up here. 00:10:59 And we also see that for the price there's an additional input field for the currency.So let's have a look behind the scenes and take a look at the metadata. 00:11:13 In the "localService" folder, you find a copy of the service's metadata.Here you find the description for the product, 00:11:25 and if we take a look at, for example, the category, we see that back there a label is being defined.This is an annotation that is read by the smart form. 00:11:38 We also see here Nullable="false", which means this is a mandatory field.If we look at the price, we see another annotation: sap:unit="CurrencyCode". 00:11:50 With that, we know that the currency code is acting as the unit here.All this information is read by the smart form and used to display this UI. 00:12:07 Okay, so far so good. We now continue... with the implementation of the Save and Cancel buttons.I go back to the view, 00:12:36 and after the content of the page, I put those two actions, which are available on the fullscreen page. 00:12:45 It's a save action and a cancel action with some predefined text values and positioning of buttons, and when they are executed, I call the event handlers "onSave" and "onCancel". 00:12:59 As usual, these event handlers are being implemented in the controller."onCancel" is pretty easy – all we do is call the existing "onNavBack" method, so we navigate back to the previous screen. 00:13:22 With "onSave" we access the model and call "submitChanges".As I said before, with that we tell the model to send the HTTP requests to the server. 00:13:36 Now the curious thing is: How do we know when this process has finished and we can do the remaining work? To achieve this, we register for a callback, 00:13:52 and the curious thing is that we do this here.So when creating the entry at that point in time we hand over this callback function, 00:14:02 which is called "_onCreateSuccess".In this callback function, we get a reference to the newly created object, which in our case is a product. 00:14:27 All right, we're on the home straight now.So we access our router and trigger a navigation to the object route, 00:14:41 and we hand over as an ID the ID of the newly created product.Then to be on the safe side, we unbind the view, so the object will no longer be shown. 00:15:04 And last but not least, we show a message toast to the user with the message that a product has been successfully created.One little detail here: 00:15:17 You set the property "closeOnBrowserNavigation" to "false" because otherwise routing would close that dialog.That is the default behavior. 00:15:25 The only problem left is that the message toast is not available yet.But we know how to handle this: "sap/m/MessageToast". 00:15:41 So we just require this up here.Okay, let's give this a shot and rerun the application. 00:15:57 As the add view is part of the routing, we directly end up in this view after the browser refresh.And now I enter a value: "my very first product". 00:16:13 I have to set a category, and this is checked, so I give a valid category I know: "Notebooks".And the same for the business partner – I know this is a valid business partner. 00:16:28 After entering the data, I hit the Save button.Now I see this error dialog... hmm. 00:16:36 This has come unexpectedly, it looks like I've made a mistake.Let's take a look at the error message... it's a bit cryptic... 00:16:52 but somewhere down there I see "Mandatory field 'CATEGORY' is empty".Hmmm, it's not empty, so it looks like my binding was not really established properly in a two-way mode. 00:17:07 Let's recheck what I've done there. I go back to the manifest.json.It looks good at a first glance. 00:17:16 Ah, but then I see that, of course, this property does not really belong to the "metadataUrlParams" – it's a separate setting.Okay, "defaultBindingMode" on the "settings" node. This looks better. 00:17:37 I save the manifest.json and I rerun the application.I enter the same data as before, I press Save, 00:17:59 and there we go: This time the binding was properly established, and the values from the input fields were written back to the model.After creating the object, the navigation is triggered, and we now see the "Product" view of "my very first product". 00:18:18 Also when I navigate back to the worklist, I find "my first" and "my very first product" in this list.So we can see that the successful creation has really happened, and just to prove it, let's refresh the application. 00:18:33 This time the data comes from the server, and there it is.So far, so good – that was the coding exercise. 00:18:44 Before we end the session, just a few more things.Actually, we made our lives a bit easy. There are some things in the real world you should really do that we didn't cover in this session. 00:18:57 Just to name them: busy indication – while saving the data, of course, you should set the view to "busy" because the user has to wait.Error handling – the standard error handling in the templates is used, but this shows the message we just saw, which is very technical, 00:19:18 and you can implement better specific error messages for that.Very important: input validation – of course, the data needs to be checked. 00:19:28 This can be done on the client and on the server, and if you do it on the server, you'll find a lot of tooling in SAPUI5.Value help – I just knew that "Notebooks" is correct and that the supplier "010000000" is correct. 00:19:45 Of course, the user doesn't know, he needs help for that, and here, too, SAPUI5 offers some help against term-based annotations and with smart controls. 00:19:56 The user can get help there with selecting suppliers and other things.Data loss confirmation – if the user wants to navigate back after entering data, we should warn him that the data would be lost. 00:20:11 That is a standard procedure.You'll find more instruction on how to handle that in the exercises. 00:20:17 However, it's not part of this session because that would mean additional stuff, and we've tried to keep it as simple as possible for this exercise. 00:20:27 So with that, I wish you good luck with mastering this slightly more difficult unit, and enjoy. Week 4 Unit 3 00:00:09 Hello and welcome to Week 4, Unit 3 of our openSAP course "Developing Web Apps with SAPUI5".My name is Michael, and in this unit you will learn about the testing features of SAPUI5. 00:00:21 Many quality features already come with the worklist template that you have worked with for some time now.And we will also write a simple unit test for a formatter that we have added in one of the previous units. 00:00:33 So let's get started by opening our manage products app, and your project should look like this if you have followed the exercises so far.What we haven't talked about yet is the "test" folder in the application. 00:00:48 The "webapp" folder contains all the productive elements of the application, meaning the HTML file, JavaScript resources, and additional data, 00:00:57 and the "test" folder contains all the testing artifacts.Inside the testing folder, we have several helper structures that help you test your application projects. 00:01:09 For example, we have this mockServer.html file, and if I call this instead of the index.html file that we've used so far, we will see that the application is loaded with simulated mock data. 00:01:25 So instead of calling the real service, an artifact called the "mock server" that is inside the application will generate products for me with a random number and random price. 00:01:37 I can still use the app, but it's all generated data and, for example, the map formatter is not working because the generated address does not have any coordinates. 00:01:47 But we can use this for our testing tools and this is just one of the features that we have in our application.So coming back to the "test" and "webapp" folders again, there's a clear separation between test and productive code, 00:02:03 and we can just remove all these testing artifacts during deployment.In Week 3 Unit 1, when we created a template, we actually deployed the application to the HCP platform, 00:02:14 and then during a hidden build step that runs in the background, the "test" folder is removed so it's not on the real Web server.The mockServer.html file loads an artifact from the project, called "mockserver" from this "localService" folder, 00:02:34 and inside here we have an artifact called "mockserver.js" and a local copy of the service metadata.So using this JavaScript file, the mock server is started up and it intercepts all the service calls that would usually go to a real backend system. 00:02:49 This way, we can test the application without a dependency to a remote system because the remote system might be down or the system is not even developed completely 00:02:59 But this way, we can still run the app and test it.I can even configure this mock server with more details. 00:03:08 For example, here you can see that there's a URL parameter called "serverDelay".And if I run the mockserver.html file and set this parameter to, for example, "3000", 00:03:21 then I can slow down the app to test, for example, the loading features of my application, and you can now see that every service call takes 3 seconds. 00:03:32 By default, the template does this with 1 second, which is already enough, and if you want to speed up your process, you can even put a "0" here, and your app will be superfast. 00:03:46 Okay.There's also a ZIP file in the Git repository that we deliver with this course, 00:03:54 and there you can find generated mock data for the service if you're not too happy with this generated stuff here in the application, and you can use real mock data that we copied from the service and you can put that in your application in a subfolder called "MockData". 00:04:11 Good.What else do we have? In the "test" folder, we have two subfolders called "unit" and "integration". 00:04:23 In the "integration" folder, we have integration tests that are written with a tool called OPA.This will be part of the next unit in this course, so I'll just quickly show you how it works and then skip it. 00:04:36 OPA tests are also written in JavaScript, but they test real user interaction.They just trigger the app like a real user would do it and do some actions on it, and that way you can test interaction steps. 00:04:51 As I said, there will be more on this in the next unit, so I just want to show you that it's there and that the app already has a basic coverage of OPA tests. 00:05:02 In this unit, we will cover unit testing more.This is in the "unit" subfolder of the "test" folder, and there is also a start page here that I can run called "unitTests.qunit.html", 00:05:15 where we can call all the unit tests.In contrast to integration tests, unit tests focus on testing logic and code behavior. 00:05:25 We isolate the unit under test and make sure that it's doing the right thing.Unit tests are technically written in QUnit, which is also part of SAPUI5. 00:05:36 You can see here that the app also has a lot of unit tests shipped with it, so you can just extend them in your custom application project. 00:05:46 Furthermore, we have this "testsuite.qunit.html" file, which runs all the unit integration tests together, and this can be used for integration scenarios, for example, in a continuous integration scenario 00:06:00 when you have larger projects or multiple developers working on the same code.In the presentation, you can find an overview of all the features that I have just talked about 00:06:11 as a reference and also linked to documentation.So basically I just wanted to tell you that this template that we've generated for our project 00:06:20 is ready to be extended for new tests and for your new features.That's what we're going to do in this unit, so let's go back to our code. 00:06:32 And if you did the exercises from the first two weeks, you'll also still have this "MyApp" project in your workspace.If not, you can just copy the code from the exercise document. 00:06:43 I want to use this formatter from the second week that determines the delivery method based on the weight of the product and just sets the delivery method, for example, "mail" if the weight is less than 500g 00:06:59 and "parcel" if it's less than 5kg, and otherwise it's "carrier delivery".So we will copy this formatter function to our project, 00:07:14 and we just put it in the "model" > "formatter.js" file.There are the other formatters from the previous units and from the template, and we just put it below here. 00:07:24 We also have a shortcut for this "getView" "getModel" method in the base controller, so I can remove this too, which will make it easier for testing. 00:07:34 I now have my formatter in the project, and I'm just quickly going to make it nice in terms of its formatting.And then I can test it. 00:07:43 The way this works is that under the "test" > "unit" folder, I have the same structure as in my real project.In my "unit" folder, I have a "model" folder, where there is also a "formatter.js" file, 00:07:57 but this time there are testing cases here that we define.This is what you also saw when I ran the unit tests, so these are the test cases that were run this time. 00:08:10 So let's start by adding tests for our delivery formatter, and I do this by defining a new QUnit module.So I type "QUnit.module" and put a "Delivery" module in here. 00:08:26 As you can see here above, each QUnit test has this function body called "QUnit.test".And I give it a meaningful description. 00:08:37 For example, our formatter is called "Delivery", so "Should return a delivery method based on the weight of the product".The second argument is a callback function that is triggered by QUnit, and this one will get a global "assert" object. 00:09:03 So when I run my test cases again now, either by calling this "unitTests.qunit.html" file or by selecting this Run configuration here – "Run unitTests.qunit.html" – 00:09:16 I initially get an error because each QUnit test should have at least one assertion.So I go back to my test and define my first assertion. 00:09:29 So using the global "assert" object, I can, for example, say "strictEqual", and then I call my formatter file – and this one is already loaded here in the "define" statement from the previous test – 00:09:44 and I can say "formatter.delivery" and just call it "my test case".And here let's use the first test. 00:09:56 For example, we want to test this mail delivery here, so we run it with, let's say, 0.3, and I think the first argument is the measure, yes, so with "KG", 0.3. 00:10:22 And I expect that this is a mail delivery.So when I run the tests again now, they still fail, but this is actually fine, 00:10:36 because when you write tests, you should do very small iterations.You can even write tests before the actual implementation to push this concept further, 00:10:45 and this is then called "test-driven development", or TDD.So we have already defined the formatter in our project, we will just do the testing part, and let's quickly analyze the error message. 00:10:59 It says "this.getModel is not a function", and this is because we directly invoked the formatter call.And the controller, which is queried in the original formatter function here with the "this.getModel" function is not loaded. 00:11:15 However, we want this because we don't want the dependency to the controller, but we have to do something in our test case for it.So we have to actually mock this call in our function, and here we can use "sinon.js", 00:11:30 which is a very helpful tool and also delivered with SAPUI5.So let's load the dependencies for sinon, and I'll just quickly copy this from the exercise document and put them here. 00:11:47 They don't have a representation in the function body, they are just loaded.And then I can do something like this, I can say "oControllerStub = sinon.stub()". 00:12:09 You can read more about this stub functionality if you google for "sinon.js".The Sinon.JS documentation has a lot of information about the stubs, 00:12:21 and you can see here that it's just defining a function that can return certain arguments, and this is how we use it in our application.So now I have my controller stub, and I can then create an "IsolatedFormatter" function... 00:12:49 by calling "formatter.delivery.bind" and then binding it to the controller stub.And instead of calling the function directly, I will call this "IsolatedFormatter" function. 00:13:06 And then I have successfully stopped my "getModel" function because I now have a copy of my controller.Let's see what happens in the test... 00:13:22 still "getModel"... let's check again... "formatter.delivery.bind"...Actually, I forgot to put something in here, so the stub also has some arguments because I need to tell it what I want to stub here. 00:13:46 So I want to stub the "getModel" method, and then here I say "sinon.stub" and this goes inside.There we go. 00:14:03 So it's just an object, and the "getModel" method is then the stub.Let's see what happens again. 00:14:11 Now it's a different error message, it says "getResourceBundle" is undefined.And this is then the next service call in my original formatter, because I now stubbed this method and I still need to stub this one. 00:14:23 Instead of doing it by hand, there's a useful functionality in the template called "FakeI18nModel.js".Let's quickly have a look at this artifact. 00:14:34 It gets a map of texts and will simply return the texts for us, because for us there's no need to test the real texts – we just want to make sure that our formatter is correct. 00:14:47 So let's use this in our application project.I'm going to my formatter.js test file again, and I'm just copying this part from the exercises 00:15:01 because it's a little bit more complicated now and it's using the arguments.So I'm just going to copy it over and then I'll explain it to you. 00:15:15 Oops! Take the whole part... There we go. 00:15:22 I still need to note the dependency, which is then... "test/unit/helper/FakeI18nModel".Now it should work. 00:15:50 So I load this "FakeI18nModel" and I define the exact same keys for the texts as I did in my real formatter file.Then I say this one loads to a static text called "mail". 00:16:03 And then in my tests, it will be like the real model, so let's see again.I think there's an error here. 00:16:20 Let's check again to see whether I did something wrong. Yes, that's a very common error: I forgot the comma here.You also need to make sure that you define it on top because these two modules don't define any variables here, 00:16:33 so just put it after the formatter file.Let's reload the tests again. 00:16:39 You now see that my new test is running fine.This is a strategy called "Make it work" – you just make a first test case work, and then you make it nice later. 00:16:50 We haven't yet covered all the functions of our formatter file.We just did one simple test, and we can do another one. 00:16:58 For example, we can also test the different measure, saying 200g will also result in mail delivery.And then I have a second test here, 00:17:11 but to make this really nice, I'd have to test the other cases too, like the parcel and the carrier delivery.And we can also make use of this code coverage tool here in QUnit to see whether we've already covered this path. 00:17:27 So if I turn this on, I can select my formatter.js file.And here you can see that the red lines are not actually covered by my test yet. 00:17:39 So far I've covered these lines, but the parcel delivery hasn't been tested yet.I could now add more test cases here, but this would get rather ugly and my test message would no longer match. 00:17:56 So I'd rather copy this function body again and create a second unit test for it.Then I can say "Should return mail delivery", and here I can say "Should return parcel delivery". 00:18:17 But then I'd have to copy this code, too, which is also not so nice.So instead I'll use this "QUnit.module" initialization process. 00:18:27 I can pass in a second argument here, which is an object.And here I can define a "setup" function that is executed before each of these QUnit test files. 00:18:39 So here I can say I create my stub, and then I can also create my "Isolated" function here, but instead of defining it locally, I put it on the "this" pointer. 00:18:54 And the only thing I then need to change in my test cases...oops, I forgot to copy the closing bracket here. 00:19:11 The only thing I need to do in my test cases is to replace these calls with the "this" pointer, too.And then I can remove all my stub code from this unit test. 00:19:26 So my unit test just got a lot simpler.There's still one error – I think this bracket needs to go away. There we go. 00:19:40 Now I can simply say I want to do my second test case, and here I call them, for example, "3" kilograms, and this should result in "parcel" delivery. 00:19:56 I also need to define my parcel text here, so the parcel delivery method is the parcel text. 00:20:13 Now I should be able to test my second option, And you can see that I now have two test cases here: one for mail delivery and one for parcel delivery. 00:20:25 Let's quickly do this for the third one, too.So I can now copy the whole function and say that carrier delivery is when, for example, a value of 5 kilograms is put in. 00:20:47 And I also need to put my text in here.And if you're wondering where I took this from, this is the "else" case here in my original formatter. 00:21:07 And this is a very good test case because it's an edge case, so everything that is less than 5 kilograms is parcel delivery, and everything else is carrier delivery. 00:21:17 So it's really important not to test every possible value, but edge cases and cases that most often return errors.So I can, for example, also test something with invalid data. 00:21:36 I can also put something like "foo" and "bar" in here, and this should also result in carrier delivery.Let's run our tests again and see if it works. 00:21:48 It looks good actually, so delivery now has three test cases, one for each branch.And if we take a look at the code coverage again, 00:22:01 we are up to 93.75%, which is a lot more than at the beginning, and you can see that there are no more red lines in this formatter function, and we have tested all the logic. 00:22:14 You might now wonder why we haven't put it into our application yet.This is because we wanted to write the test cases first, 00:22:23 so the only thing we need to do now is put the formatter in the application and test it to see whether it works, because we've already tested the logic and that should be fine. 00:22:37 So let's quickly do this.We can go to our view, and I want to put it in the object view, 00:22:49 and again I can also copy the code from the previous units or take it from the exercises – just as you wish.Because I've already used it here in the second week, and this is my "ObjectStatus" call where the delivery formatter is actually called. 00:23:07 So I can just copy this code again, put it inside my application project, and... here we go, "Object.view.xml". 00:23:20 Here in the object header it's called "statuses" aggregation, and we can put it inside.Then I also need to copy the I18n files, which I can find here because these are set by the "formatter" function, 00:23:45 and I just put it here at the end of the object view.Then I can call either the real file or the "mockServer.html" file. 00:23:58 So let's just call this one more time so that you become more familiar with it, and let's see what the "formatter" function looks like.Okay, let me check again. 00:24:19 It's not there yet, so I think I just messed something up here.I think I put it in the wrong... 00:24:44 Let's check in the console to see whether there are any errors for it.Ah, there we go. I don't know, it was a really fresh problem, but here we can see it. 00:24:55 So it has a high number because of the generated data, so it will result in carrier, but then I can also try it with the real data.So that's the complete formatter test. 00:25:07 I also just did a manual test to verify that it's working, but the logic was tested by the QUnit tests.So we have isolated the formatter from the controller and the I18n model. 00:25:18 And I've written test cases for all the logical paths, so we can make sure that the formatter does its job.Good. 00:25:28 Let's quickly have a look at the benefits of having automatic tests again.So why should you have automated tests for your application functionality? 00:25:39 Well, it will pay back a hundred times when you have a really good running application because you have a lot less manual testing and you can simply run your automated tests. 00:25:49 You can even avoid regressions when you write new features because you can run your automated tests.And you can find bugs easily in your code. 00:25:59 Then with the code coverage tool and with the test cases themselves, you have a measurable KPI that gives proof of your code quality and you can even publish it. 00:26:12 In continuous integration scenarios, you can run these tests automatically whenever somebody checks in code, and then you will spot errors easily, too. 00:26:23 You can find a lot more about QUnit in our documentation, and please remember to write tests for your application features because nobody wants to 00:26:31 search for bugs in a complex engine like the guys on the right-hand side are doing.You just want to make sure that everything runs automatically instead of having too much manual testing. 00:26:42 Good.This concludes our QUnit unit. 00:26:46 In the exercise, you will write a similar unit test for your Manage Products app.In the next unit, Martin will show you how to write integration texts with OPA. 00:26:57 I look forward to seeing you in the discussion forums.Thank you. Week 4 Unit 4 00:00:10 Hello, my name is Martin. Welcome to Week 4, Unit 4 of our openSAP course "Developing Web Apps with SAPUI5".In this unit, we will have a closer look at OPA tests, which stands for one-page acceptance tests. So let's go. 00:00:29 OPA lets you check whether your specifications are met on a component level.There is no recording tool – developers need to write test scripts in JavaScript. 00:00:40 These scripts are executed in the same runtime as the app under test, so if a test fails, developers can use common browser tools such as Chrome Developer Tools to find out what went wrong. 00:00:53 OPA makes it easy to trigger events such as pressing a button or entering a text by providing predefined actions.If you are missing an action, you can simply write a JavaScript function which does what's necessary. 00:01:08 On the other hand, you need to determine whether the result of an action is what you expected.OPA provides "matchers", which let you check the application state, for example, the value of a control's property. 00:01:24 OPA does the waiting for you – it keeps trying for up to 15 seconds or longer, calling every 400 ms until the matcher returns true.This lets you, for example, trigger a request and wait until the response has populated your view. 00:01:40 With their Given-When-Then syntax and page structuring, OPA tests are readable, and once you have established a basic set of pages, they are also easy and fun to write. 00:01:53 This makes OPA ideal for test-driven development.Let us first have a look at the OPA tests which came for free with our generated worklist application. 00:02:07 So in the "integration" > "test" folder, you find "pages", each of which can provide arrangements, actions, and assertions, which correspond to "given, when, then". 00:02:26 Here is our worklist page, which only has – as you see here – actions and... sorry for the scrolling... arrangements.So let us find out how these are used in a journey. 00:02:51 So we're going to look into the worklist journey.Here's where we find the actual OPA tests. 00:03:00 As you can see, we are using actions and assertions from our page objects in the OPA tests to populate the "when" and "then" sections. 00:03:15 We can also concatenate several actions and assertions, and we may also do that using several different pages.Here, for example, we're using action from the worklist page, while the assertion stems from the app page. 00:03:35 This way, the pages have two purposes: They serve as a repository for reusable arrangements, actions, and assertions, 00:03:44 and they make the OPA test which refers to them more readable.Let's execute our OPA test and see what that looks like. 00:04:08 Okay, as you can see, the application has started and closed several times.Here, for example, is a test to check the busy indicator. 00:04:25 There are navigation tests.There are error handling tests at the end. 00:04:52 We also test what should happen if a resource is not found.And that's it. Our OPA test is green, so we're all good. 00:05:02 We'd now like to add our own OPA tests to the worklist journey.Let's have a quick look at the application. 00:05:15 You may have noticed that the product list displays a "+" button right next to the search field in the header toolbar, and we would like to write a simple OPA test to check whether this button opens the "add" view. 00:05:29 So we'd like to check whether this works correctly.First we need to implement an action to press the worklist’s "Add" button. 00:05:41 We will do this as an action on the worklist page.So let me do this. 00:05:51 Let's go to the worklist page, the "actions" section.Okay, I'm going to add the action right here. 00:06:17 Okay, so this waits until the view is loaded and tries to find a button with this ID.Once this is found, it's going to execute a "press" action. 00:06:36 The ID is not defined yet, so we're going to... do this... now.We do it here in this "variable" section. 00:06:52 This is the ID that we can find in the worklist view.Let's have a quick look. 00:07:04 It should be in the header toolbar.And here it is, so that's the ID. 00:07:16 All right.Next we need to add a new "New Product" page, where we can place our assertion to check whether the add view is displayed. 00:07:29 So we continue by creating a new page, which we call "NewProduct.js". 00:07:45 And here...we will add the following code – I'll explain this in a second. 00:08:03 This is just an empty skeleton for a page, which has an "assertions" section here.Let us check whether the add view is displayed as an assertion into this new page. 00:08:22 So we're going to do this now.So this is our assertion – and what does it do for 00:09:00 It waits for the new products page, which is defined by the page ID and the view name, and once this is available, a "success" function is called and we just execute an ok assert. 00:09:26 If the page is not found, an error message is displayed and the OPA test will fail.We still need to declare our new page though on the "AllJourneys" page. 00:09:39 Let's quickly do that.Right, okay. 00:09:56 So with all this done, we can now add the new test, and we need to find a journey where we can place the new test, so let's put it into the "ObjectJourney". 00:10:09 Okay.I'll just copy this and explain it in a second. 00:10:24 Okay.So I'm going to add it to the very end. 00:10:40 This now first starts the app again, which is necessary because the test before tore down the application.Once this is done, we will go to our worklist page, and we need to first wait until the table has been loaded. 00:10:59 After that, we will execute our new "add" action, so this will press the "Add" button. 00:11:08 Then we come to the assertion, which is taken from our new product page, and here we would like to see the page shown, which is done here. 00:11:23 When this is done, we do some housekeeping, so we clean up and we tear down our application.Okay, so we're all set – let's see whether our new test works. 00:11:39 Okay, it should appear now as test number 6 in the worklist journey.Okay, there it is. Test number 6: Should see the "New Product" view after pressing the "Add" button. 00:12:04 We briefly saw the view and here we are.So let us continue with some words about testing strategy. 00:12:21 We learned about QUnit tests in the previous unit.Let us have a look at how these and the OPA tests fit into the overall testing pyramid. 00:12:31 Ideally, you should put most of your efforts into unit tests, which is why they are at the very bottom of the pyramid.Unit tests are automated tests which check, for example, controller functions directly, 00:12:44 using stubs, simulations, and trusted components – so components that are already tested – to stage a context.On the next level, there are the component or integration tests. 00:12:58 In the UI5 context, these are automated tests of views and their controllers of a running application.No stubs or simulation are used except for mocking backend data and simulating user input events. 00:13:14 For the two lower levels, into which developers should put most of their testing efforts, UI5 comes with third-party frameworks QUnit and Sinon, and provides OPA and a mock server for integration tests. 00:13:29 Both QUnit and OPA tests can be executed automatically as part of a build process, which is part of continuous integration.Now let us look at what is left in the pyramid. 00:13:43 System tests address applications with real backend data and/or real user input.Selenium is a tool to support such tests. 00:13:53 They can be automated as well.Usually, they do not run as robustly, so it may be difficult to run them with every build. 00:14:03 So it's hard to have them as a part of continuous integration.Finally, and this should really be the smallest portion of your testing investments, there are manual tests. 00:14:14 They are used to cover anything that cannot be covered by the other tests or to find issues that nobody has even thought about.For the latter, exploratory testing has proven to be a useful method. 00:14:27 You test a system following a certain theme called a "tour".There are, for example, "money", "supermodel", "back alley", and "antisocial" tours. 00:14:37 During an antisocial tour, for example, testers try to do anything to break the application to see how it reacts and if error messages are correct.Thank you – that's it for this unit. 00:14:50 We learned about OPA tests and had a look at the testing pyramid.In the next unit, you will learn about how to build custom controls. 00:14:59 Bye bye. Week 4 Unit 5 00:00:10 Hello and welcome to Week 4, Unit 5 of our openSAP course "Developing Web Apps with SAPUI5".My name is Thilo, and today we want to learn how to build custom controls. 00:00:21 As we said earlier, UI5 comes with a large number of controls.For example, more than 180 for scenarios supporting mobile and desktop devices. 00:00:31 Still you might have the feeling sooner or later that your project needs something that is not provided by UI5 out of the box.This is when you have to build you own custom control. 00:00:45 We have already learned that you can extend certain classes in UI5 to build upon provided capabilities and implement custom functionality.Just remember Week 2, when we learned how to extend simpleType. 00:00:58 Let's now dig deeper and have a look into the base-class architecture of UI5.In this graph, you can see how the base-class sap.ui.core.Control gets all the qualities you might expect 00:01:10 when building your own control from its subclasses, from eventing and data binding all the way up to rendering. 00:01:17 Let's not go into detail here, but keep in mind that you can extend all of these base classes to make equal use of their qualities and the ones in the inheritance chain. 00:01:29 For building a custom control, we will, of course, extend the control base class.Now we will have a look at the different options you have when creating custom controls. 00:01:40 First you can extend an existing control, for example, if you want to add capabilities to the image control while you still want to benefit from existing functionality. 00:01:49 Just think of an example for a lightbox out of the image control.The second option is to build a new control from scratch. 00:01:58 Two flavors are possible here: build a completely new control, or build a composite control that orchestrates existing controls internally.Let's go with the second option and enrich our application with something that is still missing. 00:02:16 And that is a control that allows our user to rate a product and trigger a submit for this rating.This is our agenda for today's lesson: 00:02:25 We want our users to be able to rate a control and submit it by clicking a button, the new control should throw an event and the new value should be exposed, 00:02:38 and finally, we only want the button to be enabled if the user changes his or her vote.Let's now have a look at the general buildup of a new control. 00:02:49 For this I have prepared the skeleton coding for a new custom control.Let's now create a first basic version of our control. 00:03:01 We see here that we extend the base class control, we add some metadata here – we've already seen this when we use control – 00:03:10 there's an "init" function that we will use, and finally a "renderer" function that is used to render out the control into the DOM.Let's start creating our property. 00:03:22 We said that we have a value that is created when the user rates the product, so we add a new value property here.We say that the type of this property is "float" (type: "float"), 00:03:41 and we say "defaultValue = 0".Because we have a composite control, we will handle the internal controls as aggregations – in our case, as hidden aggregation. 00:03:58 We start with "_button" and, as for the properties, we create a new object.We now say the type of this control is "sap.m.Button" (type: "sap.m.Button"), 00:04:19 we say the visibility for this aggregation is "hidden" (visibility: "hidden"), and we say we only want one button here, so "multiple: false". 00:04:36 The second one is our rating indicator, we say "_rating", that goes with the new object type here as well (type: "sap.m.RatingIndicator").We say the visibility is "hidden" here (visibility: "hidden"). 00:05:01 And we only have one here, so "multiple: false".Finally, we have an event that we want to expose – remember our agenda. 00:05:12 We say the event name should be "valueSubmit" – we can use this later in the XML to attach our handler here.Ah, of course, we have to write it correctly. 00:05:27 And this gets a new object here.We have a "parameters" object that we want to expose, and we say there's a parameter value that should come with the event, 00:05:43 and the type for that one should be "float" as well.With that, we have created our metadata for the new control – quickly save it. 00:06:00 And then go for the "init" function.In the "init" function, we will instantiate our aggregated controls and add them to the control. 00:06:11 For that I created a quick snippet here because it's a lot of typing, so let's save ourselves the time and just paste it here.We say we want a new aggregation "_rating" with the rating indicator and with the button. 00:06:30 Now we see here that we still need to load the dependencies and create the aliases – that's what Web IDE tells us.So we go up into the define and add our dependencies here. 00:06:43 We say we add a button – "sap/m/Button" – and we add a "sap/m/RatingIndicator".Now we add these to the aliases here, so we say we have the button and the rating indicator. 00:07:18 With that, we have instantiated our controls.The red marking here is gone but we still need to put some content to the renderer. 00:07:28 You see here a "renderer" function is called on rendering, and it gets two parameters, or arguments.One is the render manager, which can be used to render out the DOM content, 00:07:39 and the other one is "oControl", which is the control itself.So here we type "oRm.write", and we write our DOM structure, so we say we write a new "div" tag here and we keep it open. 00:07:58 Let's already add the closing for this "div" tag – oRm.write(">") – and finally close the entire tag – oRm.write("</div>") 00:08:19 Within this "div" tag, we now want to add the control data, so we say "oRm.writeControlData", and this expects our control. 00:08:38 This is needed to ensure that the eventing and the data binding for the control will work.Finally we want to add some CSS to display it nicely. 00:08:51 UI5 comes with standard margin and padding classes, so we will say oRm.addClass("sapUiSmallMarginBeginEnd"), which will add a small margin left and right. 00:09:17 And finally we still need to render out these classes, so we say "oRm.writeClasses".This will now display the outline of our control, but we still need to render the aggregations here, 00:09:34 and this is where we say "oRm.renderControl".And we retrieve our control from the aggregation, so we say "oControl.getAggregation("_rating")" because we need the rating first. 00:10:01 And let's copy this whole line and render out the button as well. We still need a closing semicolon here.Let's do a sanity check. One more closing semicolon. Be good citizens. 00:10:19 And this is all that's needed to render out the child controls as well as the control outline.With this, we can now display the control in our application. 00:10:31 For this, we first have to add it to the view.Now let's have a look at our application. 00:10:37 First we'll see that we have this list here, and in the product details, somewhere near to this form here, we want to have this rating indicator displayed. 00:10:47 So first we need to open our ProductDetails.view.xml, where we had the form.And in here we have to create a new namespace for this custom control. 00:11:01 Let's call it: course="opensap.manageproduct.control", because that's our namespace and it lives in the control folder. 00:11:25 With that, we can access any control in the "control" folder here in the XML, so we create a new tag with the "course" namespace, and we say "RatingIndicator". 00:11:42 Let's close the tag, and if we've done everything right, this should work. Let's give it a try.We now see that on clicking... it's not working, so let's check the issue. 00:12:01 The "RatingIndicator" control could not be found.Ah, we called it "ProductRate", so let's also call it "ProductRate" in our XML. 00:12:14 Let's quickly see whether it's good here: "ProductRate". Yes, that should do.Let's run it again... and it's still not working. 00:12:38 It's in the "control" folder... it's "ProductRate"...and our namespace... "manageproduct"... 00:13:04 "manageproducts", sorry – that should do.There we go, the control is displayed, we can see that we can use the rating indicator, 00:13:16 but the button is still not enabled if we change the rating, therefore we cannot submit the rating yet, so let's add this to our control.Let's go back to Web IDE, open our control, and add the required functionality here. 00:13:34 Because this is again a lot of coding, let's copy it and I'll run through it with you.We say we have an "onRate" function which is triggered when the user makes a rating on the rating indicator, 00:14:00 so we say we set our value to the value that has been rated and we set the button to "enabled".To achieve this, we still need to add the "liveChange" event handler to our rating control. 00:14:13 So for that we say "liveChange", and if a "liveChange" event is triggered, we call the "onRate" function here.Let's bind the "this" pointer to ensure that we're in the right context here. "liveChange" – we need an "e", and that should do. 00:14:38 And the second one is "_onSubmit", which should be fired when the button is pressed, and there we say "fireEvent".You see up there we created our "valueSubmit" event with the parameter, and here we fire that event. 00:14:51 And the value parameter is the value that we stored internally as our "controlProperty" value.So here we say we add a press handler to our button and say "this._onSubmit", and again we bind our "this" pointer. 00:15:21 Let's do a check here, add the missing semicolons.This looks good, let's give it a try. 00:15:33 And again there's something wrong."Cannot read property 'getParameter' of undefined...", let's check this. 00:15:56 "getParameter"... "oEvent"... "onRate"...Ah yes, stupid mistakes here. Of course, we don't want to do it like that. Let's try it again. 00:16:35 And if we now rate the product, we see that the button is enabled and we can now fire our "submit" event.So now we want to use this in our application. 00:16:46 For that we'll go to our product details view, where we instantiated our control, and here we add the event handler for our "valueSubmit" event, 00:17:03 and here we say on "valueSubmit", we want to call an "onSubmit" function that we will then implement on our controller.I already added it here. 00:17:14 We need a reference to our object controller in this view to be able to implement this on the object controller, so in the object controller, we create an event handler here. 00:17:30 On "valueSubmit", which is, of course, a function and gets an "oEvt" argument, and also a comma.And now we say create a new variable – var iValue = oEvt.getParameter("value")" – which is the original value parameter we put in. 00:18:03 "getParameter" And then we say "new sap.m.MessageToast.show" to show some feedback, 00:18:18 and there we say something like "your new rating is"... We don't need the options here."+ iValue". Let's add the semicolon and have a look. 00:18:48 I don't need the "new" because the message toast is not really a control.Let's now give it a try. 00:19:01 So we rate the product and on "Submit"... nothing happens, we messed it up again.I have a "valueSubmit" event, I have a "valueSubmit" event here, the controller is the right one, "onSubmit"... 00:19:22 This is correct, "valueSubmit"...Ah, of course, this should be "onSubmit". Let's try it now. 00:19:46 There we go, the new rating of 3, we see it's good.And with this we have implemented all the features that were initially on our backlog. 00:19:58 Let's now have a look at some more examples of custom controls that have been created by other SAPUI5 developers.One thing that I want to quickly show you now is the D3 chart control that has been created. 00:20:14 So let's look it up, I've already opened it in the browser.Here we see this is also a custom control built on top of D3, which is a library that can be used to visualize data. 00:20:25 There are a lot more use case examples, such as the coding here.This is to show that there are even more complex examples when it comes to creating custom controls. 00:20:43 This concludes today's unit on creating custom controls in SAPUI5.With that, we have almost finished our course. 00:20:51 I hope you liked it.In the next unit, Michael will wrap up the entire course and get you started for the final exams. 00:20:59 Good luck, and enjoy creating awesome apps with SAPUI5! Week4 Unit 6 00:00:09 Hello and welcome to this very last unit of the course "Developing Web Apps with SAPUI5".My name is Michael, and in this unit, I will quickly summarize the course for you. 00:00:20 We will have a look at the course content again and recap what we did in our application projects.And – since you have now mastered SAPUI5 – I will show you how to find other developers to exchange with in our communities. 00:00:35 So we are currently in Week 4 of the course as you can see here on this course outline.You started with the preparation week and registered your developer account. 00:00:48 Then, after getting familiar with the tools and documentation, you have seen four packed weeks of hands-on SAPUI5 coding.Maybe you have also done the coding exercises for each unit and applied the changes to your own workspace in SAP Web IDE. 00:01:05 Hopefully you have gained a lot of practical knowledge and also background information on how to develop Web apps with SAPUI5.If you haven't done so yet, you can also do the bonus exercises to score additional points – 00:01:18 they are fun to do and open until the final exam.Speaking of which, next week it's exam time, and we wish you good luck with that. 00:01:28 The exam will be a mix of questions and short exercises on what you have seen over the last few weeks.If you have paid close attention, it shouldn't be too hard for you. 00:01:39 Nevertheless, you might want to have a look at one or the other unit again to prepare yourself.You will receive a record of achievement for this course when you score more than 120 points. 00:01:52 And be sure to watch out for the deadlines for both the weekly assignment and the final exam.The online forums are still open until the end of the course, and you can discuss anything related to the course there. 00:02:05 After the course, we will have an "I like, I wish" session where you can give feedback on how we Our scenario for the course looked like this. 00:02:18 In Weeks 1 and 2, we created a Web app called “MyApp” from scratch to get familiar with SAPUI5 and learn the essentials.Then, in Weeks 3 and 4, we instantiated a template in SAP Web IDE and added more complex application features to it. 00:02:34 We then deployed the app to the SAP HANA Cloud Platform to make it available.We connected it to the back end system called ES4 using a destination that we configured in HCP. 00:02:48 This way, we could configure products and supplier data from a real service here.So instead of having a local development environment, which is, of course, also possible but more effort to set up, 00:03:01 we did cloud-based development and testing with SAP Web IDE.So you don't need to install anything and it's very helpful for development, 00:03:12 and you can just consume the services in the SAP HANA Cloud Platform using your developer account.In the presentation, you can also find a quick overview of all four weeks, but we will just do it in the application projects. 00:03:27 In Week 1, you learned the essentials by creating the Web app from scratch.This is the result of the Week 1 application that you did. 00:03:39 Here you can find the Getting Started tab in the icon tab bar, we have user controls and controllers, and we worked with data binding for the first time. 00:03:50 Finally, at the end of the week we also used containers and layouts.For example, this icon tab bar here or everything that is on the second tab Containers and Layouts. 00:04:01 We also used these form controls, talked about icons, and a lot more besides.In the second week, we worked more with data binding and added this third tab to our application. 00:04:17 There we loaded a list of products from a real service using a destination, and here you can see the services in the application.So the list is bound to an OData entity, and we defined expressions and custom formatters for the list items. 00:04:34 For example, this price color here, the price formatting in general, and also this delivery formatter were done in the second week.We also added a search for it and some grouping on the application so that you can see the data in a more interesting state. 00:04:52 In Week 3, we created a new project using a template in SAP Web IDE.Let's quickly have a look at the results of this project here. 00:05:02 We started by analyzing responsiveness and making the table responsive.Then we added these quick filters that filter a table. 00:05:13 We talked about code views and how to use fragments in XML, which you can see here.And we also added more details on the detail page. 00:05:24 So if you click on one of the items, you can see the product panel here, supplier information, and this nice map formatter here that shows the address. 00:05:37 We also adapted this application to the user's device, so if you resize the screen, you can see that some of the items are rearranged and reordered. 00:05:51 In the last week of the course, we then mastered SAPUI5, and we did that by adding further features to the application.For example, we added a new product page, which we can reach here with this "+" button. 00:06:08 And here we can add new products.For example, I can now create a product and this will also be written back to the service. 00:06:20 We also talked about testing features and we added our unit tests for a formatter.Here you can see the results of the unit tests. 00:06:34 We also created a one-page acceptance (OPA) test using a tool that is also included in SAPUI5.And here you can see the results of the integration tests carried out. 00:06:49 Finally, in the last unit of this week, we created custom controls as well as this rating control here, which we saw on the detail page.So throughout the course, we have seen all the main concepts of SAPUI5 and lots of implementation details. 00:07:09 If you did all the exercises, you should now have a lot of practical experience for your own developments.And if you want to dig deeper and are interested in more details about SAPUI5, feel free to check out the Demo Kit 00:07:27 and our great tutorials in the official documentation that are linked here on this slide.There are also other related openSAP courses, for example, we have the "Build Your Own SAP Fiori App in the Cloud" course, 00:07:41 which covers more the SAP Fiori design guidelines and development resources.And there's a course called "Developing Mobile Apps with SAP HANA Cloud Platform", 00:07:50 which covers native development of SAPUI5 apps and how to deploy them on mobile devices.Finally, we want to invite you into our communities. 00:08:03 Now that you are a newborn master of SAPUI5, you might also want to check out one of our really great communities here, which you can find here on this slide. 00:08:16 For example, in SCN we discuss a lot of SAPUI5 questions and do blog posts there.On StackOverflow we mostly discuss OpenUI5 topics. 00:08:27 Then we have our Slack channel, where we organize events and chat with you guys.We have our Twitter account. 00:08:35 There are more videos on YouTube that you can watch for training material and developer podcasts.And, of course, if you want to contribute to OpenUI5, feel free to visit our GitHub page, 00:08:46 where you will also find more information on how to create an issue or pull requests.On OpenUI5.org, we have our blog and a news feed where you can see additional details about OpenUI5. 00:09:03 We hope you had a lot of fun during this course, and wish you good luck in the final exam.And let us know what you think in the "I Wish, I Like" forum right after the course had ended. 00:09:14 On behalf of the whole course team, I say thank you for participating in this openSAP course "Developing Web Apps with SAPUI5".Bye bye, and see you soon in one of our communities. www.sap.com © 2016 SAP SE or an SAP affiliate company. All rights reserved. No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP SE or an SAP affiliate company. SAP and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP SE (or an SAP affiliate company) in Germany and other countries. Please see http://www.sap.com/corporate-en/legal/copyright/index.epx#trademark for additional trademark information and notices. Some software products marketed by SAP SE and its distributors contain proprietary software components of other software vendors. National product specifications may vary. These materials are provided by SAP SE or an SAP affiliate company for informational purposes only, without representation or warranty of any kind, and SAP SE or its affiliated companies shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP SE or SAP affiliate company products and services are those that are set forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an additional warranty. In particular, SAP SE or its affiliated companies have no obligation to pursue any course of business outlined in this document or any related presentation, or to develop or release any functionality mentioned therein. This document, or any related presentation, and SAP SE’s or its affiliated companies’ strategy and possible future developments, products, and/or platform directions and functionality are all subject to change and may be changed by SAP SE or its affiliated companies at any time for any reason without notice. The information in this document is not a commitment, promise, or legal obligation to deliver any material, code, or functionality. All forward-looking statements are subject to various risks and uncertainties that could cause actual results to differ materially from expectations. Readers are cautioned not to place undue reliance on these forward-looking statements, which speak only as of their dates, and they should not be relied upon in making purchasing decisions.