Pixelastic

You can cut our wings but we will always remember what it was like to fly.

Posts tagged with "jQuery"

Selecting checked radio button with jQuery and IE8

Ever tried to select the checked radio button of a form using jQuery ? Well I did, and hundreds of time before, and never ran into any issues.

Today's the first time, and it involves my dear friend IE8. Seems like either jQuery or IE8 had trouble with my radio button selection.

I finally managed to get what I wanted but with a sightly different syntax for IE8.

First, the markup

    <form id="myForm">
      <input name="data[Payment][value]" id="payment_1500" type="radio" value="1500" />
      <label for="payment_1500">1500</label><br />
      
      <input name="data[Payment][value]" id="payment_2000" type="radio" value="2000" />
      <label for="payment_2000">2000</label><br />
      
      <input name="data[Payment][value]" id="payment_5000" type="radio" value="5000" />
      <label for="payment_5000">5000</label><br />
      
      <button id="test">Select</button>
  </form>

Pretty simple, isn't it ? I only have three radio buttons, and I would like to get the selected value when pressing the Select button.

What should work everywhere

The following code is pretty straightforward and I expected it to just work.

var selected1 = $('#myForm input[name="data\\[Payment\\]\\[value\\]"]:checked');
var value1 = selected1.val();
console.log(value1);

Note that we have to double escape the [ and ] characters and wrap in quotes the name value. Nothing fancy, just classic string protection. This code works perfectly on Firefox and Chrome, and I deployed it in production for a few weeks.

Then, I got report of users that would have love to use the form, but got an error because no value was selected. I tested and tested it again without finding the cause. Then it occurs to me that all those reports came from user using IE8.

So I rebooted my VM, launched IE8 and was able to reproduce the bug on my first try.

What the heck is IE8 doing ?

Well, that's a deep question, and I've ask this myself countless times before. Once more, IE is doing things in its own weird way.

After some fiddling, I managed to make it work, by just slightly altering the syntax.

var selected2 = $('#myForm input[name="data\\[Payment\\]\\[value\\]"]').filter(':checked');
var value2 = selected2.val();
console.log(value2); 

Yep, that's right, I simply moved the :checked selector in its own filter call and it worked. Took me a while to figure, but this finally turned out to be an easy fix.

You can test it yourself with this jsFiddle example. Don't forget to enable the log panel in IE8 by pressing F12 before running the code.

Adding a <hr> in a tinyMCE instance

When using the default tinyMCE implementation to add an <hr> element to the editor content, the <hr> is added inside its parent <p> element (when it should be an element on its own, without such a parent).

I've added my own plugin to resolve this small issue, here is the code :

(function() {
tinymce.create('tinymce.plugins.pixelastic_hrPlugin', {
init : function(editor, url) {
// Register the command
editor.addCommand('mcepixelastic_hr', function() {
// We get the parent node
var parentNode = editor.selection.getNode(),
uniqueId = editor.dom.uniqueId();
// We insert the hr (with a unique id to select it later)
editor.execCommand('mceInsertContent', false, '<hr id="'+uniqueId+'" />');
var hr = editor.dom.select('#'+uniqueId)[0];
// We split the parent element around the hr
editor.dom.split(parentNode, hr);
// We remove the temporary id
$(hr).attr('id', null);
});
// Adding a button
editor.addButton(pluginName, { title : 'pixelastic_hr.desc', cmd : 'mcepixelastic_hr' });
}
}
});
// Register plugin
tinymce.PluginManager.add('pixelastic_hr', tinymce.plugins['pixelastic_hrPlugin']);
})();

The trick to use the editor.dom.split method to split the parent element around the <hr> element.

The dirty hacks is that there is no way to get a direct reference to a DOM element added through mceInsertContent, so we need to set a temporary unique id and then select it through this id.

I use jQuery in my example and I strongly suggest you to do the same, the selectors it provides are much sexier and helps writing these kind of plugins pretty fast.

Aborting an AJAX request with jQuery

I needed a way to abort a pending AJAX request. I had a dialog box loaded through AJAX, but I let my users the possibility to close the window before it was completely loaded.

I thought I could easily cancel a request using jQuery but it appears that there is no method to do that. I had to instead directly call the .abort() method on the XHR object returned by $.ajax.

What I learned was that aborting an XHR request still triggered the jQuery ajax complete callback, and even passed a success as second argument.

My complete callback was supposed to display the loaded window, so I really didn't want it to fire if the user closed the loading window.

I finally had to check on the XHR.status property (200 if ok, 0 if aborted) to manually stop the complete callback.

It felt surprising at first that an abort call is not intercepted as an error, but after a little more thinking, it is logical. An abort is something that the user asked for, so it shouldn't be considered an error (which is something that no one ever asked for).

On the other hand, it feels strange to consider an abort as a success too. I guess the jQuery team didn't have a lot of request for an edge case like abort and didn't wrap their API around it, leaving us with the low level XHR object instead.

How to prevent a FOUC but still gracefully degrade the jQuery UI tabs

I finally managed to fix something that was bugging me for a very long time. I'm talking about the jQuery UI tabs.

They are pretty useful actually, but I always hated that FOUC they produce. For one split second, you'll see all your tabs content, then they'll be neatly re-arranged in tabs.

What I want are my tabs displayed correctly right away, on first load.

First, the markup

Here's a typical tabs markup :

<div class="tabs">
    <ul>
        <li><a href="#firstTab">First tab</a></li>
        <li><a href="#secondTab">Second tab</a></li>
    </ul>

    <div class="tabPanel" id="firstTab">
        First tab content
    </div>
   
    <div class="tabPanel" id="secondTab">
        Second tab content
    </div>
</div>

CSS to hide all tabs if Javascript is disabled

If your Javascript is disabled, so will jQuery UI. We will then hide the <ul> because it serves no purpose here. We will only show it if js is enabled

.tabs ul { display:none; }
.js .tabs ul { display:block; }

Applying jQuery UI tabs

By doing a $('.tabs').tabs(); jQuery UI will treat your <ul> as your tab menu and all your .tabPanel as their corresponding contents. It will hide all your panels, except for the first one. It does so by adding a .ui-tabs-panel class to every .tabPanel as well as a .ui-tabs-hide to every panel it hides.

Right now, you should add another CSS rule to hide the unused panels :

.ui-tabs-hide { display: none; }

But if you look at your page now, you'll see all your tabs content before they get hidden. That is the FOUC I was talking about. The jQuery UI documentation indicate that to remove it, you should directly add the .ui-tabs-hide class to panels you'll want to hide.

As also pointed in the doc, it will not gracefully degrade because users without Javascript won't even be able to see your other tabs. Also, it asks you to add server-side logic (HTML markup with jQuery specific classes) for something that should be handled entirely client-side.

Removing the FOUC while gracefully degrade

Ok, so what I did was writing two simple rules that will directly hide all unused panels while still displaying the active one, even before jQuery UI takes action.

.js .tabPanel + .tabPanel { display:none;}

That way, no FOUC, and users without Javascript still see all the content. Unfortunatly, if you now try clicking on your tabs, you'll see that nothing happens and you get stuck with your default panel always visible.

Fixing the jQuery UI tabs

As jQuery will add new classes to my elements, I'll just have to write more specific rules that use those classes. Here's the little piece of logic I came up with :

.js .tabPanel.ui-tabs-panel { display:block;}
.js .tabPanel.ui-tabs-panel.ui-tabs-hide { display:none;}

All the jQuery panels are shown, except for the one hidden by jQuery. All those rules being more and more specific, they will get applied once the tabs are activated but still override the previous display: declarations.

Conclusion

This is how I fixed an issue that was bugging me for years. Unfortunatly the CSS rules being dependent on the markup used, I haven't yet been able to write them in a global form that could be added to the main jQuery UI css files.

Knowing if an element is in the DOM using jQuery

If you ever need to know if the element you have a reference to is really in your page (or if it is an old reference to an element you've already replaced), you can use the $.contains(container, contained) method.

I was just working on an AJAX paginated list of elements and I kept references to my table in vars to avoid re-querying it each time. But this reference where still 'correct' (ie. not null) even after having replaced the element with a new one (using .replaceWith() and even .empty() and .remove())

I added a check to see if the element was really in the DOM before using it, and if not requerying it and re-saving it in var

missing data in jQuery Ajax event ajaxSuccess

jQuery provides a nifty set of Ajax events that one could define to fire methods whenever an ajax request starts, stop, failed or succeed. It is quite useful actually, to display visual indicator once and for all without having to define the various callback on each ajax call.

But it has one flaw. The ajaxSuccess callback only give access to the event, XMLHttpRequest object and the option object. There is no direct way to get the return data from the call.

Well, you can still access it from the XMLHttpRequest responseText attribute but it is then your job to parse it according to its type, duplicating some of the code already called in the jQuery core.

You can use the $.httpData to directly parse it, giving the XHR as first argument and the data type as the second one.

$('#ajaxIndicator').bind('ajaxSuccess', function(event, xhr, options) {
var data = $.httpData(xhr, options.dataType);
}

 

Learning extJs

Today, the jquery 1.4 version was released, and today is the day where I start to learn extJs (don't ask me why).

EXTJs seems to be promising, it seems really easy to make powerful and professional-looking interfaces for handling data with it.

When learning Coldfusion I was often horrified by the really poor html example code shipped with the Coldfusion example : <font>, <span align="left">, multiple nested tables.

With EXTJs, it seems to be the same, the basic page setup tell me not to put a Doctype to my page to "avoid IE problems", as well as putting my javascript in the head of the page...

That is simply horrible advices. I hope the rest of the framewok is not of the same kind or learning it will be painful.

 

One thing that is quite frustrating with their documentation is that it is absolutly not up to date. It is refering to 1.0 or 2.0 version even if the current one is 3.0.

I resort to reading the "Learning EXTJs" book (if you know how to properly use google, you should find a full PDF online). It told me a lot of things about the approach of the framework, I sincerely recommend that you read it if you want to learn ExtJs. Too bad the book was released before the 3.0 version, but by refering to the API whenever you need to use a new class, you should be ok

 

Caracole - Antispam des adresses mails

Attention : Ce post n'est plus d'actualité. Je n'utilise plus du tout cette technique pour masquer les adresses mails car elle bloquait complétement la lisibilité aux personnes sans Javascript. Je n'ai pas encoe trouvé de meilleure alternative pour le moment, néanmoins.

Pour faire suite au billet précédent, un autre problème que j'ai rencontré dans le développement des sites était un moyen sûr d'empecher qu'une adresse mail exposée sur un site ne soit récupérée par des robots crawlers et ne deviennent la cible de leurs spams.

Je parse donc chaque texte affiché afin de remplacer les adresses mails par une version cryptée, que Javascript décryptera à l'affichage. A chaque affichage la clé de cryptage est différente. Bon, l'algorithme de cryptage est extremement simple, c'est juste une permutation alphabétique, chaque lettre est remplacée par une autre. Et la clée est indiqué dans l'attribut title de mon code généré.

Il suffit d'étudier le Javascript pour réussir à comprendre comment décrypter les mails, mais cela demande que le robot ai intégré mon codage dans ses paramètres, ce qui n'est à priori pas le cas. Et quand bien même se serait le cas, je changerai la façon de crypter la clé.

Jetez un oeil au code source de cette page si vous le souhaitez pour voir comment cette adresse : blabla@somewhere.com est écrite.

Cela devrait être quelque chose de la forme :

<span 
title="97tasbw84fv6_q3pn5yirchya15nq.p9b_jziggtc40lfolwxxk@meo26m70zj2d-sur@hev8u1-dk.3"
class="protectFromSpam">

<span class="noscript">
<span class="icon icon_notice"></span>
Cette adresse mail est protégée contre le spam, vous devez activer Javascript pour pouvoir la voir.
</span>

<span class="cryptedMail">ageh9gxvw1bag4342e</span>

</span>

Bon alors bien sur, cela nécessite Javascript activé, c'est un gros défaut, je l'admets. Un message permet de ne pas laisser l'utilisateur sans JS dans l'incompréhension, mais ça reste problématique malgré tout. Je n'ai pas réussi à faire mieux pour le moment.

Faire communiquer PHP et Javascript

Pour un projet sur lequel je suis en ce moment, je dois faire une interface d'administration assez complexe, le tout en AJAX.

Je dois manipuler des données sorties de ma base de donnée, venant de trois modèles différents, mais liés les uns avec les autres. Autant dire que j'ai de gros tableaux imbriqués de données. La page travaille vraiment beaucoup et manipule ces données constamment.

Pour éviter d'avoir à faire une requete sur ma base à chaque fois que j'ai besoin d'une information, j'ai décidé de tout charger une première fois au chargement de la page, puis de modifier coté client les variables sur lesquelles je travaille, pour ne faire des requetes au serveur qu'en upload (update/add) et ainsi éviter au maximum les requetes en download inutiles.

Pour ça, j'utilise tout d'abord le base64 de php pour passer des objets complexes (au format JSON) à mes scripts. Muhammad Hussein Fattahizadeh a pour cela mis à disposition une retranscription de l'encodage/decodage en base64 pour jQuery.

Je peux stocker ainsi mes objets de façon "dormante" dans ma page, et les décoder pour récupérer/modifier les valeurs dont j'ai besoin avant de les réencoder et de les re-ranger dans mon DOM. L'avantage de stocker ces informations en base64 c'est que je ne risque pas de causer d'erreur de syntaxe, de balise non fermée, de caractère interdit, même si je manipule des objets contenant du code HTML et que j'ai besoin de le stocker dans mon propre DOM.

J'utilise aussi à coté de ça le parseur officiel JSON pour parser mes éléments JSON. Il ne me reste plus qu'à passer mon élément JSON en paramètre aux fonction AJAX de jQuery pour mettre à jour mes données.

Caracole - Edition directe

En plus de son interface d'administration complète, Caracole permet un mode d'édition rapide directement accessible depuis le coté front-end des sites, pour corriger rapidement un titre ou un coquille qui se serait glissée quelque part.

Grâce à jQuery et cakePHP, cette fonctionnalité est très simple à utiliser. Il suffit pour cela d'avoir les droits d'administration sur le site que l'on veut modifier. Il suffit ensuite d'y naviguer avec un navigateur assez récent (cela signifie que IE6 n'y est pas convié) et s'affichera alors automatiquement en haut à gauche de l'écran un petit switch qui permet d'activer ou de désactiver en un clic le mode édition.

Lorsque le mode d'édition est activé, il suffit de cliquer sur une zone que l'on veut modifier pour la transformer automatiquement en champ de formulaire modifiable (avec intégration de tinyMCE pour les grands textes). Il suffit alors de modifier le texte exactement comme on peut le faire dans l'admin et de valider. La modification est prise en compte automatiquement.

Coté développeur, il suffit d'ajouter une class="editable" ainsi qu'un id formé de la forme "controller-champ-id". Ainsi id="posts-text-12" identifiera le champ "text" du 12e post.

C'est extremement pratique pour modifier des fautes de frappe, ou faire des tas de petites modifications, je m'en sers énormément et c'est un pas de plus pour faciliter l'administration à l'utilisateur final.