Skip to content

Instantly share code, notes, and snippets.

@dpogue
Last active December 15, 2015 04:29
Show Gist options
  • Save dpogue/5202474 to your computer and use it in GitHub Desktop.
Save dpogue/5202474 to your computer and use it in GitHub Desktop.
First-pass documentation for the API design of Cambie.

Cambie

A unified native UI plugin for Apache Cordova.

The target platforms for Cambie are:

  • iOS 5.x+
  • Android 4.x+

It is a goal to also target these platforms wherever possible:

  • Android 2.3
  • BlackBerry OS 10
  • Firefox OS
  • Windows Phone 8

Low-Level JSON Structure

Cambie uses JSON for passing the UI structure from HTML into the native platform libraries. The basic JSON structure for defining tabs, menus, and action buttons is called an Actionable and appears as follows:

{
    "label": "MyAction".
    "icon": "./assets/icons/action.png",
    "disabled": false,
    "callback": function() { ... },
    "selected": true // This applies only to tabs
}

The JSON structure for the UI layout of a page can contains any subset of the following keys:

title

The title of the current page (defaulting to the application name). This is shown generally at the top of the screen, between the navigation action and any buttons.

nav

The navigation action for the current page. It must be one of the following:

  • none
    The default value. No navigation action is used. This is generally ideal for something like the main page.

  • back
    A native back navigation item. This should navigate to the previous page.

    On iOS, this will be a chevron style button in the top left, labelled "Back".
    On Android, this will be a back arrow in the Action Bar.
    On BlackBerry, this will be a back button at the bottom left, labelled "Back".
    On FirefoxOS, this will be a small back arrow in the top left header.
    On Windows Phone, TBD

  • menu
    A button to open a side-view menu (similar to Facebook or Google+). If you want to do this, you're on your own in terms of figuring out how to implement it.

    On iOS, this will be a small button in the top left showing 3 horizontal lines.
    On Android, this will be a back arrow in the Action Bar.
    On BlackBerry, TBD
    On FirefoxOS, this will be a small button in the top left header showing 3 horizontal lines.
    On Windows Phone, TBD

  • close
    A button to dismiss, cancel, or close the current page. Usually used in an editing or details view.

    On iOS, this will be a button in the top left, labelled "Cancel".
    On Android, this will be a back arrow in the Action Bar.
    On BlackBerry, TBD
    On FirefoxOS, this will be a small × button in the top left header.
    On Windows Phone. TBD

actions

The immediate action(s) available on the current page. For an editing view, this would probably be a "Save" action. These are defined as an array of Actionables.

It is highly recommended that you keep the number of action buttons to a minimum (1 or 2) per page. Any more than 2 will probably disrupt the UI, and may result in actions being hidden by the operating system.

On iOS, these will be shown in the top right beside the title as buttons.
On Android, these will be shown in the Action Bar. They may overflow into a menu.
On BlackBerry, TBD
On FirefoxOS, these will be shown as buttons at the top in the header.
On Windows Phone, TBD

menu

Additional actions available on the current page, or globally. These are defined as an array of Actionables.

On iOS, TBD (maybe pop up a list of buttons à la Safari "Extra actions"?)
On Android, these are shown in the menu from the ActionBar.
On BlackBerry, TBD
On FirefoxOS, TBD (pop up a list of buttons?)
On Windows Phone, TBD

tabs

These are most often used for navigation between different views. These are defined as an array of Actionables, with an optional selected key to indicate the currently selected tab.

You should limit the number of tabs to a maximum of 5. More tabs may cause them to overflow into a menu.

On iOS, the tabs appear in a bar along the bottom of the screen.
On Android, the tabs appear underneath the Action Bar.
On BlackBerry, the tabs appear in a bar along the bottom of the screen.
On FirefoxOS, TBD
On Windows Phone, TBD

Parsing from HTML Structure

Using HTML5's <menu> tags and some data attributes, you could generate this JSON structure from HTML content.

Some example HTML structure might look like this:

<section id="my-app-page">

    <header data-nav="back"> <!-- data-nav becomes the nav attribute in JSON -->
    
        <!-- Becomes the title attribute in JSON -->
        <h1>Page title</h1>
    
        <!-- Becomes the actions array in JSON -->
        <menu type="toolbar">
            <!-- TODO: These are supposed to be wrapped in li elements? -->
            <a href="#">Action Item here</a>
        </menu>
    
        <!-- Becomes menu array in JSON -->
        <menu type="popup">
            <menuitem label="Menu Item 1" icon="img/menu1.png">
            <menuitem label="Menu Item 2" icon="img/menu2.png" disabled="disabled">
            <menuitem label="Menu Item 3" icon="img/menu3.png">
        </menu>
    
    </header>
    
    <p>Page content goes here</p>
    
    <footer>
        <menu type="toolbar">
            <li>
                <a href="#page2">Page 2</a>
            </li>
        </menu>
    </footer>
</section>

See the attached parser.js file below for a demo of parsing this HTML structure.

References

(function(glob) {
/**
* Polyfill for the HTMLElement click() method.
* From http://stackoverflow.com/a/6499283
*/
if (typeof HTMLElement !== 'undefined' && !HTMLElement.prototype.click) {
HTMLElement.prototype.click = function() {
var evt = this.ownerDocument.createEvent('MouseEvents');
evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
this.dispatchEvent(evt);
}
}
/**
* Our global-ish parsing function that takes an element representing a page
* and generates the JSON structure that we need.
*/
glob.parsePageNavigation = function(page_el) {
var header = page_el.querySelector('header');
var footer = page_el.querySelector('footer');
var jsobj = {};
jsobj['title'] = (function(el) {
// Default to the <title> tag
var title = document.title;
if (el.getAttribute('data-title')) {
title = el.getAttribute('data-title');
}
if (el.getAttribute('title')) {
title = el.getAttribute('title');
}
for (var i = 6; i > 0; --i) {
var heading = el.querySelector('h'+i);
if (heading) {
title = heading.textContent;
}
}
return title;
})(header);
jsobj['nav'] = (function(el) {
var nav = (el.getAttribute('data-nav') || 'none').toLowerCase();
if (nav != 'none' && nav != 'back' && nav != 'menu' && nav != 'cancel') {
console.error('Invalid navigation type: ' + nav);
return 'none';
}
return nav;
})(header);
var menu = header.querySelector('menu[type="popup"]');
jsobj['menu'] = (function(el) {
var menuitems = [];
var global_menu = document.querySelector('body > menu[type=popup]')
if (!el && !global_menu) { return menuitems; }
var children = el.querySelectorAll('menuitem');
for (var i = 0, ii = children.length; i < ii; ++i) {
var mi = {};
var child = children[i];
mi['label'] = child.getAttribute('label');
mi['icon'] = child.getAttribute('icon');
mi['disabled'] = !!child.getAttribute('disabled');
mi['callback'] = (function(target) {
return function() { target.click(); }
})(child);
menuitems.push(mi);
}
/*
* If there's a menu defined under body, we treat it as a global
* menu and merge it into the menu for each page.
*/
if (global_menu) {
var children = global_menu.querySelectorAll('menuitem');
for (var i = 0, ii = children.length; i < ii; ++i) {
var mi = {};
var child = children[i];
mi['label'] = child.getAttribute('label');
mi['icon'] = child.getAttribute('icon');
mi['disabled'] = !!child.getAttribute('disabled');
mi['callback'] = (function(target) {
return function() { target.click(); }
})(child);
menuitems.push(mi);
}
}
return menuitems;
})(menu);
var actions = header.querySelector('menu[type="toolbar"]');
jsobj['actions'] = (function(el) {
var items = [];
if (!el) { return items; }
// TODO: Do we only want a tags, or also buttons and stuff?
var children = el.querySelectorAll('a');
for (var i = 0, ii = children.length; i < ii; ++i) {
var mi = {};
var child = children[i];
mi['label'] = child.textContent;
mi['icon'] = child.getAttribute('icon');
mi['disabled'] = !!child.getAttribute('disabled');
mi['callback'] = (function(target) {
return function() { target.click(); }
})(child);
items.push(mi);
}
return items;
})(actions);
var actions = footer.querySelector('menu[type="toolbar"]');
jsobj['tabs'] = (function(el) {
var items = [];
if (!el) { return items; }
// TODO: Do we only want a tags, or also buttons and stuff?
var children = el.querySelectorAll('a');
for (var i = 0, ii = children.length; i < ii; ++i) {
var mi = {};
var child = children[i];
mi['label'] = child.textContent;
mi['icon'] = child.getAttribute('icon');
mi['disabled'] = !!child.getAttribute('disabled');
mi['callback'] = (function(target) {
return function() { target.click(); }
})(child);
items.push(mi);
}
return items;
})(actions);
return jsobj;
};
})(window);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment