ScriptUI Scaffolder

Einleitung

ScriptUI Dialoge zu programmieren ist eine große Menge Tipparbeit. Das, so dachte ich, müsste sich deutlich runterdampfen lassen. Warum soll ich für diesen Dialog

dialog

50 Zeilen Code schreiben, wenn alles, was ich festlegen will, sich auf das hier zusammendampfen lässt?

<?xml version="1.0" ?>
<root>
  <input id="vorname" label="Ihr Vorname">Max</input>
  <input id="nachname" label="Ihr Nachname" />Muster</input>
  <button id="defaultElement" label="OK" />
</root>

Das ist, was ein Scaffolder macht: Nimmt etwas Konfigurationsdaten und baut daraus umfangreichen Code.

Starten Sie das Script aus dem ExtendScript Toolkit (Visual Code, falls Sie schon umgestiegen sind).

Sie werden nach einer XML-Datei gefragt und das Script legt eine JSX-Datei daneben. In der JSX sind alle UI Element, ihre Initialisation und Standard-Eventhandler definiert.

Die Syntax der XML ist eng ans HTML angelehnt, bildet aber natürlich nur ein Subset der Optionen ab.

Den Code verwalte ich nur noch auf GitHub, wo er inkl Sample-Dateien runtergeladen werden kann.

Die Tags

Gruppierungen und Panels

<fieldset id="id" orientation="row">...</fieldset>

Eine Gruppe fasst mehrere Eingabefelder zusammen. Eine Gruppe mit einem Rahmen außenrum, nennt sich „Panel“.

Um ein Panel zu bekommen, für Sie ein <legend> Element hinzu.

<fieldset id="id">
  <legend>My Panel</legend>
  ...
</fieldset>

Mit orientation="row" werden die Kinder einer Gruppe waagerecht nebeneinander gestellt, sonst vertikal.

Dieser Code:

<?xml version="1.0" ?>
<root>
  <fieldset id="all" orientation="row">
    <fieldset id="main" >
      <legend>some input</legend>
      <input id="some_text" label="some text">the value</input>
      <input id="some_other_text" label="some other text"></input>
    </fieldset>
    <fieldset valign="top">
      <button id="defaultElement" label="OK" />
      <button id="cancelElement" label="Abbrechen" />
      <button id="weissnich" label="Wei&#xDF; nich" />
    </fieldset>
  </fieldset>
</root>

erzeugt diesen Dialog:

text input

Es gibt input und textarea.

Alle Formfelder müssen in einem Fieldset (also einer Gruppe) stehen.

<fieldset>
    <input id="text_input" label="ein Text">Das steht da</input>
    <input type="number" id="number_input" label="eine Zahl">3.1415</input>
    <textarea id="ta" label="Mehrzeilig" />
  </fieldset>

erzeugt

  • Der Vorgabewert für das Feld ist das Textknotenkind von <input> oder <textarea>
  • Sie können das Attribut type="number" setzen, der Scaffolder erzeugt dann den entsprechenden EventHandler, der die Eingabe auf Zahlen begrenzt.

Eins-von-Vielen (radiobuttons and select)

Ich verwende <select> für alle n-of-m-Auswahlen:

<fieldset id="sel">
    <select id="sel1" label="radios">
      <option>eins</option>
      <option>zwei</option>
      <option>drei</option>
    </select>
  </fieldset>
  • Wenn der select-Knoten nicht mehr als 3 option-Knoten enthält, erzeugt der Scaffolder Radiobuttons.
  • Wenn der select-Knoten das Attribut multiple="true" enthält, erzeugt der Scaffolder eine listbox, in der eine Mehrfachauswahl möglich ist.
  • Andernfalls bekommen Sie ein Dropdownmenü:

slider

<fieldset id="sld">
  <slider id="slider1" label="x" />
</fieldset>

Ich habe nicht eingebaut, dass die Min/Max-Werte im XML vorgegeben werden können, aber diese sind im Code vorbereitet und leicht nachzutragen.

w.slider1.minvalue = 0
w.slider1.maxvalue = 100
w.slider1.value = 50
w.slider1_fd.text = "50"
w.slider1.id = "slider1"
w.state.slider1 = 50
w.slider1.onChange = function() {
  w.slider1_fd.text = this.value
  this.window.state[ this.id ] = this.value
}
w.slider1_fd.onChange = function() {
  if ( ! isNaN( Number( this.text ) ) ) {
    w.slider1.value = Number(this.text)
    w.state.slider1 = Number( this.text)
  } else {
    this.text = w.slider1.value
  }
}

Ein slider besteht aus drei Teilen, die horizontal angeordnet sind:

  • Das Label
  • Der Slider
  • Ein Texteingabefeld

Die Eventhandler, die Slider und Textfeld synchronisieren, sind im Code fertig vorbereitet.

buttons

ScriptUI hat zwei besondere Buttons: defaultElement und cancelElement, die als Vorgabe auch auf Return bzw. Escape reagieren. Geben Sie einem Button einfach die ID, damit der Scaffolder das entsprechend umsetzt.

<fieldset valign="top">
  <button id="defaultElement" label="OK" />
  <button id="cancelElement" label="Abbrechen" />
  <button id="weissnich" label="Wei&#xDF; nich" />
</fieldset>

Für jeden Button sind die entsprechenden Eventhandler vorbereitet.

// ----------------- group: g0
w.g0 = w.all.add("group", undefined , "")
w.g0.orientation = "column"
w.g0.alignChildren = ["fill", "top"]
// ----------------- button: defaultElement
w.defaultElement = w.g0.add("button", undefined, "OK")
w.defaultElement.onClick= function() {
  this.window.close(1)
  alert("state\n" + this.window.state.toSource() )
}
 
// ----------------- button: cancelElement
w.cancelElement = w.g0.add("button", undefined, "Abbrechen")
w.cancelElement.onClick= function() {
  this.window.close(2)
}
 
// ----------------- button: weissnich
w.weissnich = w.g0.add("button", undefined, "Wei&#xDF; nich")
w.weissnich.onClick = function() {
  // What should happen on Click?
}

images

ScriptUI kann Bilder als statische Elemente oder als Bild-Buttons.

Für ein statisches Bild nehmen Sie einfach den Standard HTML-Tag:

<img src="name-of-image-in-same-directory-as-xml.png" id="the_image" />

Image Buttons können in der ScriptUI entweder einen Vorgang auslösen (triggern) oder einen Zustand umschalten (togglen) Setzen Sie für letzteres das toggle="true" Attribute.

<fieldset id="align">
  <legend>Alignment</legend>
  <fieldset orientation="row">
    <button id="left_btn" image="left.png" toggle="true"/>
    <button id="center_btn" image="center.png" toggle="true" value="true"/>
    <button id="right_btn" image="right.png" toggle="true"/>
  </fieldset>
</fieldset>

Trigger-Buttons sehen so aus:

<fieldset id="actions">
  <legend>Actions</legend>
  <fieldset orientation="row">
    <button id="comment_btn" image="comment.png" />
    <button id="paint_btn" image="paint.png" />
    <button id="sort_btn" image="sort.png" />
  </fieldset>
</fieldset>

Die Bilder müssen neben der XML-Datei liegen und werden vom Scaffolder als escaped-Strings im Code eingebettet. Das fertige JSX kapselt also alle Daten, die der Anwender braucht.

Sample

Das sample.xml erzeugt diesen Dialog:

Wer es lieber grafisch mag: Joonas Paakko hatte anscheinend das gleiche Problem wie ich, aber Zeit und Lust ein viiiielfaches an Zeit in die Lösung zu stecken.
Herausgekommen ist ein phänomenales Point und Click Interface:

Tolles UI zum Bauen von ScriptUI Dialogen