JQuery for Everyone: AOP in Action – loadTip Gone Wild
As George pointed out, I need a use case for the Aspect-Oriented Programming (AOP) technique. So, this article mashes up my previous work on a tool-tip-styled, AJAX-powered pop-up with AOP. While more complicated in design than the original loadTip or docTip code, this version has the most potential and should prove easier to modify.

As a beta version, this code comes fully commented with no warranty. Do with it what you will. My personal plans for this code involve the JSON loader and a very happy Content Editor Web Part.
What It Does:
loadTip allows someone with Edit access to a SharePoint page to create a dynamic tool tip that previews display form content. Some possibilities include user information, document metadata, and list item metadata.
As suggested by one of our workshop attendees, loadTip also does a better job of exposing the attachments of a list item without clicking the item.

What It Can’t Do:
loadTip can’t preview documents. loadTip can’t (for now) preview anything inside an Excel Web Access Web Part (EWA). loadTip doesn’t impress the ladies–unless they’re SharePoint end users.
What’s New (features):
Obviously, this version can create a pop-up for documents or items (or other stuff if you rolled your own web part).
I created some default behaviors with the ability to set some of your own parameters. The parameters are:
- keyword – the link text to search for
- group – the container the links are located in (defaults to the main content div)
- delay – how long to delay until starting an AJAX load (default .5 second)
loadTip now looks at where it falls on the screen to decide whether it will open left, right, above, or below the target element. It should help avoid scrolling to see loadTip’s content.


What’s New (geeky stuff):
This version of the code has comments–like a ton of comments. Hopefully, anyone learning jQuery can follow it. Also, I hope anyone who has mastered jQuery can improve on it.
I used jquery-AOP to trigger events dynamically after ExpGroupRenderData renders new HTML on the page. Because we’re using AOP and adding advices dynamically, there is no need to change the ExpGroupRenderData function if you add your own “instance” of loadTip–just fire off another initializer (e.g., initLoadTip(”coolstuff”,”inhere”,”2000″)).
Because the AOP script is not hosted publicly (like jQuery), I included it below. This makes it easier to copy-paste and test on your own server. This, however, is not recommended for production environments. You should, at a minimum, remove that code and point to a local version of the file, like this:
<script type="text/javascript"> if(typeof $.aop=="undefined"){ var aopPath="/path_to_my_javascript_files/"; document.write("<script src='",aopPath,"aop.js' type='text/javascript'><\/script>"); </script>
I now do more validation to avoid multiple bindings of events to a single element. I use multiple instances of the initialization script so there can be multiple ways to use loadTip on the same page without conflict. There are three examples of this in the code below.
<script type="text/javascript"> //check if jQuery already exists if(typeof jQuery=="undefined"){ //set the path to the jQuery library to use var jQPath="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/"; //add the reference to the page and evaluate document.write("<script src='",jQPath,"jquery.min.js' type='text/javascript'><\/script>"); } </script> <script type="text/javascript"> /* * jQuery AOP - jQuery plugin to add features of aspect-oriented programming (AOP) to jQuery. * http://jquery-aop.googlecode.com/ * * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * * Version: 1.1 */ //check if AOP already exists if(typeof $.aop=="undefined"){ (function(){var E=1;var B=2;var G=3;var C=4;var F=true;var A=function(K,L,J){var H=K[L];var I;if(J.type==E){I=function(){var M=H.apply(this,arguments);return J.value.apply(this,[M,L])}}else{if(J.type==B){I=function(){J.value.apply(this,[arguments,L]);return H.apply(this,arguments)}}else{if(J.type==C){I=function(){return J.value.apply(this,arguments)}}else{if(J.type==G){I=function(){var M={object:this,args:arguments};return J.value.apply(M.object,[{arguments:M.args,method:L,proceed:function(){return H.apply(M.object,M.args)}}])}}}}}I.unweave=function(){K[L]=H;pointcut=K=I=H=null};K[L]=I;return I};var D=function(I,H){var K=(typeof (I.target.prototype)!="undefined")?I.target.prototype:I.target;var J=[];if(H.type!=C&&typeof (K[I.method])=="undefined"){for(var L in K){if(K[L]!=null&&K[L] instanceof Function&&L.match(I.method)){J[J.length]=A(K,L,H)}}if(J.length==0){throw"No method: "+I.method}}else{J[0]=A(K,I.method,H)}return F?J:J[0]};jQuery.aop={after:function(I,H){return D(I,{type:E,value:H})},before:function(I,H){return D(I,{type:B,value:H})},around:function(I,H){return D(I,{type:G,value:H})},introduction:function(I,H){return D(I,{type:C,value:H})},setup:function(H){F=H.regexMatch}}})(); } </script> <script type="text/javascript"> /* * Copyright (c) 2008 Paul Grenier (endusersharepoint.com) * Licensed under the MIT (MIT-LICENSE.txt) */ //function to handle error in IE function handleError(){ return true; } //global variable for the loadTip container var loadTip; //global variable for AOP after advices var advicesAfter=new Array(); //function to create the loadTip container function createLoadTip(){ var html="<span>click to close </span>" +"<img style='vertical-align:text-top;' " +"src='/_layouts/images/menudark.gif' alt=''/>"; //set loadTip loadTip=$(document.createElement("div")).attr("id","loadTip") .html(html).appendTo("body"); //create content area $(document.createElement("div")).attr("id","tipContent") .appendTo("#loadTip"); //set AOP advice to get data from ExpGroupRenderData //arguments(htmlToRender[0], groupName[1], isLoaded[2]) $.aop.around({target:window,method:"ExpGroupRenderData"}, function(invocation){ if (invocation.arguments[2]=="true"){ var group="#tbod"+invocation.arguments[1]+"_"; //set groupName data to loadTip $.data(loadTip[0],"groupName",group); } return invocation.proceed(); } ); } //function to bind events to elements function loadTipEvent(e,a,delay){ //add target data to element $.data(e,"dispTarget",a); //bind mouseenter event $(e).mouseenter(function(event){ //create position variables var x = event.pageX+10; var winx = $(window).width(); var y = event.pageY; var winy = $(window).height(); var ly; var lx; //set loadTip out of window //height fix 2009-02-17: pg (1/3) loadTip.css({"top":-1000, "left":-1000, "height":""}); //check to see if this target was already loaded if($.data(loadTip[0],"dispTarget")&&$.data(loadTip[0],"dispTarget")==$.data(e,"dispTarget")){ ly = loadTip.height(); lx = loadTip.width(); //if the box would run off the page, adjust position if (ly+y > winy) y=y-ly-10; if (lx+x > winx) x=x-lx-20; //part of height fix for big boxes 2009-02-17: pg (2/3) if (y < 0) y=0; if (ly > winy) loadTip.css({"overflow-y":"auto","height":winy}); //show loadTip loadTip.css({"top":y, "left":x}).show(); }else{ //if a timer was started, stop if(loadTip.timer)clearTimeout(loadTip.timer); //start the delay timer for a data load loadTip.timer = setTimeout(function(){ //&Force=1 prevents redirection on mysites, other pages ignore //load ms-formtable into content area $("#tipContent").load(a+"&Force=1 .ms-formtable", function(){ //after data loads, remove width attributes from tds loadTip.find("td").removeAttr("width"); ly = loadTip.height(); lx = loadTip.width(); //if the box would run off the page, adjust position if (ly+y > winy) y=y-ly-10; if (lx+x > winx) x=x-lx-20; //part of height fix for big boxes 2009-02-17: pg (3/3) if (y < 0) y=0; if (ly > winy) loadTip.css({"overflow-y":"auto","height":winy}); //the following line supports calcHTML columns 2009-03-03: pg (1/2) $("#tipContent td[id='SPFieldCalculated']").each(function(){$(this).html($(this).text())}); //show loadTip loadTip.css({"top":y, "left":x}).show(); //add target data to loadTip $.data(loadTip[0],"dispTarget",a); }); },delay); } }); //bind mouseleave event $(e).mouseleave(function(event){ if(loadTip.timer)clearTimeout(loadTip.timer); }); //set loadTip to close when clicked loadTip.click(function(){ loadTip.hide(); }); } //function to find elements function initLoadTip(keyword,group,delay){ //if the loadTip box does not exist, create it if(typeof loadTip=="undefined")createLoadTip(); //use the main content zone as default if(!group)group="#MSO_ContentTable"; //set a default delay for data loads if(!delay)delay=500; //if no keyword, find links to documents //requires change of view on some OOB web parts if(!keyword)keyword="initDocs"; //for documents... if(keyword=="initDocs"){ //find tables with dref and no inner links pointing to the DispForm //this prevents double binding var arrayList=$(group+" table[dref]:not(:has(a[href*='DispForm']))"); $.each(arrayList,function(i,e){ //build a link to the DispForm var a="/"+escapeProperly($(e).attr("dref")) +"/Forms/DispForm.aspx?ID=" +$(e).attr("id"); //find the link to the document var e=$(e).find("a"); //if this link has no dispTarget data //prevents multiple bindings if (!($.data(e,"dispTarget"))){ //bind events loadTipEvent(e,a,delay); } }); }else{ //find elements matching keyword in the link var arrayList=$(group+" a[href*='"+keyword+"']"); $.each(arrayList,function(i,e){ //remove any bookmarks from the link var a=$(e).attr("href").split("#")[0]; //if this link has no dispTarget data //prevents multiple bindings if(!($.data(e,"dispTarget"))){ //bind events loadTipEvent(e,a,delay); } }); } //check the advices array to see if an advice was loaded for that keyword if ($.inArray(keyword,advicesAfter)==-1){ //add the keyword to the advices array advicesAfter.push(keyword); //create an advice after ExpGroupRenderData $.aop.after({target:window,method:"ExpGroupRenderData"}, function(){ //get the groupName data from loadTip var group=$.data(loadTip[0],"groupName"); //if loadTip has groupName data, reinitialize elements for that keyword //after ExpGroupRenderData renders new html //loaded by expanding a group if(group)initLoadTip(keyword,group,delay); } ); } } $(function(){ //handle the first error in IE window.onerror=handleError; //example of loadTip with links to DispForm,aspx (the keyword) //using the default content zone with 1 second delay initLoadTip("DispForm.aspx","",1000); //example of loadTip with userdisp (the keyword) ////using the default content zone and default .5 second delay initLoadTip("userdisp"); //example of loadTip for documents using defaults initLoadTip(); }); </script> <!-- loadTip CSS --> <style type="text/css"> #loadTip{ background: #6F9DD9; color: #fff; border: 2px solid #6F9DD9; font-size: 10px; padding: 3px; width: 342px; display: none; position: absolute; text-align: right; } #tipContent { background: #fff; padding: 3px; } </style>
UPDATE: 2009-02-17
I fixed an issue Peter noticed with a small ratio of loadTip height to window height. The new behavior makes the top of the window the highest point the box will appear at and adds a scroll bar to the box if the box is larger than the window.
UPDATE: 2009-03-03
I updated this script to render calcHTML columns (see HTML Calculated Column and jQuery calcHTML
UPDATE: 2009-03-09
With more testing I noticed that the calculated column will “reprocess” when displaying but not loading. This caused some HTML to disappear so it was removed from the script and now only processes when new data loads.
- JQuery for Everyone: Accordion Left Nav
- JQuery for Everyone: Print (Any) Web Part
- JQuery for Everyone: HTML Calculated Column
- JQuery for Everyone: Dressing-up Links Pt1
- JQuery for Everyone: Dressing-up Links Pt2
- JQuery for Everyone: Dressing-up Links Pt3
- JQuery for Everyone: Cleaning Windows Pt1
- JQuery for Everyone: Cleaning Windows Pt2
- JQuery for Everyone: Fixing the Gantt View
- JQuery for Everyone: Dynamically Sizing Excel Web Parts
- JQuery for Everyone: Manually Resizing Web Parts
- JQuery for Everyone: Total Calculated Columns
- JQuery for Everyone: Total of Time Differences
- JQuery for Everyone: Fixing Configured Web Part Height
- JQuery for Everyone: Expand/Collapse All Groups
- JQuery for Everyone: Preview Pane for Multiple Lists
- JQuery for Everyone: Preview Pane for Calendar View
- JQuery for Everyone: Degrading Dynamic Script Loader
- JQuery for Everyone: Force Checkout
- JQuery for Everyone: Replacing [Today]
- JQuery for Everyone: Whether They Want It Or Not
- JQuery for Everyone: Linking the Attachment Icon
- JQuery for Everyone: Aspect-Oriented Programming with jQuery
- JQuery for Everyone: AOP in Action - loadTip Gone Wild
- JQuery for Everyone: Wiki Outbound Links
- JQuery for Everyone: Collapse Text in List View
- JQuery for Everyone: AOP in Action - Clone List Header
- JQuery for Everyone: $.grep and calcHTML Revisited
- JQuery for Everyone: Evolution of the Preview
- JQuery for Everyone: Create a Client-Side Object Model
- JQuery for Everyone: Print (Any) Web Part(s) Plugin
- JQuery for Everyone: Minimal AOP and Elegant Modularity
- JQuery for Everyone: Cookies and Plugins
- JQuery for Everyone: Live Events vs. AOP
- JQuery for Everyone: Live Preview Pane
- JQuery for Everyone: Pre-populate Form Fields
- JQuery for Everyone: Get XML List Data with OWSSVR.DLL (RPC)
- Use Firebug in IE
- JQuery for Everyone: Extending OWS API for Calculated Columns
- JQuery for Everyone: Accordion Left-nav with Cookies Speed Test
- JQuery for Everyone: Email a List of People with OWS
- JQuery for Everyone: Faster than Document.Ready
- jQuery for Everyone: Collapse or Prepopulate Form Fields
- jQuery for Everyone: Hourly Summary Web Part
- jQuery for Everyone: "Read More..." On a Blog Site
- jQuery for Everyone: Slick Speed Test
- jQuery for Everyone: The SharePoint Game Changer
- JQuery For Everyone: Live LoadTip
@Marc,
I have a “live events” version of this script that is not published right now. That might solve the problem. It’s a lot more compact than the AOP version, so I’ll try to get around to providing that.
Hello,
thank you for the information. i will try the “live events” version when it is published
Great work! This tool helped me work around the relational DB shortcomings of OOB Sharepoint lists using data connections and lookup columns on a web part page. Result: dashboard info and quasi-rollup available at the bottom level.
It would be nice to be able to use this utility in reverse (by looking downward in the information hierarchy i.e. being able to view details of aggregated data): Any plans afoot or there any way to extend this utility to Sharepoint’s OOB CQWP?
How do I go about changing the location of where the pop-up opens? Currently, when I hover over a list item, i open to the close-right of the cursor, which is blocking my document names (depending on where you hover). I would like to move the pop-up to the far right for example, but how?
@Timmy,
In the code you see this line: var x = event.pageX+10;
and this line: var y = event.pageY;
If you change what gets added or subtracted you will move the box’s pop-up location along the given axis. For instance, to move the box further to the right by 10 pixels, change event.pageX+10 to event.pageX+20.
Paul,
Do you plan to update this to work with aop 1.2?
As usual great post!
I was able to edit the fields that show up in the hover menu.
a) In SPD, open the DispForm.aspx file
b) Insert a new SharePoint Control “Custom List Form”
c) Close the original form (do not delete it)
d) Edit the fields however you want
e) THIS IS THE KEY: The custom SharePoint Control tag in the code will not have a “Class” tag
ie:
Change this line to read:
Now the hover menu will only show the fields you want.
Thanks to the previous posters for getting me on the right track, this is perfect for our needs.
The previous post didn’t show up quite right. All you have to do is add a “class=ms-formtable” to the table tag. Then it works.
@Robin,
I didn’t know there was another release, thanks!
Question: I’m using this in a Document Library. When the user points at a file, it shows a “Description” field and a “Trainer guide” link (which is only accessible to trainers). At the bottom of the tool tip I have an email link for users to submit comments/errors regarding the file they are currently pointing at.
What I want to do is add the name of the file to the subject line of the email.
Is there a way to determine the text of the hyperlink the mouse is pointing at, and add it to a mailto: href?
@ Autosponge
“AutoSponge on October 8th, 2009 3:53 pm
@Timmy,
In the code you see this line: var x = event.pageX+10;
and this line: var y = event.pageY;
If you change what gets added or subtracted you will move the box’s pop-up location along the given axis. For instance, to move the box further to the right by 10 pixels, change event.pageX+10 to event.pageX+20.”
I have even tried setting x=150 and the window still isn’t budging. Also cleared local cache and iisreset on server. Any ideas?
Almost forgot – is there a new LoadTip webpart that is required for this new script to work as well? Currently I am calling loadtip.js from my masterpage (thus applying to all sites) and have the LoadTip web part loaded on the pages that I want the popup on.
@Timmy,
That’s really the way to change the x/y coordinates. I just tested again to make sure:
However, that also shifts the box to the right if you have a link near the right edge. To make a drastic change to this number, you may need to play with the calculations later in the function as well.
Great scripting! Couple of basic questions (sorry).
You mention pulling out the aop.js script. I don’t see that script in the code – how do I do that?
If I want to limit the fields displayed in the tool tip, where would I insert the code to filter for fields I don’t want displayed? Would those field names be the SP display or internal names?
Sorry again for the really basic questions.
@Chaz,
AOP objects live outside of this code in a separate file. I recommend they stay that way. You don’t need to do anything.
This script does not have a way for you to reduce the html coming from the target page. Instead, you would have to develop your own solution or change the target to a page that only shows certain fields (a custom display form). Either one will be a challenge.
Thanks – but I’m a bit confused now. I understand that AOP is outside your code, and up above you say:
“Because the AOP script is not hosted publicly (like jQuery), I included it below. This makes it easier to copy-paste and test on your own server. This, however, is not recommended for production environments. You should, at a minimum, remove that code and point to a local version of the file, like this:”
Since I’m looking to use your code on a production server, I’m confused when you said in your reply that I don’t need to do anything re AOP – that seems to contradict what you said in your post.
So my question re AOP is: on a production server, should I just go ahead and use your code as is, or should I change the line under “if(typeof $.aop==”undefined”){” to point to a local copy of the AOP file(s)?
Related to that: when I downloaded AOP from http://jquery-aop.googlecode.com/ the zip file didn’t include “aop.js”.
When I tried to follow the example you gave above about pointing to the AOP files I downloaded, your code didn’t work for me. It only works when I use it as posted above.
Thanks again!
@Chaz,
Sorry for the confusion. There are two contradicting schools of thought here and I’ve gone back and forth on it.
On the one hand, separating the files means that you can maintain them separately and the user can cache it. If you use AOP elsewhere, you don’t need to include it again and the user likely already has it cached. You could also upgrade AOP separately (I did not test this).
On the other hand, combining (and minifying) files will lead to better performance (when it’s not cached) since the browser does not have to stop processing to download another file.
So, I think it’s up to you. AOP is very small, so the benefits to breaking it out are mostly about code management.
If you break it out, make sure you put the AOP reference in its own script tag, just like the example above (and change the directory to match yours). If you leave it as is, the world will not stop on its axis.
Paul,
I am desperately looking to implement this,but unfortunately I am not gettig. I have one document library and I am adding CEWP on top of it. Copying the above code and pasting in the web part. But I dont see the functionality getting implemented. Please let me know if I am missing anything.
NOTE: Do we also need to add the following with the code:
if(typeof $.aop==”undefined”){
var aopPath=”/path_to_my_javascript_files/”;
document.write(”");
I appreciate your response.
@John,
I’ll publish the latest code tonight which uses live events instead of AOP. Perhaps that will help.
Thanks Paul, for your prompt response. I am really looking forward to implement this.
Thanks
John
Is there any way possible to not have the popup open off screen? I have some list items with a lot of info in them that make the box rather large, and you have to scroll down to see the entire box.
@Timmy,
See the newer loadTip Live. It should do what you want.
Where is the newer LoadTip Live located? Please link me if possible.
THANKS!!!
Hello -
Great tool. Question, can this be used on viewpost as well?
I have some blogs that I would like to use the LoadTip Live on but posts don’t have displayform they used viewpost.
Can I just simply change all instances of displayform with viewpost?
Thanks.
Hi, we have several lists that don’t use DispForm.aspx as its primary form for displaying data, so I’ve tried making the LoadTip target a different .aspx file, however, unsuccessfully. Can’t you hardcode it to target a different form?
@Gus,
You will need to change line 149 to fit the link you’re pointing to. You may (or may not) need to change the way you invoke the tip (line 196). If you did not change the default display page to your custom page, you don’t need to change it, otherwise it should match your links on the page. Use the middle parameter on line 196 to use your custom page’s content zone. Find the div that holds your data and use its name (use CSS-style identifier like I did for #MSO_ContentTable).
I really like this ToolTip but can’t it work if the list is not on the same subsites .
Can it work using webservice?
Thanks
Hi
how do I get hold of only the attachments filename?
Best Regards Mike
Hi, finally got this to work after trying all sorts. For some reason in my enviroment it would only work when I created a list view that contained a grouped by field.
Paul,
This is brilliant! Especially your latest coding and validations.
I have been reading and trying each version and this one seems to do everything we need and thensome for any of our list types.
Keep up the great work,.
Hi. My supervisor loves this and really wants to use it on our site. The problem is, we need to restrict what can be seen in the box. We still need to have all the columns visible and in the same order so people can fill out the list, but we’d like to trim it down from 10 columns showing to just 4. It is just too much to show. Thanks for any help you can give me.
Regards
Lisa
I really like the LoadTip Live and am using it to display the Editform.aspx on our Lists. Is there a way to show the OOB “OK” and “cancel” buttons at the bottom of the pop-over form?
Thanks in advance !
Sorry to be a bother. I was just wondering if anyone knew how to hide certain field in the pop up window. There’s a lot of info in the box and it would be better if we could take out / hide half of them. Thanks for any help you may provide.