Personal tools

Luca Fabbri

May 07, 2010

... and finally: what if they want tab inside Plone portlets?

A little demo (and the idea behind) for collective.portlettabber product

They want tab!

The request is quite common in recent layout that you can see all around the Web. Ok, "portlet" is common, but you can also find many examples of portlets with tabs.

What is this? The data inside portlets is split in sections that you can easily switch with a little Javascript code. The benefit is to put more information inside a tiny space, maybe showing to users only the most interesting ones when he arrive at your page.

Technically speaking this task is so simple that a blog post is not needed... but we don't know what kind of contents we want put inside the portlet tab.

Also the customer want to have the choice of use all (maybe the most part) of Plone portlets available is his installation... We really can't rewrite/overrider all portlets to get this...

...and finally (like everytime) accessibility. Data inside portlets must the accessible and the requirement 15 of the Stanca Act force us to make this available also with Javascript disabled.

Solution

Let's starts from what we can't lose:

  • accessibility of the page without Javascript
  • all Plone portlet usable as "tab"

For those two reasons the simplest way is to keep Plone portlet engine like it is. Plone portlets are working normally without Javascript, so why don't simply show tabs in only when Javascript is there?

This lead us to a solution. "Simply" generate portlet with tab using Javascript.

One more time again jQuery is our hero. The product add to Plone portal_javascript tool a new jQuery plugin for this. Is not a perfect plugin right now (is a composition of jQuery and normal Javascript OOP) but reach the target.

Here an example:

jq(document).ready(function() {
    var generatedPortlet = jq.tabbedportlet();

    generatedPortlet.makeTab("#portal-column-two .portletNews");
    generatedPortlet.makeTab("#portal-column-two .portletCalendar");
    generatedPortlet.makeTab("#portal-column-two .portlet-static-static");
    jq("#portal-column-two .visualPadding").prepend(generatedPortlet.getPortlet());
});

When page is loaded a new Javascript object is created, and calling makeTab method you can "steal" other existing portlets all around the page (simply giving a jQuery selector, a DOM element or a jQuery object wrapping the portlet).

The method also has other features, look at the pypi page below for more.

Every call to makeTab will remove the portlet and move the DOM elements of the portlet inside a new ones (that, for now, is not inside the document yet).

When you have finished, just put the result of getPortlet method wherever you want.

The final effect is quite good... the demo will show you the page with disabled Javascript, then (long life to Web Developer's Firefox extension) it is enabled again and the page reloaded...

What next?

The product is not so simple to be used by Plone site members (this is not named collective.portlet.tabber... :-))

A developer or a skinner must provide the additional Javascript inside a product/theme and he must know something about jQuery selectors... but after this starting setup... nothing more!

Another thing Ithat is not perfect is the Javascript structure, not a fully jQuery plugin. You can't fully rely on chaining right now.

More info?

http://pypi.python.org/pypi/collective.portlettabber

 

May 03, 2010

No more "display:none" CSS rules!

Filed Under:

Plone give us the right way to hide elements from the HTML page! Just use it... but what about jQuery?

It's not a news that accessibility is an important target of Plone... and Plone knows that CSS rules that use "display: none" are not readable by screen readers. However too often the display:none rules is used in themes, add-ons...

The problem is that this behavior is not so know outside the Plone core. Developers sometimes thinks that something that is hidden from the screen but present in HTML is accessible using a screen reader.
This is false.

How the screen reader works?

I'm not a real expert about it, but here what I learned.
When the page is loaded, the screen reader make a "screen-shot" of it and works on this. So elements that are hidden... for everyone!

So:

  • Use display:none if you really want something in HTML that no-one can read.
  • Do it like Plone does... apply the hiddenStructure class to your HTML element when you want something that can be accessible but not visible on you computer screen

What about Javascript/jQuery?

You like the jQuery .hide() and .show() features like me?

Well... keep in mind what you read some lines above... You use some .hide() call at load time? You use .hide() when clicking some links or buttons?

All this is not accessible...

Again, you need to think about using CSS given by Plone, so you must rely on the .addClass("hiddenStructure") and .removeClass("hiddenStructure") methods...

If you prefer give some new features to jQuery, you can also do something like this:

jQuery.fn.ploneHide = function() {
	return this.each(function() {
		jQuery(this).addClass("hiddenStructure");
	});
}

jQuery.fn.ploneShow = function() {
	return this.each(function() {
		var e = jQuery(this);
		if (e.is(":hidden")) e.show();
		e.removeClass("hiddenStructure");
	});
}

After this, you can rely on some new jQuery features!

jq(".documentByLine").ploneHide();

Apr 11, 2010

Change navigation behaviour with jQuery: collective.navigationtoggle

A requirement from one of our customer lead us to develop a very tiny Plone add-ons... After all jQuery make all the dirty work!

Navigation collapsedOne of the navigation portlet in the main site of our customer is done like the one you see on the left.

The element with arrow icon is unique and very important, but is not the element itself that links to an important document. The real useful information for end users are elements inside the section with this special icon (the subelements).

If facts the different icon itself is not enough so the customer asked us to develop an expand/collapse feature to make possible to users to not be forced to visit the not-important-section, then choose one of the subelements. For user experience the first click is only a waste of time (it's matter of usability) as the general element you see in the navigation is only a way to keep together and categorize the real infos.

Question: how many not-important-section you have in your Plone sites? Folder that only show folder_listing view or useless welcome pages?

Navigation expandedHowever: what we developed in the first version of this site was a very simple (and not configurable) expand/collapse feature, you'll see on the right.

This was a Plone 2.1 site and what you see wasn't a real Plone navigation portlet. Important subelements were all loaded with the page and a simple Javascript script make them visible/invisible.

As we recently migrated this site to Plone 3, the question was: can we reproduce the same effect keeping the Plone real navigation... and this time make the feature more reusable?

Some of you can say at this point that there are already other dynamic Javascript/AJAX navigation system for Plone (for example, I well remember collective.portlet.explore) but the problem here is different... we don't want to make all navigation(s) entries expansible/collapsible but only one (or few).
Another important fact: this is the site of an italian public company, so it must follow the Stanca Act accessibility requirements (so very restrictive in matter of client side scripts technology) and a complete Javascript UI is not a good choice.

The role of jQuery

Instead of developing some new funny navigation system, our work was focused on making the most possible client side (whit an eye on graceful degradation like the Law say).

We used jQuery to obtain a cross-browser, configurable and simple plugin for Plone that make possible to chose on which navigation elements apply the expand/collapse feature. All this client side!

Welcome to collective.navigationtoggle.

The only server side component is a simple view that query the Plone catalog to show all element inside a given folder, and return all information needed by jQuery to generate navigation sub-elements on the fly (quite simple, isn't it?).

The view return only a JSON data structure, so the HTML is generated client side. How? The code create new navigation elements cloning existing ones from the navigation itself (using parents of the trigger element), then filling them using response data.

Is this way is possible that even a non-standard Plone navigation could works normally with this product (not so sure of this... to be honest some assumption are done, like the main element structure of the navigation that must be composed of UL and LI elements).

Cache

Two different problem there: prevent browser from caching server response for a too long time, but also prevent that if a user begin to click 10.000 times onto the navigation element this will send to the server one non cached request for every click.

The first problem is quite simple playing a little bit with HTTP header sent by the server, and adding timestamps with the client side request.

For the second problem we rely on jQuery.data() fantastic method, caching the generated HTML and preventing that expand/collapse actions will ask the server for the same data.

Configuration

Right now we have a real well-know problem to solve, so the product is targeted on developers. To configure it you must provide a tiny Javascript with line(s) of code like this:

jq.collective_navigationtoggle.toggle_elements.push("/foo1/foo2");

This is a simple Javascript array stored in the jQuery plugin namespace. Data provided must be the final part of the href attribute of links inside navigations portlet. This link will no more move the user to the target page but will be gifted with the expand/collapse feature.
All the magic left is done by the power of jQuery.

Plone 4 and jQuery 1.4

The product works on Plone 4 also... no problems with jQuery 1.4 (delivered with the next version of our favorite CMS, while Plone 3.3 still rely on jQuery 1.3), in facts dropping jQuery 1.3 support can reduce the Javascript code size and complexity (jQuery 1.4 has new fantastic features... take a look!).

However a real and pretty integration like with the Plone 3 theme right now is not available in Plone 4 also. Maybe in future I can work on this (Plone 4 Sunburst is not using anymore the IMG tag, instead it generate icon usin CSS classes). If you are interested and wanna help, you are welcome!

Apr 02, 2010

One of the Devil's APIs!

Filed Under:

A tale about the importance of the name. When the name of a piece of code is important, and when a not well chosen API's name can lead to problems... at least for me.

The same bug! For the second time I found the same bug in an old piece of code... again the same silly bug!

Ok, that's my fault... again...
What I can say? I was young and zope.interface was a new entry in the Plone world... often when you begin playing with a new APIs you don't read documentation before... why read how to use a method called "getNameOfCurrentUser" or "convertToUnicode"?
I think that the name says all I need!

Even worst: you try the command and... it runs! You got the expected result, so why investigate further with documentation?

What I'm talking about? I'm talking of the Evil directlyProvides method!

Evil Imp

Let's go back to those sad days

I have an object... I need the API that make possible to provide an additional interface... this is for sure a simple task for zope.interface library...

What I only know is this: a class implements an interface, and object provide it...

When I find an API called directlyProvides I try to understand it's meaning using it's name... may be calling this I'll make possible that my object provide my interface (why directly? I don't know... maybe because the interface is not inherited by the class... it's not implemented... I really don't know!).

Is my first idea right or not? Let's try. I can use this:

>>> from zope.interface import Interface, directlyProvides
>>>
>>> class IMyClass(Interface):
...     pass
...
>>> class MyClass(object):
...     pass
... 
>>> o = MyClass()
>>> IMyClass.providedBy(o)
False
>>> directlyProvides(o, IMyClass)
>>> IMyClass.providedBy(o)
True

So it works! I found my API!

The Devil, revealed

Going back to the real life... today I know the truth:

>>> print directlyProvides.__doc__
Declare interfaces declared directly for an object

      The arguments after the object are one or more interfaces or interface
      specifications (``IDeclaration`` objects).                            

      The interfaces given (including the interfaces in the specifications)
      replace interfaces previously declared for the object.


I'm not really sure of why this API exists (I hope for some good reason, of course...).
Today I also know that the real API I need is alsoProvides (you know... also this name is right... the problem is that I found directlyProvides before the last one...).

Only a doubt about the name itself... why they didn't called it like one of those?

  • onlyProvides
  • exclusivelyProvides
  • willProvideThisAndNoMoreInterfaces

Lessons learned?

  • Read the documentation! Check for the __doc__!
  • Probably you don't need directlyProvides in Plone!

Mar 25, 2010

Giving roles to visitors using HTTP headers

Filed Under:

In a recent project we need to provide different roles to users, basing this choice to host name used to reach the Plone site

What's up?

The Plone site I'm describing here is quite normal, but customer ask us to give some special additional permissions to users that reach the Plone site from an internal domain.

In facts the wanna be still anonymous (forcing no-one to authenticate) but be able to see some documents in a special "Published internally" state.

How tho give this permission to anonymous users?

AutoRole?

I never used AutoRole before, but its clear that the idea behind is what we need. AutoRole is an interesting PAS plugin provide additional roles automatically using the IP of the client that is not what we really wanna there.
It also works well with anonymous users making some magic inside the plugin!

AutoRoleFromHost?

You can find on the Plone SVN our first attempt to use the AutoRole idea for our needs. Changing some lines of codes here and there we changed roles provided relying on HTTP_HOST used.

Problem
The HTTP_HOST works only when the client reach directly the Zope server (not exacly, but we have no controls on the Apache of that company)... and we wanna put Varnish in front of it.
Limit
What if tomorrow I need to give somewhere an additional role to users that use a specific browser, or something else? I can't spend all of my live developing AutoRoleFromSomething products!

AutoRoleFromHostHeader!

The best choice we found is to look at HTTP Header in general, making what header and what value completely configurable.

We developed and released AutoRoleFromHostHeader. Similar to AutoRole, but  you can configure it like this:

HTTP Header;regexp;role,[role,]

To make it the most general as possible, the value of the header is used as a regular expression.

Using this you can reproduce some of the AutoRole features, but you can also make something like this:

HTTP_X_FORWARDED_HOST;special\.hostname\.it;SpecialAnonymous
HTTP_USER_AGENT;(MSIE|Internet\ Explorer);BrowserlessVisitor