/* -*- coding: utf-8 mode: javascript -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 */ (function($) { $.resetTabindex = function(expr) { // Par défaut, prendre tous les champs de saisie *visibles* if (expr === undefined) expr = ":input:visible"; $(expr).attr("tabindex", 0); }; $.initTabindex = function(expr) { // Par défaut, prendre tous les champs de saisie *visibles* if (expr === undefined) expr = ":input:visible"; // D'abord calculer la plus grande valeur de tabindex var tabindex = 0; var $expr = $(expr); $expr.each(function() { var $this = $(this); 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 $this = $(this); var ti = parseInt($this.attr("tabindex")); if (!ti) $this.attr("tabindex", tabindex++); }); }; $.updateTabindex = function(expr) { if (expr === undefined) { $.resetTabindex(":input"); $.initTabindex(":input:visible"); } else { $.resetTabindex(expr); $.initTabindex(expr); } }; // 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(":input") && !$input.is("textarea, :submit, :reset, :button") }; var noSel = function(input) { if (! hasSelection(input)) return true; return input.selectionStart === input.selectionEnd; }; var allSel = function(input) { if (! hasSelection(input)) return false; var length = $(input).val().length; return 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 (noSel(input)) return input.selectionStart === 0; else return false; }; var atend = function(input) { if (! hasSelection(input)) return true; else if (noSel(input)) return input.selectionEnd === $(input).val().length; else return false; }; var hideAutocompleter = function(input) { var select = $(input).data("ac.select"); if (select) select.hide(); }; var submitForm = function(input) { var $form = $(input).closest("form"); var found = false; if ($form.length) { var phase = 0; $form.find(":input").each(function() { if (phase === 0 && this === input) { phase = 1; } else if (phase === 1 && $(this).is("input[type='submit'], button[type='submit']")) { found = true; $(this).click(); return false; } }); if (!found) { // si on ne trouve pas de bouton après l'élément, prendre le premier var $submit = $form.find("input[type='submit'], button[type='submit']").first(); if ($submit.length) { found = true; $submit.click(); } } if (!found) { // s'il n'y a aucun bouton, demander au formulaire de se soumettre lui-même $form.submit(); } } return found; }; $.navigate = function(onEnter, selectAfterFocus, initTabindex) { // Action par défaut: next if (onEnter === undefined) onEnter = "next"; // 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; if (onEnter === "submit") { $(":input").off(".navigate").on("keypress.navigate", function(e) { if (e.which === 13 && isNavigable(this)) { e.preventDefault(); submitForm(this); } }); } else if (onEnter === "next") { var willMove = false; if (initTabindex) $.initTabindex(":input"); $(":input").off(".navigate").on("keypress.navigate", function(e) { 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); e.preventDefault(); previnput(this); } else if (e.which === 13) { if (e.ctrlKey) { // Ctrl-Entrée soumet le formulaire e.preventDefault(); submitForm(this); } else if (isNavigable(this)) { // Entrée sur un champ déplace la sélection sur le champ suivant e.preventDefault(); nextinput(this, selectAfterFocus); } } }).on("keydown.navigate", function(e) { willMove = false; if (e.which === 37 && atbegin(this)) { // flèche gauche au début du champ déplace la sélection sur le champ précédent if (!hasSelection(this) || isempty(this) || !allSel(this)) { willMove = true; e.preventDefault(); } } else if (e.which === 39 && atend(this)) { // flèche droite à la fin du champ déplace la sélection sur le champ suivant if (!hasSelection(this) || isempty(this) || !allSel(this)) { willMove = true; e.preventDefault(); } } }).on("keyup.navigate", function(e) { if (e.which === 37 && willMove) { // flèche gauche au début du champ déplace la sélection sur le champ précédent //hideAutocompleter(this); previnput(this, selectAfterFocus); } else if (e.which === 39 && willMove) { // flèche droite à la fin du champ déplace la sélection sur le champ suivant //hideAutocompleter(this); nextinput(this, selectAfterFocus); } }); } else if (onEnter === "disable" || onEnter === "off") { $(":input").off(".navigate"); } }; // 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);