BT

Mobile Application Architecture with HTML5 and Javascript

Posted by David Pitt on Sep 18, 2012 |

If you’re thinking that you can prevent end-user demand for mobility, just remember this: when PCs were first introduced, IT attempted to hold them at bay. How did that work out? Mobile device proliferation is forcing IT departments to change. They must now support mobile devices, which further extends to the need for the development of mobile-friendly applications. As users get more and more savvy, simply accessing existing applications via a mobile browser doesn't fly.

Delivering a rich user interface for a mobile application can be either done natively or by using HTML5 with JavaScript. Native applications can provide a rich user experience, however it can be time-consuming and expensive to build native applications for multiple device operating systems. HTML5 with JavaScript has opened the door for device-independent user interfaces. This means that user interfaces can be created using JavaScript component libraries that render interactive user interface widgets using HTML5 elements. HTML5 has many new features that support mobility and rich user interface development.

This article walks through the frameworks and structures used to implement a rich client side JavaScript / HTML5-based mobile application. The user interface and navigation elements are all browser-resident components, while the application servers only role is to provide JSON data access for the user interface. Since the intent is to provide a reference of framework and application structure, the example implements basic functionality.

Mobile Development Considerations

Many of the approaches taken for the development of desktop browser applications can be applied to mobile browser-based applications.However, mobile applications present some additional considerations and challenges that desktop browsers do not always require. It's important to take each of them in turn.

Consider screen resolution. Having much smaller dimensions require a different user interface design for optimal user interaction with the application. HTML5 coupled with a Mobile UI widget library such as jQuery Mobile provides a cross-device way to create mobile specific user interfaces with JavaScript. The screen shot below is of a jQuery Mobile HTML5 list view:

This mobile UI is rendered using the HTML5 role identifier and CSS. Here's a snippet of the HTML tags for the list view. Notice the data-role attribute. This role is used by jQuery Mobile to render a mobile device-friendly user interface component.

<div id="stockListContainer" data-role='content'>
           <ul data-role="listview" id='tcStockList' data-inset="true" data-filter="true"></ul>
</div>

Connectivity is another big consideration. While 3G networks and WIFI are prolific today, connectivity cannot always be assumed. Luckily, HTML5 has a caching feature that allows site resources to be "cached" locally and operated in a disconnected mode. Caching is enable by adding the following to the root-level HTML element as shown below:

<!DOCTYPE HTML>
        <html manifest="cache.manifest">
                  <body>
                  ...
                  </body>
        </html>

Manifest files are text files that define resources to be cached along with other directives that control resources that can by pass the cache or what to display when a resource is not available. Updates and notifications of the cache an also be controlled when the file changes or with a JavaScript API, An example cache manifest file is shown below.

CACHE MANIFEST
# 2012-27-18:v1

# Explicitly cached resources.
CACHE:
index.html
css/stylesheet.css
Images/logo.png
scripts/main.js

# White Listed Resources, requires connectivity, will bypass cache
NETWORK:
http://api.twitter.com


# offline.html will be displayed, if *.HTML are not available.
FALLBACK:
*.html /static.html

Additionally, HTML5 provides a mechanism for server side data to be cached locally for performance and disconnected operation. A 5 meg key/value local storage mechanism is available that can store strings, or stringified JSON object. Local storage can be accessed from JavaScript using the localStorage object. The example below shows how a Backbone.Collection of Stock objects can be stored and retrieved locally as a JSON string.

var d = JSON.stringify(data);
localStorage.setItem('STOCKS', d);

var d = localStorage.getItem('STOCKS');
data = JSON.parse(d);

Applications can also be built to gracefully notify the user of "non-connectivity" and intermediate remote data access requests will be queued in the key/value local storage standard in HTML5-enabled browsers. When connectivity is restored, locally stored objects will be uploaded to the server.

Bandwidth is another concern. Minimizing server requests and payload size can be supported using AJAX support, and relies on the server for only data access using Javas Script Object Notation (JSON). JSON is much more efficient and succinct than traditional integration protocols such as XML or SOAP-based protocols.

HTML5 Application Architecture

JavaScript was never intended to be a general purpose application development language. Its original intent was to enhance the user experience in the browser by allowing dynamic HTML to be rendered and changed without having to access a remote server. This provides a perceived and real performance improvement. Mobile devices don't have the processing horsepower or the bandwidth access that desktop/laptop resident browsers do, so rich user interfaces will minimize round-trips to servers by implementing as much as possible in JavaScript and HTML5 elements on the client.

This is a departure from current server side web applications (JSP/ASP/PHP) where dynamic HTML elements are rendered on the server. In this new topology, server side elements support authentication and data access requirements, and user interaction and most application logic will reside in the client browser. This can be seen in the graphic shown below:

Additionally, it being a weakly-typed dynamic language (that allows closures and the ability to treat code blocks as datatypes) provides a lot of programming flexibility. But, that can lead to unmaintainable code. Therefore, rigor must be applied to JavaScript-based applications. Below a list of some generalized mechanisms that need to be put into place:

  • Navigation
  • Remote Data Access
  • Authentication/Authorization
  • Decouple View from Application Model (MVC pattern)
  • Modularization/Packaging
  • Dependency Management
  • Logging/Tracing
  • Exception Handling

In the Java world, frameworks exist to help support a layered application architecture. Likewise, frameworks are available in the JavaScript world to also help support a layered application architecture. For maintainability and stability, the following JavaScript frameworks are used to help enforce a modular, layered, and object oriented JavaScript application architecture.

Backbone.js

Backbone allows an object oriented way to separate and apply the model view controller (MVC) pattern to JavaScript applications. HTML5 user interfaces are separated from controller and object model implementations. Additionally, a standard navigation mechanism between user interface features is provided.

Require.js

JavaScript file and module loader framework. This framework allows dependent Java scripts to be loaded and validated when "required" by a Java script module/function. This communicates dependency information to the developer asserts an error if the JavaScript module/library is not loaded.

_Underscore.js

Underscore.js is a library that provides utility methods that allow functional programming mechanisms to be applied to collections of objects. It also provides a HTML template-ing feature.

JQuery

Library used to access and manipulate HTML DOM elements.

jQuery Mobile

HTML5 User Interface component library. Provides suite of UI controls rendered in HTML5. Provides mechanisms for event handling, as well as look and feel.

khsSherpa

Java application server framework that allows remote Java objects to be accessed via HTTP requests. It provides token-based authentication support along with automatic marshaling of Java types to JSON objects. Optional: JSONP cross-domain support is enabled.

JavaScript Folder Structure

JavaScript does not provide a standard way to organize programming elements, which are just text-based .js files. Other languages have organizing mechanisms, such as packages in Java or Namespaces in C#. Trying to define all JavaScript functions and objects in one large file is cumbersome to maintain, especially when a large part of the application will be defined in JavaScript. Therefore, file system folders under a root folder can be defined to help partition JavaScript source into areas of responsibility.

Folders will contain MVC JavaScript elements of the application. An example folder structure for the example application is shown below. Assume these folders are located at the document root of a web server/application server.

Modularity Support

JavaScript does not have a built-in mechanism to partition source code elements. Other languages do have these mechanisms, again such as Java with its packages, and C# with Namespaces. However, applications can import or include directives to bring in dependent modules. This allows applications and frameworks to modularize code improving maintainability and reuse.

The Require.js framework provides a mechanism to efficiently break up and treat JavaScript files as modules and has the ability to define, import, and access dependent modules.

The Require framework uses the Asynchronous Module Definition (AMD) framework to accomplish defining and loading dependencies. The require/AMD function to load modules is shown here:

define([modules], factory function);

Each module is an individual JavaScript file that defines a JavaScript object. When invoked, the modules are loaded and an object instance is created and passed into the factory function for the developer to use. The example below shows a JavaScript module using the require define() function load utility dependency.

define(["util"], function (util) {
          return {
          date: function(){
                      var date = new Date();
          return util.format(date);
            }
      };
});

The Require framework also has optimization features in order to help minimize file loading and increase performance.

Backbone MVC

This framework allows the popular MVC design pattern to be implemented using JavaScript. Typical web applications implement this pattern with a general purpose language on the server side using dynamic HTML generation technology, such as JSP/ASP or some kind of HTML template engine. The framework provides components or abstractions for processing user input and applying application object models to the dynamic HTML mechanism. The Backbone framework provides a way to apply these mechanisms in JavaScript, instead of generating HTML tags on the server.

HTML Template

A Backbone view is simply a static HTML file with HTML5 roles applied. Backbone provides a template mechanism, so that model attributes can be applied to the HTML when the view is rendered by a view/controller. The example application defines a JQuery Mobile list view which is defined using HTML5 role attributes. The stock-list.html HTML5 view is shown below:

<div id="stockListContainer" data-role='content'>
          <ul data-role="listview" id='tcStockList' data-inset="true" data-filter="true"></ul>
</div>

Stock Items in the list view are defined in stock-list-item.html. It applies the Backbone template mechanism for stock model JSON object detail display. HTML with template elements are shown below:

<a href='#<%=ticker%>'>
          <h4><%= ticker %></h4>
          <p><%= name %></p>
          <p>Price: <%= price %></p>
</a>

Notice that the template expressions above are similar to JSP/ASP pages, where <%= %> are used to mark locations to be replaced with attribute values from a model JSON object. HTML templates are located in the template folder.

View/Controller

An HTML view is rendered by navigating to a Backbone controller implementation. Controllers bind JavaScript object(s) to an HTML view and tell the framework to render the view along with defining and handling events. In Backbone terminology, views are controllers, and creating a Backbone view is done by extending from the framework supplied Backone.View object.

The partial example for the stockListPage.js view controller is below, where first loads required JavaScript .js files using the require.js framework. Which invokes the define([modules,...], controller()) JavaScript function that returns a Backbone view controller function. Notice how this function extends a Backbone.View object. The nice thing about the Require framework's Define function is that it loads dependent modules required by the view controller implementation. Notice how a model and HTML template modules are also provided to the view/controller module object:

define([
                'jquery',
                'backbone',
                'underscore',
                'model/stockListCollection',
                'view/stockListView',
                'text!template/stock-list.html'],
                function($, Backbone, _, StockListCollection, StockListView, stockListTemplate) {var list = {};
                            return Backbone.View.extend({
                                      id : 'stock-list-page',


When a view/controller instance is created, the initialize: function is invoked and provides a way to define events and initialize the controller's model, which can be an individual object or collection of objects.

Notice in the example stockListPage.js view/controllers initialize function, a StockListCollection object is created. Collections are also a Backbone-supplied object that provide a way to manage "collections" of JavaScript object models for the view. When this controller is invoked, the initialize() method is executed. When an instance is created, it uses jQuery selectors to apply Backbone event handlers to buttons. The initialize function snippet is shown below:

var list = {};
return Backbone.View.extend({
          id : 'stock-list-page',
          initialize : function() {
                    this.list = new StockListCollection();

                    $("#about").on("click", function(e){
                              navigate(e);
                              e.preventDefault();
          e.stopPropagation();
                              return false;
                    });


                    $("#add").on("click", function(e){
                              navigate(e);
                              e.preventDefault();
          e.stopPropagation();

                              return false;
                    });


          },

Events are associated with a view/controller method by an associating form event and a jQuery selector with a method name. The example below shows event and handling methods for the example apps and add buttons on the stock list page. Notice the navigation commands, as they will be discussed in the next section.

events: {
 "click #about" : "about",
 "click #add" : "add",
},
about : function(e) {

window.stock.routers.workspaceRouter.navigate("#about",true);
          return false;
},

add : function(e) {
                    window.stock.routers.workspaceRouter.navigate("#add",true);
          return false;
},

A View/Controllers HTML template is rendered and displayed when the render() method is sent to an instance. The render function for the stockListPage.js is shown below. You can see how it compiles a template, and then displays an HTML template which is applied to the controllers el attribute. The this.el attribute is the controller's location in the DOM when HTML will be inserted. Next, notice how another view/controller is instantiated and rendered. The StockListView controller renders the JQueryMobile list view of of stocks.

          render : function(eventName) {
                    var compiled_template = _.template(stockListTemplate);
                    var $el = $(this.el);
                    $el.html(compiled_template());
                    this.listView = new StockListView({
                              el : $('ul', this.el),
                              collection : this.list
                    });
                    this.listView.render();
                    return this;
                    },
          });
});

Navigation

Navigating between controller views is another mechanism provided by Backbone. Backbone refers to this "routing," and as such, provides a Backbone.Router object that can be extended to define navigation routes. The example application router is shown below:

define(['jquery', 'backbone', 'jquerymobile' ], function($, Backbone) {
          var transition = $.mobile.defaultPageTransition;
          var WorkspaceRouter = Backbone.Router.extend({
                    // bookmarkMode : false,
                    id : 'workspaceRouter',
                    routes : {
                              "index" : "stockList",
                              "stockDetail" : "stockDetail"
                    },
                    initialize : function() {
                              $('.back').on('click', function(event) {
                                        window.history.back();
                                        return false;
                              });
                              this.firstPage = true;
                    },
                    defaultRoute: function() {
                              console.log('default route');
                              this.runScript("script","stockList");
          },
          stockDetail: function() {
                    require(['view/stockDetailView'], function (ThisView) {
                                        var page = new ThisView();
                                        $(page.el).attr({
                                                  'data-role' : 'page',
                                                  'data-add-back-btn' : "false"
                                        });

                                        page.render();

                                        $(page.el).prependTo($('body'));

                                        $.mobile.changePage($(page.el), {
                                                  transition : 'slide'
                                        });

                    });
          },

          stockList : function() {

                              require(['view/stockListPage'], function (ThisView) {
                                        var page = new ThisView();

                                        $(page.el).attr({
                                                  'data-role' : 'page',
                                                  'data-add-back-btn' : "false"
                                        });

                                        page.render();

                                        $(page.el).prependTo($('body'));

                                        $.mobile.changePage($(page.el), {
                                                  transition : 'flip'
                                        });
                     });
                    },

          });
          return new WorkspaceRouter();
});

The Require Define function is used to provide instances of jQuery, Backbone, and jQuery Mobile instances to the overridden router function/methods. When the Router instance is created, routes are initialized with an ID and function name to execute when the "route" is navigated to. In the example above, there are two routes: #index and #stockDetail. Notice the functions defined for these routes.

The router object can be invoked to navigate to a defined view/controller with the following expression:

<aRouter>.navigate("#index");

A routing function creates an instance of a Backbone.View and invokes the render function. The snippet below is from the example extended BackBone.Router function that renders the stock list jQuery Mobile list view. Notice that in the source below how the Require framework creates an instance of the view/stockListPage Backbone view controller, then uses JQuery to adorn page attributes and render it.

// Router navigation funtion
stockList : function() {
                    require(['view/stockListPage'], function (ThisView) {
                              var page = new ThisView();
                              $(page.el).attr({
                                        'data-role' : 'page',
                                        'data-add-back-btn' : "false"
                              });
                              page.render();

                              $(page.el).prependTo($('body'));

                              $.mobile.changePage($(page.el), {
                                        transition : 'flip'
                              });
          });
        },

Collection / Model

Backbone provides a collection object that manages lists of Backbone.Model objects. View/Controller objects have attributes that reference a list or single JavaScript object. A Backbone.Collection object for the StockListItem model objects displayed by the view/controller is shown below:

define(['jquery', 'backbone', 'underscore', 'model/stockItemModel'],
function($, Backbone, _, StockListItem) {
          return Backbone.Collection.extend({
                    model : StockListItem,
                    url : 'http://localhost:8080/khs-sherpa-jquery/sherpa?endpoint=StockService&action=quotes&callback=?',
                    initialize : function() {
                              $.mobile.showPageLoadingMsg();
                              console.log('findScripts url:' + this.url);
                              var data = this.localGet();
                              if (data == null) {
                                        this.loadStocks();
                              } else {
                                        console.log('local data present..');
                                        this.reset(data);
                              }
                    },
                    loadStocks : function() {
                              var self = this;
                              $.getJSON(this.url, {
                                        }).success(function(data, textStatus, xhr) {
                                                  console.log('script list get json success');
                                                  console.log(JSON.stringify(data.scripts));
                                                  self.reset(data);
                                                  self.localSave(data);
                                        }).error(function(data, textStatus, xhr) {
                                                  console.log('error');
                                                  console.log("data - " + JSON.stringify(data));
                                                  console.log("textStatus - " + textStatus);
                                                  console.log("xhr - " + JSON.stringify(xhr));
                                        }).complete(function() {
                                                  console.log('json request complete');
                                                  $.mobile.hidePageLoadingMsg();
                                        });
                    },
                    localSave : function(data) {
                              var d = JSON.stringify(data);
                              localStorage.setItem('STOCKS', d);
                    },
                    localGet : function() {
                              var d = localStorage.getItem('STOCKS');
                    data = JSON.parse(d);
                    return data;
          }
          });
});

When the collection object is initialized, (in the example application, this happens when a view/controller creates an instance) the specified URL attribute is invoked using the jQuery AJAX mechanism to invoke a server side JSONP endpoint. The endpoint returns JSON stock objects, which are automatically mapped to the collections stockListItem model. The Backbone.Model definition for the StockItemModel is shown below:

define(['jquery',
                         'backbone',
                         'underscore'],
                         function($, Backbone, _) {
                                        return Backbone.Model.extend({
                                                  initialize : function() {

                                                  }

                                        });
});

For readers familiar with strongly typed languages, JavaScript's ability to turn JSON-formatted strings into JavaScript model objects seems likes magic.

Backbone.Model objects have a number of methods to help save and to synchronize with a server. Likewise Backbone.Collection objects have methods for synchronizing and saving to a server as well as to perform functional programming type operations. You can check out these capabilities here.

Local Storage

Other methods added to the example StockListCollection extended Backbone.Collection provide the ability to save and restore objects from the HTML5 local storage mechanism. Defined in the localSave() and localGet() methods in the above collection. Once a collection of Stock objects are obtained from the server, the HTML5 application can operate without connectivity. This example utilizes the key/value local session storage mechanism. HTML5 also provides a local relational storage mechanism referred to as webSQL. However, work towards this spec has stalled, and it is not fully supported. So, relying upon its existence could be risky. The key/value session storage is well supported. Check out this for more information regarding the webSQL spec.

Application Bootstrap / Startup

The standard Index.html starts things off, when loaded style sheets are defined along with the following tag:

<script data-main="main.js src="libs/require/require.js"/>

This invokes the Main.js function that configures and loads supporting the JavaScript libraries. The Require framework provides a nice mechanism to key a JavaScript library to simple name and a base URL location path. Since the lib folder is off of the doc root, no base URL path is required. An example is shown in the next code snippet:

require.config({
                    paths : {
                              'backbone' : 'libs/AMDbackbone-0.5.3',
                              'underscore' : 'libs/underscore-1.2.2',
                              'text' : 'libs/require/text',
                              'jquery' : 'libs/jquery-1.7.2',
                              'jquerymobile' : 'libs/jquery.mobile-1.1.0-rc.2'
                    },
                    baseUrl : ''
          });

This startup function also uses the require function to configure jQuery Mobile properties and loads the App.js script with navigates to the #index route displaying the stock-list-item.html.

The App.js script is shown below, and it initializes the workspace router instance, starts backbone, and then navigates to the #index page. Source is shown below:

define(['backbone', 'router/workspaceRouter'], function(Backbone, WorkspaceRouter) {

          "use strict";

          $(function(){
                    window.tc = {
                              routers : {
                                        workspaceRouter : WorkspaceRouter
                              },
                              views : {},
                              models : {},
                              ticker: null
                    };

                    var started = Backbone.history.start({pushState:false, root:'/HTML5BackboneJQMRequireJS/'});
                    window.tc.routers.workspaceRouter.navigate("#index", {trigger:true});
          });
});

Server Side JSON Endpoints

An application server configured to use the khsSherpa JSON framework provides URLs to endpoints that provide create, read, update, and delete methods for Lists and individual Stock objects. The framework marshals HTTP request parameters to Java Endpoint method calls and serializes Java objects to and from JSON strings.

This example project is defined and intended to be deployed as a JEE WAR component. This WAR contains both static HTML/JavaScript that is initially delivered and made resident on the clients browser and the Sherpa JSON Java application server endpoints that drive the HTML5 interface.

Here's the definition of the Java server endpoint that serves up stock quote JSON objects in JSON format. Endpoints are invoked using a HTTP URL get.

The example application only requires an endpoint to retrieve a collection of Stock objects. However, more realistic applications would require authentication and more endpoints to support CRUD operations. This framework supports these requirements. For more feature descriptions, check the framework out on Github.

Conclusion

Even though this example is simplistic in functionality, the goal was to introduce an MVC application architecture for browser-resident applications. Eliminating the need for application servers needing to render dynamic HTML for application interfaces is a key feature of the application architecture presented in this paper. JavaScript is not a natural general purpose programming language, however the explosion of mobile devices and the large adoption of HTML5 and the resistance and non-support of browser plug-in technologies, is making JavaScript with HTML5 a viable way to deliver rich browser-based applications to mobile devices.

Complete source for the example application can be found on GitHub

About The Author

David Pitt is a Sr. Solutions Architect and a Managing Partner of Keyhole Software. With nearly 25 years of IT experience, David has spent that last 15 years helping corporate IT departments adopt object technology. Since 1999, he has been leading and mentoring development teams with software development utilizing Java (JEE) and .NET (C#) based technologies. He is an author of numerous technical articles, and a co-author of a popular IBM WebSphere book that documents some of his architecture design patterns.

 

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Tell us what you think

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Nice Article... by Kiran K

Hi David,

This article is interesting and informative... I just started learning mobile apps development using HTML5 and JS. could you please provide the code of the screen shot shown in the article...

The download on the GitHub just has "Loading..." in the index.html page...

Thanks,
Kiran

Re: Nice Article... by David Pitt

Thanks Kiran,

The example stock list application requires deployment to a Java app server that houses the JSON/Endpoints. These endpoints return Stock JSON objects. Give me a day and I'll reply to this thread with some code that will "mock" the server JSON calls, so the example application can be run without server side endpoints.

David

Re: Nice Article... by Brett Jones

I just checked in some changes to the GitHub project to address your issue. I added support for mock server responses using mockjax.js (github.com/appendto/jquery-mockjax). What this does is creates fixtures - mocked up server responses.

This enables your project to run without a back-end of any sort. It is faked with mockjax.

In the project, you can load a fixture-driven version of the project by loading /index.noserver.html/ instead of /index.html. That way you should not have to mess with setting up a J2EE App server - you can run it in apache if you like.

Hope this helps,
Brett

Don't think too complicatedly by j celebpoker

I hate old school template library (some like PHP web MVC engine, Java MVC engine O/R mapping etc,etc)
It's nightmare. You must think more simple solution.

<script src="json_data"></script> that's enough. everything.

Also I hate HTML5 in mobile. (In Desktop PC, or 9~10 inch tablet HTML5 that's nice. CSS3 is beautiful but HTML5 doesn't match in small screen mobile device) iOS/Android native api use to great. Why you select HTML5 BAD user experience?

Re: Don't think too complicatedly by David Pitt

Native applications can/do provide a really nice responsive user experience. However, organizations that have to create mobile applications for many different mobile devices may not have the budget or experience to develop natively A hybrid approach using Javascript/CSS/HTML(5) with a consistent application architecture provides a simple solution. Also, organizations used to developing using JSP/ASP/Template type technologies can leverage that experience.

Thanks,
David

Nice article by senthil kumar

Hi David

I appreciate that you have introduced html5 and javascript for developing a mobile application. For many years we have been developing thin client using html and javascript, but now it is interesting to know from your article that we can develop thick client using html5 and javascript for mobile application. Is it not too costly for the mobile application to have thick client because all the client side code will rest on the mobile and installable as mobile app?

Thanks
Senthil

Re: Don't think too complicatedly by Michal Zyka

Thank you so much for this article, it is exactly what I was looking for.
I recently started to develop mobile apps in an organization that mainly develops b2b solutions based on IBM WebSphere and DB2. I would like to stick to this idea of introducing consistent architecture to my JS/CSS/HTML5/JQuery mobile/Phonegap apps and study further on this topic. Are there any other, maybe more detailed articles/books/any other resources that you know of? Books about JQuery mobile that I read didn't mention architecture at all, so that's why I am asking. Thanks! :-)

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Email me replies to any of my messages in this thread

7 Discuss

Educational Content

General Feedback
Bugs
Advertising
Editorial
InfoQ.com and all content copyright © 2006-2014 C4Media Inc. InfoQ.com hosted at Contegix, the best ISP we've ever worked with.
Privacy policy
BT