Personal tools

Luca Fabbri

Jul 13, 2010

Evil Javascript... use strip() or trim()?

Filed Under:

I hate doing the same error all the time! One time again: how to obtain a cross browser method for Javascript to remove leading and trailing whitespaces inside strings?

Every time I make the same stupid error! So, let's make quickly, a guide for obtaining this behavior.

The trim() function works only on Firefox and maybe other browsers, but not on IE.

In my experience the stript() function instead works better, both on Firefox and IE.

So what is the problem? That every time I forget to use this last one, and I use again trim instead of stript...

There is a solution for my lazy brain? Well... I use very often (AKA "always") jQuery for our projects, so... please Luca... from now just use jQuery.trim().

I feel better! No more error from now...

...

... until next time.

Jul 08, 2010

Scripted CSS Injection (or whatever better name you can find for this technique)

While trying to close a request for one of our customer for obtaining a random image portlet I tested an alternative way to deliver CSS. Using Javascript.

When Web pages load and run things

Let's start with CSS. Browsers load HTML source from the Web. Inside the page you will find resources that are CSS file. Immediately the resource is loaded and the rules inside are applied to your HTML.

Now switch to Javascript resources. For Javascript... it's the same. The Javascript code is executed as soon as it is found in the page...

...but for this reason, when we need to act using Javascript on an already loaded DOM, we rely onto Javascript events.

We read immediately the code, but the execution is postponed later, when the page is fully loaded.

As the use of jQuery became standard for those tasks (especially in Plone) we always use something like this:

jq(document).ready(function() {
    // do something
});

When this lead to problems

Although we have really no choice, there are some cases where this "postpone things" is not perfect: when we need to apply (using Javascript) CSS classes on page elements at page load time.

But we can't avoid making those actions when page is loaded.

If we don't rely on onload event, we have no ready DOM to traverse. So we can't load and change a DOM node if the page is not fully loaded (even if we put the Javascript script after the HTML that define the node).

<html>
<body>
    <div id="foo">Hello world</div>
    <script type="text/javascript">
    <!--
        var foo = document.getElementById("foo");
        alert(foo.innerHTML);
    // -->
    </script>
</body>
</html>

The code above is bad, even if you are using or not jQuery... So we really need to wait for the moment when DOM is ready. You can't act of the page DOM before it is fully loaded.

However: what is the problem applying CSS style when the DOM is loaded?

The nasty effect can be a visual flip.

The page in the browser show the DOM node with the original CSS style, then after some time (that can be not so brief sometimes if the page is full of elements and heavy scripts) the Javascript engine run your code, and the node is changed: your new CSS class or your new scripted style is applied.

<html>
<head>
    <style type="text/css">
    #foo {
        background-color: red;
    }
    </style>
    <script type="text/javascript">
    <!--
        window.onload = function() {
            ... MANY OTHER EXPENSIVE OPERATIONS
            var foo = document.getElementById("foo");
            alert(foo.innerHTML);
        }
    // -->
    </script>
</head>
<body>
    <div id="foo">Hello world</div>
    ... A LOT OF MANY AND MANY HTML NODES
    <script type="text/javscript">
    <!--
        var foo = document.getElementById("foo");
        alert(foo.innerHTML);
    // -->
    </script>
</body>
</html>

A practical example

A customer ask us to develop a Plone portlet that:

  • show some random images when the page is load
  • works behind a reverse proxy (Varnish)
  • works with Javascript disabled (accessibility and graceful degradation)

Step 1

Varnish is caching all our resource, images and also HTML for every page. We can't (and don't want) change this.
How to cache everything but some little images inside a portlet?

The idea is to use Javascript  for performing AJAX request for this portlet and obtain a structure of data. The cache of this kind of request can be avoided easily.

Step 2

So we are able to load an HTML for the portlet without images then, when the DOM is ready, we can populate the portlet waiting for the AJAX call to the server. For some time the visitor see and empty portlet that magically begin load images. The effect is pleasant (at least... it's not annoying).

But we can't!
The portlet must work also with disabled Javascript... So we must load random images also when the page is loaded.

NB: if the visitor use a browser with Javascript disabled, we can only give him some random pre-loaded images, but we can't prevent Varnish cache of the whole page. Reloading the same page will show him the same images for some minutes. This is acceptable for us (and for the customer!).

Step 3

The final result is to load the first "static" images in the portlet itself, then use Javascript as described at step 1: changing those images with new ones obtained from AJAX call.

This lead to the ugly visual flip effect I talked above.

I can't explain why (this is not my work), but see an empty section that is filled after a little delay is not ugly... instead seeing a set of images that suddenly change to other is... bothersome!

Step 4?

Ok, so we can simply load static images hidden by some CSS class, then using Javascript we can show them only after the AJAX call and...
Opss!

But in this way we don't see any image when Javascript is disabled!

Ok... step 4 aborted.

Scripted CSS Injection

The perfect world is the one where the step 4 is performed, but only with Javascript enabled.

I need a CSS that is loaded early like all other CSS in the page (so its style is applied immediately to the page) but only when Javascript is enabled.

I found a way to do this, but surfing the web I was not able to find other example like this one. So I called this approach Scripted CSS Injection (SCI)... maybe someone can point me to other original name or example?

However... how this works? Simply generating the additional CSS... with Javascript!
For this we use the standard window.write Javascript API. The window.write command is used commonly to write HTML inside windows (is more common to use it in popup windows for generating the contained HTML from scratch).

The additional Javascript is load in the page head section and it doesn't wait for DOM load. The one in our product is only one line:

document.write('<style type="text/css">.hideFlag img {display: none}</style>');

As I said at the beginning, Javascript is interpreted as CSS, so immediately when found in the page.
The browser will add to HTML the style node immediatly.

What is nice of SCI approach is obvious: a browser with no Javascript support can't add the CSS rule to the page!

Fairytale gone well

This technique finally lead us to a portlet that:

  • will show cached images if without Javascript support, but images are still random (chosen server side and changed with some delay)
  • will show random (and not cachable) images client side if Javascript is enabled
  • No ugly visual flip effects. With Javascript enabled static images are loaded hidden, then new dynamic ones are taken from the server and show. Thanks to SCI approach.

For more info, check the code of auslfe.portlet.multimedia.

Jun 29, 2010

TextAreaBound: trying my first jQuery plugin

Filed Under:

HTML has some nice attributes to control the size and bound of input fields. However those features aren't working for textarea. Our customer asked us for those features: max number of lines in a textarea, max numbers of character in the textarea AND in a line... Let's try to develop a jQuery plugin for obtaining this!

The APIs we want

We must reach this:

$(...).maxLinesLengthBound(n)
$(...).maxLinesCountBound(n)
$(...).maxTextLength(n)

Obviously, we like jQuery chaining so also this must work:

$("#textareaid").maxLinesLengthBound(20).maxLinesCountBound(15)

All of the 3 new jQuery feature take all the textarea element from the expression given and apply new bounds.

  • The maxLinesLengthBound method will put a limit on the number of lines in a textarea
  • The maxLinesCountBound method will put a limit in the number of characters of each line
  • The maxTextLength method is the most simple: put a limit to the number of total character in textareas

Bad news one: Javascript events

When adding character in Javascript we can rely on 3 events:

  • keydown event is called when a key is pressed (before it's released)
  • keyup event is called when a key came up after keydown.
  • keypress event is called when a key is... pressed! So it goes down and then up. More important, if you keep pressed a button, starting the characters-repeat, this even is called multiple times.

As you can see, the keypress is the best choice... but using this event has a great limitation.

When you rely on keydown event, the "actual" value of the field is the old one. When you bind an handler to the keyup or keypress events the value you can read is the new one. You have no way to know what was the old value of the field.

Don't think to use other events like change event. This type of event has the same problems and also is called only when the control loose focus.

Solution to bad news one

Yes, we can only read the new value of the field, but we can also use the event object and take from it the value of the pressed key, so we can know what new character will be added to the textarea

Bad news two: knowing where the cursor is (on Internet Explorer)

Unluckily in a textarea you not always append characters... you can also add new characters inside the text. The pressed key is still a very important resource, but we must also know in which position we are inside the textarea.

The task seems simple on all browsers, with only a single exception: you can read the selectionStart attribute of the textarea DOM element.

We really need this only for one of the 3 APIs we need above: the maxLinesLengthBound method. Other 2 APIs don't need to know where the cursor is...

The vary bad news is this: Internet Explorer do not support selectionStart!

Solution to bad news two (AKA: bad news three)

You can surf the Web looking for an alternative, and you will find a lot of blog post, articles and script for giving an alternative.

What is clear is that you must use some funny IE specific APIs like getBookmark and createRange... and you will fail.

I found no way to really have the same simple features. On Internet Explorer 7 I have a lot of problems when I go to the second line. Those APIs seems buggy and the browser don't see that you go on a new line.

Tired of this, I leave the problem to someone more expert than me with IE Javascript.

Wanna help me?

The jQuery plugin

You can find the plugin on the jQuery plugin official page, and also other info and the SVN repository in my Google Code space.

 

Jun 15, 2010

Imparare a configurare ZopeSkel rende l'Azienda più felice

Filed Under:

Già da qualche versione, ZopeSkel offre una serie di opzioni aggiuntive che sono purtroppo mal documentate nella pagina ufficiale del pacchetto. Ecco come usarle e configurarle per la propria comodità e per aumentare l'omogeneità dei rilasci della propria azienda!

Prima di tutto: usi ZopeSkel... vero?

La frase "non uso ZopeSkel perché sono abituato a fare le cose a mano" non è una scusa valida!

Personalmente uso ZopeSkel solo per la creazione iniziale dell'egg, poi non mi addentro mai molto nell'uso dei "paster subcommands" (ma non sempre... creare nuovi contenuti per Archetype è comodissimo), ma quello è sufficiente. Usare ZopeSkel per generare lo scheletro dei propri egg significa seguire la convenzione e lo standard!

Non mi addentro ulteriormente sull'argomento... se non usi ZopeSkel e sei incuriosito puoi leggere l'ottimo articolo che trovi su plone.it.

Le novità nascoste

Prima di tutto verifica di avere disponibile l'ultima versione (al tempo in cui scrivo siamo alla 2.17):

keul@redturtle:~$ sudo easy_install -U ZopeSkel

Notate innanzi tutto che il vostro sistema ha ora disponibile un secondo comando oltre al già conosciuto paster (hai letto l'articolo sopra, vero?). Il nuovo script è zopeskel.

Cosa fa? A cosa serve? E' principalmente una forma semplificata del primo script... diciamo un "paster for dummies", ma la vera comodità e fulcro di questo articolo è la possibilità di creare tramite lo script zopeskel un file di configurazione con alcune preferenze di default.
Possiamo finalmente usare paster e premere molte più volte "invio" (accettando la risposta predefinita) di quanto non venisse fatto fin'ora.

Lanciando la guida di zopeskel otteniamo questo:

keul@redturtle:~$ zopeskel --help
...
You can generate a starter .zopeskel file by running this script with
the --make-config-file option. This output can be redirected into
your ``.zopeskel`` file::

    bin/zopeskel --make-config-file > /path/to/home/.zopeskel
...

In pratica, nella nostra home possiamo ospitare un file con nome ".zopeskel" con alcune configurazioni predefinite e zopeskel stesso ci permette di generarne uno di esempio, con una sintassi valida, da personalizzare poi.

keul@redturtle:~$ zopeskel --make-config-file > ~/.zopeskel

Le opzioni non sono poche e forse un tantino esagerate. Potreste ad esempio avere configurazioni diverse a seconda del tipo di comando paster che state usando...
Io rimango per un uso semplice!

# This file can contain preferences for zopeskel.
# To do so, uncomment the lines that look like:
#    variable_name = Default Value

[DEFAULT]

expert_mode = all
version = 0.1.0
author = RedTurtle Technology
author_email = sviluppoplone@redturtle.net

[archetype]

# Expert Mode? (What question mode would you like? (easy/expert/all)?)
# expert_mode = easy
...

La sezione [DEFAULT] sarebbe solitamente vuota ma avete esempi completi in tutte le altre sezioni (una per ogni tipo di template paster disponibile) anche se tutte commentate.

Se come me volete configurare solo la sezione [DEFAULT], usate qualche copia incolla!

Quella postata qui sopra è esattamente la mia configurazione, che vado ad analizzare.

  • expert_mode. E' un'opzione piuttosto nuova e il default sarebbe "easy"; è interessante perché permette di porre all'utente "meno domande"... ma visto che fingo di essere uno smanettone, preferisco che mi venga chiesto "tutto".
    Scherzi a parte, alcune delle domande che vengono saltate le trovavo invece importanti.
  • version. La versione del prodotto. Il default è 1.0, ma personalmente preferisco 0.1.0, ben considerando che paster viene usato alla creazione... è molto probabile che la prima release del vostro super prodotto non sia tanto super... e che sia di qualità alpha o beta.
  • author. Nel mio sistema, a lavoro, è normale che il codice sviluppato sia della mia Azienda. Niente più scrittura a mano del nome dell'Azienda e niente più differenze tra me e il mio collega.
  • email. Di nuovo convenzione (un unico indirizzo aziendale per lo sviluppo plone)... ma potrei specificare la mia email personale!

Teniamo comunque presente che questi valori sono solo i default. La gestione dell'eccezione è sempre possibile (non diventate troppo pigri ... potete sempre cancellare la risposta che paster sta proponendo e scrivere da capo... come poi avrete fatto fin'ora!).

Un file .zopeskel in ogni azienda

Perché no? Pensare che ogni sistema per sviluppatori all'interno di un'azienda abbia questa configurazione porta uniformità al rilascio di codice alla comunità. Non sto ovviamente parlando delle prime opzioni che abbiamo illustrato, che sono più che altro preferenze davvero personali.

Potrebbe non essere il vostro caso, magari la Vostra Azienda preferisce mantenere come autore lo sviluppatore stesso (oppure mettere sempre come autore il nome del capo programmatore che vive sulle vostre spalle e che vi ruba la fama). Altre magari vorranno che tutto il codice risulti fatto dall'Azienda, ma potrebbe voler mantenere la Vostra e-mail (così che siate voi ad essere disturbati in caso di problemi).

Questo, come dicevo è come il mio sistema è configurato qui in RedTurtle... a casa mia ho un PC che ogni tanto uso per i miei personali progetti Plone. Ovviamente anche là uso paster ma in una configurazione diversa!

Provate anche voi. E' comodo...

Jun 01, 2010

Sorrento Amberjack Sprint: can you please make my Javascript simpler?!

The Javascript source of collective.amberjack need some love. Not a simple task, but something has been done (and something more will be in future).

First of all: the inherited problems

Version 1.0 of collective.amberjack.core is a simple Plone 4 compatibility version of the old 0.9 released.

When we released this we already know that the Javascript code inside was a mess, but before beginning some kind of refactoring procedure we need to know how the project will be changed in future.

The product has a core got from the Amberjack library, in last years many different programmers take part to the develop, meanwhile the product itself (and also Plone) was changing.

Amberjack original library is framework independent while Plone (that is a smart guy) rely on jQuery. Also we must not forget that Amberjack has not exactly the same target of collective.amberjack...

Wow... how many problems!

Not all those problems will be fixed in the 1.1 version, but I managed to simplify a little the code of two of the main method of amberjackPlone.js source.

How the old amberjackPlone.js was done

Saying "the code is ugly but works" is not a valid excuse... amberjackPlone.js contains two important methods:

  • highlightStep is the method that highlight all the clickable/usable elements in the page for all steps.
  • doStep is the method called to reproduce the user action on the page

What a user can do in a Plone site is a long list of different actions... list that become something like this for highlightStep:

...
if(type_obj=="checkbox" || type_obj=="radio"){
	obj.parent().addClass(theAJClass);
	obj.addClass(theAJClassBehaviour);
}
else if (type_obj=="select"){
	var highlightThis = jq(obj + " option[value="+ AjSteps[num].getValue() +"]");
	highlightThis.addClass(theAJClass);
	obj.addClass(theAJClassBehaviour);
}
else if (type_obj=="multiple_select") {
...
...going on and on...
...
else{
	obj.addClass(theAJClass);
	obj.addClass(theAJClassBehaviour);
}
...

...and like this for doStep:

...
if (type_obj == 'link') {
	AmberjackPlone.setAmberjackCookies();
	location.href = obj.attr('href');
}
else if (type_obj == 'button')
	obj.click();
else if (type_obj == 'collapsible') {
...
...going on and on and on and on...
...

Ugly enough? Do you think that the AntiIfCampaign hate us? Yes... probably...

So the first step is to remove this mess and try to port in Javascript the concept of adapter (unluckily not so flexible as Zope ones).

Even worst: many of the if statement in both doStep and highlightStep was testing the same expressions (like the "select")!

All this code has been removed.

Now both doStep and highlightStep are simply calling a sort of adapter built in the stepAdapters.js file:

AmberjackPlone.stepAdapters = {
	
	link: {
		highlight: null,
		checkStep: null,
		step: function(obj, type_obj, jq_obj, value) {
			AmberjackPlone.setAmberjackCookies();
			location.href = obj.attr('href');
		}
	},
	button: {
		highlight: null,
		checkStep: null,
		step: function(obj, type_obj, jq_obj, value) {
			obj.click();
		}
	},
	collapsible: {
...
... going on and on, but more readable!
...

For every possible action know, highlightStep and doStep will check if an handler is present in this structure (in this example the code is from the doStep):

if (AmberjackPlone.stepAdapters[type_obj] && AmberjackPlone.stepAdapters[type_obj].step)
	AmberjackPlone.stepAdapters[type_obj].step(obj, type_obj, jq_obj, value);

The highlightStep will check for highlight function.

If not if found, perform the old final else statement.

What is the checkStep section? In this sprint another group (Mirco and Simone Orsi) take a look to the feature that test if a user has completed all the steps before can go to the next page. Again, for some possible action the tour can try to test if you have finished your work.

In the old approach this will lead us to a new big and monolithic function. Now is only a new tiny method that call an handler.

What's next?

Adding some documentation, for sure...

Also other part of the code can be changed to become more clear...

...to be continued!