diff --git a/Frameworks/Ajax/Ajax/Components/AjaxHoverable.api b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.api
new file mode 100644
index 00000000000..6028e233723
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.api
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.html b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.html
new file mode 100644
index 00000000000..769ea08b7e8
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.wod b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.wod
new file mode 100644
index 00000000000..badf1948f3c
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.wod
@@ -0,0 +1,76 @@
+ComponentContent : WOComponentContent {
+
+}
+
+HasHoverTextMessageConditional: WOConditional {
+ condition = ^toolTipMessage.toString;
+}
+
+HasImageResourceConditional: WOConditional {
+ condition = ^toolTipImageFilename.toString;
+}
+
+HasNSDataConditional: WOConditional {
+ condition = ^toolTipImageData.toString;
+}
+
+HoverAreaContainer: WOGenericContainer {
+ class = hoverAreaClasses;
+ elementName = "div";
+ style = hoverAreaInlineStyle;
+}
+
+HoverImgResource: WOImage {
+ filename = ^toolTipImageFilename;
+ framework = ^toolTipImageFramework;
+}
+
+NSDataImage: WOImage {
+ data = ^toolTipImageData;
+ mimeType = ^toolTipImageMimeType;
+}
+
+ToolTipContainer: WOGenericContainer {
+ id = idStr;
+ class = toolTipClasses;
+ elementName = "div";
+ style = toolTipInlineStyle;
+}
+
+ToolTipMessageText: WOString {
+ escapeHTML = false;
+ value = ^toolTipMessage;
+}
+
+showHoverableConditional: WOConditional {
+ condition = showHoverable;
+}
+
+useJavascriptHoverEffectConditional: WOConditional {
+ condition = useJavascriptForHoverEffect;
+}
+
+toolTipIDString: WOString {
+ value = idStr;
+}
+
+toolTipWidthString: WOString {
+ value = toolTipWidth;
+}
+
+useJavascriptOffsetX: WOString {
+ value = useJavascriptOffsetX;
+}
+
+useJavascriptOffsetY: WOString {
+ value = useJavascriptOffsetY;
+}
+
+isAjaxRedrawConditional : WOConditional {
+ condition = isAjaxRequest;
+}
+
+isNotAjaxRedrawConditional : WOConditional {
+ condition = isAjaxRequest;
+ negate = true;
+}
\ No newline at end of file
diff --git a/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.woo b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.woo
new file mode 100644
index 00000000000..a076a051a7e
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/Components/AjaxHoverable.wo/AjaxHoverable.woo
@@ -0,0 +1,4 @@
+{
+ "WebObjects Release" = "WebObjects 5.0";
+ encoding = "UTF-8";
+}
\ No newline at end of file
diff --git a/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxHoverable.java b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxHoverable.java
new file mode 100644
index 00000000000..d5edbba6066
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/Sources/er/ajax/AjaxHoverable.java
@@ -0,0 +1,236 @@
+package er.ajax;
+
+/*
+ * Aaron Rosenzweig Sept. 18, 2012
+ */
+
+import com.webobjects.appserver.WOComponent;
+import com.webobjects.appserver.WOContext;
+import com.webobjects.appserver.WOResponse;
+import com.webobjects.foundation.NSArray;
+
+import er.extensions.appserver.ERXResponseRewriter;
+import er.extensions.foundation.ERXStringUtilities;
+
+/**
+ * @binding hoverableWidth num of pixels for hoverable width (200 as default) (String)
+ * @binding additionalClassHoverArea
+ * @binding additionalClassToolTip
+ * @binding showHoverable
+ * @binding additionalStyleHoverArea
+ * @binding useJavascriptForHoverEffect
+ * @binding useJavascriptOffsetX num of pixels without a 'px' on the end. Just the raw number.
+ * @binding useJavascriptOffsetY num of pixels without a 'px' on the end. Just the raw number.
+ * @binding toolTipWidth
+ * @binding additionalStyleToolTip
+ * @binding toolTipHeight
+ * @binding toolTipAutoScroll
+ * @binding toolTipDropBelow
+ * @binding toolTipDropAbove
+ * @binding toolTipDropTopRight
+ * @binding toolTipDropTopLeft
+ * @binding toolTipDropBottomRight
+ * @binding toolTipDropBottomLeft
+ * @binding advancedToolTipLeft
+ * @binding advancedToolTipRight
+ * @binding advancedToolTipTop
+ * @binding advancedToolTipBottom
+ */
+
+public class AjaxHoverable extends WOComponent {
+ protected static final String AJAX_FRAMEWORK_NAME = "Ajax";
+ protected static final String LOCAL_CSS_FILE = "ajaxHoverable.css";
+ protected static final String LOCAL_JS_FILE = "ajaxHoverable.js";
+
+ protected String _idStr = null;
+
+ public AjaxHoverable(WOContext context) {
+ super(context);
+ }
+
+ public boolean synchronizesVariablesWithBindings() {
+ return false;
+ }
+
+ public static void addWebResourcesInHead(WOResponse response, WOContext context) {
+ AjaxUtils.addStylesheetResourceInHead(context, response, AJAX_FRAMEWORK_NAME, LOCAL_CSS_FILE);
+ AjaxUtils.addScriptResourceInHead(context, response, AJAX_FRAMEWORK_NAME, LOCAL_JS_FILE);
+ }
+
+ public void appendToResponse(WOResponse response, WOContext context) {
+ addWebResourcesInHead(response, context);
+ super.appendToResponse(response, context);
+ }
+
+ public String hoverAreaClasses() {
+ String classes = "erxHoverArea";
+ String userDefined = (String) valueForBinding("additionalClassHoverArea");
+ if (ERXStringUtilities.isNotBlank(userDefined)) {
+ classes += " " + userDefined;
+ }
+ return classes;
+ }
+
+ public String toolTipClasses() {
+ String classes = "erxToolTip";
+ if (useJavascriptForHoverEffect()) {
+ classes = "erxToolTipJS";
+ }
+ String userDefined = (String) valueForBinding("additionalClassToolTip");
+ if (ERXStringUtilities.isNotBlank(userDefined)) {
+ classes += " " + userDefined;
+ }
+ return classes;
+ }
+
+ public boolean showHoverable() {
+ boolean showHoverable = true;
+ if (valueForBinding("showHoverable") != null) {
+ Object showHoverableBinding = valueForBinding("showHoverable");
+
+ if (showHoverableBinding instanceof Boolean) {
+ showHoverable = ((Boolean)showHoverableBinding).booleanValue();
+ } else if (showHoverableBinding instanceof NSArray) {
+ NSArray tempArray = (NSArray)showHoverableBinding;
+ if (tempArray.count() == 0) {
+ showHoverable = false;
+ } else {
+ showHoverable = true;
+ }
+ } else if (showHoverableBinding instanceof Number) {
+ Number tempNumber = (Number)showHoverableBinding;
+ if (tempNumber.intValue() == 0) {
+ showHoverable = false;
+ } else {
+ showHoverable = true;
+ }
+ }
+
+ }
+ return showHoverable;
+ }
+
+ public String hoverAreaInlineStyle() {
+ String userDefined = (String) valueForBinding("additionalStyleHoverArea");
+
+ return userDefined;
+ }
+
+ public boolean useJavascriptForHoverEffect() {
+ Boolean useJavascriptForHoverEffect = (Boolean) valueForBinding("useJavascriptForHoverEffect");
+
+ if (useJavascriptForHoverEffect == null) {
+ useJavascriptForHoverEffect = Boolean.TRUE;
+ }
+
+ return useJavascriptForHoverEffect.booleanValue();
+ }
+
+ public String toolTipWidth() {
+ String toolTipWidth = (String) valueForBinding("toolTipWidth");
+ return toolTipWidth;
+ }
+
+ public Number useJavascriptOffsetX() {
+ Number returnVal = Integer.valueOf(0);
+ Number offsetValue = (Number) valueForBinding("useJavascriptOffsetX");
+ if (offsetValue != null) {
+ returnVal = offsetValue;
+ }
+
+ return returnVal;
+ }
+
+ public Number useJavascriptOffsetY() {
+ Number returnVal = Integer.valueOf(0);
+ Number offsetValue = (Number) valueForBinding("useJavascriptOffsetY");
+ if (offsetValue != null) {
+ returnVal = offsetValue;
+ }
+
+ return returnVal;
+ }
+
+ public String toolTipInlineStyle() {
+ String userDefined = (String) valueForBinding("additionalStyleToolTip");
+ String inlineStyle = "width: " + toolTipWidth() + "; ";
+
+ String toolTipHeight = (String) valueForBinding("toolTipHeight");
+ if (ERXStringUtilities.isNotBlank(toolTipHeight)) {
+ inlineStyle += "height: " + toolTipHeight + "; ";
+ }
+
+ boolean toolTipAutoScroll = valueForBinding("toolTipAutoScroll") != null && ((Boolean) valueForBinding("toolTipAutoScroll")).booleanValue();
+ if (toolTipAutoScroll) {
+ inlineStyle += "overflow: auto; ";
+ }
+
+ /*
+ * The following bindings are only "guesses". They make assumptions on how big the hoverArea container is.
+ * If they do not work correctly, you'll have to forget about them and use the advanced options.
+ */
+ boolean toolTipDropBelow = valueForBinding("toolTipDropBelow") != null && ((Boolean) valueForBinding("toolTipDropBelow")).booleanValue();
+ boolean toolTipDropAbove = valueForBinding("toolTipDropAbove") != null && ((Boolean) valueForBinding("toolTipDropAbove")).booleanValue();
+ boolean toolTipDropTopRight = valueForBinding("toolTipDropTopRight") != null && ((Boolean) valueForBinding("toolTipDropTopRight")).booleanValue();
+ boolean toolTipDropTopLeft = valueForBinding("toolTipDropTopLeft") != null && ((Boolean) valueForBinding("toolTipDropTopLeft")).booleanValue();
+ boolean toolTipDropBottomRight = valueForBinding("toolTipDropBottomRight") != null && ((Boolean) valueForBinding("toolTipDropBottomRight")).booleanValue();
+ boolean toolTipDropBottomLeft = valueForBinding("toolTipDropBottomLeft") != null && ((Boolean) valueForBinding("toolTipDropBottomLeft")).booleanValue();
+
+ /*
+ * You should only use these bindings if the "drop" bindings don't suit your needs. These set the top/bot/left/right properties
+ * of the toolTip div that shows on hover. The "drop" bindings make guesses for these values on your behalf.
+ */
+ String advancedToolTipLeft = (String) valueForBinding("advancedToolTipLeft");
+ String advancedToolTipRight = (String) valueForBinding("advancedToolTipRight");
+ String advancedToolTipTop = (String) valueForBinding("advancedToolTipTop");
+ String advancedToolTipBottom = (String) valueForBinding("advancedToolTipBottom");
+
+ if (ERXStringUtilities.isBlank(advancedToolTipLeft) && ERXStringUtilities.isBlank(advancedToolTipRight) &&
+ ERXStringUtilities.isBlank(advancedToolTipTop) && ERXStringUtilities.isBlank(advancedToolTipBottom)) {
+ if (toolTipDropAbove) {
+ inlineStyle += "bottom: 1.5em; ";
+ } else if (toolTipDropTopRight) {
+ inlineStyle += "bottom: 1.5em; left: 30px; ";
+ } else if (toolTipDropTopLeft) {
+ inlineStyle += "bottom: 1.5em; right: 30px; ";
+ } else if (toolTipDropBottomRight) {
+ inlineStyle += "left: 30px; ";
+ } else if (toolTipDropBottomLeft) {
+ inlineStyle += "right: 30px; ";
+ }
+ } else {
+ if (ERXStringUtilities.isNotBlank(advancedToolTipLeft)) {
+ inlineStyle += "left: " + advancedToolTipLeft + "; ";
+ }
+
+ if (ERXStringUtilities.isNotBlank(advancedToolTipRight)) {
+ inlineStyle += "right: " + advancedToolTipRight + "; ";
+ }
+
+ if (ERXStringUtilities.isNotBlank(advancedToolTipTop)) {
+ inlineStyle += "top: " + advancedToolTipTop + "; ";
+ }
+
+ if (ERXStringUtilities.isNotBlank(advancedToolTipBottom)) {
+ inlineStyle += "bottom: " + advancedToolTipBottom + "; ";
+ }
+ }
+
+ if (userDefined == null) {
+ userDefined = "";
+ }
+ return inlineStyle + userDefined;
+ }
+
+ public String idStr() {
+ if (ERXStringUtilities.isBlank(_idStr) && useJavascriptForHoverEffect()) {
+ _idStr = "hoverable_" + ERXStringUtilities.safeIdentifierName(context().elementID());
+ }
+ return _idStr;
+ }
+
+ public boolean isAjaxRequest() {
+ return AjaxUtils.isAjaxRequest(context().request());
+ }
+
+}
\ No newline at end of file
diff --git a/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.css b/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.css
new file mode 100644
index 00000000000..e2cc28ea7d8
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.css
@@ -0,0 +1,56 @@
+.erxHoverArea {
+ /*padding:3px 0;*/
+ /* border: solid black thin; */
+ /*text-align:center;*/
+ /*font-size: 70%;*/
+ position: relative;
+ display: -moz-inline-box;
+ display: inline-block;
+}
+
+/* target IE7 and below. These two definitions must be done seperately. Together they create a true inline block for IE. */
+*:first-child+html .erxHoverArea,
+* html .erxHoverArea {
+ /*IE hack, http://www.brunildo.org/test/InlineBlockLayout.html*/
+ display: inline-block;
+}
+
+*:first-child+html .erxHoverArea,
+* html .erxHoverArea {
+ /*IE hack, http://www.brunildo.org/test/InlineBlockLayout.html*/
+ display: inline;
+}
+
+.erxHoverArea div.erxToolTip,
+.erxHoverArea div.erxToolTipJS,
+#erxToolTipContents {
+ padding: .8em;
+ /*margin-top: 3px;*/
+ border: 1px solid #000;
+ background-color: #EEE;
+ color: #000;
+ /*font-size: 50%;*/
+ font-weight: normal;
+ position: absolute;
+ text-align: left;
+ z-index: 2000000;
+ white-space: normal;
+}
+
+.erxHoverArea div.erxToolTip,
+.erxHoverArea div.erxToolTipJS {
+ display: none;
+}
+
+/* target IE7 and below. This fixes the z index stack order bug*/
+*:first-child+html div.erxHoverArea:hover,
+* html div.erxHoverArea:hover {
+ border: 0px solid white;
+ /* width: 99%; */
+ z-index: 1;
+}
+
+div.erxHoverArea:hover div.erxToolTip {
+ display: block;
+}
+
diff --git a/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.js b/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.js
new file mode 100644
index 00000000000..3ed21629e52
--- /dev/null
+++ b/Frameworks/Ajax/Ajax/WebServerResources/ajaxHoverable.js
@@ -0,0 +1,104 @@
+// Inspired by Michael Leigeber in a tutorial post here:
+// http://sixrevisions.com/tutorials/javascript_tutorial/create_lightweight_javascript_tooltip/
+var erxToolTip=function(){
+ var id = 'erxToolTip';
+ var top = 3;
+ var left = 3;
+ var maxw = 300;
+ var speed = 10;
+ var timer = 20;
+ var endalpha = 95;
+ var alpha = 0;
+ var tt,t,c,b,h;
+ var ie = document.all ? true : false;
+ var hoverActionElem = null;
+ var offsetX = 0;
+ var offsetY = 0;
+ return{
+ show:function(hoverActionElement, v, w, offsetXValue, offsetYValue){
+ if (offsetXValue) {
+ offsetX = offsetXValue;
+ }
+
+ if (offsetYValue) {
+ offsetY = offsetYValue;
+ }
+
+ if(tt == null){
+ tt = document.createElement('div');
+ tt.setAttribute('id',id);
+ t = document.createElement('div');
+ t.setAttribute('id',id + 'Top');
+ c = document.createElement('div');
+ c.setAttribute('id',id + 'Contents');
+ c.setAttribute('class', 'erxDefaultFont');
+ b = document.createElement('div');
+ b.setAttribute('id',id + 'Bottom');
+ tt.appendChild(t);
+ tt.appendChild(c);
+ tt.appendChild(b);
+ document.body.appendChild(tt);
+ tt.style.opacity = 0;
+ tt.style.filter = 'alpha(opacity=0)';
+ tt.style.position = 'absolute';
+ tt.style.zIndex = 2000000;
+ }
+ tt.style.display = 'block';
+ c.innerHTML = v;
+ tt.style.width = w ? w : 'auto';
+ if(!w && ie){
+ t.style.display = 'none';
+ b.style.display = 'none';
+ tt.style.width = tt.offsetWidth;
+ t.style.display = 'block';
+ b.style.display = 'block';
+ }
+ if(tt.offsetWidth > maxw){tt.style.width = maxw + 'px'}
+
+ // (Aaron) Use Prototype and the erxHoverArea action element
+ hoverActionElem = hoverActionElement;
+ if ( ! hoverActionElem.erxHoverAreaMouseMoveRegistered) {
+ hoverActionElem.erxHoverAreaMouseMoveRegistered = true;
+ Element.observe(hoverActionElem, 'mousemove', erxToolTip.pos);
+ }
+
+ h = parseInt(tt.offsetHeight) + top;
+ clearInterval(tt.timer);
+ tt.timer = setInterval(function(){erxToolTip.fade(1)},timer);
+ },
+ pos:function(e){
+ var u = ie ? event.clientY + document.documentElement.scrollTop : e.pageY;
+ var l = ie ? event.clientX + document.documentElement.scrollLeft : e.pageX;
+
+ // this raises the hoverable to the top
+ //tt.style.top = (u - h) + 'px';
+
+ // this lowers the hoverable to the bottom
+ tt.style.top = (u + offsetY) + 'px';
+
+ // this places the hoverable on the right
+ tt.style.left = (l + left + offsetX) + 'px';
+ },
+ fade:function(d){
+ var a = alpha;
+ if((a != endalpha && d == 1) || (a != 0 && d == -1)){
+ var i = speed;
+ if(endalpha - a < speed && d == 1){
+ i = endalpha - a;
+ }else if(alpha < speed && d == -1){
+ i = a;
+ }
+ alpha = a + (i * d);
+ tt.style.opacity = alpha * .01;
+ tt.style.filter = 'alpha(opacity=' + alpha + ')';
+ }else{
+ clearInterval(tt.timer);
+ if(d == -1){tt.style.display = 'none'}
+ }
+ },
+ hide:function(){
+ clearInterval(tt.timer);
+ tt.timer = setInterval(function(){erxToolTip.fade(-1)},timer);
+ }
+ };
+}();
\ No newline at end of file