Alien-GvaScript
view release on metacpan or search on metacpan
src/treeNavigator.js view on Meta::CPAN
// tabIndex for the tree element
elem.tabIndex = Math.max(elem.tabIndex, this.options.treeTabIndex);
this._clear_quick_navi();
if (options.keymap) {
this.keymap = options.keymap;
this.keymap.rules.push(keyHandlers);
}
else {
this.keymap = new GvaScript.KeyMap(keyHandlers);
// observe keyboard events on tree (preferred) or on document
var target = (elem.tabIndex < 0) ? document : elem;
this.keymap.observe("keydown", target, Event.stopNone);
}
this.rootElement.store('widget', this);
this.rootElement.addClassName(CSSPREFIX() + '-widget');
// selecting the first node
if (this.options.selectFirstNode) {
this.select(this.firstSubNode());
// if labels do not take focus but tree does, then set focus on the tree
if (this.options.tabIndex < 0 && elem.tabIndex >= 0)
elem.focus();
}
}
GvaScript.TreeNavigator.prototype = {
//-----------------------------------------------------
// Public methods
//-----------------------------------------------------
destroy: function() {
this._removeHandlers();
},
initSubTree: function (tree_root) {
tree_root = $(tree_root);
// get the labels of the sub tree
var labels = tree_root.select('.'+this.classes.label);
// add tabIndex per label
if (this.options.tabIndex >= 0) {
_idx = this.options.tabIndex;
labels.each(function(label) {
label.tabIndex = _idx;
});
}
// add tree navigation buttons per label
if (this.options.createButtons) {
var button = document.createElement("span");
button.className = this.classes.button;
labels.each(function(label) {
label.parentNode.insertBefore(button.cloneNode(true), label);
});
}
},
isClosed: function (node) {
return Element.hasAnyClass(node, this.classes.closed);
},
isVisible: function(elem) { // true if elem is not display:none
return !(elem.offsetWidth == 0 && elem.offsetHeight == 0);
},
isLeaf: function(node) {
return Element.hasAnyClass(node, this.classes.leaf);
},
isRootElement: function(elem) {
return (elem === this.rootElement);
},
isLabel: function(elem) {
if(elem.hasClassName(this.classes.label))
return elem;
else
return Element.navigateDom(elem, 'parentNode', this.classes.label);
},
close: function (node) {
if (this.isLeaf(node))
return;
Element.addClassName(node, this.classes.closed);
this.fireEvent("Close", node, this.rootElement);
},
open: function (node) {
if (this.isLeaf(node))
return;
Element.removeClassName(node, this.classes.closed);
var node_content = this.content(node);
// if inline content, adjust scrollbar to make visible (if necessary)
// FIXME: only works for default scrollingContainer
if (node_content) {
this.scrollTo(node, true);
}
// ajax content -> go get it
else {
this.loadContent(node);
}
this.fireEvent("Open", node, this.rootElement);
},
toggle: function(node) {
if (this.isClosed(node))
this.open(node);
else
this.close(node);
},
openEnclosingNodes: function (elem) {
var node = this.enclosingNode(elem);
while (node) {
if (this.isClosed(node))
this.open(node);
node = this.parentNode(node);
}
},
openAtLevel: function(elem, level) {
var method = this[(level > 1) ? "open" : "close"];
var node = this.firstSubNode(elem);
while (node) {
method.call(this, node); // open or close
this.openAtLevel(node, level-1);
node = this.nextSibling(node);
}
},
loadContent: function (node) {
var url = node.getAttribute('tn:contenturl');
// TODO : default URL generator at the tree level
if (url) {
var content = this.content(node);
if (!content) {
content = document.createElement('div');
content.className = this.classes.content;
var content_type = node.getAttribute('content_type');
if (content_type) content.className += " " + content_type;
content.innerHTML = "loading " + url;
node.insertBefore(content, null); // null ==> insert at end of node
}
this.fireEvent("BeforeLoadContent", node, this.rootElement);
var treeNavigator = this; // needed for closure below
var callback = function() {
treeNavigator.initSubTree(content);
treeNavigator.fireEvent("AfterLoadContent", node, this.rootElement);
};
new Ajax.Updater(content, url, {onComplete: callback});
return true;
}
},
select: function (node) {
var previousNode = this.selectedNode;
// re-selecting the current node is a no-op
if (node == previousNode) return;
// deselect the previously selected node
if (previousNode) {
var label = this.label(previousNode);
if (label) {
Element.removeClassName(label, this.classes.selected);
}
}
// select the new node
this.selectedNode = node;
if (node) {
this._assertNodeOrLeaf(node, 'select node');
var label = this.label(node);
if (!label) {
throw new Error("selected node has no label");
src/treeNavigator.js view on Meta::CPAN
|| document.body.style.overflowY == 'hidden'
|| document.body.scroll == 'no') // IE
return;
}
else {
// on element
if(container.style.overflow == 'hidden'
|| container.style.overflowY == 'hidden')
return;
}
// test if the node in 'in view'
_container_y_start = container.scrollTop;
_container_y_end = _container_y_start + container.clientHeight;
_node_y = Element.cumulativeOffset(node).top + (with_content? node.offsetHeight: 0);
// calculate padding space between the selected node and
// the edge of the scrollable container
_perc = this.options.autoScrollPercentage || 0;
_padding = container.clientHeight * _perc / 100;
// calculate delta scroll to affect on scrollingContainer
_delta = 0;
// node is beneath scrolling area
if(_node_y > _container_y_end - _padding) {
_delta = _node_y - _container_y_end + _padding;
}
// node is above scrolling area
if(_node_y < _container_y_start + _padding) {
_delta = _container_y_start - _node_y - _padding;
}
if(_delta != 0) {
// amount required to scroll to greater than available document height
if(_delta > container.clientHeight - _padding) {
// make label top
var lbl_pos = Element.cumulativeOffset(this.label(node)).top;
container.scrollTop = lbl_pos - _padding;
}
else
container.scrollTop += parseInt(_delta)
}
return;
},
label: function(node) {
this._assertNodeOrLeaf(node, 'label: arg type');
return Element.navigateDom(node.firstChild, 'nextSibling',
this.classes.label);
},
content: function(node) {
if (this.isLeaf(node)) return null;
this._assertNode(node, 'content: arg type');
return Element.navigateDom(node.lastChild, 'previousSibling',
this.classes.content);
},
parentNode: function (node) {
this._assertNodeOrLeaf(node, 'parentNode: arg type');
return Element.navigateDom(
node.parentNode, 'parentNode', this.classes.node,
this.isRootElement.bind(this));
},
nextSibling: function (node) {
this._assertNodeOrLeaf(node, 'nextSibling: arg type');
return Element.navigateDom(node.nextSibling, 'nextSibling',
this.classes.nodeOrLeaf);
},
previousSibling: function (node) {
this._assertNodeOrLeaf(node, 'previousSibling: arg type');
return Element.navigateDom(node.previousSibling, 'previousSibling',
this.classes.nodeOrLeaf);
},
firstSubNode: function (node) {
node = node || this.rootElement;
var parent = (node == this.rootElement) ? node
: this.isLeaf(node) ? null
: this.content(node);
return parent ? Element.navigateDom(parent.firstChild, 'nextSibling',
this.classes.nodeOrLeaf)
: null;
},
lastSubNode: function (node) {
node = node || this.rootElement;
var parent = (node == this.rootElement) ? node
: this.isLeaf(node) ? null
: this.content(node);
return parent ? Element.navigateDom(parent.lastChild, 'previousSibling',
this.classes.nodeOrLeaf)
: null;
},
lastVisibleSubnode: function(node) {
node = node || this.rootElement;
while(!this.isClosed(node)) {
var lastSubNode = this.lastSubNode(node);
if (!lastSubNode) break;
node = lastSubNode;
}
return node;
},
// find next displayed node (i.e. skipping hidden nodes).
nextDisplayedNode: function (node) {
this._assertNodeOrLeaf(node, 'nextDisplayedNode: arg type');
// case 1: node is opened and has a subtree : then return first subchild
if (!this.isClosed(node)) {
var firstSubNode = this.firstSubNode(node);
if (firstSubNode) return firstSubNode;
}
// case 2: current node or one of its parents has a sibling
while (node) {
var sibling = this.nextSibling(node);
if (sibling) {
if (this.isVisible(sibling))
return sibling;
else
node = sibling;
}
else
node = this.parentNode(node);
}
// case 3: no next Node
return null;
},
// find previous displayed node (i.e. skipping hidden nodes).
previousDisplayedNode: function (node) {
this._assertNodeOrLeaf(node, 'previousDisplayedNode: arg type');
var node_init = node;
while (node) {
node = this.previousSibling(node);
if (node && this.isVisible(node))
return this.lastVisibleSubnode(node);
}
// if no previous sibling
return this.parentNode(node_init);
},
enclosingNode: function (elem) {
return Element.navigateDom(
$(elem), 'parentNode', this.classes.nodeOrLeaf,
this.isRootElement.bind(this));
},
// flash the node
flash: function (node) {
var label = this.label(node);
ASSERT(label, "node has no label");
label.flash({duration: 200});
},
fireEvent: function(eventName, elem) {
var args = [eventName];
while (elem) {
args.push(elem);
elem = this.parentNode(elem);
}
args.push(this.rootElement);
return GvaScript.fireEvent.apply(this, args);
},
//-----------------------------------------------------
// Private methods
//-----------------------------------------------------
// quick navigation initialization:
// - exit navi_mode
// - clear navi_word
// - clear match result
_clear_quick_navi: function() {
if(this._quick_navi_mode !== false)
window.clearTimeout(this._quick_navi_mode);
this._quick_navi_mode = false; // quick_navi mode active (navi timer)
this._quick_navi_word = ""; // word to navigate to
this.labels_array = null; // tree labels array
},
_assertNode: function(elem, msg) {
ASSERT(elem && Element.hasAnyClass(elem, this.classes.node), msg);
},
_assertNodeOrLeaf: function(elem, msg) {
ASSERT(elem && Element.hasAnyClass(elem, this.classes.nodeOrLeaf), msg);
},
_addHandlers: function() {
Event.observe(
this.rootElement, "mouseover",
this._treeMouseOverHandler.bindAsEventListener(this));
Event.observe(
this.rootElement, "mouseout",
this._treeMouseOutHandler.bindAsEventListener(this));
Event.observe(
// observing "mouseup" instead of "click", because "click"
// on MSIE8 only fires when there is a tabindex
this.rootElement, "mouseup",
this._treeClickHandler.bindAsEventListener(this));
Event.observe(
this.rootElement, "dblclick",
this._treeDblClickHandler.bindAsEventListener(this));
},
_removeHandlers: function() {
this.rootElement.stopObserving();
this.rootElement.unregister();
},
//-----------------------------------------------------
// mouse handlers
//-----------------------------------------------------
_treeClickHandler : function(event) {
var target = Event.element(event);
// IE: click on disabled input will fire the event
// with event.srcElement null
if(target.nodeType != 1) return;
// ignore right mousedown
if(!Event.isLeftClick(event)) return;
// button clicked
if(target.hasClassName(this.classes.button)) {
// as not to fire blur_handler
// on treeNode
Event.stop(event);
return this._buttonClicked(target.parentNode);
}
// label (or one of its childElements) clicked
if(label = this.isLabel(target)) {
return this._labelClicked(label.parentNode, event);
}
},
_treeDblClickHandler : function(event) {
var target = Event.element(event);
if(target.nodeType != 1) return;
// only consider doubleclicks on labels
if(!(label = this.isLabel(target))) return;
var event_stop_mode;
// should_ping_on_dblclick was just set within _labelClicked
if (this.should_ping_on_dblclick) {
event_stop_mode = this.fireEvent("Ping", label.parentNode, this.rootElement);
}
// stop the event unless the ping_handler decided otherwise
Event.detailedStop(event, event_stop_mode || Event.stopAll);
},
_treeMouseOverHandler: function(event) {
var target = Event.element(event);
if(target.nodeType != 1) return;
if(label = this.isLabel(target)) {
Element.addClassName(label, this.classes.mouse);
Event.stop(event);
}
},
_treeMouseOutHandler: function(event) {
var target = Event.element(event);
if(target.nodeType != 1) return;
if(label = this.isLabel(target)) {
Element.removeClassName(label, this.classes.mouse);
Event.stop(event);
}
},
_buttonClicked : function(node) {
var method = this.isClosed(node) ? this.open : this.close;
method.call(this, node);
if (this.options.selectOnButtonClick) {
window.setTimeout(function() {
this.select(node);
}.bind(this), 0);
}
},
_labelClicked : function(node, event) {
// situation before the mousedown
var is_selected = (this.selectedNode == node);
var is_first_click = !is_selected;
// select node if it wasn't
if (!is_selected) this.select(node);
// should ping : depends on options.noPingOnFirstClick
var should_ping = (!is_first_click) || !this.options.noPingOnFirstClick;
// do the ping if necessary
var event_stop_mode;
if (should_ping)
event_stop_mode = this.fireEvent("Ping", node, this.rootElement);
// avoid a second ping from the dblclick handler
this.should_ping_on_dblclick = !should_ping;
// stop the event unless the ping_handler decided otherwise
Event.detailedStop(event, event_stop_mode || Event.stopAll);
},
//-----------------------------------------------------
// Keyboard handlers
//-----------------------------------------------------
_addTabbingBehaviour: function() {
if (this.options.tabIndex < 0) return; // no tabbing
var treeNavigator = this; // handlers will be closures on this
// focus handler
var focus_handler = function(e) {
var label = e._target;
label.writeAttribute('hasFocus', 'hasFocus');
var node = Element.navigateDom(label, 'parentNode',
treeNavigator.classes.nodeOrLeaf);
// not yet been selected
if(node && !label.hasClassName(treeNavigator.classes.selected)) {
treeNavigator.select (node);
}
};
// blur handler
var blur_handler = function(e) {
var label = e._target;
label.removeAttribute('hasFocus');
// deselect the previously selected node
treeNavigator.select(null);
};
// focus and blur do not bubble
// workaround per browser
focus_handler = focus_handler.bindAsEventListener(this);
blur_handler = blur_handler.bindAsEventListener(this);
this.rootElement.register('.'+this.classes.label, 'focus', focus_handler);
this.rootElement.register('.'+this.classes.label, 'blur', blur_handler );
},
//-----------------------------------------------------
// timeout handler for firing Select/Deselect events
//-----------------------------------------------------
_selectionTimeoutHandler: function(previousNode) {
this._selectionTimeoutId = null;
var newNode = this.selectedNode;
// fire events
if (previousNode != newNode) {
if (previousNode) {
this.fireEvent("Deselect", previousNode, this.rootElement);
}
if (newNode) {
this.fireEvent("Select", newNode, this.rootElement);
}
}
},
//-----------------------------------------------------
// Key handlers
//-----------------------------------------------------
_charHandler: function (event) {
var selectedNode = this.selectedNode;
if(! selectedNode) return;
// stop firefox quick search if enabled
// via "accessibility.typeaheadfind" => 'true'
Event.stop(event);
this._quick_navi_word += event.keyName; // always uppercase
src/treeNavigator.js view on Meta::CPAN
.toUpperCase().startsWith(word);
});
return match;
}
// first look ahead then look back
var matching_label = find_match(labels[1], this._quick_navi_word)
|| find_match(labels[0], this._quick_navi_word);
// found a match -> make it visible and select it
if(matching_label) {
this.openEnclosingNodes(matching_label);
var znode = this.enclosingNode(matching_label);
this.scrollTo(znode);
this.select (znode);
}
// no match -> flash the selected label
else {
this.label(this.selectedNode).flash();
}
},
_downHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode) {
var nextNode = this.nextDisplayedNode(selectedNode);
if (nextNode) {
this.scrollTo(nextNode);
this.select (nextNode);
}
else this.flash(selectedNode);
Event.stop(event);
}
// otherwise: do nothing and let default behaviour happen
},
_upHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode) {
var prevNode = this.previousDisplayedNode(selectedNode);
if (prevNode) {
this.scrollTo(prevNode);
this.select (prevNode);
}
else this.flash(selectedNode);
Event.stop(event);
}
// otherwise: do nothing and let default behaviour happen
},
_leftHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode) {
if (!this.isLeaf(selectedNode) && !this.isClosed(selectedNode)) {
this.close(selectedNode);
}
else {
var zparent = this.parentNode(selectedNode);
if (zparent) {
this.scrollTo(zparent);
this.select (zparent);
}
else
this.flash(selectedNode);
}
Event.stop(event);
}
},
_rightHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode) {
if (this.isLeaf(selectedNode)) return;
if (this.isClosed(selectedNode))
this.open(selectedNode);
else {
var subNode = this.firstSubNode(selectedNode);
if (subNode) {
this.scrollTo(subNode);
this.select (subNode);
}
else
this.flash(selectedNode);
}
Event.stop(event);
}
},
_tabHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode && this.isClosed(selectedNode)) {
this.open(selectedNode);
var label = this.label(selectedNode);
Event.stop(event);
}
},
_kpPlusHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode && this.isClosed(selectedNode)) {
this.open(selectedNode);
Event.stop(event);
}
},
_kpMinusHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode && !this.isClosed(selectedNode)) {
this.close(selectedNode);
Event.stop(event);
}
},
_kpStarHandler: function (event) {
var selectedNode = this.selectedNode;
if (selectedNode) {
var nodes = Element.getElementsByClassNames(
selectedNode,
this.classes.node
);
nodes.unshift(selectedNode);
( run in 0.559 second using v1.01-cache-2.11-cpan-02777c243ea )