Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #15626 from cctuan/921828-2
Browse files Browse the repository at this point in the history
Bug 921828 - (app-menu) [Window Management] Refactor ListMenu and move i...
  • Loading branch information
cctuan committed Jan 23, 2014
2 parents 00d8d05 + f50e115 commit 0cdbd10
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 30 deletions.
4 changes: 1 addition & 3 deletions apps/system/index.html
Expand Up @@ -121,9 +121,6 @@
<link rel="stylesheet" type="text/css" href="style/action_menu/action_menu_extended.css">
<script defer src="js/action_menu.js"></script>

<!-- Context Menu -->
<script defer src="js/context_menu.js"></script>

<!-- Statusbar -->
<link rel="stylesheet" type="text/css" href="style/statusbar/statusbar.css">
<script defer src="js/statusbar.js"></script>
Expand Down Expand Up @@ -292,6 +289,7 @@
<!-- homescreen from being displayed must come before this script. -->
<link rel="stylesheet" type="text/css" href="style/window.css">
<script defer src="js/base_ui.js"></script>
<script defer src="js/browser_context_menu.js"></script>
<script defer src="js/app_modal_dialog.js"></script>
<script defer src="js/app_chrome.js"></script>
<script defer src="js/app_transition_controller.js"></script>
Expand Down
3 changes: 2 additions & 1 deletion apps/system/js/activity_window.js
Expand Up @@ -180,7 +180,8 @@
ActivityWindow.SUB_COMPONENTS = {
'transitionController': window.AppTransitionController,
'modalDialog': window.AppModalDialog,
'authDialog': window.AppAuthenticationDialog
'authDialog': window.AppAuthenticationDialog,
'contextmenu': window.BrowserContextMenu
};

ActivityWindow.REGISTERED_EVENTS =
Expand Down
3 changes: 2 additions & 1 deletion apps/system/js/app_window.js
Expand Up @@ -456,7 +456,8 @@
AppWindow.SUB_COMPONENTS = {
'transitionController': window.AppTransitionController,
'modalDialog': window.AppModalDialog,
'authDialog': window.AppAuthenticationDialog
'authDialog': window.AppAuthenticationDialog,
'contextmenu': window.BrowserContextMenu
};

AppWindow.prototype.openAnimation = 'enlarge';
Expand Down
169 changes: 169 additions & 0 deletions apps/system/js/browser_context_menu.js
@@ -0,0 +1,169 @@

(function(window) {
'use strict';

var _ = navigator.mozL10n.get;
var _id = 0;
/**
* The ContextMenu of the AppWindow.
*
* @class BrowserContextMenu
* @param {AppWindow} app The app window instance
* where this dialog should popup.
* @extends BaseUI
*/
var BrowserContextMenu = window.BrowserContextMenu =
function BrowserContextMenu(app) {
this.app = app;
this.containerElement = app.element;
this.event = null;
// One to one mapping.
this.instanceID = _id++;
this._injected = false;
try {
app.element.addEventListener('mozbrowsercontextmenu', this);
} catch (e) {
app._dump();
}
return this;
};

BrowserContextMenu.prototype.__proto__ = window.BaseUI.prototype;
BrowserContextMenu.prototype.CLASS_NAME = 'BrowserContextMenu';

BrowserContextMenu.prototype.ELEMENT_PREFIX = 'contextmenu-';

BrowserContextMenu.prototype.customID = function am_customID() {
if (this.app) {
return '[' + this.app.origin + ']';
} else {
return '';
}
};

BrowserContextMenu.prototype.handleEvent = function bcm_handleEvent(evt) {
evt.preventDefault();
this.event = evt;
if (!this._injected) {
this.render();
}
this.show();
this._injected = true;
};

BrowserContextMenu.prototype._fetchElements = function bcm__fetchElements() {
this.element = document.getElementById(this.CLASS_NAME + this.instanceID);
this.elements = {};

var toCamelCase = function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
};

this.elementClasses = ['header', 'list'];

// Loop and add element with camel style name to Modal Dialog attribute.
this.elementClasses.forEach(function createElementRef(name) {
this.elements[toCamelCase(name)] =
this.element.querySelector('.' + this.ELEMENT_PREFIX + name);
}, this);
var cancel = document.createElement('button');
cancel.dataset.action = 'cancel';
cancel.dataset.l10nId = 'cancel';
this.elements.cancel = cancel;
};

BrowserContextMenu.prototype._registerEvents =
function bcm__registerEvents() {
var elements = this.elements;
elements.list.addEventListener('click', this.selectedHandler.bind(this));
};

BrowserContextMenu.prototype.selectedHandler =
function bcm_selectedHandler(evt) {
evt.preventDefault();
var target = evt.target;
var action = target.dataset.action;
if (action && action === 'cancel') {
this.hide();
return;
}

var value = target.dataset.value;
if (!value) {
return;
}
value = parseInt(value, 10);
this.hide();

this.event.contextMenuItemSelected &&
this.event.contextMenuItemSelected(value);
};

BrowserContextMenu.prototype.view = function bcm_view() {
return '<form class="contextmenu" role="dialog" tabindex="-1"' +
' data-type="action" ' +
'id="' + this.CLASS_NAME + this.instanceID + '">' +
'<header class="contextmenu-header"></header>' +
'<menu class="contextmenu-list"></menu>' +
'</form>';
};

BrowserContextMenu.prototype.kill = function bcm_kill() {
this.containerElement.removeChild(this.element);
};

BrowserContextMenu.prototype.show = function bcm_show() {
if (!this.event) {
return;
}
var evt = this.event;
var detail = evt.detail;
if (!detail.contextmenu || detail.contextmenu.items.length === 0) {
return;
}
var choices = detail.contextmenu.items;
this.buildMenu(this._listItems(choices));
this.element.classList.add('visible');
evt.preventDefault();
};

BrowserContextMenu.prototype.buildMenu = function bcm_show(items) {
this.elements.list.innerHTML = '';
items.forEach(function traveseItems(item) {
var action = document.createElement('button');
action.dataset.value = item.value;
action.textContent = item.label;
if (item.icon) {
action.classList.add(item.iconClass || 'icon');
action.style.backgroundImage = 'url(' + item.icon + ')';
}
this.elements.list.appendChild(action);
}, this);

this.elements.cancel.textContent = _('cancel');
this.elements.list.appendChild(this.elements.cancel);
};

BrowserContextMenu.prototype._listItems = function bcm__listItems(choices) {
var items = [];

choices.forEach(function(choice, index) {
items.push({
label: choice.label,
icon: choice.icon,
value: index
});
});
return items;
};

BrowserContextMenu.prototype.hide = function bcm_hide() {
this.element.blur();
this.element.classList.remove('visible');
if (this.app) {
this.app.focus();
}
};
}(this));
25 changes: 0 additions & 25 deletions apps/system/js/context_menu.js

This file was deleted.

14 changes: 14 additions & 0 deletions apps/system/style/window.css
Expand Up @@ -244,6 +244,10 @@
visibility: visible;
}

.appWindow > .contextmenu {
z-index: 2;
}

.appWindow > .modal-dialog {
z-index: 3;
}
Expand Down Expand Up @@ -282,7 +286,12 @@
display: none;
}

.appWindow > .contextmenu {
display: none;
}

.appWindow > .modal-dialog.visible,
.appWindow > .contextmenu.visible,
.appWindow > .authentication-dialog.visible,
.authentication-dialog > .authentication-dialog-http.visible {
display: block;
Expand Down Expand Up @@ -313,6 +322,11 @@
display: block;
}

.appWindow > form[role="dialog"][data-type="action"],
.appWindow > form[role="dialog"][data-type="object"] {
top: 2rem;
}

.appWindow > .screenshot-overlay {
z-index: -1;
visibility: hidden;
Expand Down
79 changes: 79 additions & 0 deletions apps/system/test/unit/browser_context_menu_test.js
@@ -0,0 +1,79 @@
/*global mocha, MocksHelper, MockL10n, AppWindow, BrowserContextMenu */

'use strict';

mocha.globals(['AppWindow', 'BrowserContextMenu', 'System', 'BaseUI']);

requireApp('system/test/unit/mock_l10n.js');
requireApp('system/test/unit/mock_orientation_manager.js');
requireApp('system/test/unit/mock_app_window.js');

var mocksForAppModalDialog = new MocksHelper([
'AppWindow'
]).init();

suite('system/BrowserContextMenu', function() {
var stubById, realL10n, stubQuerySelector;
mocksForAppModalDialog.attachTestHelpers();
setup(function(done) {
realL10n = navigator.mozL10n;
navigator.mozL10n = MockL10n;

stubById = this.sinon.stub(document, 'getElementById');
var e = document.createElement('div');
stubQuerySelector = this.sinon.stub(e, 'querySelector');
stubQuerySelector.returns(document.createElement('div'));
stubById.returns(e);
requireApp('system/js/system.js');
requireApp('system/js/base_ui.js');
requireApp('system/js/browser_context_menu.js', done);
});

teardown(function() {
navigator.mozL10n = realL10n;
stubById.restore();
stubQuerySelector.restore();
});

var fakeAppConfig1 = {
url: 'app://www.fake/index.html',
manifest: {},
manifestURL: 'app://wwww.fake/ManifestURL',
origin: 'app://www.fake'
};

var fakeContextMenuEvent = {
type: 'mozbrowsercontextmenu',
preventDefault: function() {},
detail: {
contextmenu: {
items: [
{
label: 'test0',
icon: 'test'
}]
}
}
};

test('New', function() {
var app1 = new AppWindow(fakeAppConfig1);
var md1 = new BrowserContextMenu(app1);
assert.isDefined(md1.instanceID);
});

test('launch menu', function() {
var app1 = new AppWindow(fakeAppConfig1);
var md1 = new BrowserContextMenu(app1);

md1.handleEvent(fakeContextMenuEvent);
assert.isTrue(md1.element.classList.contains('visible'));
assert.equal(
md1.elements.list.querySelector('button:first-child').textContent,
fakeContextMenuEvent.detail.contextmenu.items[0].label);
assert.equal(
md1.elements.list.querySelector('button:first-child').
style.backgroundImage,
'url("' + fakeContextMenuEvent.detail.contextmenu.items[0].icon + '")');
});
});

0 comments on commit 0cdbd10

Please sign in to comment.