Christian Krebs avatar Christian Krebs committed f110623

Updated tooltip widget.

Comments (0)

Files changed (4)

resources/scripts/dom.js

 if (!window.opera)
 {
-  window.opera = 
+  window.opera =
   {
     postError: function(a){console.log(a);},
     stpVersion: true
   {
     opera.postError('setter: '+this.nodeName + ', '+scroll_top);
     setter.call(this, scroll_top);
-  });  
+  });
   Element.prototype.__defineGetter__('scrollTop', function()
   {
     var scroll_top = getter.call(this);
   });
 })();
 */
-if (document.createElementNS && 
+
+/* testing in Chrome or FF
+if (document.createElementNS &&
     document.createElement('div').namespaceURI != 'http://www.w3.org/1999/xhtml')
-{  
+{
   Document.prototype.createElement = document.createElement = function(name)
   {
     return this.createElementNS('http://www.w3.org/1999/xhtml', name);
   };
 }
+*/
 
 if (!Element.prototype.contains)
 {
   Element.prototype.contains = function(ele)
   {
-    if (ele == this)
-      return true;
-    var all = this.getElementsByTagName('*'), i = 0, cur = null;
-    for (; (cur = all[i]) && cur != ele; i++);
-    return Boolean(cur);
+    while (ele && ele != this)
+      ele = ele.parentNode;
+    return Boolean(ele);
   }
 }
 
+if (!Element.prototype.insertAdjacentHTML)
+{
+  Element.prototype.insertAdjacentHTML = function(position, markup)
+  {
+    if (position == 'beforeend')
+    {
+      var div = this.appendChild(document.createElement('div'));
+      div.innerHTML = markup;
+      var range = document.createRange();
+      range.selectNodeContents(div);
+      this.replaceChild(range.extractContents(), div);
+      return this.firstElementChild;
+    }
+  }
+}
+
+if (typeof document.createElement('div').classList == 'undefined')
+{
+  Element.prototype.__defineGetter__('classList', function()
+  {
+    return this.className.split(/\s+/);
+  });
+  Element.prototype.__defineSetter__('classList', function(){});
+}
+
 /**
  * @fileoverview
  * Helper function prototypes related to DOM objects and the DOM
 
 Element.prototype.render = Document.prototype.render = function(args, namespace)
 {
-
-  if (typeof args == 'string' && this.nodeType == 1)
+  var args_is_string = typeof args == 'string';
+  if (this.nodeType == 1 && args_is_string ||
+      (args.length == 1 && typeof args[0]  == 'string' && /</.test(args[0])))
   {
-    this.insertAdjacentHTML('beforeend', args);
+    this.insertAdjacentHTML('beforeend', args_is_string ? args : args[0]);
     return this.firstElementChild;
   }
 
         else if (namespace)
           ele = doc.createElementNS(namespace, first_arg.slice(prefix_pos + 1));
         else
+        {
           ele = first_arg in CustomElements ? CustomElements[first_arg].create() : doc.createElement(first_arg);
+        }
         i++;
       }
       arg = args[i];
       {
         if (typeof args[i] != 'string')
         {
-          throw "TemplateSyntaxError, expected 'string', got " + 
-                (typeof args[i]) + "for TEXT or KEY";
+          throw "TemplateSyntaxError, expected 'string', got " +
+                (typeof args[i]) + " for TEXT or KEY";
         }
         if (typeof args[i + 1] == 'string')
         {
 
 Element.prototype.re_render = function(args)
 {
-  var div = document.createElement('div');
-  var doc_frag = document.createDocumentFragment();
-  div.render(args);
-  while (div.firstChild)
-    doc_frag.appendChild(div.firstChild);
-  this.parentNode.replaceChild(doc_frag, this);
+  var parent = this.parentNode, ret = [];
+  if (parent)
+  {
+    var div = document.createElement('div');
+    var doc_frag = document.createDocumentFragment();
+    div.render(args);
+    while (div.firstChild)
+    {
+      ret.push(doc_frag.appendChild(div.firstChild));
+    }
+    parent.replaceChild(doc_frag, this);
+    return ret;
+  }
+  return null;
 }
 
 /**
 /**
  * Add the css class "name" to the element's list of classes
  * fixme: Does not work with dashes in the name!
+ * Note: Uses get/setAttribute instead of .className so it will
+ * work on both html and svg elements
  */
 Element.prototype.addClass = function(name)
 {
-  if (!(new RegExp('\\b' + name + '\\b')).test(this.className))
+  var c = this.getAttribute("class");
+  if (!(new RegExp('\\b' + name + '\\b')).test(c))
   {
-    this.className = (this.className ? this.className + ' ' : '') + name;
+    this.setAttribute("class", (c ? c + ' ' : '') + name);
   }
   return this;
 };
 
 /**
  * Remove the css class "name" from the elements list of classes
+ * Note: Uses get/setAttribute instead of .className so it will
+ * work on both html and svg elements
  */
 Element.prototype.removeClass = function(name)
 {
+  var c = this.getAttribute("class");
   var re = new RegExp(name + ' ?| ?' + name);
-  if (re.test(this.className))
+  if (re.test(c))
   {
-    this.className = this.className.replace(re, '');
+    this.setAttribute("class", c.replace(re, ''));
   }
   return this;
 };
 };
 
 /**
- * Returns the next sibling of the element that is an element. Ignores
- * nodes that are not elements
- */
-Element.prototype.getNextSiblingElement = function()
-{
-  var next = this.nextSibling;
-  while (next && next.nodeType != 1)
-  {
-    next = next.nextSibling;
-  }
-  return next;
-};
-
-// deprecated, use getBoundingClientRect instead
-Element.prototype.getTop = function()
-{
-  var c = this, o_p = null, top = c.offsetTop;
-  while (o_p = c.offsetParent)
-  {
-    top += o_p.offsetTop;
-    c = o_p;
-  }
-  return top;
-};
-
-/**
  * Insert node after target in the tree.
  */
 Element.prototype.insertAfter = function(node, target)
   {
     this.appendChild(node);
   }
-};
-
-/**
- * Returns an array of all children on the element that are also elements
- */
-Element.prototype.getChildElements = function()
-{
-  var children = this.childNodes, ret = [], c = null, i = 0;
-  for ( ; c = children[i]; i++)
-  {
-    if (c.nodeType == 1)
-    {
-      ret[ret.length] = c;
-    }
-  }
-  return ret;
+  return node;
 };
 
 /**
   var box = this.getBoundingClientRect();
   var client_x = box.left + box.width * .5;
   var client_y = box.top + box.height * .5;
-  event.initMouseEvent(type, true, true, window, 1, 
-                       window.screenLeft + client_x, 
-                       window.screenTop + client_y, 
-                       client_x, client_y, 
-                       ctrl_key, alt_key, shift_key, false, 
+  event.initMouseEvent(type, true, true, window, 1,
+                       window.screenLeft + client_x,
+                       window.screenTop + client_y,
+                       client_x, client_y,
+                       ctrl_key, alt_key, shift_key, false,
                        0, null);
   this.dispatchEvent(event);
 };
 Element.prototype.get_scroll_container = function()
 {
   var scroll_container = this;
-  while (scroll_container && 
+  while (scroll_container &&
          scroll_container.scrollHeight <= scroll_container.offsetHeight)
     scroll_container = scroll_container.parentNode;
   return (scroll_container == document.documentElement ||
   * A class to store a scroll position and reset it later in an asynchronous
   * environment.
   * The class takes a target as initialisation argument.
-  * The scroll position is stored for the first scroll container 
+  * The scroll position is stored for the first scroll container
   * in the parent node chain of that target. The root element is
-  * disregarded as scroll container (this is a bit too Dragonfly specific. 
-  * Better would be to check the overflow property of the computed style to 
+  * disregarded as scroll container (this is a bit too Dragonfly specific.
+  * Better would be to check the overflow property of the computed style to
   * find a real scroll container).
   * Resetting the scroll position can be done with or without argument.
-  * Without argument. it resets the scrollTop and scrollLeft properties 
-  * of the scroll container to the stored values. With a target argument, 
-  * it scroll the target in the exact same position as the target of 
+  * Without argument. it resets the scrollTop and scrollLeft properties
+  * of the scroll container to the stored values. With a target argument,
+  * it scroll the target in the exact same position as the target of
   * the initialisation.
   */
 Element.ScrollPosition = function(target)
 }
 
 /**
-  * To reset the scroll position. 
-  * Without target, scrollTop and scrollleft are restored to 
+  * To reset the scroll position.
+  * Without target, scrollTop and scrollleft are restored to
   * the initialisation values.
-  * If target is set, the target is scrolled in the exact same position 
+  * If target is set, the target is scrolled in the exact same position
   * as the target of the initialisation.
+  * A secondary container can be specified. This will be used in case the
+  * initial scroll container was not set in get_scroll_container().
   */
-Element.ScrollPosition.prototype.reset = function(target)
+Element.ScrollPosition.prototype.reset = function(target, sec_container)
 {
   if (this._scroll_container)
   {
     if (target)
     {
       var target_box = target.getBoundingClientRect();
-      this._scroll_container.scrollTop -= this._delta_top - 
+      this._scroll_container.scrollTop -= this._delta_top -
                                           (target_box.top - this._container_top);
-      this._scroll_container.scrollTop -= this._delta_left - 
+      this._scroll_container.scrollTop -= this._delta_left -
                                           (target_box.left - this._container_left);
     }
     else
     {
       this._scroll_container.scrollTop = this._scroll_top;
-      this._scroll_container.scrollLeft = this._scroll_left; 
+      this._scroll_container.scrollLeft = this._scroll_left;
+    }
+  }
+  else if (sec_container)
+  {
+    var scroll_container = sec_container.get_scroll_container();
+    if (scroll_container)
+    {
+      scroll_container.scrollTop = 0;
+      scroll_container.scrollLeft = 0;
     }
   }
 }
 
-/* currently broken in Opera */
-Element.prototype.getWidth = function(e)
-{
-  var style = window.getComputedStyle(this, null);
-  return this.offsetWidth
-    - parseInt(style['paddingLeft'])
-    - parseInt(style['paddingRight'])
-    - parseInt(style['borderLeftWidth'])
-    - parseInt(style['borderRightWidth']);
-};
-
-Element.prototype.spliceInnerHTML = function(str)
-{
-  this.insertAdjacentHTML('afterEnd', str);
-  /*
-  var
-  temp = this.ownerDocument.createElement('div-parser'),
-  range = this.ownerDocument.createRange();
-  temp.innerHTML = str;
-  if(this.nextSibling)
-  {
-    range.selectNodeContents(this.parentNode.insertBefore(temp, this.nextSibling));
-  }
-  else
-  {
-    range.selectNodeContents(this.parentNode.appendChild(temp));
-  }
-  this.parentNode.replaceChild(range.extractContents(), temp);
-  */
-};
-
-/**
- * Get the first contained element with name nodeName
- */
-Element.prototype.getFirst = function(nodeName)
-{
-  return this.getElementsByTagName(nodeName)[0];
-};
-
-/**
- * Get the last contained element with name nodeName
- */
-Element.prototype.getLast = function(nodeName)
-{
-  var all = this.getElementsByTagName(nodeName);
-  return all[all.length - 1];
-};
-
-/**
- * Get the previous element of the same name as "current" that is a
- * child of the element. Return null if there is no such element.
- */
-Element.prototype.getPreviousSameNamed = function(current)
-{
-  var
-  nodeName = current && current.nodeName,
-  all = this.getElementsByTagName(nodeName),
-  cur = null,
-  i = 0;
-
-  for ( ; (cur = all[i]) && cur != current; i++);
-  return cur && all[i-1] || null;
-};
-
-/**
- * Same as getPreviousSameNamed but finds the next element
- */
-Element.prototype.getNextSameNamed = function(current)
-{
-  var
-  nodeName = current && current.nodeName,
-  all = this.getElementsByTagName(nodeName),
-  cur = null,
-  i = 0;
-
-  for ( ; (cur = all[i]) && cur != current; i++);
-  return cur && all[i+1] || null;
-};
-
-/**
- * Get the next element of the same name as "target" that is a
- * sibling of the element. Return null if there is no such element.
- */
-Element.prototype.getNextSameNamedSibling = function(target)
-{
-  var
-  next = this.nextSibling,
-  name = this.nodeName;
-
-  while (next && next.nodeName != name)
-  {
-    next = next.nextSibling;
-  }
-
-  return next;
-};
-
-/**
- * Same as getNextSameNamedSibling but finds previous sibling
- */
-Element.prototype.getPreviousSameNamedSibling = function(target)
-{
-  var
-  previous = this.previousSibling,
-  name = this.nodeName;
-
-  while (previous && previous.nodeName != name)
-  {
-    previous = previous.previousSibling;
-  }
-
-  return previous;
-};
-
 /**
  * Returns the next element for which the function "filter" returns true.
  * The filter functions is passed two arguments, the current candidate element
     }
   }
   return null;
-}
+};
 
 /* Get an attribute of the first hit of a DOM traversal. */
 Element.prototype.get_attr = function(traverse_type, name)
     }
   }
   return null;
-}
+};
 
+if (!Element.prototype.matchesSelector)
+{
+  Element.prototype.matchesSelector =
+    Element.prototype.oMatchesSelector ?
+    Element.prototype.oMatchesSelector :
+    function(selector)
+    {
+      var sel = this.parentNode.querySelectorAll(selector);
+      for (var i = 0; sel[i] && sel[i] != this; i++);
+      return Boolean(sel[i]);
+    }
+};
+
+/* The naming is not precise, it can return the element itself. */
+
+Element.prototype.get_ancestor = function(selector)
+{
+  var ele = this;
+  while (ele)
+  {
+    if (ele.nodeType == 1 && ele.matchesSelector(selector))
+    {
+      return ele;
+    }
+    ele = ele.parentNode;
+  }
+  return null;
+};
 
 /**
  * Make sure the element is visible in its scoll context.
   }
 };
 
+Element.prototype.hasTextNodeChild = function()
+{
+  for (var i = 0, child; child = this.childNodes[i]; i++)
+  {
+    if (child.nodeType == document.TEXT_NODE)
+    {
+      return true;
+    }
+  }
+  return false;
+}
+
 /**
  * Get the text content of the first node in Node with the name nodeName
  * Escapes opening angle brackets into less than entities. If node is not
 };
 
 /**
- * Get the value of an attribute called attr from the first child node called
- * nodeName. If node is not found, returns null
- * @argument nodeName {string} node name
- * @argument attr {string} attribute name
- * @returns {string}
- */
-Node.prototype.getAttributeFromNode = function(nodeName, attr)
-{
-  var node = this.getElementsByTagName(nodeName)[0];
-  if (node)
-  {
-    return node.getAttribute(attr);
-  }
-  return null;
-};
-
-/**
  * Returns the index of item in the nodelist
  * (The same behaviour as js1.6 array.indexOf)
  * @argument item {Element}
   return -1;
 };
 
+/**
+ * Return the sum of all the values in the array. If selectorfun is given,
+ * it will be called to retrieve the relevant value for each item in the
+ * array.
+ */
+Array.prototype.sum = function(selectorfun)
+{
+  if (selectorfun)
+  {
+    return this.map(selectorfun).sum();
+  }
+  else
+  {
+    var ret = 0;
+    this.forEach(function(e) { ret += e });
+    return ret
+  }
+};
+
+Array.prototype.unique = function()
+{
+  var ret = [];
+  this.forEach(function(e) { if (ret.indexOf(e) == -1) {ret.push(e) }});
+  return ret;
+}
+
+Array.prototype.__defineGetter__("last", function()
+{
+   return this[this.length - 1];
+});
+
+Array.prototype.__defineSetter__("last", function() {});
+
+Array.prototype.extend = function(list)
+{
+  this.push.apply(this, list);
+  return this;
+};
+
+Array.prototype.insert = function(index, list, replace_count)
+{
+  this.splice.apply(this, [index, replace_count || 0].extend(list));
+  return this;
+};
+
+
 StyleSheetList.prototype.getDeclaration = function(selector)
 {
   var sheet = null, i = 0, j = 0, rules = null, rule = null;
   return style && style.getPropertyValue(property) || '';
 };
 
-/**
- * Make sure there is a getElementsByClassName method if there is no native
- * implementation of it
- * @deprecated All verison of opera in which the dragonfly client runs should have this by now
- */
-(function() {
-  if (!document.getElementsByClassName)
-  {
-    Document.prototype.getElementsByClassName = Element.prototype.getElementsByClassName = function()
-    {
-      var eles = this.getElementsByTagName("*"),
-          ele = null, ret =[], c_n = '', cursor = null, i = 0, j = 0;
-      for ( ; c_n = arguments[i]; i++)
-      {
-        arguments[i] = new RegExp('(?:^| +)' + c_n + '(?: +|$)');
-      }
-      for (i = 0; ele = eles[i]; i++)
-      {
-        c_n = ele.className;
-        for (j = 0; (cursor = arguments[j]) && cursor.test(c_n); j++);
-        if (!cursor)
-        {
-          ret[ret.length] = ele;
-        }
-      }
-      return ret;
-    }
-  }
-})();
-
 if (!(function(){}).bind)
 {
   Function.prototype.bind = function (context)
  */
 String.prototype.isdigit = function()
 {
-  return !(/\D/.test(this));
+  return this.length && !(/\D/.test(this));
 };
 
-Array.prototype.extend = function(list)
+Array.prototype.contains = String.prototype.contains = function(str)
 {
-  this.push.apply(this, list);
-}
+  return this.indexOf(str) != -1;
+};
+
+String.prototype.startswith = function(str)
+{
+  return this.slice(0, str.length) === str;
+};
+
+String.prototype.endswith = function(str)
+{
+  return this.slice(this.length - str.length) === str;
+};
+
+String.prototype.zfill = function(width)
+{
+  return this.replace(/(^[+-]?)(.+)/, function(str, sign, rest) {
+    var fill = Array(Math.max(width - str.length + 1, 0)).join(0);
+    return sign + fill + rest;
+  });
+};
+
+String.prototype.ljust = function(width, char)
+{
+  return this + Array(Math.max(width - this.length + 1, 0)).join(char || ' ');
+};
+
+/**
+ * Capitalizes the first character of the string. Lowercases the rest of
+ * the characters, unless `only_first` is true.
+ */
+String.prototype.capitalize = function(only_first)
+{
+  var rest = this.slice(1);
+  if (!only_first)
+  {
+    rest = rest.toLowerCase();
+  }
+  return this[0].toUpperCase() + rest;
+};
+
+
+
+/**
+ * Local ISO strings, currently needed as datetime-local input values
+ * http://dev.w3.org/html5/markup/input.datetime-local.html#input.datetime-local.attrs.value
+ */
+Date.prototype.toLocaleISOString = function()
+{
+ return new Date(this.getTime() - this.getTimezoneOffset() * 1000 * 60).toISOString().replace('Z','');
+};
 
 /**
  * Convenience function for loading a resource with XHR using the get method.

resources/ui-scripts/tooltip/tooltip.css

 {
   position: absolute;
   z-index: 100;
+  padding: 5px;
+}
+
+#tooltip-background
+{
   background-color: yellow;
   border-radius: 5px;
   padding: 1px 5px;

resources/ui-scripts/tooltip/tooltip.js

-var TooltipManager = function() {};
-
+var Tooltips = function() {};
 
 (function()
 {
   this.register = function(name, keep_on_hover) {};
   this.unregister = function(name, tooltip) {};
 
-  var Tooltip = function(keep_on_hover) 
-  {
-    this._init(keep_on_hover);
-  };
+  var Tooltip = function() {};
 
   Tooltip.prototype = new function()
   {
       */
     this.onhide = function(){};
 
+    this.ontooltipenter = function(){};
+
+    this.ontooltipleave = function(){};
+
     /**
       * To show the tooltip.
       * By default the tooltip is positioned in relation to the element
       * the optional 'box' argument that box is used instead to position
       * the tooltip.
       * @param content {String or Template} The content for the tooltip. Optional.
-      * If not set the "data-tooltip-text" value on the target element will be 
+      * If not set the 'data-tooltip-text' value on the target element will be 
       * used instead.
       * @param box The box to position the tootip. Optional. Only left, top and 
       * bottom are used. By default the box is given by the intersection of the 
       * To hide the tooltip.
       */
     this.hide = function(){};
-
-    /**
-      * A flag to indicate if the tooltip should be hidden or not on hovering
-      * the tooltip itself.
-      */
-    this.keep_on_hover = false;
-    
-    /* private */
-
-    this._init = function(keep_on_hover)
-    {
-      this.keep_on_hover = Boolean(keep_on_hover);
-    };
     
     /* implementation */
     
     {
       _hide_tooltip(this);
     };
+
+    /**
+      * Default implementation.
+      */
+    this.ontooltip = function(event, target)
+    {
+      this.show();
+    };
   };
 
 
   const DATA_TOOLTIP_TEXT = "data-tooltip-text";
   const HIDE_DELAY = 120;
   const SHOW_DELAY = 110;
-  const DISTANCE_X = 7;
-  const DISTANCE_Y = 7;
+  const DISTANCE_X = 0;
+  const DISTANCE_Y = -3;
 
   /* private */
 
   var _tooltips = {};
   var _is_setup = false;
   var _tooltip_ele = null;
+  var _tooltip_ele_first_child = null;
   var _current_tooltip = null;
   var _last_handler_ele = null;
   var _last_event = null;
     {
       if (ele == _tooltip_ele)
       {
-        if (_current_tooltip && _current_tooltip.keep_on_hover)
+        if (_current_tooltip && _current_tooltip._keep_on_hover)
         {
           _last_handler_ele = null;
           _clear_show_timeout();
         if (_current_tooltip != _tooltips[name])
         {
           _current_tooltip = _tooltips[name];
-          _tooltip_ele.innerHTML = "";
-          _tooltip_ele.appendChild(_current_tooltip._container);
+          _tooltip_ele_first_child.innerHTML = "";
+          _tooltip_ele_first_child.appendChild(_current_tooltip._container);
         }
         _last_handler_ele = ele;
         _last_event = event;
   var _set_show_timeout = function()
   {
     _clear_hide_timeout();
-    _clear_show_timeout();
-    _show_timeouts.push(setTimeout(_handle_show_tooltip, SHOW_DELAY));
+    if (!_show_timeouts.length)
+      _show_timeouts.push(setTimeout(_handle_show_tooltip, SHOW_DELAY));
   };
 
   var _clear_show_timeout = function()
 
   var _handle_show_tooltip = function(event, ele, name)
   {
+    _clear_show_timeout();
     if (_last_event && _last_handler_ele)
       _current_tooltip.ontooltip(_last_event, _last_handler_ele);
   };
     if (_current_tooltip)
       _current_tooltip.onhide();
 
-    _tooltip_ele.innerHTML = "";
+    _tooltip_ele_first_child.innerHTML = "";
     _tooltip_ele.style.cssText = "";
     _current_tooltip = null;
     _last_handler_ele = null;
         if (typeof content == "string")
           _current_tooltip._container.textContent = content;
         else
-          _current_tooltip._container.render(content);
+          _current_tooltip._container.clearAndRender(content);
       }
 
       if (!box && _last_handler_ele)
       {
-        var _handler_ele_box = _last_handler_ele.getBoundingClientRect();
-        box = {top: _handler_ele_box.top,
-               bottom: _handler_ele_box.bottom,
-               left: _last_event ? _last_event.clientX : _handler_ele_box.left};          
+        var handler_ele_box = _last_handler_ele.getBoundingClientRect();
+        box = {top: handler_ele_box.top,
+               bottom: handler_ele_box.bottom,
+               left: _last_event ? _last_event.clientX : handler_ele_box.left};          
       }
 
       if (box)
       _hide_tooltip();
   };
 
+  var _on_tooltip_enter = function(event)
+  {
+    if (_current_tooltip && _current_tooltip.ontooltipenter)
+      _current_tooltip.ontooltipenter(event);
+  };
+
+  var _on_tooltip_leave = function(event)
+  {
+    if (_current_tooltip && _current_tooltip.ontooltipleave)
+      _current_tooltip.ontooltipleave(event);
+  };
+
   var _setup = function()
   {
-    document.addEventListener('mouseover', _mouseover, false);
-    var tmpl =['div', 'id', 'tooltip-container', 'style', 'top: -100px;'];
+    document.addEventListener("mouseover", _mouseover, false);
+    var tmpl = ["div", 
+                ["div", "id", "tooltip-background"],
+                "id", "tooltip-container",
+                "style", "top: -100px;"];
     _tooltip_ele = (document.body || document.documentElement).render(tmpl);
+    _tooltip_ele.addEventListener('mouseenter', _on_tooltip_enter, false);
+    _tooltip_ele.addEventListener('mouseleave', _on_tooltip_leave, false);
+    _tooltip_ele_first_child = _tooltip_ele.firstChild;
   };
 
   /* implementation */
         document.addEvenetListener("load", _setup, false);
       _is_setup = true;  
     }
-    _tooltips[name] = new Tooltip(keep_on_hover);
-    _tooltips[name]._container = document.render(['div', 'class', 'tooltip']);
+    _tooltips[name] = new Tooltip();
+    _tooltips[name]._container = document.render(["div", "class", "tooltip"]);
+    _tooltips[name]._keep_on_hover = Boolean(keep_on_hover);
     return _tooltips[name];
   };
 
       _tooltips[name] = null;
   };
   
-}).apply(TooltipManager);
+}).apply(Tooltips);

ui-elements/tooltip/index.html

 	overflow: auto;
 }
 
+#viewport > div
+{
+	float: left;
+	padding: 10px;
+}
+
+h2
+{
+	width: 150px;
+	padding: 10px;
+	margin: 0;
+	background-color: hsl(0, 0%, 90%);
+	border-radius: 5px;
+	text-align: center;
+}
+
+ul
+{
+	list-style: none;
+	padding: 0;
+}
+
 span
 {
 	border: 1px solid hsl(0, 0%, 95%);
 }
+
+span:hover,
+.hover
+{
+	background-color: hsl(210, 100%, 80%);
+}
+
+.url-table
+{
+	border-collapse: collapse;
+}
+
+.url-table td
+{
+	vertical-align: top;
+	padding: 0 5px;
+}
 </style>
 <script src="../../resources/scripts/dom.js"></script>
 <script src="../../resources/ui-scripts/tooltip/tooltip.js"></script>
 
 <script>
 
+var URL = function(url) {this._init(url)};
+
+URL.PROPS = ["hash",
+             "host",
+             "hostname",
+             "href", 
+             "pathname",
+             "port",
+             "protocol",
+             "search"];
+
+URL.prototype = new function()
+{
+
+	URL.PROPS.forEach(function(prop)
+	{
+		this.__defineGetter__(prop, function()
+		{
+			if (!this._a)
+			{
+				this._a = document.createElement('a');
+				this._a.href = this._url;
+			}
+			return this._a[prop];
+		});
+		this.__defineSetter__(prop, function(){});
+	}, this);
+
+	this.__defineGetter__('filename', function()
+	{
+		var pos = this.pathname.lastIndexOf('/');
+		return pos > -1 ? this.pathname.slice(pos + 1) : "";
+	});
+
+	this.__defineSetter__('filename', function(){});
+
+	this._init = function(url)
+	{
+		this._url = url;
+	}
+};
+
+var urls =
+[
+	"http://www.nzz.ch/images/nzzexecutive_marktplatzteaser_gcp_teaserTopNews_1.4483277.1263198714.jpg",
+	"http://www.nzz.ch/file/1.13345026.1321549760!thumb.jpg",
+	"http://nzz.wemfbox.ch/cgi-bin/ivw/CP/nzzonline/nachrichten/startseite?r=&d=16476.42141779151&x=1024x768",
+	"http://www.nzz.ch/js/swfobject-2.2.js",
+	"http://www.nzz.ch/js/jquery-1.4.2.min.js",
+	"http://nzz.wemfbox.ch/blank.gif",
+	"http://aka-cdn-ns.adtech.de/apps/300/Ad6116652St3Sz154Sq101433066V0Id1/pochrono_realtime_1_300x600_en.swf?targetTAG=_blank&clickTarget=_blank&pathTAG=http%3A//aka-cdn-ns.adtech.de/apps/300/Ad6116652St3Sz154Sq101433066V0Id1/&closeTAG=javascript%3AcloseAdLayer2945478%28%29&openTAG=javascript%3AopenAdLayer2945478%28%29&expandTAG=javascript%3Aexpand2945478%28%29&collapseTAG=javascript%3Acollapse2945478%28%29&clicktarget=_blank&clickTarget=_blank&clickTARGET=_blank&CURRENTDOMAIN=www.nzz.ch",
+	"http://www.nzz.ch/js/jquery.scrollto-1.4.2.js",
+	"http://www.nzz.ch/js/jquery.fancybox-1.3.1.custom.js",
+	"http://www.nzz.ch/js/jquery-ui-1.8.5.custom.min.js",
+	"http://www.nzz.ch/js/init-15666.js",
+	"http://www.nzz.ch/js/initDepartment-15666.js",
+	"http://nzz.wemfbox.ch/2004/01/survey.js",
+	"http://www.nzz.ch/statistic?cid=2.138&r=&d=77787.47876330526",
+	"http://et.twyn.com/sense?pubid=154290&page=http://www.nzz.ch/",
+	"http://nzz01.webtrekk.net/631747235379823/wt?p=312,www_nzz_ch.,1,1024x768,32,1,1321612723532,0,1024x728,1&tz=1&eid=2132161208300845516&one=0&fns=0&la=en&cp1=1_Kategorie_Home&eor=1",
+	"http://adserver.adtech.de/bind?ckey1=twynet;cvalue1=NONE;expiresDays=0;adct=204;misc=1321612516271",
+	"http://www.google-analytics.com/ga_beta.js",
+	"http://www.google-analytics.com/__utm.gif?utmwv=5.2.2&utms=4&utmn=830551340&utmhn=www.nzz.ch&utmcs=utf-8&utmsr=1024x768&utmsc=32-bit&utmul=en&utmje=1&utmfl=10.1%20r102&utmdt=Startseite%20(NZZ%20Online%2C%20Neue%20Z%C3%BCrcher%20Zeitung)&utmhid=1533244502&utmr=-&utmp=%2F&utmac=UA-1925661-1&utmcc=__utma%3D120822999.1564935119.1321612084.1321612084.1321612084.1%3B%2B__utmz%3D120822999.1321612084.1.1.utmcsr%3D(direct)%7Cutmccn%3D(direct)%7Cutmcmd%3D(none)%3B&utmu=q~",
+	"http://cdn.cxense.com/cx.js",
+	"http://static.chartbeat.com/js/chartbeat.js",
+	"http://ping.chartbeat.net/ping?h=nzz.ch&p=%2F&u=ecnt4hxuukmfg90z.1321612084170&d=nzz.ch&g=8912&n=1&c=0&x=0&y=1&w=728&j=45&R=0&W=0&I=1&r=&b=96&t=fof2qtsygfr8sgpj&i=Startseite%20(NZZ%20Online%2C%20Neue%20Z%C3%BCrcher%20Zeitung)&_",
+	"http://comcluster.cxense.com/Repo/rep.html?ver=1&typ=pgv&rnd=irqjy4p8fuls&acc=9222267064078882903&sid=9222267064078882904&pid=0&loc=http%3A%2F%2Fwww.nzz.ch%2F&ref=&gol=&pgn=&ltm=1321612726486&tzo=-60&res=1024x768&jav=1&bln=en&cks=1321612084231477152327&ckp=13216120842311298368390&chs=utf-8&fls=1&flv=Shockwave%20Flash%2010.1%20r102"
+].map(function(url){return new URL(url);});
+
 window.onload = function()
 {
-	TooltipManager.register("test", true).ontooltip = function(event, target)
+	var tmpl = [];
+	var cur = null;
+
+	tmpl.push(cur = ['div', ['h2', 'default']]);
+	for (var i = 1, ul; i < 4; i++)
 	{
-		this.show();
+		cur.push(ul = ['ul']);
+		for (var j = 0; j < 7; j++)
+			ul.push(['li', 
+			        ['span', 'item ' + (i * j),
+			                 'data-tooltip', 'test',
+			                 'data-tooltip-text', 'test ' + (i * j)]]);
+		
 	}
+
+	tmpl.push(cur = ['div', ['h2', 'keep on hover']]);
+	for (var i = 1, ul; i < 4; i++)
+	{
+		cur.push(ul = ['ul']);
+		for (var j = 0; j < 7; j++)
+			ul.push(['li', 
+			        ['span', 'item ' + (i * j),
+			                 'data-tooltip', 'test2',
+			                 'data-tooltip-text', 'test ' + (i * j)]]);
+		
+	}
+
+	tmpl.push(cur = ['div', ['h2', 'keep on hover with fixed offset']]);
+	for (var i = 1, ul; i < 4; i++)
+	{
+		cur.push(ul = ['ul']);
+		for (var j = 0; j < 7; j++)
+			ul.push(['li', 
+			        ['span', 'item ' + (i * j),
+			                 'data-tooltip', 'test3',
+			                 'data-tooltip-text', 'test ' + (i * j)]]);
+		
+	}
+
+	tmpl.push(cur = ['div', ['h2', 'URLs']]);
+	cur.push(ul = ['ul']);
+	ul.extend(urls.map(function(url, index, urls)
+	{
+		return ['li', ['span', url.filename, 
+		                       'data-tooltip', 'test-url',
+		                       'data-index', String(index)]];
+	}));
+		
+	
+
+	document.getElementById('viewport').render(tmpl);
+	
+	Tooltips.register("test");
+
+	(function()
+  {
+  	var tooltip = Tooltips.register("test2", true);
+  	var last_target = null;
+  	var reset_last_target = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  	tooltip.ontooltip = function(event, target)
+  	{
+  		this.show();
+  		last_target = target;
+  	};
+
+   	tooltip.ontooltipenter = function()
+  	{
+  		if (last_target)
+  			last_target.addClass('hover');
+  	};
+
+  	tooltip.ontooltipleave = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  })();
+
+ 	(function()
+  {
+  	var tooltip = Tooltips.register("test3", true);
+  	var last_target = null;
+  	var reset_last_target = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  	tooltip.ontooltip = function(event, target)
+  	{
+  		var box = target.getBoundingClientRect();
+  		this.show(null, {top: box.top, bottom: box.bottom, left: box.left + 30});
+  		last_target = target;
+  	};
+
+   	tooltip.ontooltipenter = function()
+  	{
+  		if (last_target)
+  			last_target.addClass('hover');
+  	};
+
+  	tooltip.ontooltipleave = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  })();
+
+ 	(function()
+  {
+  	var tooltip = Tooltips.register("test-url", true);
+  	var last_target = null;
+  	var reset_last_target = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  	tooltip.ontooltip = function(event, target)
+  	{
+  		var box = target.getBoundingClientRect();
+  		var url = urls[target.getAttribute('data-index')];
+  		var tmpl = ['table'].extend(URL.PROPS.reduce(function(list, prop)
+	  	{
+	  		if (url[prop])
+  				list.push(['tr', ['td', prop], ['td', url[prop]]]);
+  			return list;
+  		}, []));
+  		tmpl.push('class', 'url-table');
+
+  		// this.show(tmpl, {top: box.top, bottom: box.bottom, left: box.left + 30});
+  		this.show(tmpl);
+  		last_target = target;
+  	};
+
+   	tooltip.ontooltipenter = function()
+  	{
+  		if (last_target)
+  			last_target.addClass('hover');
+  	};
+
+  	tooltip.ontooltipleave = function()
+  	{
+  		if (last_target)
+  			last_target.removeClass('hover');
+  	};
+
+  })();
+
 }
 </script>
 
-<div id="viewport">
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-<ul>
-	<li><span data-tooltip="test" data-tooltip-text="test 1">item 1</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 2">item 2</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 3">item 3</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 4">item 4</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 5">item 5</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 6">item 6</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 7">item 7</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 8">item 8</span></li>
-	<li><span data-tooltip="test" data-tooltip-text="test 9">item 9</span></li>
-</ul>
-
-
-</div>
+<div id="viewport"></div>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.