/*************************************************************
*
* Copyright (c) 2015-2016 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Class of context menus.
*
* @author volker.sorge@gmail.com (Volker Sorge)
*/
/// <reference path="abstract_menu.ts" />
/// <reference path="html_classes.ts" />
/// <reference path="item.ts" />
/// <reference path="menu_store.ts" />
var ContextMenu;
(function (ContextMenu_1) {
class ContextMenu extends ContextMenu_1.AbstractMenu {
/**
* @constructor
* @extends {AbstractMenu}
*/
constructor() {
super();
/**
* Flag to avoid redoing taborder if we are between elements.
* @type {boolean}
*/
this.moving = false;
/**
* A store the menu belongs to.
* @type {MenuStore}
*/
this.store_ = new ContextMenu_1.MenuStore(this);
/**
* Registry of currently open widgets.
* @type {Array.<Postable>}
*/
this.widgets = [];
this.variablePool = new ContextMenu_1.VariablePool();
}
/**
* Parses a JSON respresentation of a variable pool.
* @param {JSON} json The JSON object to parse.
* @return {ContextMenu} The new context menu object.
*/
static parse({ menu: menu }) {
if (!menu) {
ContextMenu_1.MenuUtil.error(null, 'Wrong JSON format for menu.');
return;
}
// The variable id is currently ignored!
let { pool: pool, items: items, id: id } = menu;
let ctxtMenu = new ContextMenu();
// TODO: Try and catch with error
pool.forEach(ctxtMenu.parseVariable.bind(ctxtMenu));
ctxtMenu.parseItems(items);
return ctxtMenu;
}
/**
* @override
*/
generateHtml() {
super.generateHtml();
this.frame = document.createElement('div');
this.frame.classList.add(ContextMenu_1.HtmlClasses['MENUFRAME']);
//// TODO: Adapt to other browsers.
let styleString = 'left: 0px; top: 0px; z-index: 200; width: 100%; ' +
'height: 100%; border: 0px; padding: 0px; margin: 0px;';
this.frame.setAttribute('style', 'position: absolute; ' + styleString);
let innerDiv = document.createElement('div');
innerDiv.setAttribute('style', 'position: fixed; ' + styleString);
this.frame.appendChild(innerDiv);
innerDiv.addEventListener('mousedown', function (event) {
this.unpost();
this.unpostWidgets();
this.stop(event);
}.bind(this));
}
/**
* @override
*/
display() {
document.body.appendChild(this.frame);
this.frame.appendChild(this.getHtml());
this.focus();
}
/**
* @override
*/
escape(event) {
this.unpost();
this.unpostWidgets();
}
/**
* @override
*/
unpost() {
super.unpost();
if (this.widgets.length > 0) {
return;
}
this.frame.parentNode.removeChild(this.frame);
let store = this.getStore();
if (!this.moving) {
store.insertTaborder();
}
store.getActive().focus();
}
/**
* @override
*/
left(event) {
this.move_(this.store_.previous());
}
/**
* @override
*/
right(event) {
this.move_(this.store_.next());
}
/**
* @return {HTMLElement} The frame element wrapping all the elements of the
* menu.
*/
getFrame() {
return this.frame;
}
/**
* @return {MenuStore} The store of this menu.
*/
getStore() {
return this.store_;
}
/**
* @param {Event|number|HTMLElement} numberOrEvent The overloaded first
* argument.
* @param {number} isY The y coordinate.
*/
post(numberOrEvent, isY) {
if (typeof (isY) !== 'undefined') {
if (!this.moving) {
this.getStore().removeTaborder();
}
super.post(numberOrEvent, isY);
return;
}
let node;
let x;
let y;
let keydown = false;
if (numberOrEvent instanceof Event) {
let event = numberOrEvent;
x = event['pageX'], y = event['pageY'];
if (!x && !y && event['clientX']) {
x = event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft;
y = event.clientY + document.body.scrollTop +
document.documentElement.scrollTop;
}
node = event.target;
keydown = event.type === 'keydown';
this.stop(event);
}
else {
node = numberOrEvent;
}
this.getStore().setActive(node);
this.anchor = this.getStore().getActive();
if ((keydown || (!x && !y)) && node) {
let offsetX = window.pageXOffset || document.documentElement.scrollLeft;
let offsetY = window.pageYOffset || document.documentElement.scrollTop;
let rect = node.getBoundingClientRect();
x = (rect.right + rect.left) / 2 + offsetX;
y = (rect.bottom + rect.top) / 2 + offsetY;
}
let rect = node.getBoundingClientRect();
let menu = this.getHtml();
let margin = 5;
if (x + menu.offsetWidth > document.body.offsetWidth - margin) {
x = document.body.offsetWidth - menu.offsetWidth - margin;
}
// Not sure what these do!
//
// // if (MENU.isMobile) {
// // x = Math.max(5,x-Math.floor(menu.offsetWidth/2)); y -= 20
// // }
// // MENU.skipUp = event.isContextMenu;
this.post(x, y);
}
/**
* Registers widgets that are opened by the menu.
* @param {Postable} widget The open widget.
*/
registerWidget(widget) {
this.widgets.push(widget);
}
/**
* Removes an opened widgets.
* @param {Postable} widget The closed widget.
*/
unregisterWidget(widget) {
let index = this.widgets.indexOf(widget);
if (index > -1) {
this.widgets.splice(index, 1);
}
if (this.widgets.length === 0) {
this.unpost();
}
}
/**
* Closes all widgets that were opened from this menu.
*/
unpostWidgets() {
this.widgets.forEach(x => x.unpost());
}
/**
* Moves to the given next element.
* @param {HTMLELement} next The next element in the sequence.
* @private
*/
move_(next) {
if (this.anchor && next !== this.anchor) {
this.moving = true;
this.unpost();
this.post(next);
this.moving = false;
}
}
/**
* Parses a JSON respresentation of a variable and inserts it into the
* variable pool of the context menu.
* @param {JSON} json The JSON object to parse.
*/
parseVariable({ name: name, value: value, action: action }) {
this.getPool().insert(new ContextMenu_1.Variable(name, value, action));
}
}
ContextMenu_1.ContextMenu = ContextMenu;
})(ContextMenu || (ContextMenu = {}));