Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
214
gaia/shared/js/option_menu.js
Executable file
214
gaia/shared/js/option_menu.js
Executable file
|
|
@ -0,0 +1,214 @@
|
|||
'use strict';
|
||||
|
||||
/*
|
||||
Generic action menu. Options should have the following structure:
|
||||
|
||||
|
||||
new OptionMenu(options);
|
||||
|
||||
options {
|
||||
|
||||
items: An array of menu options to render
|
||||
eg.
|
||||
[
|
||||
{
|
||||
name: 'Lorem ipsum',
|
||||
l10nId: 'lorem',
|
||||
l10nArgs: 'ipsum',
|
||||
method: function optionMethod(param1, param2) {
|
||||
// Method and params if needed
|
||||
},
|
||||
params: ['param1', '123123123']
|
||||
},
|
||||
....
|
||||
,
|
||||
|
||||
|
||||
Last option has a different UI compared with the previous one.
|
||||
This is because it's recommended to use as a 'Cancel' option
|
||||
{
|
||||
name: 'Cancel',
|
||||
l10nId: 'Cancel'
|
||||
method: function optionMethod(param) {
|
||||
// Method and param if needed
|
||||
},
|
||||
params: ['Optional params'],
|
||||
|
||||
// Optional boolean flag to tell the
|
||||
// menu button handlers that this option
|
||||
// will not execute the "complete" callback.
|
||||
// Defaults to "false"
|
||||
|
||||
incomplete: false [true]
|
||||
}
|
||||
],
|
||||
|
||||
// Optional header text or node
|
||||
header: ...,
|
||||
|
||||
// additional classes on the dialog, as an array of strings
|
||||
classes: ...
|
||||
|
||||
// Optional section text or node
|
||||
section: ...
|
||||
|
||||
// Optional data-type: confirm or action
|
||||
type: 'confirm'
|
||||
|
||||
// Optional callback to be invoked when a
|
||||
// button in the menu is pressed. Can be
|
||||
// overridden by an "incomplete: true" set
|
||||
// on the menu item in the items array.
|
||||
complete: function() {...}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
var OptionMenu = function(options) {
|
||||
if (!options || !options.items || options.items.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Create a private, weakly held entry for
|
||||
// this instances DOM object references
|
||||
// More info:
|
||||
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/WeakMap
|
||||
var handlers = new WeakMap();
|
||||
// Retrieve items to be rendered
|
||||
var items = options.items;
|
||||
// Create the structure
|
||||
this.form = document.createElement('form');
|
||||
this.form.dataset.type = options.type || 'action';
|
||||
this.form.dataset.subtype = 'menu';
|
||||
this.form.setAttribute('role', 'dialog');
|
||||
this.form.tabIndex = -1;
|
||||
|
||||
var classList = this.form.classList;
|
||||
|
||||
if (options.classes) {
|
||||
classList.add.apply(classList, options.classes);
|
||||
}
|
||||
|
||||
// We append title if needed
|
||||
if (options.header) {
|
||||
var header = document.createElement('header');
|
||||
|
||||
if (typeof options.header === 'string') {
|
||||
header.textContent = options.header || '';
|
||||
} else if (options.header.l10nId) {
|
||||
header.setAttribute('data-l10n-id', options.header.l10nId);
|
||||
} else {
|
||||
header.appendChild(options.header);
|
||||
}
|
||||
|
||||
this.form.appendChild(header);
|
||||
}
|
||||
if (options.section) {
|
||||
var section = document.createElement('section');
|
||||
|
||||
if (typeof options.section === 'string') {
|
||||
section.textContent = options.section || '';
|
||||
} else {
|
||||
section.appendChild(options.section);
|
||||
}
|
||||
|
||||
this.form.appendChild(section);
|
||||
}
|
||||
|
||||
// We append a menu with the list of options
|
||||
var menu = document.createElement('menu');
|
||||
menu.dataset.items = items.length;
|
||||
|
||||
// For each option, we append the item and listener
|
||||
items.forEach(function renderOption(item, index) {
|
||||
var button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
if (item.l10nId) {
|
||||
navigator.mozL10n.setAttributes(button, item.l10nId, item.l10nArgs);
|
||||
} else if (item.name && item.name.length) {
|
||||
button.textContent = item.name || '';
|
||||
} else {
|
||||
// no l10n or name, just empty item, don't add to the menu
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap the last button in a container in order to style it differently.
|
||||
if (index === items.length - 1) {
|
||||
var lastButtonWrapper = document.createElement('span');
|
||||
lastButtonWrapper.className = 'last-button-container';
|
||||
menu.appendChild(lastButtonWrapper);
|
||||
lastButtonWrapper.appendChild(button);
|
||||
} else {
|
||||
menu.appendChild(button);
|
||||
}
|
||||
|
||||
// Add a mapping from the button object
|
||||
// directly to its options item.
|
||||
item.incomplete = item.incomplete || false;
|
||||
|
||||
handlers.set(button, item);
|
||||
});
|
||||
|
||||
this.form.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
this.form.addEventListener('transitionend', function(event) {
|
||||
if (event.target !== this.form) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.form.classList.contains('visible') && this.form.parentNode) {
|
||||
this.form.remove();
|
||||
} else {
|
||||
// Focus form for accessibility
|
||||
this.form.focus();
|
||||
}
|
||||
|
||||
document.body.classList.remove('dialog-animating');
|
||||
}.bind(this));
|
||||
|
||||
menu.addEventListener('click', function(event) {
|
||||
var action = handlers.get(event.target);
|
||||
var method;
|
||||
|
||||
// Delegate operation to target method. This allows
|
||||
// for a custom "Cancel" to be provided by calling program.
|
||||
//
|
||||
// Further operations should only be processed if
|
||||
// an actual button was pressed.
|
||||
if (typeof action !== 'undefined') {
|
||||
method = action.method || function() {};
|
||||
|
||||
method.apply(null, action.params || []);
|
||||
|
||||
// Hide action menu when click is received
|
||||
this.hide();
|
||||
|
||||
if (typeof options.complete === 'function' && !action.incomplete) {
|
||||
options.complete();
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
// Appending the action menu to the form
|
||||
this.form.appendChild(menu);
|
||||
};
|
||||
|
||||
// We prototype functions to show/hide the UI of action-menu
|
||||
OptionMenu.prototype.show = function() {
|
||||
// Remove the focus to hide the keyboard asap
|
||||
document.activeElement && document.activeElement.blur();
|
||||
|
||||
if (!this.form.parentNode) {
|
||||
document.body.appendChild(this.form);
|
||||
|
||||
// Flush style on form so that the show transition plays once we add
|
||||
// the visible class.
|
||||
this.form.clientTop;
|
||||
}
|
||||
this.form.classList.add('visible');
|
||||
document.body.classList.add('dialog-animating');
|
||||
};
|
||||
|
||||
OptionMenu.prototype.hide = function() {
|
||||
this.form.classList.remove('visible');
|
||||
};
|
||||
Loading…
Reference in a new issue