217 lines
9.3 KiB
JavaScript
217 lines
9.3 KiB
JavaScript
/* -*- 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);
|