Fixes to make nested popups work

Thus making the export button a lot more useful
This commit is contained in:
Jermolene
2014-11-21 17:07:03 +00:00
parent 27c9e7269e
commit b882a0dff1
9 changed files with 154 additions and 67 deletions
+116 -53
View File
@@ -19,40 +19,7 @@ Creates a Popup object with these options:
var Popup = function(options) {
options = options || {};
this.rootElement = options.rootElement || document.body;
};
Popup.prototype.show = function(options) {
this.cancel();
this.title = options.title;
this.wiki = options.wiki;
this.anchorDomNode = options.domNode;
$tw.utils.addClass(this.anchorDomNode,"tc-popup");
this.rootElement.addEventListener("click",this,false);
};
Popup.prototype.handleEvent = function(event) {
// Dismiss the popup if we get a click on an element that doesn't have .tc-popup class
if(event.type === "click") {
var node = event.target;
while(node && !$tw.utils.hasClass(node,"tc-popup")) {
node = node.parentNode;
}
if(!node) {
this.cancel();
}
}
};
Popup.prototype.cancel = function() {
if(this.anchorDomNode) {
$tw.utils.removeClass(this.anchorDomNode,"tc-popup");
this.anchorDomNode = null;
}
this.rootElement.removeEventListener("click",this,false);
if(this.title) {
this.wiki.deleteTiddler(this.title);
this.title = null;
}
this.popups = []; // Array of {title:,wiki:,domNode:} objects
};
/*
@@ -60,38 +27,134 @@ Trigger a popup open or closed. Parameters are in a hashmap:
title: title of the tiddler where the popup details are stored
domNode: dom node to which the popup will be positioned
wiki: wiki
force: if specified, forces the popup state to true or false
force: if specified, forces the popup state to true or false (instead of toggling it)
*/
Popup.prototype.triggerPopup = function(options) {
// Get the current popup state tiddler
var value = options.wiki.getTextReference(options.title,"");
// Check if the popup is open by checking whether it matches "(<x>,<y>)"
var state = !this.readPopupState(options.title,value);
if("force" in options) {
console.log("triggerPopup",options)
// Check if this popup is already active
var index = -1;
for(var t=0; t<this.popups.length; t++) {
if(this.popups[t].title === options.title) {
index = t;
}
}
// 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) {
// Set the position if we're opening it
this.cancel();
options.wiki.setTextReference(options.title,
"(" + options.domNode.offsetLeft + "," + options.domNode.offsetTop + "," +
options.domNode.offsetWidth + "," + options.domNode.offsetHeight + ")");
this.show(options);
} else {
this.cancel();
this.cancel(index);
}
};
Popup.prototype.handleEvent = function(event) {
console.log("handleEvent",event)
if(event.type === "click") {
// 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++;
}
// Cancel
this.cancel(cancelLevel);
}
};
/*
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;
}
// Then count the number of ancestor popups
node = domNode;
while(node) {
if($tw.utils.hasClass(node,"tc-popup")) {
popupCount++;
}
node = node.parentNode;
}
var info = {
popupLevel: popupCount,
isHandle: isHandle
};
console.log("Returning popupInfo",info)
return info;
};
/*
Display a popup by adding it to the stack
*/
Popup.prototype.show = function(options) {
console.log("show",options)
// Find out what was clicked on
var info = this.popupInfo(options.domNode);
// Cancel any higher level popups
this.cancel(info.popupLevel);
// Store the popup details
this.popups.push({
title: options.title,
wiki: options.wiki,
domNode: options.domNode
});
// Set the state tiddler
options.wiki.setTextReference(options.title,
"(" + options.domNode.offsetLeft + "," + options.domNode.offsetTop + "," +
options.domNode.offsetWidth + "," + options.domNode.offsetHeight + ")");
// Add the click handler if we have any popups
if(this.popups.length > 0) {
console.log("Adding click handler")
this.rootElement.addEventListener("click",this,true);
}
};
/*
Cancel all popups at or above a specified level or DOM node
level: popup level to cancel (0 cancels all popups)
*/
Popup.prototype.cancel = function(level) {
console.log("cancel",level)
var numPopups = this.popups.length;
level = Math.max(0,Math.min(level,numPopups));
for(var t=level; t<numPopups; t++) {
var popup = this.popups.pop();
if(popup.title) {
popup.wiki.deleteTiddler(popup.title);
}
}
if(this.popups.length === 0) {
console.log("Removing click handler")
this.rootElement.removeEventListener("click",this,false);
}
};
/*
Returns true if the specified title and text identifies an active popup
*/
Popup.prototype.readPopupState = function(title,text) {
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/,
result = false;
if(this.title === title) {
result = popupLocationRegExp.test(text);
}
return result;
Popup.prototype.readPopupState = function(text) {
console.log("readPopupState",text)
var popupLocationRegExp = /^\((-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+),(-?[0-9\.E]+)\)$/;
return popupLocationRegExp.test(text);
};
exports.Popup = Popup;
+14 -10
View File
@@ -462,20 +462,24 @@ Returns an object with the following fields, all optional:
exports.parseTextReference = function(textRef) {
// Separate out the title, field name and/or JSON indices
var reTextRef = /^\s*([^!#]+)?(?:(?:!!([^\s]+))|(?:##(.+)))?\s*/mg,
match = reTextRef.exec(textRef);
match = reTextRef.exec(textRef),
result = {};
if(match && reTextRef.lastIndex === textRef.length) {
// Return the parts
return {
title: match[1],
field: match[2],
index: match[3]
};
if(match[1]) {
result.title = match[1];
}
if(match[2]) {
result.field = match[2];
}
if(match[3]) {
result.index = match[3];
}
} else {
// If we couldn't parse it (eg it started with a)
return {
title: textRef
};
// If we couldn't parse it
result.title = textRef
}
return result;
};
/*
+7 -3
View File
@@ -36,15 +36,19 @@ ButtonWidget.prototype.render = function(parent,nextSibling) {
// Create element
var domNode = this.document.createElement("button");
// Assign classes
var classes = this["class"].split(" ") || [];
var classes = this["class"].split(" ") || [],
isPoppedUp = this.popup && this.isPoppedUp();
if(this.selectedClass) {
if(this.set && this.setTo && this.isSelected()) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
}
if(this.popup && this.isPoppedUp()) {
if(isPoppedUp) {
$tw.utils.pushTop(classes,this.selectedClass.split(" "));
}
}
if(isPoppedUp) {
$tw.utils.pushTop(classes,"tc-popup-handle");
}
domNode.className = classes.join(" ");
// Assign other attributes
if(this.style) {
@@ -101,7 +105,7 @@ ButtonWidget.prototype.isSelected = function() {
ButtonWidget.prototype.isPoppedUp = function() {
var tiddler = this.wiki.getTiddler(this.popup);
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(this.popup,tiddler.fields.text) : false;
var result = tiddler && tiddler.fields.text ? $tw.popup.readPopupState(tiddler.fields.text) : false;
return result;
};