Your first app

  • Revision slug: Web/Apps/Quickstart/Build/Your_first_app
  • Revision title: Your first app
  • Revision id: 474011
  • Created:
  • Creator: chrisdavidmills
  • Is current revision? No
  • Comment

Revision Content

Open web apps give web developers exactly what they've wanted for years: a cross-platform environment dedicated to installable apps created with just HTML, CSS, and JavaScript (Firefox OS being the first dedicated open web apps platform). This guide aims to get you up and running quickly with a basic architecture and build instructions so that you can create the next great app!

If you'd like to follow along with this guide, you can download our quick start app template. Find more out about what this contains by reading our Apps template guide.

App Structure

Packaged vs. Hosted Apps

There are two types of open web apps: packaged and hosted. Packed apps are essentially a zip file containing all of of an apps assets: HTML, CSS, JavaScript, images, manifest, etc. Hosted apps are run from a server at a given domain, just like a standard website. Both app types require a valid manifest. When it comes time to list your app on the Firefox Marketplace, you will either upload your app as a zip file or provide the URL to where your hosted app lives.

Made in partnership with our friends at Treehouse: Check them out!

For the purposes of this guide, you'll be creating a hosted app which will live at your localhost address. Once your app is ready to list on the Firefox Marketplace, you can make the decision to bundle as a packed app or launch as a hosted app.

App Manifests

Every Firefox app requires an manifest.webapp file at the app root. The manifest.webapp file provides important information about the app, like version, name, description, icon location, locale strings, domains the app can be installed from, and much more (Only the name and description are required). The simple manifest included within the app template looks like:

{
  "version": "0.1",
  "name": "Open Web App",
  "description": "Your new awesome Open Web App",
  "launch_path": "/app-template/index.html",
  "icons": {
    "16": "/app-template/app-icons/icon-16.png",
    "48": "/app-template/app-icons/icon-48.png",
    "128": "/app-template/app-icons/icon-128.png"
  },
  "developer": {
    "name": "Your Name",
    "url": "http://yourawesomeapp.com"
  },
  "locales": {
    "es": {
      "description": "Su nueva aplicación impresionante Open Web",
      "developer": {
        "url": "http://yourawesomeapp.com"
      }
    },
    "it": {
      "description": "Il vostro nuovo fantastico Open Web App",
      "developer": {
        "url": "http://yourawesomeapp.com"
      }
    }
  },
  "default_locale": "en"
}

Made in partnership with our friends at Treehouse: Check them out!


A basic manifest will do to get started. For more details about manifests, read App Manifest.

App Layout & Design

Responsive design has become increasingly important as more screen resolutions become standard on different devices. Even if your app's main target is mobile platforms such as Firefox OS, other devices will likely have access to it as well. CSS media queries provide a means to adapt layout to device, as shown in this skeleton CSS example:

/* The following are examples of different CSS media queries */

/* Basic desktop/screen width sniff */
@media only screen and (min-width : 1224px) {
  /* styles */
}

/* Traditional iPhone width */
@media
  only screen and (-webkit-min-device-pixel-ratio : 1.5),
  only screen and (min-device-pixel-ratio : 1.5) {
  /* styles */
}

/* Device settings at different orientations */
@media screen and (orientation:portrait) {
  /* styles */
}
@media screen and (orientation:landscape) {
  /* styles */
}

There are many JavaScript and CSS frameworks available to aid in responsive design and mobile app development (Bootstrap, etc.); choose the framework(s) that best fit your app and development style.

Web APIs

JavaScript APIs are being created and enhanced as quickly as devices are. Mozilla's WebAPI effort brings dozens of standard mobile features to JavaScript APIs. A list of device support and status is available on the WebAPI page. Of course JavaScript feature detection is still the best practice:

// If this device supports the vibrate API...
if('vibrate' in navigator) {
    // ... vibrate for a second
    navigator.vibrate(1000);
}

In the following example snippet we modify the display style of a <div> based on changes in the device's battery state:

// Create the battery indicator listeners
(function() {
  var battery = navigator.battery || navigator.mozBattery || navigator.webkitBattery,
      indicator, indicatorPercentage;

  if(battery) {
    indicator = document.getElementById('indicator'),
    indicatorPercentage = document.getElementById('indicator-percentage');

    // Set listeners for changes
    battery.addEventListener('chargingchange', updateBattery);
    battery.addEventListener('levelchange', updateBattery);

    // Update immediately
    updateBattery();
  }

  function updateBattery() {
    // Update percentage width and text
    var level = (battery.level * 100) + '%';
    indicatorPercentage.style.width = level;
    indicatorPercentage.innerHTML = 'Battery: ' + level;
    // Update charging status
    indicator.className = battery.charging ? 'charging' : '';
  }
})();

In the code sample above, once you confirm that the Battery API is supported, you can add event listeners for chargingchange and levelchange to update the element's display. Try adding the following to the quickstart template, and see if you can get it working.

Check the WebAPI page frequently to keep up to date with device API statuses!

Install API functionality

In our sample quickstart app template, we've implemented an install button that you can click when viewing the app as a standard Web page, to install that site on Firefox OS as an app. The button markup is nothing special:

<button id="install-btn">Install app</button>

This button's functionality is implemented using the Install API (see install.js):

var manifest_url = location.href + 'manifest.webapp';

function install(ev) {
  ev.preventDefault();
  // define the manifest URL
  // install the app
  var installLocFind = navigator.mozApps.install(manifest_url);
  installLocFind.onsuccess = function(data) {
    // App is installed, do something
  };
  installLocFind.onerror = function() {
    // App wasn't installed, info is in
    // installapp.error.name
    alert(installLocFind.error.name);
  };
};

// get a reference to the button and call install() on click if the app isn't already installed. If it is, hide the button.
var button = document.getElementById('install-btn');

var installCheck = navigator.mozApps.checkInstalled(manifest_url);

installCheck.onsuccess = function() {
  if(installCheck.result) {
    button.style.display = "none";
  } else {
    button.addEventListener('click', install, false);
  };
};

Let's run through briefly what is going on:

  1. We get a reference to the install button and store it in the variable button.
  2. We use navigator.mozApps.checkInstalled to check whether the app defined by the manifest at http://people.mozilla.com/~cmills/location-finder/manifest.webapp is already installed on the device. This test is stored in the variable installCheck.
  3. When the test is successfully carried out, its success event is fired, therefore installCheck.onsuccess = function() { ... } is run.
  4. We then test for the existance of installCheck.result using an if statement. If it does exist, meaning that the app is installed, we hide the button. An install button isn't needed if it is already installed.
  5. If the app isn't installed, we add a click event listener to the button, so the install() function is run when the button is clicked.
  6. When the button is clicked and the install() function is run, we store the manifest file location in a variable called manifest_url, and then install the app using navigator.mozApps.install(manifest_url), storing a reference to that installation in the installLocFind variable. You'll notice that this installation also fires success and error events, so you can run actions dependant on whether the install happened successfully or not.

You may want to verify the implementation state of the API when first coming to Installable web apps.

Note: Installable open web apps have a "single app per origin" security policy; basically, you can't host more than one installable app per origin. This makes testing a bit more tricky, but there are still ways around this, such as creating different sub-domains for apps, testing them using the Firefox OS Simulator, or testing the install functionality on Firefox Aurora/Nightly, which allow installable web apps to install on the desktop. See FAQs about apps manifests for more information on origins.

WebRT APIs (Permissions-based APIs)

There are a number of WebAPIs that are available but require permissions for that specific feature to be enabled. Apps may register permission requests within the manifest.webapp file like so:

// New key in the manifest: "permissions"
// Request access to any number of APIs
// Here we request permissions to the systemXHR API
"permissions": {
    "systemXHR": {}
}

The three levels of permission are as follows:

  • Normal — APIs that don't need any kind of special access permissions.
  • Privileged — APIs available to developers to use in their applications, as long as they set access permissions in the app manifest files, and distribute them through a trusted source.
  • Certified — APIs that control critical functions on a device, such as the call dialer and messaging services. These are generally not available for third party developers to use.

For more information on app permission levels, read Types of packaged apps. You can find out more information about what APIs require permissions, and what permissions are required, at App permissions.

It's important to note that not all Web APIs have been implemented within the Firefox OS Simulator.

Tools & Testing

Testing is incredibly important when supporting mobile devices. There are many options for testing installable open web apps:

Firefox OS Simulator

Installing and using the Firefox OS Simulator is the easiest way to get up and running with your app. After installed the simulator is accessible via the Tools -> Web Developer -> Firefox OS Simulator menu. The simulator launches with a JavaScript console so that you may debug your application from within the simulator!

Unit Testing

Unit tests are extremely valuable when testing on different devices and builds. jQuery's QUnit is a popular client-side testing utility, but you can use any set of testing tools you'd like.

Installing Firefox OS on a Device

Since Firefox OS is an open source platform, code and tools are available to build and install Firefox OS on your own device. Build and installation instructions, as well as notes on what devices it can be installed on, can be found on MDN.

Dedicated Firefox OS developer preview devices are also available: read our Developer preview phone page for more information.

App Submission and Distribution

Once your app is complete, you can host it yourself like a standard web site or app (read Self-publishing apps for more information), or it can be submitted to the Firefox Marketplace. Your app's manifest will be validated and you may choose which devices your app will support (i.e. Firefox OS, Desktop Firefox, Firefox Mobile, Firefox Tablet). Once validated you may add additional details about your app (screenshots, descriptions, price, etc.) and officially submit the app for listing within the Marketplace. Once approved your app is available to the world for purchase and installation.

More Marketplace & Listing Information

  1. Submitting an App to the Firefox OS Marketplace
  2. Marketplace Review Criteria
  3. App Submission Video Walkthrough

Revision Source

<article>
  <p>Open web apps give web developers exactly what they've wanted for years: a cross-platform environment dedicated to installable apps created with just HTML, CSS, and JavaScript (Firefox OS being the first dedicated open web apps platform). This guide aims to get you up and running quickly with a basic architecture and build instructions so that you can create the next great app!</p>
  <p>If you'd like to follow along with this guide, you can download our <a href="https://github.com/chrisdavidmills/mdn-app-template">quick start app template</a>. Find more out about what this contains by reading our <a href="/en-US/docs/Project:MDN/Style_guide/Sample_app_coding_guidelines#Apps_template">Apps template</a> guide.</p>
  <h2 id="App_Structure">App Structure</h2>
  <h3 id="Packaged_vs._Hosted_Apps">Packaged vs. Hosted Apps</h3>
  <p>There are two types of open web apps: <code>packaged</code> and <code>hosted</code>. Packed apps are essentially a <code>zip</code> file containing all of of an apps assets: HTML, CSS, JavaScript, images, manifest, etc. Hosted apps are run from a server at a given domain, just like a standard website. Both app types require a valid manifest. When it comes time to list your app on the Firefox Marketplace, you will either upload your app as a zip file or provide the URL to where your hosted app lives.</p>
  <p><iframe allowfullscreen="" frameborder="0" height="270" src="https://www.youtube.com/embed/Q7x-B13y33Q/?feature=player_detailpage" width="480"></iframe></p>
  <div class="video-caption">
    <span>Made in partnership with our friends at Treehouse:</span> <a class="button" href="http://teamtreehouse.com/?cid=1154">Check them out!</a>
    <hr />
  </div>
  <p>For the purposes of this guide, you'll be creating a hosted app which will live at your <code>localhost</code> address. Once your app is ready to list on the Firefox Marketplace, you can make the decision to bundle as a packed app or launch as a hosted app.</p>
  <h3 id="App_Manifests">App Manifests</h3>
  <p>Every Firefox app requires an manifest.webapp file at the app root. The <code><a href="https://marketplace.firefox.com/developers/docs/manifests">manifest.webapp</a></code> file provides important information about the app, like version, name, description, icon location, locale strings, domains the app can be installed from, and much more (Only the name and description are required). The simple manifest included within the app template looks like:</p>
  <pre>
<code>{
&nbsp; "version": "0.1",
&nbsp; "name": "Open Web App",
&nbsp; "description": "Your new awesome Open Web App",
&nbsp; "launch_path": "/app-template/index.html",
&nbsp; "icons": {
&nbsp;&nbsp;&nbsp; "16": "/app-template/app-icons/icon-16.png",
&nbsp;&nbsp;&nbsp; "48": "/app-template/app-icons/icon-48.png",
&nbsp;&nbsp;&nbsp; "128": "/app-template/app-icons/icon-128.png"
&nbsp; },
&nbsp; "developer": {
&nbsp;&nbsp;&nbsp; "name": "Your Name",
&nbsp;&nbsp;&nbsp; "url": "http://yourawesomeapp.com"
&nbsp; },
&nbsp; "locales": {
&nbsp;&nbsp;&nbsp; "es": {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "description": "Su nueva aplicación impresionante Open Web",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "developer": {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "url": "http://yourawesomeapp.com"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; },
&nbsp;&nbsp;&nbsp; "it": {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "description": "Il vostro nuovo fantastico Open Web App",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "developer": {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "url": "http://yourawesomeapp.com"
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;&nbsp; }
&nbsp; },
&nbsp; "default_locale": "en"
}
</code></pre>
  <div class="video-caption">
    <p><iframe allowfullscreen="" frameborder="0" height="270" src="https://www.youtube.com/embed/dgAUgHQOm8M#t/?feature=player_detailpage" width="480"></iframe></p>
    <p><span>Made in partnership with our friends at Treehouse:</span> <a class="button" href="http://teamtreehouse.com/?cid=1154">Check them out!</a></p>
    <hr />
  </div>
  <p>A basic manifest will do to get started. For more details about manifests, read <a href="/en-US/docs/Web/Apps/Manifest">App Manifest</a>.</p>
  <h2 id="App_Layout_.26_Design">App Layout &amp; Design</h2>
  <p>Responsive design has become increasingly important as more screen resolutions become standard on different devices. Even if your app's main target is mobile platforms such as Firefox OS, other devices will likely have access to it as well. <a href="//developer.mozilla.org/docs/CSS/Media_queries">CSS media queries</a> provide a means to adapt layout to device, as shown in this skeleton CSS example:</p>
  <pre>
<code>/* The following are examples of different CSS media queries */

/* Basic desktop/screen width sniff */
@media only screen and (min-width : 1224px) {
  /* styles */
}

/* Traditional iPhone width */
@media
  only screen and (-webkit-min-device-pixel-ratio : 1.5),
  only screen and (min-device-pixel-ratio : 1.5) {
  /* styles */
}

/* Device settings at different orientations */
@media screen and (orientation:portrait) {
  /* styles */
}
@media screen and (orientation:landscape) {
  /* styles */
}</code></pre>
  <p>There are many JavaScript and CSS frameworks available to aid in responsive design and mobile app development (<a href="http://twitter.github.com/bootstrap">Bootstrap</a>, etc.); choose the framework(s) that best fit your app and development style.</p>
  <h2 id="Web_APIs">Web APIs</h2>
  <p>JavaScript APIs are being created and enhanced as quickly as devices are. Mozilla's <a href="https://wiki.mozilla.org/WebAPI">WebAPI</a> effort brings dozens of standard mobile features to JavaScript APIs. A list of device support and status is available on the <a href="https://wiki.mozilla.org/WebAPI">WebAPI</a> page. Of course JavaScript feature detection is still the best practice:</p>
  <pre>
<code>// If this device supports the vibrate API...
if('vibrate' in navigator) {
    // ... vibrate for a second
    navigator.vibrate(1000);
}</code></pre>
  <p>In the following example snippet we modify the display style of a <code>&lt;div&gt;</code> based on changes in the device's battery state:</p>
  <pre>
<code class="brush: js">// Create the battery indicator listeners
(function() {
  var battery = navigator.battery || navigator.mozBattery || navigator.webkitBattery,
      indicator, indicatorPercentage;

  if(battery) {
    indicator = document.getElementById('indicator'),
    indicatorPercentage = document.getElementById('indicator-percentage');

    // Set listeners for changes
    battery.addEventListener('chargingchange', updateBattery);
    battery.addEventListener('levelchange', updateBattery);

    // Update immediately
    updateBattery();
  }

  function updateBattery() {
    // Update percentage width and text
    var level = (battery.level * 100) + '%';
    indicatorPercentage.style.width = level;
    indicatorPercentage.innerHTML = 'Battery: ' + level;
    // Update charging status
    indicator.className = battery.charging ? 'charging' : '';
  }
})();</code></pre>
  <p>In the code sample above, once you confirm that the <a href="https://developer.mozilla.org/en-US/docs/DOM/window.navigator.battery">Battery API</a> is supported, you can add event listeners for <code>chargingchange</code> and <code>levelchange</code> to update the element's display. Try adding the following to the quickstart template, and see if you can get it working.</p>
  <p>Check the <a href="https://wiki.mozilla.org/WebAPI">WebAPI</a> page frequently to keep up to date with device API statuses!</p>
  <h3 id="Install_API_functionality">Install API functionality</h3>
  <p>In our sample quickstart app template, we've implemented an install button that you can click when viewing the app as a standard Web page, to install that site on Firefox OS as an app. The button markup is nothing special:</p>
  <pre class="brush: html">
&lt;button id="install-btn"&gt;Install app&lt;/button&gt;</pre>
  <p>This button's functionality is implemented using the Install API (see install.js):</p>
  <pre class="brush: js">
var manifest_url = location.href + 'manifest.webapp';

function install(ev) {
&nbsp; ev.preventDefault();
&nbsp; // define the manifest URL
&nbsp; // install the app
&nbsp; var installLocFind = navigator.mozApps.install(manifest_url);
&nbsp; installLocFind.onsuccess = function(data) {
&nbsp;&nbsp;&nbsp; // App is installed, do something
&nbsp; };
&nbsp; installLocFind.onerror = function() {
&nbsp;&nbsp;&nbsp; // App wasn't installed, info is in
&nbsp;&nbsp;&nbsp; // installapp.error.name
&nbsp;&nbsp;&nbsp; alert(installLocFind.error.name);
&nbsp; };
};

// get a reference to the button and call install() on click if the app isn't already installed. If it is, hide the button.
var button = document.getElementById('install-btn');

var installCheck = navigator.mozApps.checkInstalled(manifest_url);

installCheck.onsuccess = function() {
&nbsp; if(installCheck.result) {
&nbsp;&nbsp;&nbsp; button.style.display = "none";
&nbsp; } else {
&nbsp;&nbsp;&nbsp; button.addEventListener('click', install, false);
&nbsp; };
};
</pre>
  <p>Let's run through briefly what is going on:</p>
  <ol>
    <li>We get a reference to the install button and store it in the variable <code>button</code>.</li>
    <li>We use <code>navigator.mozApps.checkInstalled</code> to check whether the app defined by the manifest at <code>http://people.mozilla.com/~cmills/location-finder/manifest.webapp</code> is already installed on the device. This test is stored in the variable <code>installCheck</code>.</li>
    <li>When the test is successfully carried out, its success event is fired, therefore <code>installCheck.onsuccess = function() { ... }</code> is run.</li>
    <li>We then test for the existance of <code>installCheck.result</code> using an <code>if</code> statement. If it does exist, meaning that the app is installed, we hide the button. An install button isn't needed if it is already installed.</li>
    <li>If the app isn't installed, we add a click event listener to the button, so the <code>install()</code> function is run when the button is clicked.</li>
    <li>When the button is clicked and the <code>install()</code> function is run, we store the manifest file location in a variable called <code>manifest_url</code>, and then install the app using <code>navigator.mozApps.install(manifest_url)</code>, storing a reference to that installation in the <code>installLocFind</code> variable. You'll notice that this installation also fires success and error events, so you can run actions dependant on whether the install happened successfully or not.</li>
  </ol>
  <p>You may want to verify the <a href="/en-US/docs/Web/Apps/JavaScript_API">implementation state of the API</a> when first coming to Installable web apps.</p>
  <div class="note">
    <p>Note: Installable open web apps have a "single app per origin" security policy; basically, you can't host more than one installable app per origin. This makes testing a bit more tricky, but there are still ways around this, such as creating different sub-domains for apps, testing them using the Firefox OS Simulator, or testing the install functionality on Firefox Aurora/Nightly, which allow installable web apps to install on the desktop. See <a href="/en-US/docs/Web/Apps/FAQs/About_app_manifests">FAQs about apps manifests</a> for more information on origins.</p>
  </div>
  <h2 id="WebRT_APIs_(Permissions-based_APIs)">WebRT APIs (Permissions-based APIs)</h2>
  <p>There are a number of WebAPIs that are available but require permissions for that specific feature to be enabled. Apps may register permission requests within the <code>manifest.webapp</code> file like so:</p>
  <pre>
<code>// New key in the manifest: "permissions"
// Request access to any number of APIs
// Here we request permissions to the systemXHR API
"permissions": {
    "systemXHR": {}
}</code></pre>
  <p>The three levels of permission are as follows:</p>
  <ul>
    <li>Normal — APIs that don't need any kind of special access permissions.</li>
    <li>Privileged — APIs available to developers to use in their applications, as long as they set access permissions in the app manifest files, and distribute them through a trusted source.</li>
    <li>Certified — APIs that control critical functions on a device, such as the call dialer and messaging services. These are generally not available for third party developers to use.</li>
  </ul>
  <p>For more information on app permission levels, read <a href="https://developer.mozilla.org/en-US/docs/Web/Apps/Packaged_apps#Types_of_packaged_apps" title="/en-US/docs/Web/Apps/Packaged_apps#Types_of_packaged_apps">Types of packaged apps</a>. You can find out more information about what APIs require permissions, and what permissions are required, at <a href="/en-US/docs/Web/Apps/App_permissions">App permissions</a>.</p>
  <div class="note">
    <p>It's important to note that not all Web APIs have been implemented within the Firefox OS Simulator.</p>
  </div>
  <h2 id="Tools_.26_Testing">Tools &amp; Testing</h2>
  <p>Testing is incredibly important when supporting mobile devices. There are many options for testing installable open web apps:</p>
  <h3 id="Firefox_OS_Simulator">Firefox OS Simulator</h3>
  <p>Installing and using the <a href="https://marketplace.firefox.com/developers/docs/firefox_os_simulator">Firefox OS Simulator</a> is the easiest way to get up and running with your app. After installed the simulator is accessible via the Tools -&gt; Web Developer -&gt; Firefox OS Simulator menu. The simulator launches with a JavaScript console so that you may debug your application from within the simulator!</p>
  <h3 id="Unit_Testing">Unit Testing</h3>
  <p>Unit tests are extremely valuable when testing on different devices and builds. jQuery's <a href="http://qunitjs.com">QUnit</a> is a popular client-side testing utility, but you can use any set of testing tools you'd like.</p>
  <h3 id="Installing_Firefox_OS_on_a_Device">Installing Firefox OS on a Device</h3>
  <p>Since Firefox OS is an open source platform, code and tools are available to build and install Firefox OS on your own device. Build and installation instructions, as well as notes on what devices it can be installed on, can be found on <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox_OS/Platform">MDN</a>.</p>
  <p>Dedicated Firefox OS developer preview devices are also available: read our <a href="https://marketplace.firefox.com/developers/dev_phone">Developer preview phone page</a> for more information.</p>
  <h2 id="App_Submission_and_Distribution">App Submission and Distribution</h2>
  <p>Once your app is complete, you can host it yourself like a standard web site or app (read <a href="/en-US/docs/Web/Apps/Publishing/Self-publishing_Apps">Self-publishing apps</a> for more information), or it can be <a href="https://marketplace.firefox.com/developers/submit/app/manifest">submitted</a> to the <a href="https://marketplace.firefox.com">Firefox Marketplace</a>. Your app's manifest will be validated and you may choose which devices your app will support (i.e. Firefox OS, Desktop Firefox, Firefox Mobile, Firefox Tablet). Once validated you may add additional details about your app (screenshots, descriptions, price, etc.) and officially submit the app for listing within the Marketplace. Once approved your app is available to the world for purchase and installation.</p>
  <h3 id="More_Marketplace_.26_Listing_Information">More Marketplace &amp; Listing Information</h3>
  <ol>
    <li><a href="//developer.mozilla.org/docs/Apps/Submitting_an_app">Submitting an App to the Firefox OS Marketplace </a></li>
    <li><a href="/developers/docs/review">Marketplace Review Criteria </a></li>
    <li><a href="http://s.vid.ly/embeded.html?link=8k2n4w&amp;autoplay=false">App Submission Video Walkthrough </a></li>
  </ol>
</article>
Revert to this revision