web-dev-qa-db-fra.com

Comment passer une plage dans une fonction personnalisée dans Google Spreadsheets?

Je veux créer une fonction qui prend dans une plage ... quelque chose comme:

function myFunction(range) {
  var firstColumn = range.getColumn();
  // loop over the range
}

Une cellule le référencerait en utilisant:

=myFunction(A1:A4)

Le problème est que, lorsque j'essaie de le faire, le paramètre semble ne transmettre que les valeurs à la fonction. Ainsi, je ne peux utiliser aucune des méthodes Range telles que getColumn() . Lorsque j'essaie de le faire, cela donne l'erreur suivante:

error: TypeError: Impossible de trouver la fonction getColumn dans l'objet 1,2,3.

Comment puis-je envoyer une plage réelle plutôt que simplement les valeurs à l'une de mes fonctions personnalisées?

43
Senseful

J'ai donc longuement et durement cherché une bonne réponse à cette question et voici ce que j'ai trouvé:

  1. un paramètre de plage non modifié passe dans le valeurs des cellules de la plage, pas la plage elle-même (comme Gergely l'a expliqué) ex: quand vous faites ceci =myFunction(a1:a2)

  2. pour utiliser une plage dans votre fonction, vous devez d'abord la passer sous forme de chaîne (ex: =myFunction("a1:a2")), puis la transformer en plage avec le code suivant à l'intérieur de la fonction:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Enfin, si vous souhaitez bénéficier des avantages de la transmission d'une plage (comme la copie intelligente de références de plage à d'autres cellules) ET que vous souhaitez la transmettre sous forme de chaîne, vous devez utiliser une fonction par défaut qui accepte une plage en tant que paramètre et génère une chaîne que vous pouvez utiliser. Je détaille les deux voies que j'ai trouvées ci-dessous, mais je préfère la deuxième.

    Pour toutes les feuilles de calcul Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Pour les nouvelles feuilles de calcul Google: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

21
Nathan Hanna

range est traité comme le tableau 2d de javascript. Vous pouvez obtenir le nombre de lignes avec range.length et le nombre de colonnes avec range[0].length. Si vous voulez obtenir la valeur de la ligne r et de la colonne c, utilisez: range[r][c].

9
Lipis

Lorsque vous transmettez un Range à une fonction de feuille de calcul Google, la structure exécute implicitement paramRange.getValues() et votre fonction reçoit les valeurs de la plage sous forme de tableau à 2 dimensions de chaînes, de nombres ou d'objets (comme Date). L'objet Range n'est pas transmis à votre fonction de feuille de calcul personnalisée.

La fonction TYPEOF() ci-dessous vous indiquera le type de données que vous recevez en tant que paramètre. La formule

=TYPEOF(A1:A4)

appellera le script comme ceci:

 function CalculateCell () {
 var resultCell = SpreadsheetApp.getActiveSheet (). getActiveCell (); 
 var paramRange = SpreadsheetApp.getActiveSheet (). getRange ('A1: A4') ; 
 var paramValues ​​= paramRange.getValues ​​(); 
 
 var resultValue = TYPEOF (paramValues); 
 
 resultCell.setValue (resultValue); 
} 
 fonction TYPEOF (valeur) {
 if (typeof valeur! == 'objet') 
 renvoie le typeof valeur; 
 
 var details = ' '; 
 if (valeur instance de tableau) {
 détails + =' ['+ valeur.longueur +'] '; 
 if (valeur [0] instance de tableau) 
 détails + = '[' + valeur [0]. longueur + ']'; 
} 
 
 var className = value.constructor.name; 
 return className + details; 
} 
6
Gregory Herendi

En guise d'alternative, inspiré par le commentaire de @Lipis, vous pouvez appeler votre fonction avec les coordonnées de ligne/colonne comme paramètres supplémentaires:

=myFunction(B1:C2; ROW(B1); COLUMN(B1); ROW(C2); COLUMN(C2))

Sous cette forme, la formule peut facilement être copiée (ou glissée) dans d'autres cellules et les références de cellule/plage seront automatiquement ajustées.

Votre fonction de script doit être mise à jour en tant que telle:

function myFunction(rangeValues, startRow, startColumn, endRow, endColumn) {
  var range = SpreadsheetApp.getActiveSheet().getRange(startRow, startColumn, endRow - startRow + 1, endColumn - startColumn + 1);
  return "Range: " + range.getA1Notation();
}

Notez que la fonction getRange prend startRow, startColumn, puis nombre de lignes et nombre de colonnes - pas fin ligne et fin colonne. Ainsi, l'arithmétique.

3
Vidar S. Ramdal

Cela peut être fait. Vous pouvez obtenir une référence à la plage passée en analysant la formule dans la cellule active, qui est la cellule contenant la formule. Cela suppose que la fonction personnalisée est utilisée seule et non dans une expression plus complexe: par exemple, =myfunction(A1:C3) et non =sqrt(4+myfunction(A1:C3)).

Cette fonction renvoie le premier index de colonne de la plage transmise.

function myfunction(reference) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var args = formula.match(/=\w+\((.*)\)/i)[1].split('!');
  try {
    if (args.length == 1) {
      var range = sheet.getRange(args[0]);
    }
    else {
      sheet = ss.getSheetByName(args[0].replace(/'/g, ''));
      range = sheet.getRange(args[1]);
    }
  }
  catch(e) {
    throw new Error(args.join('!') + ' is not a valid range');
  }

  // everything so far was only range extraction
  // the specific logic of the function begins here

  var firstColumn = range.getColumn();  // or whatever you want to do with the range
  return firstColumn;
}
3
user79865

J'ai étendu l'excellente idée de l'utilitaire answer de user79865 pour le faire fonctionner dans davantage de cas et avec plusieurs arguments passés à la fonction personnalisée.

Pour l'utiliser, copiez le code ci-dessous, puis dans votre fonction personnalisée, appelez GetParamRanges() comme ceci, en lui transmettant votre nom de fonction:

function CustomFunc(ref1, ref2) {

  var ranges = GetParamRanges("CustomFunc"); // substitute your function name here

  // ranges[0] contains the range object for ref1 (or null)
  // ranges[1] contains the range object for ref2 (or null)

  ... do what you want

  return what_you_want;
}

Voici un exemple pour renvoyer la couleur d'une cellule:

/**
* Returns the background color of a cell
*
* @param {cell_ref} The address of the cell
* @return The color of the cell
* @customfunction
*/
function GetColor(ref) {

  return GetParamRanges("GetColor")[0].getBackground();
}

Voici le code et quelques explications supplémentaires:

/**
* Returns an array of the range object(s) referenced by the parameters in a call to a custom function from a cell
* The array will have an entry for each parameter. If the parameter was a reference to a cell or range then 
* its array element will contain the corresponding range object, otherwise it will be null.
*
* Limitations:
* - A range is returned only if a parameter expression is a single reference.
*   For example,=CustomFunc(A1+A2) would not return a range.
* - The parameter expressions in the cell formula may not contain commas or brackets.
*   For example, =CustomFunc(A1:A3,ATAN2(4,3),B:E) would not parse correctly.
* - The custom function may not appear more than once in the cell formula.
* - Sheet names may not contain commas, quotes or closing brackets.
* - The cell formula may contain white space around the commas separating the custom function parameters, or after
*   the custom function name, but not elsewhere within the custom function invocation.
* - There may be other limitations.
* 
* Examples:
*   Cell formula: =CUSTOMFUNC($A$1)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: range object for cell A1 in the sheet containing the formula
*
*   Cell formula: =CUSTOMFUNC(3, 'Expenses'!B7)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell B7 in sheet Expenses
* 
*   Cell formula: =sqrt(4+myfunction(A1:C3))
*   Usage:        var ranges = GetParamRanges("MyFunction");
*   Result:       ranges[0]: range object for cells A1 through C3 in the sheet containing the formula
*
*   Cell formula: =CustomFunc(A1+A2, A1, A2)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell A1 in the sheet containing the formula
*                 ranges[2]: range object for cell A2 in the sheet containing the formula
*
* @param {funcName} The name of the custom function (string, case-insensitive)
* @return The range(s) referenced by the parameters to the call
*/
function GetParamRanges(funcName) {
  var ourSheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var re = new RegExp(".+" + funcName + "\\s*\\((.*?)\\)","i");
  var ranges=[]; // array of results

  try {
    var args = formula.match(re)[1].split(/\s*,\s*/) // arguments to custom function, separated by commas
    // if there are no args it fails and drops out here

    for (var i=0; i<args.length; i++) {
      var arg=args[i].split('!'); // see if arg has a sheet name
      try {
        if (arg.length == 1) { // if there's no sheet name then use the whole arg as the range definition
          ranges[i] = ourSheet.getRange(arg[0]);
        }
        else { // if there's a sheet name, use it (after removing quotes around it)
          var sheetName=arg[0].replace(/'/g, '');
          var otherSheet=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
          ranges[i] = otherSheet.getRange(arg[1]);
        }
      }
      catch(e) { // assume it couldn't be identified as a range for whatever reason
        ranges[i]=null;
      }
    }
  }
  catch(e) {}

  return ranges
}
2
Ian Viney

Je travaillais là-dessus il y a quelques mois et je suis arrivé à un kludge très simple: créez une nouvelle feuille avec le nom de chaque cellule comme contenu: la cellule A1 pourrait ressembler à:

= arrayformula(cell("address",a1:z500))

Nommez la feuille "Ref". Ensuite, lorsque vous avez besoin d'une référence à une cellule sous forme de chaîne au lieu de son contenu, vous utilisez:

= some_new_function('Ref'!C45)

Bien sûr, vous devrez vérifier si la fonction reçoit une chaîne (une cellule) ou un tableau 1D ou 2D. Si vous obtenez un tableau, il aura toutes les adresses de cellules sous forme de chaînes, mais à partir de la première cellule et de la largeur et de la hauteur, vous pourrez déterminer ce dont vous avez besoin.

1
HardScale

Je distingue deux fonctions personnalisées différentes dans Google Spreadsheet, via le script Google Apps:

  1. Celui qui appelle une API; SpreadsheetApp, ( exemple )
  2. Celui qui ne fait aucun appel, ( exemple )

La première fonction est capable de faire presque n'importe quoi. En plus d'appeler le service Tableur, il peut faire appel à GmailApp ou à tout service mis à disposition par Google. Les appels d'API ralentiront le processus. Une plage peut être transmise ou récupérée via la fonction pour accéder à toute la méthode disponible.

La deuxième fonction est limitée à la "feuille de calcul" uniquement. Ici, on peut utiliser JavaScript pour retravailler les données. Ces fonctions sont normalement très rapides. Une plage passée n'est rien d'autre qu'un tableau 2D contenant des valeurs.


Dans votre question, vous commencez par appeler la méthode .getColumn(). Cela indique clairement que vous avez besoin d'une fonction personnalisée de type 1, comme décrit ci-dessus.

Voir la réponse suivante réponse pour savoir comment définir la feuille de calcul et la feuille afin de créer une fonction personnalisée.

1

Voici ma compilation des réponses précédentes

**
 *
 * @customfunction
 **/
function GFR(){
  var ar = SpreadsheetApp.getActiveRange();
  var as = SpreadsheetApp.getActive();
  return ar.getFormula()
    .replace(/=gfr\((.*?)\)/i, '$1')
    .split(/[,;]/)
    .reduce(function(p, a1Notation){
      try{
        var range = as.getRange(a1Notation);
        p.Push(Utilities.formatString(
          'Is "%s" a Range? %s', 
          a1Notation,
          !!range.getDisplayValues
        ));
      } catch(err){
        p.Push([a1Notation + ' ' + err.message]);
      }
    return p;
  },[]);
}

Il prend la formule actuelle et vérifie s'il s'agit d'une plage.

=GFR(A1:A5;1;C1:C2;D1&D2) retourne

|Is "A1:A5" a Range? true|
|1 Range not found       |
|Is "C1:C2" a Range? true|
|D1&D2 Range not found   |

Pour la composition complète, le TC peut appeler quelque chose comme ceci

var range = as.getRange(a1Notation);
var column = range.getColumn();
0
contributorpw

J'ai travaillé dessus toute la matinée et j'ai vu des preuves de ce que les non-réponses ci-dessus discutent. Senseful et je veux tous les deux l’ADRESSE de la cellule transmise, pas la valeur. Et la réponse est très facile. Cela ne peut pas être fait.

J'ai trouvé des solutions de contournement qui consistent à déterminer quelle cellule contient la formule. Il est difficile de dire si cela aurait aidé Senseful ci-dessus. Alors qu'est-ce que je faisais?

The data.

     [A]      [B]       [C]       [D]     [E]     [F]       [H]
[1] Name      Wins     Losses    Shots   Points   Fouls   Most recent
                                                          WL Ratio
[2] Sophia     4         2         15      7       1         0
[3] Gloria     11        3         11      6       0         0
[4] Rene       2         0         4       0       0         0
[5] Sophia     7         4         18      9       1         1.5

La colonne H est celle de Sophia (Victoires - Précédentes)/(Pertes - Précédentes)

(7 - 4)/(4 - 2) = 1,5

Mais nous ne savons pas dans quelle ligne Sophia est apparue auparavant.
Tout cela peut être fait en utilisant VLOOKUP si vous codez A en tant que colonne de nom. Après VLOOKUP, je récupérais des caractères #NA (nom introuvable) et # DIV0 (dénominateur zéro) et je les enveloppais avec = IF (IF (...)) pour afficher du texte plus agréable dans ces conditions. J'avais maintenant une expression monstrueusement grande qui était lourde et impossible à maintenir. Je voulais donc une expansion des macros (n'existe pas) ou des fonctions personnalisées.

Mais lorsque j'ai créé un assistant SubtractPrevValue (cellule), il recevait "7" au lieu de B5. Il n'y a pas de moyen intégré pour obtenir des objets de cellule ou de plage à partir des arguments transmis.
Si je demande à l'utilisateur de saisir manuellement le nom de la cellule entre guillemets, je peux le faire ... SubtractPrevValue ("B5"). Mais ce sont vraiment les ischio-jambiers copier/coller et les caractéristiques relatives des cellules.

Mais ensuite j'ai trouvé une solution de contournement.

SpreadsheetApp.getActiveRange () IS LA CELLULE qui contient la formule. C'est tout ce que j'avais vraiment besoin de savoir. Le numéro de la ligne. La fonction suivante prend un numéro de colonne NUMERIC et soustrait l'occurrence précédente de cette colonne.

function SubtractPrevValue(colNum) 
{
  var curCell = SpreadsheetApp.getActiveRange();
  var curSheet = curCell.getSheet();
  var curRowIdx = curCell.getRowIndex();
  var name = curSheet.getRange(curRowIdx, 1).getValue();  // name to match
  var curVal =  curSheet.getRange(curRowIdx, colNum).getValue();

  var foundRowIdx = -1;
  for (var i=curRowIdx-1;i>1;i--)
  { 
    if (curSheet.getRange(i, 2).getValue() == name)
    {
      return curVal - curSheet.getRange(i, colNum).getValue();
    }
  }
  return curVal;  //default if no previous found
}

Mais ensuite, j'ai découvert que c'était vraiment très lent. Un délai de une et deux secondes s’affiche alors que le message "Thinking" (Penser ...) me ramène à la formule de feuille de calcul massivement illisible et impossible à maintenir.

0
Mark C

Créer une fonction et passer la plage en argument:

function fn(input) {
    var cell = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange(SpreadsheetApp.getActiveRange().getFormula().match(/=\w+\((.*)\)/i)[1].split('!'));
    // above ... cell is "range", can convert to array?
    var sum=0;
    for (var i in cell.getValues() ){
        sum = sum + Number(cell.getValues()[i]);
    }  
    return sum;
}

Utilisation dans un tableur:

=fn(A1:A10)

Il affichera la valeur sous la forme sum(A1:A10).

Au lieu de fournir l'objet "Range", les développeurs Google transmettent en gros un type de données aléatoire. J'ai donc développé la macro suivante pour imprimer la valeur de l'entrée.

function tp_detail(arg)
{
    var details = '';

    try
    {
        if(typeof arg == 'undefined')
           return details += 'empty';

        if (typeof arg !== 'object')
        {
            if (typeof arg == 'undefined')
                return details += 'empty';

            if (arg.map)
            {
                var rv = 'map: {';
                var count = 1;
                for (var a in arg)
                {
                    rv += '[' + count + '] ' + tp_detail(a);
                    count = count + 1;
                }
                rv += '}; '
                return rv;
            }
            return (typeof arg) + '(\'' + arg + '\')';
        }


        if (arg instanceof Array)
        {
            details += 'arr[' + arg.length + ']: {';
            for (var i = 0; i < arg.length; i++)
            {
                details += '[' + i + '] ' + tp_detail(arg[i]) + ';';
            }
            details += '} ';
        }
        else
        {

            var className = arg.constructor.name;
            return className + '(' + arg + ')';
        }
    }
    catch (e)
    {
        var details = '';
        details = 'err : ' + e;
    }


    return details;
}
0
Real Name