Skip to Content
Author's profile photo Harald Schubert

How to Write Testable SAPUI5 Applications

SAPUI5 is SAP’s latest, HTML5-based UI technology that allows you to build rich, interactive Web applications. Not only does it come with an extensive, themable widget library containing everything you need ranging from simple buttons to complex table controls, date pickers and charts, but it is also a fully-fledged MVC framework featuring state-of-the-art concepts like data binding, OData support and view templates.

In this blog post, I want to focus on the MVC aspects of SAPUI5, showing you how to best design a UI5 application and how to do so in manner that the application is well structured, easily maintainable, and – most importantly – testable. To this end, we will develop a simple task management application, unit test it, and theme it:

/wp-content/uploads/2013/03/todos_179057.png

Introduction

Traditionally, Web applications have followed a synchronous request/response model where a user issues a request through a browser, the server receives and processes the request, produces an HTML response, and finally hands back the response to the browser for it to render HTML on the screen. This approach was heavily server-centric in that not only business but also presentation logic would typically be handled by the Web / application server. Typically, server-side Web frameworks would greatly simplify the development of such applications by offering extensive support for MVC-style application design, view template languages, O/R mapping tools, and more. More recent frameworks of that kind are Ruby on Rails and Python-based Django.

With the advent of Web 2.0 and AJAX-enabled applications, more and more functionality these days is being implemented on the client side – the browser. This usually means heavy JavaScript development. Traditional MVC frameworks are not particularly suited to support this style of Web development. As a consequence, new client-side Web frameworks have emerged, for example Backbone.js, AngularJS, and Ember.js. SAPUI5 belongs to the same group of frameworks with the additional advantage that it also serves as a widget library.

The rest of this article is structured as follows: first, we will take a brief look at the application we are going to build. We will then take a short detour and revisit the MVC design pattern. After that, we will look into how MVC works with SAPUI5 and how we can use the structure it enforces to our advantage in order to properly test the application. I’ll close with a summary.

TodoMVC

TodoMVC is a Github-hosted project that implements one and the same JavaScript Web application in a variety of different client-side Web frameworks. The goal is to show the nature of each framework and give people means to compare them and chose the one which best suits their needs:

/wp-content/uploads/2013/03/todomvc_179068.png

TodoMVC is a simple application which let’s you do manage todos. You can create new todos, mark them as done, filter, and delete them. We will implement most but not all of its features. You can find the source code of the SAPUI5 version on CodeExchange.

MVC

The most common design pattern to separate concerns when building user interfaces is MVC – Model View Controller. It is also one of the most controversial patterns. There are probably as many definitions of MVC as there are variations. In the following, we will go with a classic interpretation of the pattern to have a common understanding. You might find other definitions on the Web. Don’t let this bother you – what in the end counts is the content, not the label.

Basic MVC knows the following components:

  • Model. Manages the behavior and data of the application domain, responds to requests for information about its state (usually from the view), and responds to instructions to change state (usually from the controller).
  • View. Manages the display of information.
  • Controller. Interprets mouse and keyboard events from the user, informing the model and/or the view to change as appropriate.

MVC.png

This pattern works well with classic request/response Web applications: the controller (e.g. a Servlet) receives an HTTP request, retrieves data from the model (e.g. a database table), passes the data to and invokes the view (e.g. an HTML template), and finally passes the response back to the client.

While conceptually separating responsibilities, classic MVC has the drawback that (a) the controller is coupled to the view and (b) the view has direct access to the model. With client-side Web applications written in plain JavaScript, this can easily lead to code smells where the controller directly manipulates the DOM and presentation logic sneaks into the view, respectively. This is particularly often the case if the view uses free-style coding instead of being written in some sort of templating language, for example.

MVC in SAPUI5

SAPUI5 employs a slight variation of MVC. When used appropriately, the resulting code exhibits a vastly improved structure, is better maintainable, and allows for proper unit testing. The most important differences to classic MVC are:

  • Data binding. Allows you to declaratively bind a UI control attribute (e.g. the value of a text field) to a property in the model. This minimizes the amount of boilerplate code needed to keep view and model in sync and greatly simplifies the controller. It also removes the direct dependency of the view onto the model. The view just knows the controller (see below) and the controller knows how to wire view and model together via data binding.
  • View types. SAPUI5 offers three different ways to write views: views scripted in JavaScript, XML-based view templates, and view templates expressed in JSON. Each view type has its pros and cons and depending on your use case and personal preference, one or the other might better suit your needs. For the purpose of this blog post, I will only be using scripted views but I would strongly recommend looking into the template-based alternatives for simpler use cases. Consult the reference documentation for details. The table below offers a small (probably biased) comparison:
    Scripted Views (JSViews) XML Views (XMLViews) JSON Views (JSONViews)
    Flexibility

    High

    Medium Low
    Syntax Compact (if done properly) Verbose Compact
    Recommendation For advanced views with dynamic nature
    (e.g. content added on-the-fly)
    For mostly static forms
    with custom HTML/CSS
    For mostly static forms
    without custom HTML/CSS
  • Dependency reversal. A minor point but still relevant: SAPUI5 reverses the dependency between view and controller (the controller now only interacts with the view through a very slim interface – if at all needed) which makes it easier to stub the view when testing the controller.

The following picture shows SAPUI5-style MVC. For the design-pattern-savvy people among you, you will notice similarities to the MVP/SC pattern (Model View Presenter/Supervising Controller). I won’t cover it here but Martin Fowler has a nice summary.

SAPUI5_MVC.png

The pattern works as follows: the view (including any of the UI controls it holds) directly handles user input and either relies on data binding to update the model or invokes appropriate handler methods on the controller (for more advanced use cases). In the latter case, the controller might for example fetch some data from the backend, update the model with it and have the data binding in turn update the view. In exceptional cases, the controller might even call the view to for example add or remove a UI control, change the page layout, etc.

Getting your Hands Dirty

Now that we have conceptually understood what MVC is and how SAPUI5 uses it, let’s go ahead and apply what we learnt by implementing the todo application. It might be best for you to just download the code from CodeExchange and open it in your text editor of choice as this will give you the possibility to digest the code at your own pace and in the order you prefer. In this post, I will highlight the most relevant parts only.

Application Structure

I have organized the code as follows:

/wp-content/uploads/2013/03/top_179224.png

You could organize it differently and SAPUI5 is pretty flexible in that respect. I personally like to have the HTML files in the main folder and place all other content in sub-folders below (img/, css/, js/, …). Below the js/ folder, you will find a single todo folder containing all of the modules used in the todo application – be it UI controls, formatters, models, or MVC components. In a productive scenario, you will probably have more elaborate namespaces for better structuring. In this sample project, I wanted to keep things simple.

Bootstrapping

Like with any good Web application, the starting point of our application is an index.html file. It’s really nothing more than what you would expect so I’ll omit it here. The only interesting thing is that it loads and executes the js/app.js JavaScript module below:

jQuery.sap.registerModulePath("todo", "js/todo");
// build the application root view and place on page
var oRootView = sap.ui.view({
    type : sap.ui.core.mvc.ViewType.JS,
    id : "todoView",
    viewName : "todo.Todo"
});
oRootView.placeAt("main");

Here, we basically instantiate the main view of our application and place it on the page inside a DOM element with ID ‘main’. The arguments we pass to the sap.ui.view(…) function specify the name of the view (‘todo.Todo’) and that it is a scripted view we are dealing with. Together with any module paths we registered, this will allow SAPUI5 to calculate the URL of the JavaScript module it has to load (‘js/todo/Todo.view.js’ in this case). Note it is not the controller name we specify here – the view knows the controller (not the other way around).

The View

I implemented the view in plain JavaScript as this gave me the highest degree of flexibility. A JSView has two methods that have to be implemented: getControllerName() and createContent(). The first one is used by the framework to instantiate the right controller and the second one is invoked to essentially create the UI control hierarchy.

Here is how it looks:

sap.ui.jsview("todo.Todo", {
     getControllerName : function() {
          return "todo.Todo";
     },
     controls : [],
     //...
     createContent : function(oController) {
          // ...
          // Row repeater that will hold our todos
          var todosRepeater = sap.ui.commons.RowRepeater("todo-list", {
               design : sap.ui.commons.RowRepeaterDesign.Transparent,
               numberOfRows : 100
          });
          // ...
          // A template used by the row repeater to render a todo
          var todoTemplate = new sap.ui.commons.layout.HorizontalLayout({
               content : [ new sap.ui.commons.CheckBox({
                    checked : "{done}"
               }).attachChange(function() {
                    oController.todoToggled(this.getBindingContext());
               }), new sap.ui.commons.TextField({
                    value : "{text}",
                    editable : false
               }).attachBrowserEvent("dblclick", function(e) {
                    this.setEditable(true);
               }).attachChange(function() {
                    this.setEditable(false);
                    oController.todoRenamed(this.getBindingContext());
               }).addStyleClass("todo").addCustomData(completedDataTemplate) ]
          });
          // Helper function to rebind the aggregation with different filters
          todosRepeater.rebindAggregation = function(filters) {
               this.unbindRows();
               this.bindRows("/todos/", todoTemplate, null, filters);
          };
          // Initially, we don't filter any todos
          todosRepeater.rebindAggregation([]);
          this.controls.push(todosRepeater);
          // ...
     },
     // This is an example for how the controller interacts with
     // the view only via a specific (mockable) interface.
     postMessage : function(message) {
          // ...
     },
     // ...
});

The code shown takes care of creating a check box and a text field for each todo in the model. All we have to do is define a UI template for a todo and set up an aggregation binding between the row repeater and the path in the model (‘/todos’) which holds (and this is important for this to work) an array of todos. Then, each todo will in turn be bound to an instance of the template as defined by the data binding declarations. We can also provide filters to consider in the binding (this is used when showing all, active, and completed todos, respectively).

As you see, the code is pretty straightforward. It contains no conditionals, no loops, just plain sequential logic. I employed a functional programming style and method chaining to keep the code concise and I recommend you follow the same practice. The curly brackets in the strings define the data binding to the model. Note that the model does not know about the model’s details: there is not a single place where the model is retrieved or even instantiated. This will be taken care of by the controller.

The Controller

The controller is responsible for retrieving / storing the model from / in the backend. For demo purposes, we use HTML5 localStorage. Access to it is encapsulated in a separate module (‘TodoPersistence’) and the controller keeps an instance of it around to do the actual work. The controller also provides handler methods the view can call in return to a user action. For example, when the user enters a new todo and hits the Enter key, the createTodo(…) method is called. Note that because of a current limitation in SAPUI5 data binding on arrays, I have to forcefully update the bindings in this particular case. This will be addressed in a future SAPUI5 release.

jQuery.sap.require("todo.TodoPersistency");
sap.ui.controller("todo.Todo", {
     // Stores todos permanently via HTML5 localStorage
     store : new todo.TodoPersistency("todos"),
     // Stores todos for the duration of the session
     model : null,
     // Retrieve todos from store and initialize model
     onInit : function() {
          this.model = new sap.ui.model.json.JSONModel(
               this.store.isEmpty() ? this.store.set({
                    todos : []
               }).get() : this.store.get());
          this.getView().setModel(this.model);
     },
     // Create a new todo
     createTodo : function(todo) {
          this.model.setProperty("/todos/", this.model.getProperty("/todos/")
               .push({
                    "id" : Date.now(),
                    "done" : false,
                    "text" : todo
               }));
          this.store.set(this.model.getData());
          this.model.updateBindings(true);
     },
     // ...
});

Other Modules

I have factored out those parts of the view and controller that I could isolate and which could potentially be reused at other places. I for example created a slightly improved version of the standard text field UI control which supports HTML5 placeholder values and offers an autofocus property. I also created a bunch of formatters that I use for converting from one data type to another during a data binding. Finally, I created the already mentioned thin wrapper around HTML5 localStorage.

Theming

For those of you who already know SAPUI5 it will be clear that the UI shown above is not part of the standard themes that come with the framework. It requires considerable styling effort. The goal of this blog post rather was to show you can make SAPUI5 look however you want and that’s why I wanted to get as close to the original theme as possible. The core of the original TodoMVC application is the base.css file. I was forced to make some changes to adapt to some of the pecularities of how UI5 controls are built but large portions could be taken over.

I will not outline each and every change but overall, the following was needed:

  • Use the base SAPUI5 theme instead of, say sap_goldreflection; this will reduce the amount of chrome SAPUI5 adds by default to the minimum
  • Employ the same HTML structure in the index.html file
  • Assign the right CSS classes to your UI5 controls
  • For everything else, adapt the CSS selectors as needed; keep the CSS specificity rules in mind when changing selectors to keep them clean

Unit Testing the Application

Brilliant. So now I made you write a todo application following a nifty structure I have deemed to be best. But what’s the benefit? After all, you want this to be worth at least something and not just about code style, right? Well, you’ll see shortly.

To begin with, I’d like to again point you to the fact that we split our code into well-defined, self-contained modules each of which can now be tested in isolation. If we for example wanted to test the todo persistency, here is how we could do it (I am using QUnit as a test framework but you might have other preferences):

jQuery.sap.registerModulePath("todo", "../js/todo");
jQuery.sap.require("sap.ui.thirdparty.qunit");
jQuery.sap.require("todo.TodoPersistency");
// Example test showing a simple module test
test("Set, get, and delete via todo persistency", function() {
     // Setup
     var sut = new todo.TodoPersistency("foo");
     var dummy = {
          val : "val"
     };
     // Exercise
     sut.set(dummy);
     var result = sut.get();
     sut.remove();
     // Assert
     equal(result.val, "val");
     equal(sut.isEmpty(), true);
});

That was the easy part. Testing the main (MVC) part of our application requires some further thoughts, though: what part of MVC exactly is worth testing? To me, the view is something I can hardly unit test. Thanks to data binding, the view is purely about visualization and contains hardly any logic (we moved all presentation logic into the controller) and I’d rather have a bunch of Selenium smoke / scenario tests rather than trying to drive a UI with a unit test (which would also be against the philosophy of unit tests). So model and controller it is. Testing the model should not work differently than testing any other non-UI-related module such as the todo persistency so I won’t repeat myself here. The controller is really what we are interested in.

As we saw, the controller does not do any DOM manipulation (and if there was the need to manipulate the DOM as a response to some user input, it would be in the view’s responsibility to do so – the controller would just call the view). All the controller does is react to calls from the view and update the model. And this is what we are going to test. There is only one problem: the controller has a getView() method it invokes to get hold of the view. At this point, you will probably raise your eyebrows and point me to my initial claim that the controller doesn’t interface with the view directly but only through a very narrow interface. I won’t deny this is not exactly the case here: there is no interface. The reason why is simple: JavaScript doesn’t support the notion of interfaces. Due to its dynamic nature, the best I can do in JavaScript is remember what methods and properties the controller accesses and refer to this as the interface. As we don’t want to instantiate the actual view, we will provide a stub for it. To this end, we will overwrite the getView() method of the controller – something that is particularly easy in JavaScript. We could do this manually but I want you to have a look at a wonderful stubbing/mocking framework available to the JavaScript community: Sinon.JS.

jQuery.sap.registerModulePath("todo", "../js/todo");
jQuery.sap.require("sap.ui.thirdparty.qunit");
jQuery.sap.require("sap.ui.thirdparty.sinon");
jQuery.sap.require({
     modName : "todo.Todo",
     type : "controller"
});
// Example test showing how a controller can be tested
test("Create a single todo", function() {
     // Setup
     var sut = sap.ui.controller("todo.Todo");
     sinon.stub(sut, "getView").returns({
          // This is where we stub the view interface
          setModel : function() {
          },
          postMessage : function() {
          }
     });
     // Follow UI5 controller life cycle by calling onInit()
     sut.onInit();
     // Exercise
     sut.createTodo("foo");
     // Assert
     var todos = sut.model.getProperty("/todos/");
     equal(todos.length, 1);
     equal(todos[0].done, false);
     equal(todos[0].text, "foo");
});

As you can see, with a little bit of extra effort we could instantiate the controller and provide it with a fake view that implements the interface to the extent needed for the test (thanks to the absence of real interfaces, you don’t have to stub what you don’t access). We could then simulate user input and verify the functional correctness of the controller by introspecting the model. Sinon.JS also provides sophisticated stubbing functionality so if you wanted to check that the controller called the view back in a certain way, you would have the handrails needed to do so.

Summary

In this blog post, I have outlined a blueprint for how to implement SAPUI5 user interfaces in a sustainable way that ensures proper maintainability and allows unit testing of not only the model but also the controller of an MVC application.

I am looking forward to your feedback!

Update (07/04/2013)

We now have a feature-complete, pixel-perfect version of TodoMVC built with SAPUI5 live on Github! This version is based on the CDN version of SAPUI5. Unfortunately, IE doesn’t support loading SAPUI5 from there (the reasons are documented here). Please make sure to use Chrome, Opera, or Firefox when visiting this site.

You can download the sources on http://todomvc.com/

/wp-content/uploads/2013/03/todomvc_ui5_240863.png

Assigned Tags

      44 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jason Scott
      Jason Scott

      This is a fantastic blog Harald - thankyou! It ties together all the concepts; shows some nifty javascript techniques that really simplify the code and you have a focus on testing... You've also extended a UI5 control. Perfect. The community really needs this sort of stuff. Its helped me understand it all and set me up for how to layout my own applicatons, which I'm just starting to write.  😎

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi Scott,

      I am glad you like it.

      I'm also planning to post the code to the TodoMVC Github project. Hopefully this will increase the reach of the article even further!

      Regards,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald,

      Thanks for sharing, very interesting. Hopefully we see more blogs like this, I think there would be a lot of interest in hearing how internally in SAP SAPUI5 development is done at scale, design, testing, build, deploy etc.

      From your code it looks like Sinon.JS will be added as a third party library in future releases of SAPUI5, I am interested in knowing whether it will be possible to Mock and Fake OData Model calls with this library?

      Cheers

      JSP

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      exactly, Sinon.JS will be added starting with version SAPUI5 v1.10. Sinon allows you to fake XHR requests (http://sinonjs.org/docs/#server) and by that I strongly believe you can fake OData services as well (SAPUI5 uses datajs internally which in turn reiles on XHR). I haven't tried this myself but would be interested to hear from you how it goes.

      Regards,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald,

      I just checked out the code and it works well on version 1.8.4, cannot tell the difference between this and the other versions of todos i have running on my machine, well done mate.

      I downloaded Sinon and placed it in the thirdparty directory, the tests work fine also. I will start using Sinon and let you know how i get on.

      I noticed your app uses window.localStorage, any reason for not using the sap.storage module?

      Cheers

      JSP

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      true, it runs on older versions as well. Still, certain things, specifically striking-out the todos when you complete them, only works with v1.10.

      Regarding localStorage: I just wanted to write a module to show that aspect as well but you are right - sap.storage would be the way to go.

      Regards,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald,

      I didn't notice the striking, good to see support for data-* is coming. I went to TodoMVC and read the TodoMVC specification for routing and thought i would give it a shot.

      Using Director.js together with the UI5 EventBus module i was able to get routing working in about 5 lines of code, surprised myself works well.

      Cheers

      JSP

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      super, this sounds great. If you want to contribute it, feel free to join the CodeExchange project.

      Regards,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald,

      I created a branch and committed my changes to it.

      Cheers

      JSP

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      super, many thanks!

      Regards,

      Harald

      Author's profile photo Jason Scott
      Jason Scott

      Hi John, Just wondering what does jQuery.sap.storage give you over and above window.localStorage? Looks pretty much the same to me...

      Author's profile photo John Patterson
      John Patterson

      Hi Jason

      "I noticed your app uses window.localStorage, any reason for not using the sap.storage module?"

      I asked this question cause there is no example application using this module, wanted to know if there was some underlying reason for its non use.

      You are right the code is pretty much the same as window.local.storage.

      Why use it? i see the advantage being it provides a consistent API to handle storage cross-browser, supports local, global and memory storage types, ideally would gracefully degrade for the devices where the browsers didn't support say localStorage  . In the future I would like to see it extended to use SQLite / SQL Anywhere etc.

      Cheers

      JSP


      Author's profile photo Jason Scott
      Jason Scott

      ok. localStorage seems to be pretty much standardized across the browsers now but I can see the point. Having said that I'll just leave it as window.localStorage for now until a more compelling reason comes along.  😉

      Just been playing around with director.js that I spotted from your code branch. Pretty cool little framework. Thanks for pointing it out.

      I may be using it wrong (most likely!) but I've had to make some minor changes to get it to work whereby you can save the different routes as favorites - or just enter them directly as a url.

      The two issues were around the correctly selected button (all, active, completed) - it wasn't being set when you started from a url like: "http://localhost:65257/TodoMVC/#/completed".

      To fix this up I slightly modified the changeSelection method on the view:

      "

          changeSelection: function(selMode, filters) {

              this.todosSelection.setSelectedButton(selMode);

              this.repeater.rebindAggregation(filters);

          }

      "

      You can see above that I now send the selectionMode as well as the filter so that the correct button can be set.

      There was also an issue when you used the root url which was fixed by changing the redirect from "Router({ "/:filter": this.routeListener }).init();" to "Router({ "/:filter": this.routeListener }).init("all");"

      Now if I save a favorite at any point it works or if I directly enter in a url route it also works.

      Not sure if calling the changeSelection method on the view from the controller is a good thing or not - it would be better if there was a way to do it with data binding (and there probably is).

      edit: forgot to add a little bit of jQuery was needed to keep the focus on the new-todo field -> Drawing of the selected buttons played up a bit without this:

      In the button select function: "$("#new-todo").focus();".

      Regards... Jason.

      Author's profile photo John Patterson
      John Patterson

      Hi Jason

      I pushed a change for the button selection, you may have an old version.

      changeSelection : function(mode,filters) {

      this.repeater.rebindAggregation(filters);

      this.todosSelection.setSelectedButton(mode);

      }

      The specification for Routing

      #/ (all - default), #/active and #/completed

      You are right with the init("all"), to meet the spec '#/' = all would mean creating the routes individually. I noticed at the time that a few of the samples omitted this requirement, easy to fix.

      Feel free to make changes to branch.

      JSP

      Author's profile photo Jason Scott
      Jason Scott

      This is turning into a story....

      Just wondering what you think the benefits of using the EventBus are? For example - in your code with director.js your routeListener simply fires an EventBus event, but it could just as easily have directly set the selected todos instead of waiting for the EventBus handler to do it. ?? Why add the extra layer of abstraction?

      --Jason. 

      Author's profile photo John Patterson
      John Patterson

      You are right about the EventBus being an added layer of abstraction.

      The best practice says it is used for loose coupled communication between views.

      https://sapui5.netweaver.ondemand.com/sdk/#docs/guide/BestPractice.html

      Subjective, but I think EventBus has a place when there is only one view, its use shouldn't be limited to communication between views but communication between models and other objects. Interested to hear others opinions.

      What i wanted to do and should have done was incorporate todoSelected into the routeListener.

      JSP

      Author's profile photo John Patterson
      John Patterson

      Jason

      I just commit changes to the branch, hopefully the EventBus makes more sense.

      JSP

      Author's profile photo Bertram Ganz
      Bertram Ganz
      Author's profile photo Jason Scott
      Jason Scott

      Cool.

      On another topic: Have you tried running the test.html file consecutive times..? It dies every second time on the test routing step. It seems that sinon is have issues with re-setting up the stub of getView().

      I've followed sinons guide to wrap it in a sandbox so that the stub is restored after each test but it seems to make no difference...

      If I remove the previous test steps the routing test works perfectly.

      Are you seeing the same issue?

      Author's profile photo Jason Scott
      Jason Scott

      I think its a sinon bug.?!? If I re-order the test steps it works as well. The problem occurs when you try to re-stub a function and return a different method signature, which is what happens with the routing test.

      With the initial tests the stub always returns the same method signature so it just-happens to work fine.

      Author's profile photo John Patterson
      John Patterson

      thanks for reminding me to update the test.

      I agree with you its a sinon bug, i changed the signature so same on all tests and works fine on consecutive runs.

      Author's profile photo Jason Scott
      Jason Scott

      ok so I'm not going crazy then... I've raised it as an issue with sinonjs.org.

      Author's profile photo John Patterson
      John Patterson

      pushed updated test

      Author's profile photo Jason Scott
      Jason Scott

      Found the testing issue....  It's not a sinon.js problem even though all the pointers were to it. I removed the sap.ui.controller and replaced it with a dummy object and it all worked, pretty much proving it wasn't sinon with the issue.

      The cause was the UI5 Event Bus. Just need to be careful to "unsubscribe" to any events after use (in the teardown method). Without doing this it creates a strange issue with the "this" value not representing the stubbed object when the event is fired (but only every second time!).

      Took about 2 hours to debug the bugger!

      Author's profile photo John Patterson
      John Patterson

      nice one

      Author's profile photo Former Member
      Former Member

      Hi Harald,

      As per other people's comments, this is a great, really informative and well thought-out blog.

      Thanks very much!

      Regards,

      Gareth.

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Thanks!

      -- Harald

      Author's profile photo Manuel Bellet
      Manuel Bellet

      Simply wonderful! Thanks!

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Thanks!

      Author's profile photo Jason Scott
      Jason Scott

      Hi Harald, just a question for you about the coding style in the module for the TodoPersistency:

      Why have you used a prototype instead of just adding the methods to the constructor function? Is it because you were playing around with inheritance?

      Regards... Jason.

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi Jason,

      true, you could have done that. My intention was to avoid duplicating the functions (including their code) with each persistency you instantiate (this would happen when done the way you describe it). For that, I created a prototype object containing my methods that then is wired as the parent object to each persistency. This way, I instantiate the functions only once.

      Cheers,

      Harald

      Author's profile photo Jason Scott
      Jason Scott

      Good work!

      To make the immediate function a function expression I think its supposed to be wrapped in an additional () as well, like this:

      todomvc.TodoPersistency.prototype = (function() {

                var storage = window.localStorage;

                return {

                          get : function() {

                                    return JSON.parse(storage.getItem(this.name));

                          },

                          set : function(data) {

                                    return storage.setItem(this.name, JSON.stringify(data));

                          },

                          remove : function() {

                                    storage.removeItem(this.name);

                                    return this; // for method chaining

                          },

                          isEmpty : function() {

                                    return !(this.get());

                          }

                };

      }());

      Though it does seem to work without it....

      I actually implemented the module pattern slightly differently:

      todomvc.TodoPersistency = (function(storage) {

                // public API -- constructor

                var Constructor = function(aName) {

                          this.name = aName;

                };

                // public API -- prototype

                Constructor.prototype = {

                          constructor : todomvc.TodoPersistency,

                          get : function() {

                                    return JSON.parse(storage.getItem(this.name));

                          },

                          set : function(data) {

                                    return storage.setItem(this.name, JSON.stringify(data));

                          },

                          remove : function() {

                                    storage.removeItem(this.name);

                                    return this; // for method chaining

                          },

                          isEmpty : function() {

                                    return !(this.get());

                          }

                };

                // return the constructor

                return Constructor;

      }(window.localStorage));

      Both ways seem to achieve exactly the same thing.  😉

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Thanks for sharing. True, this is not by the book. Wrapping in parentheses is clearer (even here you could wrap around the function only or around the function and the call to the function). One of these gray areas where JavaScript does magic ^^.

      Cheers,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald

      Just found this request from you to add your SAPUI5 project to the todomvc github site.

      https://github.com/tastejs/todomvc/pull/511

      I think this is a brilliant idea, with the HTML views you could easily meet the remaining requirements. Why didn't you progress with this?

      Cheers

      John P

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      true. This is still on my list. I didn't manage timewise till date. Now I published an update to my fork. Let's see how it goes.

      Cheers

      Harald

      Author's profile photo John Patterson
      John Patterson

      Awesome, if there is anything i can do to help, let me know

      John P

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      sure, you could help. I've reached the point where it's about pixel-perfect positioning of each and every element. Look at the last comment from @passy on Github and you know what I mean. If you can suggest those (hopefully minor) changes, that'd help...

      BR,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald

      Amazing job mate, well done.

      I think i understand what @passy is saying when he overlays the 2 apps, will try and replicate and find what causes the differences.

      Cheers

      John P

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      super, thanks. Let me know when you have results ready.

      Cheers,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald

      Got it a bit closer, reminds me of sapscript

      https://github.com/schubertha/todomvc/pull/1/

      Cheers

      John P

      Author's profile photo Harald Schubert
      Harald Schubert
      Blog Post Author

      Hi John,

      thanks for sharing! Based on some of your suggestions I have managed to get the final version out. SAPUI5 is now live on TodoMVC!

      Kind regards,

      Harald

      Author's profile photo John Patterson
      John Patterson

      Hi Harald

      I saw that last overlay, I think you got it as close to pixel perfect as possible.

      Congratulations on getting the app live on TodoMVC, I think it is a very good example of what is possible with SAPUI5.

      Cheers

      John P

      Author's profile photo Former Member
      Former Member

      Hi,

      is this table, especially the row Recommendation, still up to date?

      I'm currently trying to find out when to choose JavaScript or XML-views but I can't seem to find anything regarding this topic.

      http://content.screencast.com/users/jul_win/folders/Jing/media/fc8d6c21-5069-4720-bd52-ac7a50dcb7b6/2014-11-13_1358.png

      It would be even more interesting to know which type of view SAP is supporting the most and will be continued in the future.

      Thanks and regards

      Julian

      Author's profile photo Kimmo Jokinen
      Kimmo Jokinen

      Hi Julian,

      Here's a link to a recent discussion on view types and when to use which:

      Which type of view is best...JS view or XML view or HTML view or JSON view?

      Regards,

      Kimmo