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:
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:
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.
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/CSSFor 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.
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:
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/
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. 😎
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
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
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
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
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
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
Hi John,
super, this sounds great. If you want to contribute it, feel free to join the CodeExchange project.
Regards,
Harald
Hi Harald,
I created a branch and committed my changes to it.
Cheers
JSP
Hi John,
super, many thanks!
Regards,
Harald
Hi John, Just wondering what does jQuery.sap.storage give you over and above window.localStorage? Looks pretty much the same to me...
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
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.
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
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.
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
Jason
I just commit changes to the branch, hopefully the EventBus makes more sense.
JSP
A great blog, Harald, and a very helpful complement to my SAPUI5 material Get to Know the UI Development Toolkit for HTML5 (aka SAPUI5) and New E2E Tutorial: Gain Hands-on Developer Experience with SAPUI5 and SAP NetWeaver Gateway. Thanks, Bertram
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?
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.
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.
ok so I'm not going crazy then... I've raised it as an issue with sinonjs.org.
pushed updated test
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!
nice one
Hi Harald,
As per other people's comments, this is a great, really informative and well thought-out blog.
Thanks very much!
Regards,
Gareth.
Thanks!
-- Harald
Simply wonderful! Thanks!
Thanks!
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.
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
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. 😉
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
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
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
Awesome, if there is anything i can do to help, let me know
John P
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
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
Hi John,
super, thanks. Let me know when you have results ready.
Cheers,
Harald
Hi Harald
Got it a bit closer, reminds me of sapscript
https://github.com/schubertha/todomvc/pull/1/
Cheers
John P
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
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
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.
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
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