Hooking into Liferay's Javascript functions

Have you ever wanted to plugin to Liferay's javascript functions, but didn't want to modify the original Javascript, and didn't want to branch off of it? I know I have.

For instance, let's say that there is a Liferay javascript function that always adds a class, but you don't want to add that class, and instead want to use your own. Well, there is a little known method that does this that's in Liferay 4.3.x.

The method is called Liferay.Util.actsAsAspect. This little method adds AOP (aspect oriented programming) features, that allow you to hook into existing Javascript without a plugin framework being in place.

So, I'll use a good example of this. In Liferay, if you don't want the dock to be a drop down, you can go into dock.vm in your theme, and remove the class name of interactive-mode off of the main dock container.
This allows you to easily disable that feature.
But recently, we needed to remove this class, but didn't want to keep a floating copy of dock.vm where the only difference is one little class name.

So we could either modify the file anyways, or we could use Javascript to remove the class name. But then there become a race condition, where you're trying to race against the Dock javascript to remove the class before it can find the class and begin the transformation.

Well, that's where Liferay.Util.actsAsAspect comes in.

What it does is it takes the object you pass to it, and augments it to contain three new methods: before, after, and around.

So, in our case, we needed to modify Liferay.Dock (the class that creates the dock), and we needed a way to guarantee that the class name interactive-mode was removed, thereby preventing the Dock class from even seeing that there is a dock to modify.

So, here is what we did:

Liferay.Util.actsAsAspect(Liferay.Dock);

Liferay.Dock.before(
    'init',
    function(){
        jQuery('.interactive-mode').removeClass('interactive-mode');
    }
);

So, you see, now every time Liferay.Dock.init() runs, it will always execute our little function we passed into it first.

We can also run Javascript after, using Liferay.Dock.after() and passing in the name of our method, and a function we wish to execute.

We can also wrap the init function with code, like so:

Liferay.Dock.around(
'init',
function () {
    var dock = jQuery('.interactive-mode');
    dock.removeClass('interactive-mode');
   
    this.yield(); // This line executes the original function
   
    dock.addClass('interactive-mode'); // This line now adds the class back after the function fires
}
);

Now, the next question you may be asking is, how do you do this for classically defined functions (you know the ones that look something like: onLoad() or _88_updatePage()), and don't have a parent object that we can manipulate.

Well, we actually can manipulate because all functions defined like this: function _88_updatePage() {}, all exist inside of the "window" object. The "window" object is the global namespace where normal functions are added by default.

So, here is how we would execute a function before _88_updatePage get's executed:

Liferay.Util.actsAsAspect(window);

window.before(
'_88_updatePage',
function (month, day, year){
    alert('I am just about to update the page');
}
);

So, anyways, I thought that this would be helpful to people who need to hook into our existing Javascript, and can't wait till we implement our formal JS event system :)
 

博客
You can't even imagine how timely this post was... I was about 2 minutes away from asking you how to best extend an existing function...

This is very clever...
ok! what if the methods has params... do I just cascade those down?

Liferay.Navigation.around(
'_removePage',
function (params) {
...
this.yield(params) ;
...
}
) ;
Yes sir emoticon Just like that.
I have this:

Liferay.Util.actsAsAspect(Liferay.Navigation);

Liferay.Navigation.around(
'_removePage',
function (obj, instance) {
alert('test');
}
);

new Liferay.Navigation(
{
layoutIds: [<%= ListUtil.toString(layouts, "layoutId") %>],
navBlock: '#navigation',
hasPermission: <%= GroupPermissionUtil.contains(permissionChecker, portletGroupId.longValue(), ActionKeys.MANAGE_LAYOUTS) %>
}
);

this doesn't work... I'm doing something wrong aren't I?
Does an instance of an object need to be treated differently?
Yeah, sorry, I should have mentioned that. For instantiable classes, you would modify that objects prototype, like so:

Liferay.Util.actsAsAspect(Liferay.Navigation.prototype);

Liferay.Navigation.prototype.around(...)

That will also make sure that every instance of it will get your function with it.
Hi,
I'm using Liferay 6.0.5 and I used the your method to hook into some navigation javascript. The problem I have is that for IE8 I get this message: 'Liferay.Navigation.prototype' is null or not an object. Do you know what the problem might be?

Thanks
I was pleasantly surprised that this post was still dead on for LR 6.0 EE SP1. The Example Ray was working through above was exactly what we needed to do. Our end result looks like so:

<aui:script position="inline" use="liferay-navigation">
Liferay.Util.actsAsAspect(Liferay.Navigation.prototype);
Liferay.Navigation.prototype.around('_removePage',
function(event){
alert('before');
//do your work here
this.yield(event);
alert('after');
//do more work here
});
</aui:script>