How can jQuery help me today? pt. 1

So, when we adopted jQuery, I think I should have done more to evangelize it within Liferay. For most web developers in the world, it's really taken off in popularity because the concept is tied to an existing development paradigm, eg. CSS.

However, that doesn't mean everyone in Liferay is familiar with CSS. But the benefit for the backend folks is that it let's you focus on only having to learn one set of selectors based on an existing and widely supported standard, rather than having to relearn a whole new language paradigm.

So first things first, I'll do a quick lay of the land with jQuery that will help you get up to speed, and then I'll launch into examples that can show you some useful tips that can help you get stuff done today.

First, the concept behind jQuery is that you operate on the DOM (the structure that represents HTML elements on the page).
In normal Javascript development you do everything on the DOM elements directly.
For example:
document.getElementById('banner').style.display = 'none';
document.getElementsByTagName('body')[0].className += ' classic';

Those snippets there find the elements in the DOM, and modify their properties directly. Now, there are a lot of ways to do things like this, and honestly, a lot of them get confusing really quickly. For instance, if you want to change an attribute on an element, some people set the property directly, some people use setAttribute(attr), etc. And every browser has quirks related to this.

So the purpose behind jQuery is to "query" the DOM, and return you back a set of elements (even if its only one element) and operate on that collection.

You can think of every jQuery object as a bucket of DOM elements, and every jQuery method you do on that object is automatically done on every element in the bucket.

One thing commonly done in web dev is to get all elements that match some criteria (querying the DOM), and often, you want to grab everything with a certain class name.

In CSS you would do it like so:
.liferay-element {}

In normal JS you would do it like this:

var allElements = document.getElementsByTagName('*');
var matchedElements = [];
for (var i=0; i < allElements.length; i++) {
    var el = allElements[i];
    if (el.className.indexOf('liferay-element') > -1) {
        matchedElements[i] = el;
    }
};

And then you would have your collection of matched elements.

So how would you do this in jQuery?

var matchedElements = jQuery('.liferay-element');

Looks pretty familiar, right?

So now that we have matched elements, what could we do with this? A whole ton of good stuff.

Let's say we wanted to fire an alert box when we click on each of those elements, how would we do it?

matchedElements.click(function(){
    alert('you clicked me!');
})

Or what about adding another class to each element?

matchedElements.addClass('new-class');

Or wait, let's say that we have a collection, and we want to make all of them have a red border, BUT, if the element is a span, we want a blue border?
Easy-peasy-lemon-squeezy.

matchedElements.css('border', '1px solid #f00');
matchedElements.filter('a').css('border-color', '#00c');

Notice the filter portion? The filter method reduces a current collection down to a smaller set based on a jQuery selector (or other things, but you can look at the documentation [http://docs.jquery.com] for more info).

So you may be saying "Okay Nate, that's great and all, but you're really boring me here. I don't want to add ugly borders to my elements, and I don't want to hear you ever say that easy-peasy line again, I want to DO STUFF!".

So let's do stuff.

One common thing that we've all done numerous times is use a checkbox to select all checkboxes in a set, sort of a select/deselect all option.

So let's assume we have a group of checkboxes, that don't have a classname, don't have an id, and don't have the same name.
But we know the name attrbiute all starts with the same thing, in this case:
"<portlet:namespace />check"

So, we have our checkbox that acts as the trigger, and but it doesn't start with the same name.

Our example HTML would be this:

<input type="checkbox" id="<portlet: namespace />trigger" />
<input type="checkbox" name="<portlet: namespace />check1" />
<input type="checkbox" name="<portlet: namespace />check2" />
<input type="checkbox" name="<portlet: namespace />check3" />
<input type="checkbox" name="<portlet: namespace />check4" />

Here is how we would toggle all of the checkboxes in jQuery:

var trigger = jQuery('#<portlet:namespace />trigger');
trigger.click(
    function(event){
        jQuery('[@name^=<portlet:namespace />check]').attr('checked', this.checked);
    }
);


So let's go by that, line by line, so we know what we're doing:

var trigger = jQuery('#<portlet:namespace />trigger');

The # sign in CSS signifies an ID, so in this case, we're getting an element by it's ID.

trigger.click(

We're now assigning a click event to our trigger. The events that we add need a function passed in, and the function that is passed in has two special things about it, 1 is that the argument it gets is the event object. I won't go into detail here about what the event object has on it, but it let's you do all kinds of things.
2, however, is that the scope of the function is changed a bit so that "this" points to the element that you're working with. So in this case, this points to the DOM element of our trigger.

function(event){
As mentioned above, here is the start of our function, with the event parameter.

And here is where the magic happens:

jQuery('[@name^=<portlet:namespace />check]').attr('checked', this.checked);

That's kinda nuts right?

Well, jQuery lets you query objects based on parameters, and you can also do minor regular expressions in it. CSS also allows you to do this (in every browser, of course, except IE 6).
It's not a direct port of CSS in this case, but of xpath, in that you have to use the @ sign. The newer versions of jQuery don't require the @ sign, but in Liferay pre-5.1, we have to use the @ sign.

So I'll break this line up:

jQuery('[@name^=<portlet:namespace />check]')

Find every element whose name attribute begins with <portlet:namespace />check. The begins with is done by this part:
^=
if we wanted to say every element whos name ENDS with, we would do:
$=

.attr('checked', this.checked)
This sets the checked attribute of every element we found to whatever the checked state is of the current element.
So if the current element's checked attribute is set to false (unchecked) all these elements will be unchecked. If it is checked, so will all of those elements.

Okay, but if you ask me, that's kind of a lame example. That's something we've been doing since time immemorial(like since 1999 when Ben Franklin gathered all the animals on the Ark and crossed the Delaware river), and while it's fast, it's not like everyone is screaming "HELP ME CHECK LITTLE BOXES!"

But what if we wanted to do an ajax call on a page that updated a div with the results and show a loading animation so the user isn't wondering what's going on?
Well first, we need to make sure the URL that returning the HTML we need.
Secondly, let's assume the div we want to update has an id of portletBox, and the link we're clicking points to the URL resource, and has an ID of linkTrigger.

Here's our HTML:

<div id="portletBox">
    Existing Text is here....
</div>

<a href="http://liferay.com/test/?p_p_state=exclusive" id="linkTrigger">Click me to update our text</a>.

Here's how we'd do it:

var linkTrigger = jQuery('#linkTrigger');
var updateDiv = jQuery('#portletBox');

linkTrigger.click(
    function(event) {
        updateDiv.html('<div class="loading-animation"></div>').load(this.href);
        return false;
    }
);

Let's go down a bit at a time:

This of course grabs our elements to work with.

var linkTrigger = jQuery('#linkTrigger');
var updateDiv = jQuery('#portletBox');

Now we'll add a click handler
linkTrigger.click(
    function(event){}

This is where we do our work
updateDiv.html('<div class="loading-animation"></div>').load(this.href);
return false;

Let's analyze this a tiny bit. When we click the link, we're first grabbing updateDiv and replacing all of it's HTML with a div that handles the loading-animation.
Right on the end of it, we're doing .load(this.href), which performs an AJAX call and updates the jQuery elements with the results of the AJAX call.

Lastly, we have "return false;". What does this do exactly?
Well, in every browser event, there is a default action. In the case of a link, the browsers default action is to follow the link. However, in our case, we don't want to follow that link, but instead stay on the current page.
When you return false, it prevents the default action from ever taking place.

This also works with every event, for instance, with forms, if you want to do some stuff when you submit the browser, but want to prevent the actual form from submitting, you would return false.

So, that about does it for right now. I'm going to think up some more (useful) examples of things jQuery can do to make your development life a lot easier.

Is there anything you'd like me to cover, for instance, doing animations, or manipulating html elements, etc?

博客
Hi, this all sounds very nice. I have a question however and I can't find much direct information about useing javascript in Liferay

Firstly, how does one go about adding jQuery plugins in LIferay, I am trying this with v5.0.1. Do I just add the javacript in the theme directory or what?

Secondly, why is it that if I try and import js directly in portal-normal.vm pages cease to render correctly. Any other pointers/gotchas you know I'm going to run into would be much appreciated.

Thanks
Hi Sena,
To include jQuery plugins, you would simply put them in the theme's javascript/ folder. To reference them, you can use our built in macro like so:

#js("$javascript_folder/jquery_plugin_file.js")

Secondly, I'm not sure why the theme wouldn't render correctly, unless the javascript is doing something odd, or there is some sort of error during the inclusion.
Two questions for you:
When you say "renders incorrectly" what exactly do you mean? Does the content show up, but just looks off, or does it not render at all, etc?
Secondly, how are you trying to import this javascript? When you say directly, I'm not sure if you mean directly in the <head> of the document with a call to an external javascript file, or embedded somewhere in the body with the javascript inline.

Either way, it shouldn't give you render issues, unless the html is invalid, or the Javascript is writing html to the document that's invalid.

Thanks Sena,
Thanks Nate, I got it working eventually, I'm not sure eactly what was wrong as I had definitely tried the #js("$javascript_folder/jquery_plugin_file.js") type command earlier. Its possible that other changes I made tot he HTML my have been causing problems. Thanks for your response though.

Thanks
S
Hi Nate, Great work! I am in the process of implementing Liferay 4.3.4 and would like to see an example of how to utilize JQuery tabs within a Liferay CMS VM Template. Is this possible? I saw that the tabs plugin is delivered with version 4.3.4.

Thanks
Hello Nate, i've read your article but i'm not sure about it could work in my scenario.

I'm developing in EXT enviroment, with Struts Portlets.

You're talking about this:

<a href="http://liferay.com/test/?p_p_state=exclusive" id="linkTrigger">Click me to update our text</a>.

Here's how we'd do it:

var linkTrigger = jQuery('#linkTrigger');
var updateDiv = jQuery('#portletBox');

linkTrigger.click(
function(event) {
updateDiv.html('<div class="loading-animation"></div>').load(this.href);
return false;
}
);

But, in my case, i 've an action:

var linkTrigger = jQuery('#linkTrigger');
var updateDiv = jQuery('#portletBox');

linkTrigger.click(
function(event) {
updateDiv.html('<div class="loading-animation"></div>').load("ext/app/my_action");
return false;
}
);

But it doesn't work, always get error page.

What i'm doing wrong?

Many thanks!
Hi Manuel,
I believe you need to simply create a portlet url for your action, which you could do from Javascript like so:

var actionURL = Liferay.PortletURL.createActionURL();
actionURL.setParameter('ext/app/my_action');

Then you could easily do: updateDiv.html('<div class="loading-animation"></div>').load(actionURL.toString());

But if you're trying to load the results into a div, you probably need to create a render URL. Here is some more information here on create portlet urls in Javascript: http://www.liferay.com/web/eduardo.lundgren/blog/-/blogs/1820035
Thanks for the comment Manuel.
Hello Nate, many thanks for your quick answer.

I'm trying it, but i cannot get success. I'm not reaching the StrutsAction defined for "ext/app/detail". The div shows the page from a call this jQuery event (current page for the portlet)

Here is my jQuery call:

jQuery("[id^=view_]").click(
function ()
{
var renderURL = Liferay.PortletURL.createRenderURL();
renderURL.setParameter("<portlet:namespace/>struts_action","/ext/app/detail");
renderURL.setParameter("iddetail", jQuery(this).attr("iddetail") );
alert( "renderURL: " + renderURL.toString() );

jQuery("#detailDiv").dialog({ modal: true, resizable: true, width: 500, height: 500 });
jQuery("#detailDiv").dialog('open');
jQuery("#detailDiv").load(renderURL.toString() );
}
);