Mini Page Layout CSS in Visualforce Page

I've found the solution for this issue. The point is to give the popup an unique id. So we can't use the object reference id because it is a shared value on the page (like @JesseAltman said in his answer). Now we need to generate this ID manually and use an imitation of the popup. For that i will use an apex:variable tag to generate ID if this form: hover123:

Visualforce:

<apex:outputPanel style="width:300px;" layout="block">

<!-- Here defining a new variable for the fake id -->
<apex:variable value="{!0}" var="rowNumber" />

<apex:pageBlock mode="maindetail">
    <apex:pageBlockTable value="{!accs}" var="a">
        <apex:column value="{!a.name}"/>

        <apex:column headerValue="Some header">
            <a href="/{!a.test2__r.id}" id="hover{!rowNumber}" 
                                        position="relative"
                                        onblur="LookupHoverDetail.getHover('hover{!rowNumber}').hide();" 
                                        onfocus="LookupHoverDetail.getHover('hover{!rowNumber}', '/{!a.test2__r.id}/m?retURL=%2F{!a.test2__r.id}&isAjaxRequest=1').show();" 
                                        onmouseout="LookupHoverDetail.getHover('hover{!rowNumber}').hide();" 
                                        onmouseover="LookupHoverDetail.getHover('hover{!rowNumber}', '/{!a.test2__r.id}/m?retURL=%2F{!a.test2__r.id}&isAjaxRequest=1').show();">
                {!a.test2__r.name}
            </a>
        </apex:column>

        <apex:column headerValue="1">
            <!-- Increasing the value of the variable -->
            <apex:variable var="rowNumber" value="{!rowNumber + 1}" />
        </apex:column>

    </apex:pageBlockTable>
</apex:pageBlock>

</apex:outputPanel>

Apex (here i have the same test2__c reference in each account):

public with sharing class MyClass {

    // Just a list with some accounts
    public List<Account> accs { get; set; }

    public MyClass() { 
        accs = [Select id, test2__c, test2__r.id, test2__r.name, Name 
                From Account 
                Limit 100];
    }
}

This will looks like this:

enter image description here


I came across this post trying to find a solution to the same issue, In my case i found that the popup was selecting the correct ID along the x axis but moving the position on the popup outside of the panel i wanted. So when i highlighted the linked item if it was say 800px on a scroller. It would move the popup to 800px outside the scroller.

This was the first issue I found. There were also ID issues along the X and Y for different commandLinks & when I reordered my page the popups would again be scrambled.

I worked out that the broken / buggy SalesForce code was within LookupHoverDetail.prototype.position. In here we were returning getObjX and getObjY wrongly.

So within my visualforce page I placed some overwriting code to replace the wrong possitions.

function getObjX(a) {
    var left = 0;       
    while(a){
        left += (a.offsetLeft - a.scrollLeft + a.clientLeft);
        a = a.offsetParent;
    }
    return left;
}
function getObjY(a) {
    var top = 0;        
    while(a){
        top += (a.offsetTop - a.scrollTop + a.clientTop);
        a = a.offsetParent;
    }
    return top;
}

Next I had to deal with the ID's being non-unique, I call this function after every Ajax event so that if I order the page, the Id's are to the correct links still. This function will execute at the same time as the others on load.

function fixSalesforce() {
    $FFDC('.lineItem A').each(function(index){
        var me = $FFDC(this);
        var aId = me.attr('id');
        if (aId != null){
        var trId = me.closest('tr').attr('id');
            if (aId.indexOf(trId) != 0){
            var newId = trId + aId;
            me.attr('id', newId);
            me.attr('onmouseover', me.attr('onmouseover').replace( new RegExp(aId, 'g' ), newId));
            me.attr('onmouseout', me.attr('onmouseout').replace( new RegExp(aId, 'g' ), newId));
            me.attr('onfocus', me.attr('onfocus').replace( new RegExp(aId, 'g' ), newId));
            me.attr('onblur', me.attr('onblur').replace( new RegExp(aId, 'g' ), newId));
        }
        }
    });
}

The aim of the above function is to make the ID's unique by prefixing them with the row ID which I put on the TR.

{
      <apex:variable value="{!0}" var="tRow" /> 
      <apex:repeat var="tl"value="{!viewState.BankStatement.TransactionLineItems}"id="tRow1"> 
      <tr id="tRow{!tRow}" class="lineItem{!IF(tl.isChecked,'selected','')}">
      <apex:variable var="tRow"value="{!tRow+1}"/>
}

Within all CommandLinks I then placed onComplete="Refresh();"

This is a function that will call the fixSalesforce() function from earlier.


I've run into this issue before as well. It seems to be a combination of Javascript and CSS. Looking at their Javascript, this is the function that appears to be called

function LookupHoverDetail(a, b) {
    this.id = a;
    this.width = LookupHoverDetail.STANDARD_BUBBLE_WIDTH;
    this.bubbleOffset = Sfdc.userAgent.isIE6 ? 5 : 14;
    this.height = LookupHoverDetail.STANDARD_BUBBLE_HEIGHT;
    this.hover = document.createElement("div");
    this.hover.id = a + "Hover";
    this.hover.className = "individualPalette lookupHoverDetail lookupHoverDetailLoading lookupHoverDetailOverridable";
    this.hover.innerHTML = '<div class="topLeft"><div class="bPageBlock"><div class="pbBody">' + LC.getLabel("Global", "loading") + '<div><div class="pbFooter"><div class="bg"><div></div></div><div>';
    document.body.appendChild(this.hover);
    var c = this;
    addEvent(this.hover, "mouseover", function () {
        c.show()
    }, true);
    addEvent(this.hover, "mouseout", function () {
        c.hide()
    }, true);
    this.hover = new iframeShim(this.hover);
    this.originalClass = "";
    this.fadingIn = this.fadingOut = null;
    this.loaderURL = b;
    this.loaded = false
}
LookupHoverDetail.STANDARD_BUBBLE_WIDTH = 302;
LookupHoverDetail.STANDARD_BUBBLE_HEIGHT = 262;
LookupHoverDetail.SHOW_DELAY = 800;
LookupHoverDetail.HIDE_DELAY = 250;
LookupHoverDetail.stopLoading = false;
LookupHoverDetail.hovers = {};
LookupHoverDetail.getHover = function (a, b) {
    var c = window.Shepherd;
    if (c && b) b = c.fixRetUrl(b);
    if (LookupHoverDetail.hovers[a]) return LookupHoverDetail.hovers[a];
    c = new LookupHoverDetail(a, b);
    return LookupHoverDetail.hovers[a] = c
};
LookupHoverDetail.hideAllHovers = function () {
    var a = LookupHoverDetail.hovers,
        b;
    for (b in a) a.hasOwnProperty(b) && a[b].hide()
};
LookupHoverDetail.prototype.show = function () {
    if (this.fadingOut) {
        clearTimeout(this.fadingOut);
        this.fadingOut = null
    } else {
        var a = this;
        if (!this.fadingIn) this.fadingIn = setTimeout(function () {
            a.showNow()
        }, LookupHoverDetail.SHOW_DELAY)
    }
};
LookupHoverDetail.prototype.showNow = function () {
    if (!this.loaded) if (this.loaderURL != null) {
        var a = this;
        Sfdc.Ajax.get(this.loaderURL, function (b) {
            a.load(b)
        }, {
            failure: function (b) {
                a.load(b)
            }
        })
    } else return;
    this.position();
    this.hover.setStyle("visibility", "visible");
    this.fadingIn = null
};
LookupHoverDetail.prototype.hide = function () {
    if (this.fadingIn) {
        clearTimeout(this.fadingIn);
        this.fadingIn = null
    } else {
        var a = this;
        this.fadingOut = setTimeout(function () {
            a.hideNow()
        }, LookupHoverDetail.HIDE_DELAY)
    }
};
LookupHoverDetail.prototype.hideNow = function () {
    this.hover.setStyle("visibility", "hidden");
    this.fadingOut = null
};
LookupHoverDetail.prototype.load = function (a) {
    this.hover.div.innerHTML = a;
    Util.evalScriptsUnderElement(this.hover.div);
    this.originalClass = this.hover.div.firstChild.className;
    this.height = this.hover.div.offsetHeight;
    delStyleClass(this.hover.div, "lookupHoverDetailLoading");
    this.position();
    this.loaded = true
};
LookupHoverDetail.prototype.position = function () {
    var a = getElementByIdCS(this.id),
        b = getObjX(a),
        c = getObjY(a),
        d = a.offsetWidth,
        e = a.offsetHeight,
        f = getScrollX(),
        g = getScrollY(),
        h = getWindowWidth(),
        i = getWindowHeight();
    a = this.originalClass + " ";
    if (c + e + this.height < g + i) {
        a += "top";
        c += e
    } else {
        a += "bottom";
        c -= this.height
    }
    if (b + d - this.bubbleOffset + this.width < f + h) {
        a += "Left";
        b = b + d / 2 - this.bubbleOffset
    } else {
        a += "Right";
        b = b + d / 2 - this.width
    }
    this.hover.setStyle("left", b + "px");
    this.hover.setStyle("top", c + "px");
    this.hover.div.firstChild.className = a;
    if (this.hover.div.firstChild) if (b = Util.hasStyleEndsWith(this.hover.div.firstChild, "Override")) {
        delStyleClass(this.hover.div, "lookupHoverDetailOverridable");
        delStyleClass(this.hover.div.firstChild, b);
        addStyleClass(this.hover.div, b)
    }
};

I would guess that Salesforce most likely uses the same ID for the same object referenced twice on a page (so if you have a lookup field to the same object twice on the page, they probably share the same ID), so when this LookupHoverDetail method is used, it looks for the first instance in the DOM which is at the top of the page.

To be completely honest with you, I am not sure how you can fix this. You will need to override Salesforce's included Javascript.