/* -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 */ ;(function($) { var DEFAULT_EXPR = ":input:visible"; $.resetTabindex = function(expr) { // Par défaut, prendre tous les champs de saisie *visibles* if (expr === undefined) expr = DEFAULT_EXPR; // Réinitialiser tabindex $(expr).each(function() { $(this).attr("tabindex", 0); }); }; $.initTabindex = function(expr) { // Par défaut, prendre tous les champs de saisie *visibles* if (expr === undefined) expr = DEFAULT_EXPR; // D'abord calculer la plus grande valeur de tabindex var tabindex = 0; var $expr = $(expr); $expr.each(function() { var ti = parseInt($(this).attr("tabindex")); if (ti > tabindex) tabindex = ti; }); // Puis initialiser les éléments restants à partir de tabindex + 1 tabindex += 1; $expr.each(function() { var ti = parseInt($(this).attr("tabindex")); if (!ti) $(this).attr("tabindex", tabindex++); }); }; $.updateTabindex = function(expr) { if (expr === undefined) { $.resetTabindex(":input"); $.initTabindex(DEFAULT_EXPR); } else { $.resetTabindex(expr); $.initTabindex(expr); } }; // éléments de formulaires navigables avec la touche Entrée $.NAVIGATE_INCLUDES = ":input"; // éléments de formulaire qui ne sont pas navigables avec la touche Entrée, // à exclure de la liste sélectionnée avec $.NAVIGATE_INCLUDES $.NAVIGATE_EXCLUDES = "textarea, :submit, :reset, :button"; $.navigate = function(expr, selectAfterFocus, initTabindex) { // Par défaut, prendre tous les champs de saisie *visibles* if (expr === undefined) expr = DEFAULT_EXPR; // Faut-il sélectionner le champ après avoir changé le focus? if (selectAfterFocus === undefined) selectAfterFocus = true; // Faut-il initialiser tabindex pour les éléments sélectionnés? Sinon, // il FAUT spécifier les valeurs de tabindex pour tous les champs // concernés par la navigation if (initTabindex === undefined) initTabindex = true; // Dans les fonctions suivantes, input est un objet non wrappé par // jQuery. $input est le même objet wrappé par jQuery. var isdefined = function(value) { return value !== null && value !== undefined; } var isempty = function(input) { return $(input).val().length == 0; } var hasSelection = function(input) { // Tester si l'élément peut contenir une sélection. Typiquement, // il s'agit d'un champ de saisie de texte. try { return isdefined(input.selectionStart) } catch (e) { } } var isNavigable = function(input) { // Tester si l'élément est navigable avec la touche Entrée // Par défaut, on prend tous les éléments de type :input sauf textarea et submit var $input = $(input); return $input.is($.NAVIGATE_INCLUDES) && !$input.is($.NAVIGATE_EXCLUDES); } var noSel = function(input) { return input.selectionStart == input.selectionEnd; } var allSel = function(input) { var length = $(input).val().length; return length > 0 && input.selectionStart == 0 && input.selectionEnd == length; } var noSelOrAllSel = function(input) { return noSel(input) || allSel(input); } var focusinput = function(tabindex, select) { if (tabindex > 0) { var $input = $("[tabindex='" + tabindex + "']"); $input.focus(); if (select) $input.select(); } } var previnput = function(input, select) { var tabindex = $(input).attr("tabindex"); if (tabindex) focusinput(parseInt(tabindex) - 1, select); } var nextinput = function(input, select) { var tabindex = $(input).attr("tabindex"); if (tabindex) focusinput(parseInt(tabindex) + 1, select); } var atbegin = function(input) { if (! hasSelection(input)) return true; else if (noSelOrAllSel(input)) return input.selectionStart == 0; else return false; } var atend = function(input) { var length = $(input).val().length; if (! hasSelection(input)) return true; else if (noSelOrAllSel(input)) return input.selectionEnd == length; else return false; } var hideAutocompleter = function(input) { var select = $(input).data("ac.select"); if (select) select.hide(); } if (initTabindex) $.initTabindex(expr); // Installer les handlers pour backspace, entrée, flèches gauche et droite. var state = { 'autocomplete': false, 'leftKeyThenPrevinput': false, 'rightKeyThenNextinput': false, }; $(expr).keypress(function (e) { if (state.autocomplete) { // Si on est dans l'état auto-complétion, ne pas traiter les // caractères pour éviter les conflits state.autocomplete = false; } else if (e.which == 8 && hasSelection(this) && isempty(this)) { // backspace sur un champ vide déplace la sélection sur le champ précédent hideAutocompleter(this); previnput(this); return false; } else if (e.which == 13) { if (e.ctrlKey) { if ($.wosubmit) { $.wosubmit(this); return false; } } else if (isNavigable(this)) { // entrée sur un champ déplace la sélection sur le champ suivant nextinput(this, selectAfterFocus); return false; } } }).keydown(function (e) { // intercepter flèches gauche et droite pour ne pas avoir d'effet de bord qui // gênerait le traitement des événements dans keyup var key = e.which; if (key == 37 && atbegin(this) && !allSel(this)) { state.leftKeyThenPrevinput = true; return false; } else if (key == 39 && atend(this) && !allSel(this)) { state.rightKeyThenNextinput = true; return false; } }).keyup(function (e) { var key = e.which; if (state.leftKeyThenPrevinput) { // flèche gauche au début du champ déplace la sélection sur le champ précédent state.leftKeyThenPrevinput = false; hideAutocompleter(this); previnput(this, selectAfterFocus); } else if (state.rightKeyThenNextinput) { // flèche droite à la fin du champ déplace la sélection sur le champ suivant state.rightKeyThenNextinput = false; hideAutocompleter(this); nextinput(this, selectAfterFocus); } else if (key == 40 || (state.autocomplete && key == 38)) { // flèche basse: on va sur un élément de l'auto-complétion. // Si on est déjà dans l'état auto-complétion, la flèche haute // est reconnue aussi state.autocomplete = true; } else { // Toute autre touche désactive le mode auto-complétion state.autocomplete = false; } }).attr("autocomplete", "off"); }; // Activer le focus et placer le curseur à la fin de la ligne spécifiée. // Par défaut, il s'agit de la première ligne (soit line==0) // Si setSelectionRange n'est pas supporté, c'est un NOP $.fn.cursorAtEOFL = function(line) { if (line === undefined) line = 0; return this.each(function() { var $this = $(this); if (this.setSelectionRange) { var text = $this.val(); var pos = 0; while (line-- >= 0) { var index = text.search('\r?\n'); if (index != -1) { // index est la position avant [CR]LF, indexnl la position après [CR]LF var indexnl = index; if (text.charAt(indexnl) == '\r') indexnl++; indexnl++; if (line == -1) { // à la dernière ligne, considérer index, parce // qu'on veut se placer avant [CR]LF pos += index; } else { // avant la dernière ligne, considérer indexnl, parce // qu'on veut avoir un compte exact pos += indexnl; } text = text.substr(indexnl); } else { pos += text.length; break; } } this.setSelectionRange(pos, pos); } $this.focus(); }); }; })(jQuery);