Intro

Sundbybergs stad

Development & UX guidelines

Welcome! This documentation is intended for developers and designers looking to create HTML5-based services for the platform.

If this is your first time here, you should start by reading the chapter how to develop services.

If you think anything is misleading or missing, please get in touch with us at helloumbra@hellofuture.se. Happy developing!

This documentation matches the framework versions 2.1 and up.

How to develop services

The environment is made up of two parts. On one hand there is the native app for Android and iOS, and on the other hand there are services built with HTML5, which are loaded from within the native application. This combination makes the most of the strengths of native applications as well as those of the latest web technologies.

Native app

The native application is a smartphone app with support for Android 4.1+ and iOS 7+. It works as a container that communicates with a backend via an API. The backend and database keeps track of all the services, what categories they belong to, and developer devices.

Any single service consists of two parts: metadata and an URL. The metadata is the information the native app needs to present the service to the user. This metadata includes the name of the service, a description about it and its icon and the URL of the service. This URL links to the actual HTML5 service files.

Services

A service is a HTML5 application that is loaded from the native application on the smartphone. The service itself can be hosted on any server, use any technology, and be developed in any programming language. That's why there is no backend framework.

Frontend framework

There is, however, a frontend framework. This exists to maintain a consistent interface and a coherent user experience across many services. An added benefit is that it speeds up the development process. A fully functional mobile service can be produced with only HTML5 and JavaScript. The framework consists of most common design elements and ensures that these elements work as intended on the supported devices.

Sometimes it will become necessary to write custom CSS that is not included in the front end framework. When that is the case, make sure that the design is consistent with the rest of the framework and that it complies with any visual guidelines. Also make sure to thoroughly test custom CSS on the supported devices.

Getting started

If you haven't already you should familiarize yourself with the concept of what a service is. After you have done that you should:

  1. Become a developer
  2. Set up a development environment
  3. Develop the service
  4. Publish the service

I. Become a developer

As a registered developer, you can test and debug your own, not yet published, services through the app itself.

1. Download the app

Download the apps in their respective stores. Search for "Sundbybergs stad".

2. Find your unique device ID

iOS

Go to your iPhone's settings, scroll down to 'Sundbybergs stad'. There you'll find your device ID.

Android

Open the 'Sundbybergs stad' app. Press the settings icon in upper right corner. Your device ID is visible in the lower right corner.

3. Sign up as a developer

II. Test environment

There are many ways in which you can test services you are developing.

  • On a developer enabled device (recommended)
  • Webkit-based desktop browser
  • Mobile browser
  • Mobile simulator

Testing locally

You can test parts of your services in any Webkit browser (like Safari or Chrome). There is an iOS simulator inside Apple's IDE Xcode that can come in handy when testing locally. But keep in mind that UIWebView in the app functions a little bit different from the simulator. For instance iOS5 Safari uses a faster JavaScript engine than UIWebView, meaning that animations and JavaScript could appear slower in the actual app. UIWebView also ignores the HTML5 cache manifest, so that can not be used.

That said: The only way to make sure that something really works as intended is to test it on the device itself.

Differences between test environments
Desktop browser Mobile browser Sundbybergs stad
Viewport size
Touch gestures
SMS/Tel-links
Communication with the app
Camera
GPS

Quick guides to testing locally

Testing in the web browser

  1. Install Apache and PHP. If you do not already have it installed or are not sure how to do this there are software packages that includes a "turn-key" environment for Apache, MySQL and PHP. For Windows there is WAMP and for Mac there is MAMP
  2. Once this is working you can open services and test them in the browser. Make sure that you are using a webkit browser (preferably Chrome) and not Firefox or Internet Explorer

Testing service in native app

  1. Upload the service that is being developed on a public web server with PHP installed.
  2. Register as a developer to flag your device as "developer enabled"
  3. Inside the app: On the home screen, tap the button "+Se alla tjänster", scroll all the way to the bottom and press "Lägg till Ad Hoc-tjänst". If your device is not developer enabled this button will not be showing. If that is the case, make sure that you registered your device as a developer.
  4. Enter the name of the service as well as the URL to the location where you put the service.

Testing locally, but in the native app (removing the need to deploy changes to a server all the time)

  1. Install a proxy solution on your machine that lets you access localhost from your phone. There are different ways of doing this. CharlesProxy works on both Windows and Mac.
  2. Inside the app: On the home screen, tap the button "+Se alla tjänster", scroll all the way to the bottom and press "Lägg till Ad Hoc-tjänst". If your device is not developer enabled this button will not be showing. If that is the case, make sure that you registered your device as a developer.

III. Development

The services in Sundbybergs stad are created with HTML5, CSS and JavaScript, which is also the building blocks of the frontend framework.

HTML5

The framework is set up so that you can use all kinds of predefined classes in your HTML code to get the layout of your choice with the constraints of a smartphone. The framework handles the output for all the different platforms. Read more in the Framework reference.

CSS

If you are using the elements included in the frontend framework you will not need to write any custom CSS. If you need additional elements or need to customize the look and feel of your service, you can load your own CSS file or use internal styles. Read more about CSS.

JavaScript

The frontend framework uses JavaScript that is transformed to native functions in the smartphone app. For best performance you should use the methods in this documentation, but you can always use plain JavaScript or include any additional libraries you need to build your service. Read more about JavaScript in Sundbybergs stad.

Read more

You can find more details in the Technical overview, or see the full documentation in the Framework reference.

Contribute

If you find bugs or have suggestions for features that you think should be included in the framework, feel free to send an e-mail to helloumbra@hellofuture.se. Thank you!

IV. Publishing

Publishing services is handled by Sundbybergs stad. Contact Sundbybergs stad on e-mail appar@sundbyberg.se when you are ready to publish your service.

The following information should be included in the e-mail:

  • The name of your service
  • A description of the service
  • A URL to the service

User experience

The main user experience objective of Sundbybergs stad is making services that feel easy, intuitive and nice to use. Designing for mobile devices demands a different outlook than when designing for the desktop. The main difference is the constraints in terms of screen size, as well as uncertainties regarding connectivity speed. There is simply no way of knowing whether the user is sitting comfortably at home with a reliable WiFi-connection, or is on the go using a slower connection.

In order to offer guidance we have collected some guidelines that take these considerations into account. We hope they can help you create good services for Sundbybergs stad.

Do not overdecorate

  • Minimise usage of unnecessary graphical elements
  • Try to keep the interface as simple as possible
  • Let the content take centre stage
  • Polish visual details to convey the feeling of quality and trustworthiness

Design for regular people

  • Use a personable tone
  • Pick out the content that is relevant
  • Arrange content in a logical way
  • Explain what needs to be explained
  • Remove that which is not needed
  • Write informative headlines
  • Avoid advanced sentences
  • Use common words, and explain any jargon if you have to use it

Only show what is needed at the time

  • Don't show huge amounts of data on screen at the same time. It may be more helpful for the user if you present them with a navigation tree (see Navigation) or perhaps some kind of search functionality.
  • Forget the old adage that fewer clicks are always better (or taps as is the case when designing for mobile). As long as the taps are relevant and lead the user forward towards their goals it is ok to have more of them.

What is a service?

The ultimate objective of a service is to be easy to use, smart and helpful in the everyday lives of citizens.

Of course a service can be many different things. They can be stand alone, or part of a larger service network. They can be micro-services in that they perform a small but specific function. Opening hours is a good example of a micro-service that is simple, yet can be very useful in the context of the everyday life of the user.

How to identify good service opportunities

Looking at mobile usage there are three main things that a user will be looking to do:

  • I need something often
  • I need something right now
  • I am bored and need something to do

In the case of Sundbybergs stad, services should mainly make use of the first two behaviours. If you identify something that needed quickly and often, the potential for a useful service is definitely there.

Graphical guidelines

grafik

The main purpose of this documentation is to guide you to design a service that fits within Sundbybergs stad. It is not a manual and should not be seen as such. Depending on the context and situation there is always be room to design the service your own way. It is, however, recommended that you follow the basic UI elements unless there are strong reasons not to do so.

Using fonts, margins and colors that are not default will remove cohesion between different services that may harm the overall experience of the platform.

Layout

Rule #1: Keep it simple. Complex layouts on small screens is generally not a good idea. More than one column is not recommended. More information about the specific user interface elements can be found in the Framework reference.

There are a lot of standard elements in the framework that you can use. What you won't find in the framework is images, icons and other graphical elements that can create a personality and make your service shine. It's possible and encouraged that you add graphics of your own but be aware of platform inconstancies. Make sure to test colors, legibility and functionality across the many supported platforms and screen sizes.

Fonts

The native app and the services uses Helvetica Neue throughout. Always use Helvetica Neue unless exceptions are required. The font is embedded in the framework already and loading additional fonts can be to the detriment of performance.

Other fonts can be used if needed, but in that case it should mostly be for headers and other protruding graphical elements.

Colors

Accessibility

From an accessibility perspective, think about using colors that has sufficient contrast. Remember to use other means than color for providing interface clues.

Technical overview

This documentation is about the frontend framework and how to use it to develop services based on this framework. For a more general overview have a look at the Architecture.

Architecture

architecture

HTML5

Each html file can contain one or more pages. Each page is included in a section tag.

Service outline

The example below shows a basic outline of a service. There you can see the one script-tag that makes up the whole frontend framework. Custom CSS and JavaScript is loaded as usual. Each HTML5-file may contain one or more pages. Each page inside the service is a section. Place your first section after the opening body-tag.

<!DOCTYPE html> 
<html>
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, 
			initial-scale=1.0, user-scalable=no" />
		<meta name="format-detection" content="telephone=no" />
		<!-- title (not used) -->
		<title>The name of the service</title>
		<!-- frontend framework -->
		<script src="http://ff-sundbyberg.helloumbra.com/current/"></script>
		<!-- optional stylesheet for this service -->
		<link rel="stylesheet" href="assets/css/myservice.css" />
	</head>
	<body>
		<!-- each body must contain att least one section 
			that is shown as a page -->
		<section>
			<!-- each section should have a header -->
			<header>
				<!-- each header should have a <h1> that represents 
					the page title -->
				<h1>Page one</h1>
			</header>
			<!-- page elements¹ -->
		</section>
		<!-- optional additional sections (pages) -->
		<section>
			<!-- […] -->
		</section>
		<section>
			<!-- […] -->
		</section>
		<!-- all javascript should be included 
			after the last section element -->
		<!-- optional javascript for this service -->
		<script src="assets/js/myservice.js"></script>
	</body>
</html>

¹ Page elements included in the framework:

Pages

Each section can have a data-ff-role-attribute that configure the page in the following ways:

<section id="page-1" data-ff-role="home default swipe-in">
	<!-- […] -->
</section>
<section id="page-2" data-ff-role="swipe-in">
	<!-- […] -->
</section>
data-ff-role attributes for pages
default This attribute states which section will show first. If there is no default attribute anywhere the first section in the source will be shown.
home Mark a section as home. A home section will not have a back button in the navigation bar.
no-cache Do not cache if the section is loaded with xhr multiple times.
swipe-in Load section with swipe animation (if the link was loaded with data-ff-role="xhr")

Pages loaded with xhr

If a page is loaded with data-ff-role="xhr"> only two elements of that page will be loaded. All script-elements with data-ff-role="include" set, as well as all section elements.

To see an example of a resource loaded with xhr, look at this. Due to the fact that xhr-resources only load script and section-tags, the CSS definitions should be included in the previous page. Each DOM-attribute id should only be used once in your service.

<!DOCTYPE html> 
<html>
	<!-- if this is loaded with xhr, then … -->
	
	<!-- … the header is ignored -->
	<head><!-- head definitions --></head>
	<body>
		<!-- … every section is loaded -->
		<section><!-- section content --></section>
		<section><!-- section content --></section>

		<!-- … this script is loaded since it's using data-ff-role=include -->
		<script src="include-me.js" data-ff-role="include"></script>

		<!-- … this script isn't loaded because data-ff-role=include is missing -->
		<script src="ignore-me.js"></script>
	</body>
</html>

Read the Page reference to learn more about the page system in the frontend framework.

CSS

If you are using only elements found in the frontend framework you don't need to add custom css at all. If you however want a different look and feel for your service you need to write custom css.

You can include your css directly after the frameworks css. If you only have some lines of css code you can use an internal stylesheet using the style element to reduce HTTP requests.

You should not use classnames that start with ff- since these are reserved for the frontend framework

Each selector should contain at least one id or classname to avoid changing the general look and feel provided by the framework.

Use Data URI schema to reduce HTTP requests.

Remember that you write for WebKit only and therefore can use a lot of advanced css3 technologies. But there are limitations both in iOS and Android. If you want to use a special feature check first on http://caniuse.com/ if it works on Android 4.1+ and iOS 7+.

JavaScript

jQuery is included in the framework, and for touch events like swipe or tap there's also the jQuery plugin Hammer. There are two global variables used to call methods. The dollar sign, $, is used to call jQuery and FF is used to call the frontend framework. Only use variables and methods included in this documentation, as everything else is subject to change in future versions of the framework.

The following libraries are included in the framework:

Using your own JavaScript code

You can always include your own javascript file besides the framework.

<!-- put after the last section element, before the ending body tag -->
<!-- js frontend framework -->
<script src="http://ff-sundbyberg.helloumbra.com/current/"></script>
<!-- optional javascript for this service -->
<script src="assets/js/application.js"></script>

To initialize your own javascript code use FF.Service.ready(), which executes after the page and its content is loaded.

FF.Service.ready(function() {

	/* initialize service */

});

For full documentation see Framework reference.

Asynchronous methods and jQuery Deferred objects.

Some of the Layout independent methods are asynchronous, because they have to wait, for example for a http request to return or a user to take a picture with the camera.

These methods do not return the result of the request but a Promise provided by jQuery. This Promise object will provide several chainable methods. The function in done() is executed if the request was successful, the function in fail() is executed if an error occured. These functions holds arguments from the asynchronous call in the app, so the parameters can be different from different calls. This is the general structure:

requestSomethingAsynchronously(id)
	/* the promise object provides the methods .done() and .fail() */
	.done(function(responseStatus) { /* parameters depend on the asynchronous method */
		/* this is executed if th call was successful */
		FF.Modal.alert(responseStatus);
	})
	.fail(function(errorCode, errorMessage) { /* parameters depend on the asynchronous method */
		/* this is executed if there was an error */
		FF.Modal.alert('Error #' + errorCode);
	})
;

These are the asynchronous methods:

Framework reference

Here you'll find the full technical documentation.

Pages

Each HTML5-file may contain one or more pages. A page is created using a section-tag. Place your first section after the opening body-tag and fill it with the content of your service. If you need to use multiple pages you can simply add more section-tags. In the example below you can see an example of how this works.

<!DOCTYPE html> 
<html>
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, 
			initial-scale=1.0, user-scalable=no" />
		<meta name="format-detection" content="telephone=no" />
		<!-- title (not used) -->
		<title>The name of the service</title>
		<!-- frontend framework -->
		<script src="http://ff-sundbyberg.helloumbra.com/current/"></script>
		<!-- optional stylesheet for this service -->
		<link rel="stylesheet" href="assets/css/myservice.css" />
	</head>
	<body>
		<!-- each body must contain att least one section 
			that is shown as a page -->
		<section>
			<!-- each section should have a header -->
			<header>
				<!-- each header should have a <h1> that represents 
					the page title -->
				<h1>Page one</h1>
			</header>
			<!-- page elements¹ -->
		</section>
		<!-- optional additional sections (pages) -->
		<section>
			<!-- […] -->
		</section>
		<section>
			<!-- […] -->
		</section>
		<!-- all javascript should be included 
			after the last section element -->
		<!-- optional javascript for this service -->
		<script src="assets/js/myservice.js"></script>
	</body>
</html>

Page data-ff-roles:

HTML section-tags can have a data-ff-role-attribute that configures the page. All available data-ff-role-attributes are listed below:

data-ff-role attributes for section elements
default This attribute states which section will show first. If there is no default attribute anywhere the first section in the source will be shown.
home Mark a section as home. A home section will not have a back button in the navigation bar.
no-cache Do not cache if the section is loaded with xhr multiple times.
swipe-in Load section with swipe animation (if the link was loaded with data-ff-role="xhr")

Loding pages with xhr (ajax)

If a page is loaded with a link that has data-ff-role="xhr" (see example) only two elements of that page will be loaded. All script-elements with data-ff-role="include" set, as well as all section elements.

Below is an example of a resource loaded with xhr. Due to the fact that xhr-resources only load script and section-tags, the CSS definitions should be included in the previous page.

<!DOCTYPE html> 
<html>
	<!-- if this is loaded with xhr, then … -->
	
	<!-- … the header is ignored -->
	<head><!-- head definitions --></head>
	<body>
		<!-- … every section is loaded -->
		<section><!-- section content --></section>
		<section><!-- section content --></section>

		<!-- … this script is loaded since it's using data-ff-role=include -->
		<script src="include-me.js" data-ff-role="include"></script>

		<!-- … this script isn't loaded because data-ff-role=include is missing -->
		<script src="ignore-me.js"></script>
	</body>
</html>

Page elements included in the framework:

There are several useful page elements included in the front end framework. These can be used to quickly build out a service. It's either the actual html element, or you can set a class name to add specific behaviour and/or layout.

List of page elements (children of body>section)
article.ff-content (or *.ff-content) A text content wrapper
nav.ff-menu > ul Navigation
ul.ff-form Form elements
table Tables
ul.ff-swipe Swipe containers for or image galleries
dl.ff-accordion Accordions that can contain content to show/hide
ul.ff-tab-navigation +
*.ff-content[data-ff-role="tab-group"]
Tabs that can contain content

Page api

Static methods:

FF.Page.getActive

Returns the active page which is visible right now.

MethodFF.Page.getActive
Request params(none)
Responsea FF.Page object

FF.Page.getByNode

Returns a page by a given jQuery node

MethodFF.Page.getByNode
Request params(object) jquery node or (string) selector
Responsea FF.Page object or a FF.NoPage object

Snippet pages-getByNode.js is missing.

FF.Page.addSection

Adds a section as a page. It may be a section that already lies in the DOM or a section, html snippet that should be added to the DOM as well

MethodFF.Page.addSection
Request params(mixed) input, (bool) addToDom
Response(none)

Snippet pages-addsection.js is missing.

Object methods:

page.getNode

Returns the section-node, which contains the HTML-presentation of the page

Methodpage.getNode
Request params(none)
Response(object) jquery node

page.equals

Tests if a page equals another page

Methodpage.equals
Request params(FF.Page) other page
Response(bool) equality

page.exists

Tests if this page exists

Methodpage.exists
Request params(none)
Response(bool) page existance

page.findNodes

Find nodes in the page. A shortcut for page.getNode().find)()

Methodpage.findNodes
Request params(string) selector
ResponsejQuery nodes

page.getId

Returns the id of the page, taken from the id-attribute of the section-node

Methodpage.getId
Request params(none)
Response(string) page id

page.getTitle

Returns the title of the page, taken from the h1 element in the section header

Methodpage.getTitle
Request params(none)
Response(string) page title

page.hasDataRole

Checks, if the page has a certain data role

Methodpage.hasDataRole
Request params(string) data role
Response(bool) existance of this data role

page.triggerDomChange

Triggers an event that signals the framework that it should re-init the page, e.g. to add click handlers on links

Methodpage.triggerDomChange
Request params(none)
Response(none)
FF.Service.ready(function() {
	$.get(DATA_URL).done(function(content) {
		$('#main > header').after(content);
		FF.Page.getByNode('#main').triggerDomChange();
	});
});

page.on

Runs a callback if a page event is triggered

Methodpage.on
Request params(string) event, callback
Response(none)

Snippet pages-getByNode.js is missing.

Typography

As a default setting, content spans all the way out to the edges of the screen. If you want your content to make use of the globally used settings for margins you should wrap the content in <article class="ff-content">. Text, lists and normal content should use margins in most cases.

<article class="ff-content">
	<h2>Big headline</h2>
	<h3>Smaller headline</h3>
	
	<p>A normal paragraph with text. Flygande bäckasiner 
	söka hwila på mjuka tuvor. Go fröjd: squaw på bekväm lyxchintz.</p>
				
	<h4>Headline</h4>
	<ul>
		<li>List item</li>
		<li>List item</li>
		<li>List item</li>
	</ul>
</article>

Read more on Graphical guidelines

Tables

Complex tables generally do not work well on small screens. If you can avoid them you should. Often times it is better to simply list content vertically. But if you really need to use tables there are a few alternatives to choose from.

<table>
	<!-- optional header -->
	<caption>Standard table</caption>
	<!-- /optional header -->
	<thead>
		<tr>
			<th>Column 1</th>
			<th>Column 2</th>
			<th>Column 3</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th>Row 1</th>
			<td>47.23</td>
			<td>18.07</td>
		</tr>
		<tr>
			<th>Row 2</th>
			<td>118.20</td>
			<td>25.37</td>
		</tr>
		<tr>
			<th>Row 3</th>
			<td>199.21</td>
			<td>—</td>
		</tr>
	</tbody>
</table>

There are many classes you can use to style tables:

Class HTML element Description
ff-alternate table Zebra striping
ff-hlined table Shows horizontal lines between rows
ff-vlined table Shows vertical lines between columns
ff-horizontal-scroll table Make the table scrollable to the side
ff-center th, td Centers a cell
ff-right th, td Aligns text to the right
ff-minimum-cell th, td Minimizes cell width without line breaks
ff-post span Centered & aligns text to the left
ff-pre span Centered & aligns text to the right

Alternate rows

Table with alternating rows.

<table class="ff-alternate">
	<caption>Alternate table</caption>
	<thead>
		<tr>
			<th class="ff-minimum-cell">Column 1</th>
			<th>Column 2</th>
			<th>Column 3</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th>Row 1</th>
			<td class="ff-right">47.23</td>
			<td class="ff-right">18.07</td>
		</tr>
		<tr>
			<th>Row 1</th>
			<td class="ff-right">-127.23</td>
			<td class="ff-right">1800.07</td>
		</tr>
	</tbody>
</table>

Horizontal lines

Tables with horizontal lines uses the class .ff-hlined, additionally you can use .ff-vlined to show vertical lines, see example in the Centered table.

<table class="ff-hlined">
	<caption>Horizontally lined table</caption>
	<thead>
		<tr>
			<th>Column 1</th>
			<th>Column 2</th>
			<th>Column 3</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<th>Row 1</th>
			<td>47.23</td>
			<td>18.07</td>
		</tr>
		<tr>
			<th>Row 3</th>
			<td>199.21</td>
			<td>—</td>
		</tr>
	</tbody>
</table>

Centered table

.ff-pre and .ff-post is helpful if you want to center text after a certain line. This is useful for centering opening hours where the hours are to be centered. The text in ff-pre and ff-post can be no longer than 40% of the full width of the table.

<table class="ff-vlined">
	<tbody>
		<tr>
			<th>Monday to friday</th>
			<td class="ff-center">
				<span class="ff-pre">9</span> – <span class="ff-post">22, Fr: 23</span>
			</td>
		</tr>
		<tr>
			<th>Saturday</th>
			<td class="ff-center">
				<span class="ff-pre">10</span> – <span class="ff-post">17</span>
			</td>
		</tr>
		<tr>
			<th>Sunday</th>
			<td class="ff-center">
				<span class="ff-pre">New: 11</span> – <span class="ff-post">16</span>
			</td>
		</tr>
	</tbody>
</table>

Wide table

Table with horizontal scroll for wide tables.

<table class="ff-horizontal-scroll">
	<caption>Table with horizontal scroll</caption>
	<tbody>
		<tr>
			<th>Lorem ipsum dolor sit amet</th>
			<td>consectetur adipisicing elit</td>
			<td>sed do eiusmod tempor incididunt</td>
			<td>ut labore et dolore magna aliqua.</td>
			<td>Ut enim ad minim veniam, quis nostrud</td>
		</tr>
	</tbody>
</table>

Form elements

Forms uses the standard form tag with the class .ff-content, together with an ul-tag (unordered list) with the class .ff-form. Every li element can have specific classes that decides the appearence and behaviour. These are the classes you can use:

Class Description
ff-inlinelabel Input field with the label inside it
ff-input Input field with a label above it
ff-textarea Textarea with a label above it
ff-radio Radio button
ff-toggleonoff On/off checkbox
ff-checkbox Normal checkboxes
ff-imageupload Image upload interface

Buttons

Buttons are mainly used inside forms, but can be used in other contexts. Read more about buttons.

Text fields

A regular form looks like this. The submit button is outside of the ul-element. Text fields use HTML5 input types and attributes to control the behaviour. Be sure to set the right class on the li tag to get the right layout.

<form action="#" class="ff-content">
	<ul class="ff-form">
		<li class="ff-inlinelabel">
			<label for="elt-10">Label</label>
			<input id="elt-10" type="tel" 
					value="070-12345678" required="required" />
		</li>
		<li class="ff-inlinelabel">
			<label for="elt-11">Label</label>
			<input id="elt-11" type="text" pattern="[sS]venss?on"
					placeholder="Placeholder text" />
		</li>
		<li class="ff-inlinelabel">
			<label for="elt-12">Label</label>
			<input id="elt-12" type="email" required="required" />
		</li>
		<li class="ff-input">
			<label for="elt-2">Label above input:</label>
			<input id="elt-2" type="text" />
		</li>
		<li class="ff-textarea">
			<label for="elt-3">Textarea:</label>
			<textarea id="elt-3"></textarea>
		</li>
	</ul>
	<button type="submit">Submit</button>
</form>

Radio buttons

Use radios if the user have a few options. Wrap you radio buttons with <li class="ff-radio">, see example below.

<form action="#" class="ff-content" 
	data-ff-error-message="Error: You need to fill in all the required details!">
	<ul class="ff-form">
		<li class="ff-radio">

			<label for="elt-1">Either this:</label>
			<input  id="elt-1" type="radio" name="example" 
				checked="checked" value="" />

			<label for="elt-2">… or this:</label>
			<input  id="elt-2" type="radio" 
				name="example" value="" />

		</li>
	</ul>
	<button type="submit">Send</button>
</form>

Checkboxes

Wrap you checkboxes with <li class="ff-checkbox">, see example below. See also: Switches.

<ul class="ff-form">
 	<li class="ff-checkbox">

		<label for="elt-4">#1 (normal):</label>
		<input  id="elt-4" type="checkbox" name="number1" />

		<label for="elt-5">#2 (checked):</label>
		<input  id="elt-5" type="checkbox" checked="checked" 
			name="number2" />

	</li>
</ul>

Switches

Switches are similar to Checkboxes, and can be switched on or off.

<ul class="ff-form">

	<li class="ff-toggleonoff">
		<label for="elt-1">On/off button (checkbox):</label>
		<input  id="elt-1" type="checkbox" name="setting1" />
	</li>

	<li class="ff-toggleonoff">
		<label for="elt-2">On/off button (checkbox on):</label>
		<input  id="elt-2" type="checkbox" checked="checked" 
			name="setting2" />
	</li>

</ul>

Image upload

Image upload sends a quadratic image that the user can add by taking a photo or picking from library. The form element requires the attribute enctype="multipart/form-data" for an image upload to work, as well as an input type="file" (the same as regular html).

Container attributes for input type="file"
Attribute Values Description
data-ff-type ff-image-element
ff-image-element(multiple)
Type of widget
data-ff-size [integer] pixelsize Size of square image in pixels. If not set the full image will be used.
data-ff-quality [integer] JPEG quality sent, number should be 0-100 (60 is default).
data-ff-max-number [integer] Max number of images for multiple images

Be sure to place containers for each input field to trigger the correct behaviour, see example below.

<form action="serverscript" class="ff-content" 
	enctype="multipart/form-data" 
	data-ff-error-message="An error occured">
	<ul class="ff-form">
		<li class="ff-imageupload">
			<label for="elt-12">Image upload (one)</label>
			<div data-ff-type="ff-image-element" id="singleimage" 
				data-ff-size="1024">
				<input type="file" name="image" value="23" />
			</div>
		</li>
		<li class="ff-imageupload">
			<label for="elt-12">Image upload (max 3)</label>
			<div data-ff-type="ff-image-element(multiple)" 
				data-ff-max-number="3" id="multipleimages">
				<input type="file" name="images[]" />
			</div>
		</li>
	</ul>
	<button type="submit">Send</button>
</form>

Validation

The forms uses HTML5 validation. Read more.

If the user submits a form with errors an error message will be shown, and interrupts the submit. As a default there is no error message in this dialogue, but that can be defined by using data-ff-error-message.

<form action="#" class="ff-content" 
	data-ff-error-message="Error in form">
	<!-- form elements -->
</form>

Buttons

Buttons are mainly used inside forms, but can be used in other contexts. They can, for example, be useful if the user needs to perform an action like send something, or request some sort of information.

Full or half-sized buttons

Buttons that utilize the full width of the screen and half of the width of the screen are preferred. The color of the button is determined by the category the service belongs to.

Secondary button

If you want to add a button with less visual weight, with a dimmed out look, it is possible to do so by adding the class ff-dimmed to the button. This is a way of distinguishing two buttons from each other as primary and secondary actions.

A button can either use the element button or use the a-element with a class of ff-button added to it.

<button>Regular button</button>
<a href="#" class="ff-button">Link as regular button</a>

<button type="submit">Submit button</button>
<button type="submit" class="ff-dimmed">Dimmed button</button>

<!-- half width buttons -->

<a href="#" class="ff-half-button">Send</a>
<a href="#" class="ff-half-button ff-dimmed">Cancel</a>

<button class="ff-half-button">Half width button</button>

Share

Share content to social networks like Twitter, Facebook, mail etc.

Example

<!DOCTYPE html>
<html lang="sv">
	<body>
		<section id="ff-social" data-ff-role="swipe-in">
			<header>
		        <h1>FF.Social</h1>
		    </header>
			<div class="ff-content">
				<button class="ff-share" 
					data-ff-share-url="http://example.org" 
					data-ff-share-text="Share this interesting url">
					Share
				</button>
			</div>
		</section>
	</body>	
</html>

Images

Images are used in the same way as usual in html, but keep in mind that the service will be used across a large variety of screen sizes (although only portrait mode is used). Therefore it is often practical to use images that cover the whole width of the screen. How large the image should be is a balancing act between performance and image quality. Generally though, the image should be from 320 to 720 pixels wide.

To make the image cover the width of the screen use classname .ff-full-width.

<!-- image that uses the whole screen width -->
<img src="assets/image.jpg" class="ff-full-width" />

Widgets

Other useful widgets.

Tabs

Use tabs if you are building a service with only two or three views ("pages"), the tabs makes it easy to switch between the views.

Use to:

  • Switch between 2-3 (more than 3 does not work) sections on the same page.

Implementation:

<section>
	<header>
		<h1>Tabs</h1>
	</header>
	<ul class="ff-tab-navigation" data-ff-target="my-tabs">
		<li data-ff-role="default">Tab 1</li>
		<li>Tab 2</li>
		<li>Tab 3</li>
	</ul>
	<div data-ff-role="tab-group" id="my-tabs" class="ff-content">
		<section>
			<h1>Tab 1</h1>
			<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do.</p>
		</section>
		<section>
			<h1>Tab 2</h1>
			<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.</p>
		</section>
		<section>
			<h1>Tab 3</h1>
			<p>Duis aute irure dolor in reprehenderit in voluptate velit esse.</p>
		</section>
	</div>
</section>

Accordions

Accordions give a good overview over a lot of information when you don't want the user to switch to another page.

Use to:

  • Show/hide sections on a page

Implementation:

<section>
	<header>
		<h1>Show/hide sections</h1>
	</header>
	<dl class="ff-accordion">
		<dt>Section one</dt>
		<dd>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</dd>
		<dt>Section two</dt>
		<dd>Quis nostrud exercitation ullamco laboris nisi ut aliquip.</dd>
		<dt>Section three</dt>
		<dd>Eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat.</dd>
	</dl>
</section>

Swipe containers

Galleries is an easy way to show more than one image where you let the user swipe to navigate between the images.

Use to:

  • Show a gallery of images

Implementation:

<section>
	<header>
		<h1>Gallery</h1>
	</header>
	<ul class="ff-swipe">
		<li><img src="img/gallery1.jpg" alt="gallery1" /></li>
		<li><img src="img/gallery2.jpg" alt="gallery2" /></li>
		<li><img src="img/gallery3.jpg" alt="gallery3" /></li>
	</ul>
</section>

Layout independent methods

General methods of the framework you can call directly from your javascript to simplify things for your service.

FF.App

FF.App.openInBrowser

Open url in external browser.

MethodFF.App.openInBrowser
Request params(string) url
Response(bool)
true = was opened in external browser

Example

	$('#open-in-browser').submit(function(e) {
		e.preventDefault();
		FF.App.openInBrowser($('#url').val());
	});

FF.App.isAvailable

Shows if you are in inside the app view or not.

MethodFF.App.isAvailable
Request params(none)
Response(bool)
true = app/WebView
false = web browser

FF.App.getOS

Check what OS the user has.

MethodFF.App.getOS
Request params(none)
Response(string) "ios" or "android"

FF.App.isIOS

Check if on IOS.

MethodFF.App.isIOS
Request params(none)
Response(bool) true if on iOS device.

FF.App.isAndroid

Check what OS the user has.

MethodFF.App.isAndroid
Request params(none)
Response(bool) true if on Android device.

FF.App.getUid

Returns the device-id of smartphone. It is unique per app.

MethodFF.App.getUid
Request params(none)
Response(string) uid

FF.App.isDeveloperEnabled

Check if user has developer status, any debug output should check this first.

MethodFF.App.isDeveloperEnabled
Request params(none)
Response(bool) true if user has developer status

FF.Console

With this object it’s possible to log to the app console that is activated for all devices that are developer enabled. The methods are a subset of window.console. All methods behave the same but show different styles in the app console. Keep in mind that FF.Console only takes one string parameter.

FF.Console.info

Example

FF.Console.info('This is an information');

FF.Console.log

Example

FF.Console.log('This is a log message');

FF.Console.warn

Example

FF.Console.warn('This is a warning');

FF.Console.error

Example

FF.Console.error('This is an error');

Before deploying a final version of your service you should remove all FF.Console calls.

FF.Env

FF.Env.getUserInfo

Returns javascript object with info that the user has added in the app.

MethodFF.Env.getUserInfo
Request params(none)
Response(object) {(string) firstName:, (string) lastName:, (string) phone:, (string) email:}

Example

{
	"firstName": "Robin",
	"lastName": "Robinsson",
	"phone": "070 000 00 00",
	"email": "someone@example.org"
}

FF.Env.getServiceInfo

Returns information about the service.

MethodFF.Env.getServiceInfo
Request params(none)
Response(object) {(int) id:, (string) name:}

FF.Env.getClientInfo

Returns the client string which is a unique string per app, e.g. whitelabel. This can be useful if you develop a service that should run on several apps but have different features or layout on each app.

MethodFF.Env.getClientInfo
Request params(none)
Response(object) {(string) name:}

FF.GoogleAnalytics

The framework uses Google Analytics for statistics. You don’t need to include GA by yourself, since it is part of the framework. Pages will be tracked automatically, therefore you will need these methods only if you want to track special events.

FF.GoogleAnalytics.trackEvent

Tracks an GoogleAnalytics event.

MethodFF.GoogleAnalytics.trackEvent
Request params(action, opt_label, opt_value, opt_noninteraction)
All opt_ params are optional.
Response(none)

Have a look at Googles Event tracking guide for a description of the request parameters. But keep in mind that Googles first parameter category is provided automatically by this framework using the service name.

Example:

FF.GoogleAnalytics.trackEvent('Search', 'Search:' +  needle);

FF.GoogleAnalytics.trackPageView

This can be used if you use a custom solution to show pages. Example:

MethodFF.GoogleAnalytics.trackPageView
Request params(url)
Response(none)

Example:

FF.GoogleAnalytics.trackPageView(location.href);

FF.History

FF.History.push

Sends the user to another page.

MethodFF.History.push
Request params(FF.Page-object) page
Response(none)

FF.History.back

Sends the user to the previous page.

MethodFF.History.back
Request params(none)
Response(none)

FF.HttpRequest

If you just need the possibility to use the camera and send a POST request containing images it’s much easier to use the image upload form component instead of using the FF.HttpRequest and the FF.Media objects.

FF.HttpRequest.requestPost

Sends an asynchronous post request that can contain form data and images. Use this method if you want to send data that contains images since this is not possible with JavaScript XHR. For standard forms however you can use JavaScript XHR or even better jQuerys ajax methods.

This method is asynchronous.

MethodFF.HttpRequest.requestPost
Request paramseither (formNode)
or (url, formData, requestHeaders)
Async response(status, response, responseHeaders)
parameter legend
NameTypeDescription
formNode REQUEST A jQuery node containing the used form. It will translate form.action to url and the form elements to formData
url REQUEST The url the post request should be send to
formData REQUEST An array of objects in the form of jQuerys serializeArray. If name starts with @, e.g. @media, it is a reference to an internal media
requestHeaders REQUEST A javascript object with additional request headers. Headers could be any HTTP request header e.g. WWW-Authenticate or Cookie
status RESPONSE The HTTP status code
response RESPONSE The response as a string
responseHeaders RESPONSE A javascript object containing all response headers
url = 'http://example.org/form/request';
formData = [
	{
		name:  'firstname',
		value: 'Robin'
	},
	{
		name:  'lastname',
		value: 'Robinsson'
	},
	{
		name:  'myimage',
		value: imageId
	}
];
headers = {
	"X-Custom-Foo": "A custom header",
	"X-Custom-Bar": "Another custom header"
};
FF.HttpRequest.requestPost(url, formData, headers)
	.done(function(status, response, responseHeaders) {
		FF.Modal.alert(status);
	})
	.fail(function(code, message) {
		FF.Modal.alert('Error ' + code);
	})
;

FF.Media

If you just need the possibility to use the camera and send a POST request containing images it’s much easier to use the image upload form component instead of using the FF.HttpRequest and the FF.Media objects.

FF.Media.isAvailable

checkes if the device provides media access in some way. In most cases it will be a camera but it could be an image library to choose from as well.

MethodFF.Media.isAvailable
Request params(none)
Response(bool) camera or image library is available

FF.Media.requestImage

This is the main method of FF.Media. It will open the camera and allow the user to take a photo, to choose an image from the library, or to abort the camera usage. If a picture was taken the app creates three different sizes of the image, called thumb, preview and image. The first two can be accessed from and used in the framework, the latter one contains a full size image that can be sent away to a server with the method FF.HttpRequest.requestPost. If the user cancels taking a picture or choosing an image from the library the three response arguments are set to null.

This method is asynchronous.

MethodFF.Media.requestImage
Request params (object) {
    (int) imageQuality (between 0 and 100, optional, default is 60),
    (int) imageSize (max image size in pixels, optional),
    (int) previewSize (max preview size in pixels, optional),
    (int) thumbSize (max thumb size in pixels, optional)
}
Async response(id, thumbUrl, previewUrl).
id is a unique reference to the media stored in the app
thumbUrl and previewUrl contain urls to the image sources
All values are null on user abort

Example

var config = {
	imageQuality: 80,
	imageSize: 2048
};

FF.Media.requestImage(config)
	.done(function(id, thumbUrl, previewUrl) {
		$('<img>')
			.attr('src', thumbUrl)
			.addClass('thumb')
			.appendTo('#image-container')
		;
	})
	.fail(function(code, message) {
		$('<p>')
			.text('image could not be loaded')
			.addClass('error')
			.appendTo('#image-container')
		;
	})
;

FF.Media.getMetaData

Gets data for a certain image id. Have a look at the method above for explanation of thumbUrl and previewUrl.

MethodFF.Media.getMetaData
Request params(int) the media id given by FF.Media.requestImage
Response(object) {(string) thumbUrl:, (string) previewUrl:}
or null if no media exists with the given id.

FF.Media.deleteCachedImage

This method allowes you to inform the app that it can delete an image ressource you do not longer need.

MethodFF.Media.deleteCachedImage
Request params(int) the media id given by FF.Media.requestImage
Response(none)

FF.Position

FF.Position.isAvailable

Tests if it's possible to get the users location via gps or other means.

MethodFF.Position.isAvailable
Request params(n/a)
Response(bool)

If this method returns false it means that the device has no gps (or other technique for getting the geolocation), that the user has deactivated the gps in general, or that the user forbid the app to use gps.

FF.Position.requestPosition

An asynchronous request that returns a javascript object with information about the users location (latitude/longitude). If the reverseData parameter is set to true the device will also try to fetch readable information about the location such as city and country. Use this only if you need the data, since it will increase the execution time because of the app requesting additional data per http.

This method is asynchronous.

MethodFF.Position.requestPosition
Request params(bool) reverseData
Async response(object) {(float) lat:, (float) lng:, optional (object) reverseData:}
FF.Position.requestPosition(true)
	.done(function(lat, lng, reverseData) {
		new FF.Modal.alert('Ah, so you are in ' + reverseData.country + '?');
	})
	.fail(function(code, message) {
		new FF.Modal.ErrorPopup({
			content: 'Error: ' + message,
			buttons: {'true': 'OK'}
		}).open();
	});

FF.Storage

It's possible to add values to an local cache, or storage. For example the users input if they suddenly go offline.

FF.Storage.set

Saves information in storage.

MethodFF.Storage.set
Request params(string) key:, (mixed) value
Response(bool)
true = successful

FF.Storage.isset

Checks if the offline storage has information for that key.

MethodFF.Storage.isset
Request params(string) key
Response(bool)
true = is saved

FF.Storage.get

Fetch the information of a specific key in the storage.

MethodFF.Storage.get
Request params(string) key
Response(mixed)

FF.Storage.unset

Deletes all informations associated with a key in offline storage.

MethodFF.Storage.unset
Request params(string) key
Response(bool)
returns true if successful

FF.Storage.drop

Deletes the the storage for the current service (all keys).

MethodFF.Storage.drop
Request params(none)
Response(bool)
returns true if successful

FF.Version

FF.Version.getAppVersion

Version of native app.

MethodFF.Version.getAppVersion
Request params(n/a)
Response(string)

FF.Version.getFrameworkVersion

Version of the frontend framework.

MethodFF.Version.getFrameworkVersion
Request params(n/a)
Response(string)

FF.Version.compare

Method to compare versions, either framework or app version. Two strings are compared and tested with the help of the comparison operator.

MethodFF.Version.compare
Request params(string), (string), comparison operator (see example and table below)
Response(bool)
if (FF.Version.compare(FF.Version.getFrameworkVersion(), '2', 
	FF.Version.HIGHER_OR_EQUAL)) {
	// code for Framework version 2.0 or higher
}

if (FF.Version.compare(FF.Version.getAppVersion(), '2.0.1', 
	FF.Version.LOWER)) {
	// code for App version lower than 2.0.1
}

The comparison operators are constants of the object FF.Version.

Comparison operator Usage Description
LOWER FF.Version.LOWER First is lower than second argument
LOWER_OR_EQUAL FF.Version.LOWER_OR_EQUAL First is lower than or equal to second argument
EQUAL FF.Version.EQUAL First is equal to second argument
NOT_EQUAL FF.Version.NOT_EQUAL First is not equal to second argument
HIGHER FF.Version.HIGHER First is higher than second argument
HIGHER_OR_EQUAL FF.Version.HIGHER_OR_EQUAL First is higher than or equal to second argument

FF.Viewport

Returns information about the viewport of the users mobile device.

FF.Viewport.getWidth

MethodFF.Viewport.getWidth
Request params(n/a)
Response(int) width in pixels

FF.Viewport.getHeight

MethodFF.Viewport.getHeight
Request params(n/a)
Response(int) height in pixels

FF.Viewport.getDevicePixelRatio

MethodFF.Viewport.getDevicePixelRatio
Request params(n/a)
Response(float) pixel ratio