Modernes JavaScript: Array.filter()

Während der JavaScript-Dialekt, der in InDesign implementiert ist, seit CS3 so gut wie keine Entwicklung durchgemacht hat, wurde die Sprache im Web-Kontext ständig voran getrieben.

Wenn man einmal in nodejs oder im Frontend-Bereich programmiert hat, schaut man manchmal mit einiger Wehmut auf den arg beschnittenen Sprachumfang, der in InDesign zur Verfügung steht.

Ganz oben auf meiner Liste von „hätte ich gern“ stehen die Array-Funktionen wie Array.map, Array.resolve oder heute: Array.filter.

Array.filter()

Was macht diese Funktion?

Wenn Sie nach „Array filter polyfill“ (erklär ich unten) suchen, landen Sie auf dieser Seite, die als einfaches Beispiel diesen Code zeigt:

var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
 
var result = words.filter( function( word ) { return word.length > 6 } );
 
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

Array.filter() erzeugt ein neues Array und füllt dieses mit jenen Array-Items, für die die übergebene Funktion einen true-Wert zurückgibt.

Array.filter() am InDesign-Beispiel

Ich brauchte grad in einem Script die Funktion, alle unbenutzten Farben im Dokument zu löschen. Meines Wissen gibt die Scripting-Schnittstelle das nicht her, also muss ich über die Menübefehle gehen.

Der reine Löschen-Code sieht dann so aus:

  var m = app.menus.item( "$ID/SwatchesPanelPopup");
  var mi = m.menuItems.item("$ID/SelectAllUnused");
  var ma = mi.associatedMenuAction;
  ma.invoke();
  mi = m.menuItems.item("$ID/Delete Swatch...");
  var ma = mi.associatedMenuAction;
  ma.invoke();

Wenn ich wissen will, welche Farben ich gelöscht hab, muss ich die Liste-vorher mit der Liste-nachher abgleichen.

Das geht dann so:

function clean_colors() {
  var cols_prev = doc.swatches.everyItem().name; 
 
  var m = app.menus.item( "$ID/SwatchesPanelPopup");
  var mi = m.menuItems.item("$ID/SelectAllUnused");
  var ma = mi.associatedMenuAction;
  ma.invoke();
  mi = m.menuItems.item("$ID/Delete Swatch...");
  var ma = mi.associatedMenuAction;
  ma.invoke();
 
  var cols_after = doc.swatches.everyItem().name.join("\t");
  var cols_del = cols_prev.filter( function (c) {
    return cols_after.search( c ) === -1;
  } );
  return cols_del.length ? cols_del : null;
}

Ich hab also in cols_prev die Liste der Farben voher und in cols_after die Liste der Farben nachher, zu einem String zusammengefasst, den ich dann einfach durchsuchen kann.

cols_del bekommt die neu erstellte gefilterte Liste: Ist ein Name aus der vorher-Liste hinterher nicht mehr vorhanden, wurde die Farbe gelöscht. Logisch.

Polyfill

Wie kann ich nun diese Funktion für InDesign verfügbar machen?

Auf der oben bereits verlinkten Mozilla-Seite steht ganz unten der sogenannte „Polyfill“-Code:

if (!Array.prototype.filter){
  Array.prototype.filter = function(func, thisArg) {
    'use strict';
    if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) )
        throw new TypeError();
 
    var len = this.length >>> 0,
        res = new Array(len), // preallocate array
        t = this, c = 0, i = -1;
    if (thisArg === undefined){
      while (++i !== len){
        // checks to see if the key was set
        if (i in this){
          if (func(t[i], i, t)){
            res[c++] = t[i];
          }
        }
      }
    }
    else{
      while (++i !== len){
        // checks to see if the key was set
        if (i in this){
          if (func.call(thisArg, t[i], i, t)){
            res[c++] = t[i];
          }
        }
      }
    }
 
    res.length = c; // shrink down array to proper size
    return res;
  };
}

Der Grund dafür ist, dass es auch noch alte, alte Browser im Einsatz gibt, die auch kein modernes JavaScript können. Wer als Webentwickler auch diese User nicht abhängen will, ist in der selben Situation wie wir als InDesign-Scripter.

Deswegen haben gute Leute an der Lösung getüftelt.

Binden Sie den zum Beispiel in einer init() Funktion in Ihr Script ein und filtern Sie drauf los.