Tuesday, May 06, 2008

CSS and javascript progress meter

For a little video project, my daughter wanted a computer screen displaying a fake progress bar. I haven't played with GUI programming languages for ages (the last time must have been with Delphi Pascal some 15 years ago). So I thought this should certainly be possible to do in a web browser with some CSS and javascript, and would also be an opportunity to learn some javascript and DOM interaction basics.

I first found a few examples on the web, but they all used animated GIFs. I didn't want to bother with creating animated gifs, and besides, I wanted to be able to style the progress window using only CSS, so that sizes and colors could be changed quickly enough for my impatient daughter.

So this is a solution using only CSS and javascript, and no image at all. Have a look at the source code of this CSS and javascript progress bar demo.

Both the CSS and the javascript are embedded in the HTML file.

(Tested in Firefox 2.0.0.14, Opera 9.01, Safari 3.1.1, MSIE 5.01 and 6.0. All in WinXP SP2).

Labels: , , , , ,

Tuesday, July 04, 2006

Liens email dans les pages web

J'ai eu un peu de mal à retrouver ce lien, alors je suppose que ça vaut la peine de l'inclure dans un petit article de blog. (Finalement, mon attention a été détournée par de nombreux à-côtés; les liens pertinents sont dans les tout derniers paragraphes).

Bien qu'on puisse considérer que la lutte anti-spam s'apparente à un ré-arrangement des transats sur le pont du Titanic, il faut quand même encore mettre des liens email dans des pages web. Là, bien sûr, ils sont immédiatement collectés pour servir à nous inonder de "pourriels", comme disent les français.

Comment avoir des liens "mailto:" cliquables, sans que tous les robots de spam en profitent? Les trucs simples comme "user [at] example [dot] com", ou les plus compréhensibles mais très peu pratiques images montrant le texte de l'adresse ne sont pas cliquables. N'essayez même pas les vieux trucs avec les entités HTML (@ pour "@" etc.); ça fait longtemps que ça ne marche plus. Il n'existe plus guère de robot qui s'y prenne les pieds.

Ce qui marche bien est le javascript, que les robots collecteurs d'adresse ne décodent pas; du moins pas encore, à ma connaissance. Mais la plupart de ces astuces javascript qu'on trouve sur le web utilisent un chiffrage antique (littéralement), et même pas sa version ROT13, mais un décalage de 1. Etant donné qu'une simple expression Perl comme s/(.)/chr(ord($1)-1)/eg; permet de le décoder, je doute que ça résiste longtemps. Tiens pendant que j'y suis, voilà un script Perl complet qui suffit pour extraire d'une page web les emails cryptés de cette façon (quel que soit le décalage utilisé):
use LWP::Simple;
my $url = shift or die "Usage: $0 URL\n";
my $html = get($url);

while ( $html =~ /href=['"]javascript:.*?\(['"](.*?)['"]\)/g ) {
my $crypted = $1;
foreach my $i (-25..25) {
my $try = $crypted;
$try =~ s/(.)/chr(ord($1)+$i)/eg;
if ($try =~ /@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}/) {
print "$try (i=$i)\n";
}
}
}

Bref, je cherchais un peu mieux que ça.

Un certain Jim à qui on essayait de vendre un de ces scripts minables a poussé la solution javascript usuelle (autre exemple ici) à un niveau de cryptage un peu plus actuel. Avec Email Protector, il fournit une page où on peut crypter son adresse email. Il n'y a plus qu'à copier le code fourni aux bons endroits, après avoir séparé du code le lien mailto: lui-même et les fonctions javascript aux noms "obfusqués" et pittoresques. Certains trouveront cette solution exagérée, mais justement, je trouve aussi un certain charme là où d'autres ne verront que du "over-engineered".

Update: Finalement, j'utilise ça régulièrement, et mes suis même adapté le cryptage des adresses en un petit script Perl (disponible ici en .pl et en .bat), pour pouvoir l'utiliser directement à la ligne de commande et obtenir la partie à coller dans le HTML:

#mailcrypt 11 31 user@example.com
<!-- crypted email link -->
<a href="javascript:var N=341,D=43;bid('127 300 95 104 4 95 263 202 318 7 147 95 271 99 133 318',N,D)"
title="Click to send email"
class="mail"<Email</a>

Au préalable, il faut évidemment ajouter
<script type="text/javascript" src="/js/emailProtector.js"></script> dans la page, et sauver l'excellent script de Jim Tucek.

Labels: , , , , , ,

Thursday, March 30, 2006

Simple multi-language web site

There are many ways to direct users of a multi lingual web site to the pages
in their language. But the quick search I made didn't return solutions which
I liked, so I did my own. I'm sure the same method has been used many times,
but it's sometimes faster to re-invent a little wheel than to go through zillions
of Google search results.

I had to work on a small site with about 20 pages, in which every language
is in it's own subdirectory (www.example.com/fr/, www.example.com/en/, etc.)
and the file names remain the same.

This simple structure made it pretty easy, and the solution makes it trivial
to add another language.

(For a lot of background about languages and a different solution involving the standard "content language negotiation", see Dan's Web Tips: Languages).

The way I choose works like this:

  • One line in every page calls a PHP script to display the language menu.
  • A cookie holds the visitor's preferred language.
  • Javascript updates the cookie when the visitor switches languages.
  • The main index page redirects visitors to the correct language, using:
    1. either the cookie set in a previous visit,
    2. or the browsers preferred language,
    3. or a pre-configured default language.
  • Finally, a few CSS rules take care of the menu's look

The redirector script

I used a little Perl script on www.example.com/index.cgi. It first looks for
a cookie with the user's preferred language. If there is no cookie (usually
because this is a first-time visit), it uses the HTTP_ACCEPT_LANGUAGE
header sent by the browser to find the best guess. If that doesn't work either,
it uses a default language. Finally, it redirects the user to the selected language,
while also setting the cookie for the next visit.

I probably could have done the index page in PHP, but I didn't know PHP at
all when I started so it was easier for me in Perl. (If someone feels like sharing
a PHP translation, you are welcome to post it here as a comment).

#!/usr/bin/perl

# Redirect to correct language version of the site.
# Use cookie if present. Otherwise, parse the HTTP_ACCEPT_LANGUAGE header

# Author: Milivoj Ivkovic "mi\x40alma.ch"

use CGI qw(:cgi);
my $VERSION = 0.3;

my %available_languages = (
fr => 'fr/',
de => 'de/',
en => 'en/',
default => 'fr/',
);
my $wanted_language = 'default';

my $q = new CGI;

# try cookie
my $cookie_lang = $q->cookie("lang");
if ( exists $available_languages{$cookie_lang} ) {
$wanted_language = $cookie_lang;
}
else { # try HTTP_ACCEPT_LANGUAGE
# Get languages from HTTP_ACCEPT_LANGUAGE, (ignore local variants like en_US, en_GB, etc.).
# Don't bother removing duplicates
my @langs = map { substr($_, 0, 2) } split(/,/, $ENV{HTTP_ACCEPT_LANGUAGE});
foreach my $l (@langs) {
if (exists $available_languages{$l}) {
$wanted_language = $l;
last;
}
}
}

# if nothing worked, we still have the default in $wanted_language which was set at the beginning

# fix URL
my $url = $q->url(-path_info=>1);

# (some servers give us a url ending with 2 "/", so we need "/+" in the
# regex below instead of just "/".
# Otherwise we may end up with "http://example.com//en/" which doesn't look nice.)
$url =~ s|/+[^/]*$|/$available_languages{$wanted_language}|;

# redirect, and also set the cookie for next time
print $q->redirect ( -uri => $url,
-cookie => cookie( -name=>"lang",
-value=>$wanted_language,
-path=>"/",
-expires=>"+1y",
),
);

The javascript setLang function

Anytime the user switches the language, this small javascript function is called
to update a cookie, so the preference is saved between sessions.

function setLang(l) {
// Called from links which switch the language.
// Sets cookie with "lang=fr" or other language code, valid for 1 year, on all the site
var expire = new Date();
expire.setTime(expire.getTime() + 3600000*24*365);
document.cookie = 'lang='+l+";expires="+expire.toGMTString()+";path=/";
}

The language menu

Since all the pages happened to be in PHP, I did the language menu in PHP,
and added an include() to every page.

<?php include("../common/lang-selector.php") ?>

The PHP lang-selector.php script

This is the PHP script which displays the language menu.

<!-- php lang selector -->
<div id="lang-selector">
<?php
$langs = array(
"fr" => "Français",
"de" => "Deutsch",
"en" => "English",
);
$self=$_SERVER['PHP_SELF'];
$pattern = "{^/[^\/]+/}i";
$links = array();
foreach ($langs as $l => $lang) {
$is_current = ! ( strpos($self, "/$l/") === false );
$url = preg_replace($pattern, "/$l/", $self);
$link = "<a href=\"$url\" class=\"lang-"
. ($is_current ? "current\">" : "other\" onclick=\"setLang('$l')\">")
. " $lang </a> ";
array_push($links, $link);
}
echo " " . implode(" | ", $links) . "\n";
?>
</div>
<!-- end php lang selector -->

The finishing touch: CSS

The look of the language menu is done with CSS. This is what I used for that
particular site:

#lang-selector {
font-size: x-small;
text-align: left;
white-space: nowrap;
padding-bottom: 1em;
color: #CCCCCC;
}

a.lang-current,
a.lang-current:visited,
a.lang-current:hover
{
color: #CCCCCC;
text-decoration: underline;
}

a.lang-other,
a.lang-other:visited
{
color: #333333;
text-decoration: none;
}

a.lang-other:hover
{
text-decoration: underline;
background: #F7EBDB;
}

@media print {
#lang-selector {
display: none;
}
}

By the way, the site for which this was first set up is http://lesouffledudesert.com/.

When we added English to the site, the steps were:

  • Copy the /fr directory to a new /en directory
  • Add "en" => "English", to lang-selector.php
  • Add en => 'en/', to index.cgi
  • And of course, translate all pages, which was the real work and where
    this little multi-language system doesn't help...

Labels: , , , , ,