Mobile Device APIs Style Guide
From MemberWiki
Change log
2008-09-26
Various changes to reflect discussion at the 2008-09-25 phone call.
- Beefed up the introduction to attempt to be clear about what this document is trying to do and what it isn't trying to do. Emphasize that the document was created at the request of the OMTP to reflect the "Ajax/JavaScript perspective". Indicate that we recognize that there are other perspectives that OMTP and W3C need to take into account, also.
- In the section on Requesting and Checking Permissions, removed our previous recommendations for
canUse()
andapiPermissionsCheck()
and instead this section now simply attempts to describe the requirement for browser-based applications to be able to request the use of device APIs - Added some editorial comments to the Directness section to reflect discussion during the 2008-09-25 phone call
- Added a recommendation that OMTP define its APIs on its own global object (e.g.,
window.bondi
) in order to prevent conflict from future overlapping specs from the W3C - Add new section about error handling with sync APIs, where we identify some alternatives, but then say there isn't one preferred way.
Introduction
This wiki page is a work-in-progress at OpenAjax Alliance to provide a set of guidelines to the industry about how to design good JavaScript APIs for mobile device services. OpenAjax Alliance has developed this wiki page in response to requests from some of the leadership at OMTP, which requested guidelines from the Ajax community on how to design friendly and useful APIs. The recommendations reflect our best understanding of requirements around Mobile Device APIs found in the BONDI documents, and also informed by earlier requirements work down at OpenAjax Alliance (/member/wiki/Mobile_Device_APIs_Requirements).
We are hoping that the OMTP BONDI initiative and activities at W3C around mobile device APIs will review these suggestions and factor them into their upcoming APIs that are under development.
The Ajax perspective
This wiki page represents the point of view of the Ajax (JavaScript) developer. This document attempts to provide recommendations that (we believe) would result in APIs that will be both easy to understand and easy to use for web developers with experience developing applications that use Ajax techniques and/or use popular Ajax libraries as they develop Web pages. The recommendations have been informed by what we have seen as common API patterns that have proven to be successful within the Ajax community. The Ajax ecosystem consists of hundreds of Ajax toolkits which battle for popularity with each other, thereby providing a strong element of natural selection. The document assumes that API patterns found in popular Ajax toolkits are likely to reflect good design (otherwise, developers wouldn't use those toolkits).
This document focuses on JavaScript developers only. There is no consideration for programmers using other languages. We recognize that other organizations, such as W3C, have to take into account other programming languages and might find that our recommendations are deficient because they do not map well to other languages. In particular, we do not attempt define APIs in terms of an IDL because this document (and OpenAjax Alliance in general) is focused exclusively on JavaScript developers.
This document presumes a blank slate and is not constrained by prior work at W3C. The recommendations in this document describe what we feel are APIs in an ideal world, ignoring existing precedent, such as the W3C DOM, W3C DOM Events, W3C Selectors API, and W3C DCCI. We recognize that other organizations, such as W3C, have to take into account existing precedent.
We do, however, give strong attention to recent work at W3C on the geolocation specification because in general these APIs are in strong alignment with the design patterns found in the Ajax world today.
Overview of subsequent sections
The sections below attempt to represent trends within the Ajax toolkit community that reflect best practices among the leading Ajax toolkits for how they tend to package their APIs.
The sections below also provide evaluative statements about how well the latest public draft of the W3C geolocation APIs (http://dev.w3.org/geo/api/spec-source.html) aligns with the proposed guidelines. At the end of this document are some forward-looking ideas about whether the W3C geolocation APIs could be used as a design model for other device APIs, such as contact list and interacting with the local email databases.
Specific guidelines
Simplicity
- The most popular Ajax toolkits have worked hard to:
- Minimize keystrokes (e.g., Prototype and jQuery use the "$" object for commonly used APIs)
- Use short but expressive API names
- Modularize their features in a elegant, remixable manner
- Prototype example (note:
$
is a shortcut fordocument.getElementById()
):
var d = $('myDiv'); d.hide(); ... d.show();
- jQuery example (note: for
$(x)
, thex
is a CSS selector and$(...)
returns a nodelist):
$('.slide2').slidePanel({target:'.target2'});
- Evaluating geolocation against these guidelines
- geolocation is about as simple and direct as it can be, given that it is an async API
navigator.geolocation.getCurrentPosition(callbackFunc1); var watchId = navigator.geolocation.watchPosition(callbackFunc2);
Directness
- Design APIs so that the 80% case is simple and direct
- Properties should just be available via JavaScript dot notation (i.e., don't require getter functions)
- For example,
HTMLImageElement.width
is more convenient thanHTMLImageElement.getAttribute('width')
- For example,
- Avoid the generalization trap
- Dojo 0.4 tried to define a generalized approach to client-server communication
dojo.io.bind(...)
- In Dojo 0.4, the above API covered multiple transport layers (queues of iframes, XMLHTTP, mod_pubsub, LivePage, etc.)
- Dojo 1.0 tossed out the generalized approach and offered direct access to XHR functionality
dojo.xhrGet(...)
- One particular generalization trap to try to avoid with Device APIs: invoke through an API handler
- Avoid this:
apihandler=loadAPI(...); apihandler.getCurrentPosition(...);
- Long-term, browsers will support device APIs natively
- Don't complicate the API for the long-term majority of developers in order to serve the minority case where a plugin needs to be loaded or initialized in order for an API to work
- (Note: there has been discussion [1] that an indirect approach using handlers provides for better security by preventing hijacking of APIs, but others have disputed the claim that an indirect approach is necessary or improves security. Also, apparently the W3C Widgets spec is looking at indirect approaches, but as stated earlier, this document provides guidelines about Ajax-friendly API approaches without taking into account precedent or current work at other organizations.)
- Avoid this:
- Dojo 0.4 tried to define a generalized approach to client-server communication
- Reconciling directness with multiple APIs for same generic feature
- The direct approach does not allow multiple APIs to be available for the same feature because the direct approach puts the APIs at a standard global location, such as navigator.geolocation, and in some scenarios allowing multiple implementations has advantages.
- As the industry adds Mobile Device API support to their platforms, it is certain that there will be multiple specs floating around for the same feature area. For example, two different platforms might offer contact list APIs in advance of the creation of a standard. Also, multiple standards organizations (e.g., OMTP and W3C) might be working on overlapping specifications as they sort out how to unify their efforts.
- Recommendation: We recommend that the BONDI effort define its APIs on a
[window.]bondi.
or[window.]omtp.
global object to prevent collision with future W3C work in similar areas.
- Evaluating geolocation against these guidelines
- geolocation allows direct access to its APIs
var lat = navigator.geolocation.lastPosition.latitude; navigator.geolocation.getCurrentPosition(callbackFunc1); // async API var watchId = navigator.geolocation.watchPosition(callbackFunc2); // listener API
Consistency
- Strive for consistency across APIs within the same feature area
- Strive for consistency across similar APIs found in other feature areas
- If an API design pattern already exists in a related feature area, you probably want to reuse that design pattern
- Evaluating geolocation against these guidelines
- geolocation has good consistency within itself
- geolocation is a pioneering device API - no existing API precedents to follow
Navigator object
- The "navigator" object is an appropriate place for device APIs
- Justification: Here is one definition for the "navigator" object: "The Navigator object of JavaScript returns useful information about the visitor's browser and system."
- Evaluating geolocation against these guidelines
- geolocation puts its APIs on the "navigator" object
navigator.geolocation.getCurrentPosition(callbackFunc);
Recognize the widespread usage of Ajax libraries
- Most desktop Ajax developers today use an Ajax/JavaScript library
- Even simple XHR is usually accessed via an Ajax library
- For XHR, under the covers, for older versions of IE, Ajax libraries initialize an ActiveX control
- Even simple XHR is usually accessed via an Ajax library
- Most desktop browser plugin users include a standard snippet of JavaScript to initialize the plugin
- Nearly all Flash users embed the same N lines of JavaScript at the top of every HTML page that includes a Flash movie
- Same was true with Adobe SVG Viewer users (back in the day)
- Assumption about the mobile world in the future
- Most mobile Ajax developers will also use an Ajax library
- Most mobile developers that use plugins will find themselves using a standard snippet of JavaScript
- Assumption: in scenarios where device APIs are provided through browser plugins
- The loading and initialization logic for these browser plugins will be handled through a standard snippet of JavaScript
- This standard snippet is likely to be available within popular Ajax libraries
- Implications
- Applications developers will probably only need to worry about two things
- Finding the appropriate Ajax libraries that he needs for his application
- Implement the logic for his application
- Most application developers won't have to worry about the details around dynamic loading of plugins
- Dynamic loading of plugins should be orthogonal to using the device APIs
- Implementers of mobile device APIs should make sure that any JavaScript snippets that are needed by their implementation are discoverable on the Web. (The motivation behind this is that, unlike the desktop, mobile developers often cannot rely on view source and copy/paste of the HTML that is delivered to the mobile phone.)
- Applications developers will probably only need to worry about two things
- Evaluating geolocation against these guidelines
- geolocation is designed to work both as a native feature within browsers or as an add-on feature available from a Google Gears plugin (both of which are good)
- In either case, there is no need to load and/or initialize geolocation before it can be used (which is good)
Async APIs vs Sync APIs vs Both
- For simplicity reasons, it is generally better to only offer one way to accomplish a given function
- Therefore, avoid offering two versions of an API (i.e., async and sync)
- Use an async API when a given API might sometimes not return immediately
- Otherwise, use a Sync API
- Sync APIs make application development easier than async APIs
- Evaluating geolocation against these guidelines
- geolocation uses Async APIs for its key features, which is justifiable because in some circumstances there might be a delay before the requested information is available
navigator.geolocation.getCurrentPosition(callbackFunc1); var watchId = navigator.geolocation.watchPosition(callbackFunc2);
Error Handling with Sync APIs
- We have had a specific request from people at OMTP about whether we can make a specific recommendation for how APIs should handle error situations with synchronous APIs
- Here are 3 common patterns for error handling:
- Sync function returns an error code within the return value
- Sync function throws an exception
- Sync function invokes a callback (which allows for commonality with how async APIs must work by necessity)
- We do not have a specific recommendation in this area because we do not observe a common approach within the Ajax community. Which approach to use is a matter of taste and opinion
Async Callbacks for Success and Failure
- The two most common scenarios for an async request are normal success and otherwise (which usually indicates an error)
- Two reasonable approaches to identifying callbacks
- Identify two separate callbacks for success and failure
- Allows a simpler successCallback function because it doesn't have to analyze the return code
- Having a separate callback for failure emphasizes to the developer that he should implement error handling (e.g., the possibility that permission will be denied)
- Have a single callback and pass a parameter that indicates success or failure (this is what XHR does)
- Identify two separate callbacks for success and failure
- Evaluating geolocation against these guidelines
- geolocation has successCallback as the first parameter and has an optional second parameter for an errorCallback. This is a reasonable approach that is no worse than other alternatives.
void getCurrentPosition(in PositionCallback successCallback [, in PositionErrorCallback errorCallback [, in PositionOptions options]]);
API Availability
- Object existence check: A commonly used pattern within the Ajax community is to simply check on the presence of the JavaScript object which holds the API. For example, to see if the current browser supports the XMLHttpRequest APIs exist, Ajax toolkits use the following line:
if (typeof XMLHttpRequest != "undefined") {...}
- Observation: we are not recommending use of DCCI to determine the availability of a given API. The "JavaScript way" is to do a simple object existence check.
- Evaluating geolocation against these guidelines
- geolocation assumes that applications will check for geolocation availability using an object existence check
if (typeof navigator.geolocation != "undefined") {...}
Version Checking
- Today's state of the art
- On desktop computers, Ajax libraries do browser-specific messy analysis to determine what version of a particular API is available on a given browser.
- That's workable on desktop computers because there are only two major desktop platform and only five major browsers.
- But for mobile browsers, there are thousands of distinct phone models with many differences among the browser engines
- Recommendation: All APIs should include the following properties (or something similar) on the root JavaScript object for a given API:
- version - Identifies the version number for the specification which the user agent has implemented.
- compliance
- Indicates whether the user agent claims to implement the API in its entirety
- Possible values: "full" and "partial".
- Strongly recommend that user agents implement full support.
- If partial support, then application has to use messy techniques for determining which subset is supported (e.g., analyze the root JavaScript object for the given API or maintain a table of what features are supported by which platforms)
- Evaluating geolocation against these guidelines
- geolocation does not provide versioning properties
Requesting and Checking Permissions
- Background
- Workflows - 3 primary workflows to address
- Web browsing, where a Web page might want to use device APIs (e.g., a mapping web site that wants to show the user's current location). Note that this is zero-install option.
- Installed widgets. We assume that the user goes through an install process, which allows prompting about which device APIs to allow the widget to use.
- Device-resident applications. (Once again, there is an installer.)
- Scenarios
- An API might be available, but a given application might not have permission to use it
- Some applications will want to include JavaScript to check to see if they have permission to use a particular API, and invoke different logic depending on the result of the check
- Permissions might be determined at runtime, particularly when the user will be prompted to allow a particular application to access an API
- Some applications will use multiple device APIs. If the user is going to be prompted to approve the application's ability to use device APIs, often we will want to prompt the user to approve multiple APIs in response to a single prompt.
- Security problems with runtime permission checking
- If a web page can either check to see if it has permissions to access device APIS, this might allow malicious web site to do systematic attempts to look for mobile environment vulnerabilities. There are some people who feel that a "canUse()" API that allows a web page to check for the rights to use a particular API would allow for easier attacks than requiring the web page to actually try to use a particular API, but it is possible to design a "canUse()" API to have the same security features as the APIs for actually using the particular service.
- If a web page can dynamically request access to an API, this might lead to a user prompt "Ok to allow foo.com to access your contact list". Experience has shown that a large percentage of users will say OK to such prompts.
- Workflows - 3 primary workflows to address
- Recommendation: A previous version of the document proposed
canUse()<code> and <code>apiPermissionsCheck()
methods, but because of security concerns that have been expressed, we changed this section to simply list the requirements and some discussion.
JavaScript Associative Arrays for Extensibility and Future Growth
- Ajax libraries commonly use JavaScript associative arrays within APIs to allow for addition of new features in the future without impacting the parameter lists within existing APIs
- Recommendation: It is often a good idea to have one of the parameters to a function be an associative array which will allow the addition of new properties on that associative array in the future
- Evaluating geolocation against these guidelines
- geolocation's APIs include an optional associative array as the last parameter on its APIs
interface Geolocation { ... void getCurrentPosition(in PositionCallback successCallback [, in PositionErrorCallback errorCallback [, in PositionOptions options]]); ... int watchPosition(in PositionCallback successCallback [, in PositionErrorCallback errorCallback [, in PositionOptions options]]); ... } interface PositionCallback { void handleEvent(in Position position); // position is an associative array };
Namespace "prefixes" for implementation-specific extensions
- To prevent collisions between implementation extensions and future versions of API specs
- Non-prefixed properties should be reserved for future versions of the official spec
- Implementation specific extensions should be prefixed
- Some approaches to namespacing used today in industry
- XML accomplishes namespace prefixes via XML Namespaces (e.g.,
<myNS:myCustomElement/>
). However, colons aren't allowed in JavaScript identifiers. - CSS accomplishes namespace prefixes via syntax like this:
-webkit-appearance:checkbox
. However, hyphens aren't allowed in JavaScript identifiers. - Ajax libraries have adopted the convention of localizing their extensions to the browser runtime on a single JavaScript object. For example, most of dojo is located at
window.dojo
and most of jQuery is atwindow.jQuery
.
- XML accomplishes namespace prefixes via XML Namespaces (e.g.,
- Recommendation: Implementation-specific extensions should use a CSS-like approach to namespacing, but use "$" instead of "-". Therefore,
$AcmeCorp$somepropname
, assuming that "AcmeCorp" defined the extension. - Evaluating geolocation against these guidelines
- geolocation doesn't say anything about namespace prefixes
W3C Geolocation APIs as a model for other device APIs
Are the W3C geolocation APIs a good model for other device APIs? The answer is yes. Here is a sketch for how other device APIs might look following the pattern established by the geolocation APIs. (We used /member/wiki/OpenAjax_Device_APIs_Modules to help build this analysis.)
Note that the parameters to the APIs would align with what the W3C has done with geolocation: successCallback, errorCallback, and options.
Phone call APIs
- navigator.phone.call() - calls a given number
- navigator.phone.answer() - answers the current call
- navigator.phone.hangup() - terminates the current call
- navigator.phone.{inbox,outbox,missed,all} - list management APIs (e.g., delete, get details)
- navigator.phone.{watchIncoming(),watchOutgoing(),watchMissed()}
SMS and MMS
- navigator.{sms,mms}.send() - sends an SMS or MMS,
- navigator.{sms,mms}.{inbox,outbox,drafts} - list management APIs (e.g., delete, get details)
- navigator.{sms,mms}.{watchIncoming(),watchOutgoing()}
- navigator.email.send() - sends an email
- navigator.email.{inbox,outbox,drafts,trash} - list management APIs (e.g., delete, get details)
- navigator.email.{watchIncoming(),watchOutgoing()} (there will be a number of other email APIs, but I am only providing a sketch)
AddressBook
- navigator.contacts: list management APIs (e.g., add, delete, get details)
Calendar
- navigator.calendar.{inbox,outbox,drafts,trash} - list management APIs (e.g., add, delete, get details)
- navigator.calendar.{addReminder()}
Tasks
- navigator.tasks: list management APIs (e.g., add, delete, get details)
- navigator.tasks.{addReminder()}
Power
- navigator.power.{powerSource,chargeLevel,criticalBattery,charging,...}
- navigator.power.watchCriticalBattery(),watchChargeCompleted(),watchPowerSourceChanged(),,...}
Network
- navigator.network.{current,cellular,wifi,bluetooth}.{signalStrength,networkType,criticalBattery,roaming,,...}
- navigator.network.watchOutOfCoverage(),,...}
Time
(Not sure what exists already in the browser, but if we need to invent something, here goes)
- navigator.clock.{date,time,timezone}
- navigator.clock.watchTimeZoneChange(),,...}
Media
- navigator.resources.{images,audio, video} - list management APIs (e.g., delete, get details)
- (need other APIs for capture and annotate)
File I/O
- W3C is working on APIs in this area
Camera
- navigator.camera.*
Effectors
- navigator.effectors.mode
- navigator.effectors.setMode()
- navigator.effectors.watchModeChange()
Accelerometer
- navigator.accelerometer.mode
- navigator.accelerometer.setMode()
- navigator.accelerometer.watchModeChange())
Memory
- navigator.memory.watchOutOfMemory()