2012-06-19 15:47:35 +00:00
|
|
|
/*\
|
2012-07-14 14:57:36 +00:00
|
|
|
title: $:/core/modules/utils/dom/popup.js
|
2012-06-19 15:47:35 +00:00
|
|
|
type: application/javascript
|
|
|
|
module-type: utils
|
|
|
|
|
2012-08-03 14:09:48 +00:00
|
|
|
Module that creates a $tw.utils.Popup object prototype that manages popups in the browser
|
2012-06-19 15:47:35 +00:00
|
|
|
|
|
|
|
\*/
|
|
|
|
(function(){
|
|
|
|
|
|
|
|
/*jslint node: true, browser: true */
|
|
|
|
/*global $tw: false */
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
/*
|
2012-07-14 14:52:35 +00:00
|
|
|
Creates a Popup object with these options:
|
2012-06-19 15:47:35 +00:00
|
|
|
rootElement: the DOM element to which the popup zapper should be attached
|
|
|
|
*/
|
2012-07-14 14:52:35 +00:00
|
|
|
var Popup = function(options) {
|
2012-06-19 15:47:35 +00:00
|
|
|
options = options || {};
|
2014-11-22 10:19:03 +00:00
|
|
|
this.rootElement = options.rootElement || document.documentElement;
|
2014-11-21 17:07:03 +00:00
|
|
|
this.popups = []; // Array of {title:,wiki:,domNode:} objects
|
2012-06-19 15:47:35 +00:00
|
|
|
};
|
|
|
|
|
2014-11-21 17:07:03 +00:00
|
|
|
/*
|
|
|
|
Trigger a popup open or closed. Parameters are in a hashmap:
|
|
|
|
title: title of the tiddler where the popup details are stored
|
2018-08-09 18:39:58 +00:00
|
|
|
domNode: dom node to which the popup will be positioned (one of domNode or domNodeRect is required)
|
|
|
|
domNodeRect: rectangle to which the popup will be positioned
|
2014-11-21 17:07:03 +00:00
|
|
|
wiki: wiki
|
|
|
|
force: if specified, forces the popup state to true or false (instead of toggling it)
|
2018-08-09 18:39:58 +00:00
|
|
|
floating: if true, skips registering the popup, meaning that it will need manually clearing
|
2014-11-21 17:07:03 +00:00
|
|
|
*/
|
|
|
|
Popup.prototype.triggerPopup = function(options) {
|
|
|
|
// Check if this popup is already active
|
2016-01-03 18:05:08 +00:00
|
|
|
var index = this.findPopup(options.title);
|
2014-11-21 17:07:03 +00:00
|
|
|
// Compute the new state
|
|
|
|
var state = index === -1;
|
|
|
|
if(options.force !== undefined) {
|
|
|
|
state = options.force;
|
|
|
|
}
|
|
|
|
// Show or cancel the popup according to the new state
|
|
|
|
if(state) {
|
|
|
|
this.show(options);
|
|
|
|
} else {
|
|
|
|
this.cancel(index);
|
|
|
|
}
|
2012-06-19 15:47:35 +00:00
|
|
|
};
|
|
|
|
|
2016-01-03 18:05:08 +00:00
|
|
|
Popup.prototype.findPopup = function(title) {
|
|
|
|
var index = -1;
|
|
|
|
for(var t=0; t<this.popups.length; t++) {
|
|
|
|
if(this.popups[t].title === title) {
|
|
|
|
index = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return index;
|
|
|
|
};
|
|
|
|
|
2012-07-14 14:52:35 +00:00
|
|
|
Popup.prototype.handleEvent = function(event) {
|
2013-11-12 22:02:26 +00:00
|
|
|
if(event.type === "click") {
|
2014-11-21 17:07:03 +00:00
|
|
|
// Find out what was clicked on
|
|
|
|
var info = this.popupInfo(event.target),
|
|
|
|
cancelLevel = info.popupLevel - 1;
|
|
|
|
// Don't remove the level that was clicked on if we clicked on a handle
|
|
|
|
if(info.isHandle) {
|
|
|
|
cancelLevel++;
|
2013-11-12 22:02:26 +00:00
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
// Cancel
|
|
|
|
this.cancel(cancelLevel);
|
2012-06-19 15:47:35 +00:00
|
|
|
}
|
2012-06-19 15:56:29 +00:00
|
|
|
};
|
2012-06-19 15:47:35 +00:00
|
|
|
|
2014-11-21 17:07:03 +00:00
|
|
|
/*
|
|
|
|
Find the popup level containing a DOM node. Returns:
|
|
|
|
popupLevel: count of the number of nested popups containing the specified element
|
|
|
|
isHandle: true if the specified element is within a popup handle
|
|
|
|
*/
|
|
|
|
Popup.prototype.popupInfo = function(domNode) {
|
|
|
|
var isHandle = false,
|
|
|
|
popupCount = 0,
|
|
|
|
node = domNode;
|
|
|
|
// First check ancestors to see if we're within a popup handle
|
|
|
|
while(node) {
|
|
|
|
if($tw.utils.hasClass(node,"tc-popup-handle")) {
|
|
|
|
isHandle = true;
|
|
|
|
popupCount++;
|
|
|
|
}
|
|
|
|
if($tw.utils.hasClass(node,"tc-popup-keep")) {
|
|
|
|
isHandle = true;
|
|
|
|
}
|
|
|
|
node = node.parentNode;
|
2013-11-12 22:02:26 +00:00
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
// Then count the number of ancestor popups
|
|
|
|
node = domNode;
|
|
|
|
while(node) {
|
|
|
|
if($tw.utils.hasClass(node,"tc-popup")) {
|
|
|
|
popupCount++;
|
|
|
|
}
|
|
|
|
node = node.parentNode;
|
2012-06-19 15:47:35 +00:00
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
var info = {
|
|
|
|
popupLevel: popupCount,
|
|
|
|
isHandle: isHandle
|
|
|
|
};
|
|
|
|
return info;
|
2012-06-19 15:47:35 +00:00
|
|
|
};
|
|
|
|
|
2012-11-06 14:11:11 +00:00
|
|
|
/*
|
2014-11-21 17:07:03 +00:00
|
|
|
Display a popup by adding it to the stack
|
2012-11-06 14:11:11 +00:00
|
|
|
*/
|
2014-11-21 17:07:03 +00:00
|
|
|
Popup.prototype.show = function(options) {
|
2018-11-29 09:49:11 +00:00
|
|
|
// Find out what was clicked on
|
|
|
|
var info = this.popupInfo(options.domNode);
|
|
|
|
// Cancel any higher level popups
|
|
|
|
this.cancel(info.popupLevel);
|
2018-11-25 10:42:21 +00:00
|
|
|
|
2016-01-03 18:05:08 +00:00
|
|
|
// Store the popup details if not already there
|
2018-08-09 18:39:58 +00:00
|
|
|
if(!options.floating && this.findPopup(options.title) === -1) {
|
2016-01-03 18:05:08 +00:00
|
|
|
this.popups.push({
|
|
|
|
title: options.title,
|
|
|
|
wiki: options.wiki,
|
2019-07-26 16:13:53 +00:00
|
|
|
domNode: options.domNode,
|
|
|
|
noStateReference: options.noStateReference
|
2016-01-03 18:05:08 +00:00
|
|
|
});
|
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
// Set the state tiddler
|
2018-08-09 18:39:58 +00:00
|
|
|
var rect;
|
|
|
|
if(options.domNodeRect) {
|
|
|
|
rect = options.domNodeRect;
|
|
|
|
} else {
|
|
|
|
rect = {
|
|
|
|
left: options.domNode.offsetLeft,
|
|
|
|
top: options.domNode.offsetTop,
|
|
|
|
width: options.domNode.offsetWidth,
|
|
|
|
height: options.domNode.offsetHeight
|
|
|
|
};
|
|
|
|
}
|
2018-11-18 19:16:46 +00:00
|
|
|
var popupRect = "(" + rect.left + "," + rect.top + "," +
|
|
|
|
rect.width + "," + rect.height + ")";
|
|
|
|
if(options.noStateReference) {
|
|
|
|
options.wiki.setText(options.title,"text",undefined,popupRect);
|
|
|
|
} else {
|
|
|
|
options.wiki.setTextReference(options.title,popupRect);
|
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
// Add the click handler if we have any popups
|
|
|
|
if(this.popups.length > 0) {
|
|
|
|
this.rootElement.addEventListener("click",this,true);
|
2012-11-06 14:11:11 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-14 15:56:13 +00:00
|
|
|
/*
|
2014-11-21 17:07:03 +00:00
|
|
|
Cancel all popups at or above a specified level or DOM node
|
|
|
|
level: popup level to cancel (0 cancels all popups)
|
2013-10-14 15:56:13 +00:00
|
|
|
*/
|
2019-10-12 13:08:52 +00:00
|
|
|
Popup.prototype.cancel = function(level) {
|
2014-11-21 17:07:03 +00:00
|
|
|
var numPopups = this.popups.length;
|
|
|
|
level = Math.max(0,Math.min(level,numPopups));
|
|
|
|
for(var t=level; t<numPopups; t++) {
|
2019-10-12 13:08:52 +00:00
|
|
|
var popup = this.popups.pop();
|
|
|
|
if(popup.title) {
|
|
|
|
if(popup.noStateReference) {
|
|
|
|
popup.wiki.deleteTiddler(popup.title);
|
|
|
|
} else {
|
|
|
|
popup.wiki.deleteTiddler($tw.utils.parseTextReference(popup.title).title);
|
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(this.popups.length === 0) {
|
|
|
|
this.rootElement.removeEventListener("click",this,false);
|
2013-10-14 15:56:13 +00:00
|
|
|
}
|
2014-11-21 17:07:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
Returns true if the specified title and text identifies an active popup
|
|
|
|
*/
|
|
|
|
Popup.prototype.readPopupState = function(text) {
|
|
|
|
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/;
|
|
|
|
return popupLocationRegExp.test(text);
|
2013-10-14 15:56:13 +00:00
|
|
|
};
|
|
|
|
|
2012-07-14 14:52:35 +00:00
|
|
|
exports.Popup = Popup;
|
2012-06-19 15:47:35 +00:00
|
|
|
|
|
|
|
})();
|