Ein kurzer Überblick über den Einsatz von JavaScript in Antetype Web Preview in Antetype selber, der Live Preview und im exportierten Prototyp (Web Viewer). Der von Antetype Web Preview generierte Prototyp baut zwar auf Web-Technologien auf, es werden aber keine statischen HTML/CSS-Dateien generiert, sondern es werden erst JavaScipt-Objekte aufgebaut, die eine Struktur analog zu den Antetype-Objekten (Screen, Zelle, Widget etc.) haben, die dann HTML und CSS generieren, welches zur Anzeige verwendet wird.

Antetype Web Genereller Aufbau

Wie im alten Antetype auch, gibt es natürlich die Definition der Widgets/Screens/Zellen et. cetera pp. Im Gegensatz zu früher (bis zur Beta :) ) läuft das komplette Rendering/Layout, Event-handling etc. im Browser ab. (In Antetype selber wird eine WebView, quasi ein eingebetteter Safari verwendet).

HTHML/CSS wird direkt im Browser/WebView generiert, und nicht in Antetype selber. (Mit Antetype meine ich das macOS-Programm). Damit das alles funktioniert gibt es mehr oder weniger alle Objekte auch direkt als JavaScript-Objekte.

Antetype.app              :   Browser
--------------------------+---------------------------------------------------------
Nativ                     :   JavaScript                    HTML/CSS
                          :
Screen                    :   Screen                        <body>
    Cell 1                :           Cell 1                    <cell>
        Foo               :               Foo                       <cell/>
        Bar               :               Bar                       <cell></cell>
    Rectangle             :           Rectangle                 <cell>
        Widget cell       :               Widget cell               <cell /></cell


...

Z.B. gibt es für die Definition eines Screens, den linken Part in Antetype. In die WebView/Live Preview werden diese Daten übertragen, daraus wird im Browser dieselbe Datenstruktur aufgebaut, welche dann benutzt wird um das HTML/CSS <3> zu generieren, welches für die Darstellng verwendet wird.

Die einzelnen Zellen z.B. kennen ihr Widget, können mehrere States haben. Mehr oder weniger dasselbe wie Antetype selber. Wenn man die eingebauten Script-Actions verwendet werden immer die JavaScript-Objekte verwendet, nicht die generierten DOM-Elemente. Aber keine Angst, das Umwandeln ist einfach:

Von einem JavaScript-Zellen-Objekt zum DOM-Element, einfach DOMElement aufrufen:

let cell = targetCells[0]               // cell ist eine Antetype-Zelle

let e = cell.DOMElement                 // e ist nun das entsprechende HTMLElement

// Unterschiede:
console.log(e.tagName)                  // ... gibt CELL aus
if (cell.name == "Rectangle")  .....    // name ist der Zellenname in Antetype

andersrum geht es natürlich auch:

let e = document.getElementById("id384738z34")  // e ist ein normales HTMLElement
let cell  = e.figure                            // cell ist ein Antetype-Objekt:

if (cell.activeState.name == "Foo") ....

Möglichkeiten JavaScript einzubinden

header_include.html

Der Inhalt dieser Datei wird direkt in das Template welches Antetype Web Preview, der exportierte Web Viewer oder die Live-Preview verwendet. Hier ist der beste Ort, um längere JavaScripts zu hinterlegen, oder andere JavaScript-Libraries einzubinden.

<script>
function foo() {
    alert("bar");
}
</script>

definiert eine globale Funktion foo. Natürlich kann man auch externe scripte einbinden:

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>

bindet chart.js in der Version 2.7.2 ein.

Bestes Beispiel ist das Template InteractiveStuff. An die Datei kommt man mittels Context-Menü im Finder:

Show Package Content…

und kann diese Datei dann in jedem Text-Editor öffnen:

Package Content

Nach dem Speichern nur noch die Live-Preview (oder die Antetype-WebView) neu laden, damit die Änderungen auch benutzt werden. Derzeit kann man JavaScript-Dateien leider nur vom Server laden, und nicht lokal referenzieren.

Script Actions

Überall wo man normale Antetype Web Preview-Aktionen ausführen kann (Change State, Hide/Show etc.) kann man auch Script-Actions verwenden. Das script wird mittels eval() ausgeführt.

window.open("https://ergosign.de")

zum Beispiel öffnet im Web Viewer oder der Live Preview ein neues Fenster mit der Ergosign-Homepage. Im GUI sieht das folgendermassen aus:

  • Add Event

  • Die angelegte Change Width-Action in eine Script-Action umwandeln und JavaScript eingeben:

Script Action Edit

Im Interaction Inspector wird die erste Zeile des Quellcodes angezeigt:

Script Action Inspector

In der Live Preview (oder der exportiertem Web Viewer) wird nun nach dem Klick auf den Button die angegebene Webseite in einem neuen Fenster/Tab geöffnet.

Script Actions und Zugriff auf die Antetype-JavaScript-Objekte

Bis jetzt haben wir nur Möglichkeiten gesehen, die es erlauben eigenes JavaScript im Prototyp zu benutzen. Damit kann man natürlich auf das DOM vom Browser zugreifen und Dinge verändern, aber es gibt noch eine zweite Möglichkeit. Wie oben kurz angerissen existieren die Antetype- Objekte wie Screen, Zelle, Widget, States etc. auch in JavaScript. Dies kann man benutzen um direkt (ohne mit IDs oder ähnlichem auf Objekte zuzugreifen).

Caution
die folgenden Sachen sind noch Work in progress. Nicht sicher ob das alles so bleibt

Wie in normalen Antetype-Interaktionen kann man sich ja die targetCells recht flexibel zusammenstellen. Dazu kann man in jedem ActionSet die elemente frei wählen:

Action Set Elements

(standardmässig wird hier die Zelle eingetragen, auf der die Action liegt, kann aber frei gewählt werden. Die Zelle selber wird mit einem "*" im Namen gekennzeichnet). Zusammen mit dem specifier:

Specifier

werden die targetCells bestimmt, auf denen die Action ausgeführt wird. In der Script-Action steht in der Variable targetCells ein Array dieser Zellen. Dies sind Objekte der Klasse GDWidgetInstanceCell (oder subklassen) und nicht die DOM-Objekte die der Browser zum rendern verwendet.

JavaScript-Objekte die in einer Scipt-Action zur Verfügung stehen:

Name Beschreibung

targetCells

Array der targetCells (Antetype objekte)

at

Globales AntetypeWeb-Objekt

event

DOM-event (falls vorhanden, derzeit nicht für load/unload-screen

action

das Action-objekt

Im eingebetteten HTML

Nicht im strikten Sinn JavaScript, aber dieses Feld ist die beste Möglichkeit vorhandenen Content einzubinden, oder HTML-Elemente zu verwenden, die Antetype nicht nativ unterstützt.

Beispiel: Youtube Video

Um ein Video von YouTube einzubinden, einfach per Rechtsklick auf das Video "Copy embed code" auswählen:

Youtube Embed

und dann einfach ins HTML-Feld einfügen:

HTML embed
Caution
Derzeit kann das Maushandling Beim Editieren Probleme machen, im Zweifel am besten im Popover/Style-Inspector stylen. Auch gibt es kein Unterschied zwischen Edit und Presentation mode.

Beispiel HTML benutzen

In HTML5 gibt es einige interessante controls, die man auch in einem Prototyp benutzen kann. Hier z.B. input type="range":

<input type="range" style="width: 100%">
Slider

zeigt einen schönen, funktionalen Slider an, den man auch prima mit JavaScript steuern, Events abfangen etc.

CSS Property

Manchmal reicht es auch einfach die vorhandenen Elemente durch CSS-Properties die wir nicht direkt unterstützen zu stylen. Hierfür verwendet man am besten das CSS-Feld im Styleinspector:

CSS Property

CSS-Properties die hier eingegeben werden landen in denselben CSS-Rules wie das von Antetype generierte CSS. D.H. die Properties verhalten sich wie die von Antetype generierten. States, Widgets etc. alles funktioniert genauso damit.

Einfachstes Beispiel wäre eine Basic-Zelle und das generierte CSS (schematisch, die CSS-Selektoren sind ein wenig anders):

Im Inspector geben wir im CSS-Feld folgendes ein:

text-transform: uppercase;
-webkit-text-decoration: underline dotted red;

das generierte CSS sieht dann so aus:

#zelle {
    /* von Antetype erzeugt: */
    background-color: red;
    width: 20px;
    ...

    /* zum Schluss kommen die extra-Properties: */
    text-transform: uppercase;
    -webkit-text-decoration: underline dotted red;
}

Wie man an dem Beispiel sieht muss man sich selber darum kümmern CSS zu verwenden, welches in den gewünschten Browsern funktioniert. Die eingegebenen Werte werden immer an den Schluss geschrieben, somit ist es auch möglich generiertes CSS zu überschreiben (aber dann sollte man wissen was man macht).

Note
!important wird derzeit nicht unterstützt. Auch wird keine Syntax-validierung vorgenommen. Aus technischen Gründen wird der Block nicht einfach als Text reinkopiert sondern das CSSStyle-Objekt wird verändert. Was zur Folge hat das man derzeit nur Properties benutzen kann die Safari/WebKit auch unterstützt.

Wichtige JavaScript-Objekte

Wie weiter oben beschrieben gibt es recht viele JavaScript-Objekte von Antetype die man auch in Script verwenden kann. Hier mal ein grober Überblick welche Objekte es so gibt, und wie man sie verwenden kann. (Im Antetype Repository befinden sich die Definition in den Dateien Server/at_servr/priv/satic/viewer.js und Server/at_servr/priv/static/model.js. Use the source Luke

Antetype

Auf oberster Ebene befindet sich das Objekt Antetype AntetypeWeb API. Viele Methoden/Properties werden in Antetype selber benutzt, aber es gibt auch einige interessante Methoden die man per Skript aufrufen kann.

// Screenwechsel:
Antetype.gotoNextScreen()
Antetype.gotoPreviousScreen()

// current screen:
let screen = Antetype.currentScreen
console.log(`Screen ${screen.name} ist aktiv`)

Screens, Zellen etc.

Screens, Basic Cells, Widget cells, etc. sind alles Subklassen von GDCompositeFigure. Für hier sind alle Methoden/Properties definiert für alle Zelltypen vorhanden sind.

// Beispiel für orderedComponents:
// gib die Namen der Zellen auf screen-Ebene aus:

let screen = Antetype.currentScreen
screen.orderedComponents.forEach( c => console.log(c.name) )

neben orderedComponents welches die Kinder einer Zelle zurückgibt, kommt man mit container wieder eine Ebene höher.

// script-Action:

let clickedCell = targetCells[0]
let parent = clickedCell.container
console.log(`parent: ${parent.name}`)

Properties der Zellen

Lesen

Alle Zellen von Antetype haben aber abgesehen von der Hierarchie, die durch GDCompositeFigure definiert sind, auch noch die Properties. GDWidgetInstanceCell definiert die nötigen Methoden.

Note
Die folgenden Methoden geben die Werte zurück, egal ob sie im Widget definiert, überschrieben oder individual sind.

Um Werte von Properties abzufragen dient die Methode valueForKeyInStateWithIdentifier(key, stateIdentifier). Deren erster Parameter ein String ist, der bestimmt, welches Property gemeint ist, der zweite der identifier des states. Für den aktuellen state ist das cell.activeStateIdentifier.

// gib die Breite in der JavaScript-Konsole aus:
let cell = targetCells[0]
console.log(cell.valueForKeyInStateWithIdentifier("width", cell.activeStateIdentifier))

gibt auf der Console den Wert der Breite aus.

Caution
Mit dieser Methode kann man alle Properties abfragen, ob die gebraucht werden oder nicht. "width" gibt einen Wert zurück, der nur bei manueller Breite auch der tatsächlichen Größe entspricht.

Schreiben

Wenn man Properties setzen will (und gleichzeitig die Darstellung anpassen) muss man den Weg über das Antetype-Objekt gehen. Die Methode cellSetPropertyInState setzt eine Property.

// Script-action: setzt den Textinhalt auf "foo" von der Zelle:
let cell = targetCells[0]
at.cellSetPropertyInState(cell, "textString", "foo", cell.activeState)

Liste der Properties

vollständige Liste der Properties die Antetype kennt.

Name Beschreibung

x

left (number) nur sinnvoll, wenn Zelle im Freelayout liegt

y

top (number) ebenso

width

width (number) nur wenn horizontalResizing == GDFixResizing

height

height (number) nur wenn verticalResizing == GDFixResizing

rotationAngle

in Grad

cellType

GDRectangleCellType, GDTriangleCellType, GDCircleCellType, GDVectorCellType

flexHeightPercentage

flex -höhe (0-100) nur wenn verticalResizing = GDFlexResizing

flexWidthPercentage

flex -breite (0-100) nur wenn horizontalResizing

isMovable

(boolean)

isSelectable

(boolean)

isVisible

FIXME: blickt keiner

isDisplay

FIXME: hier auch

isContentClipped

boolean

isDropTarget

boolean

scrollable

GDNoScrolling, GDVerticalScrolling, GDHorizontalScrolling, GDAutoScrolling

maximumHeight

minimumHeight

maximumWidth

minimumWidth

verticalResizing

GDFixResizing (manual), GDFlexResizing (stretch), GDIntrinsicResizing (fit)

horizontalResizing

GDFixResizing (manual), GDFlexResizing (stretch), GDIntrinsicResizing (fit)

cornerRadiusBottomLeft

cornerRadiusBottomRight

cornerRadiusTopLeft

cornerRadiusTopRight

horizontalAlignment

GDLeftAlignment, GDCenterAlignment, GDRightAlignment

verticalAlignment

GDLeftAlignment, GDCenterAlignment, GDRightAlignment

marginBottom

marginLeft

marginRight

marginTop

paddingBottom

paddingLeft

paddingRight

paddingTop

opacity

0-1.0

borderBottomWidth

borderLeftWidth

borderRightWidth

borderTopWidth

borderBottomType

GDBorderTypeSolid, GDBorderTypeDashed, GDBorderTypeDotted

borderLeftType

borderRightType

borderTopType

borderBottomColor

CPColor

borderLeftColor

CPColor

borderRightColor

CPColor

borderTopColor

CPColor

layoutPolicyCode

GDFixedLayoutPolicyCode, GDHorizontalBoxLayoutPolicyCode, GDVerticalBoxLayoutPolicyCode, GDAlignmentLayoutPolicyCode

layoutWrap

(boolean)

backgroundPainterType

GDNoPainterType, GDColorPainterType, GDGradientPainterType, GDImagePainterType

backgroundColor

CPColor, nur wenn backgroundPainterType=GDColorPainterType

backgroundGradient

CTGradient, nur wenn backgroundPainterType=GDGradientPainterType

backgroundGradientAngle

gradient-winkel (0-360)

backgroundGradientIsRadial

radialer gradient

backgroundImageResource

GDImageResource (backgroundPainterType ==GDImagePainterType)

backgroundImageHorizontalAlignment

GDLeftAlignment, GDCenterAlignment, GDRightAlignment

backgroundImageVerticalAlignment

GDTopAlignment, GDCenterAlignment, GDBottomAlignment

backgroundImageHorizontalOperation

GDOriginalSizeImageOperation, GDStretchImageOperation, GDTileImageOperation

backgroundImageVerticalOperation

backgroundImageProportionalScale

boolean

dropShadow

boolean

dropShadowAngle

0-360

dropShadowSize

number

dropShadowOffset

number

dropShadowBlur

number

dropShadowOpacity

0-1

dropShadowColor

CPColor

textRichText

not used anymory

textString

Textinhalt der Zelle (string)

textFont

SChrift GDFont

textColor

CPColor

textHorizontalAlignment

GDLeftAlignment, GDCenterAlignment, GDRightAlignment, GDJustifiedAlignment

textVerticalAlignment

GDTopAlignment, GDCenterAlignment, GDBottomAlignment

textAntialias

not used anymore :(

textWordWrap

textTraits

not used

textLineHeightMultiply

(boolean), wether the following property is x N or N px

textLineHeight

line height

isEditableText

(boolean) is editable in Prototype

textShadow

 (boolean)

textShadowOffset

textShadowOpacity

textShadowAngle

textShadowBlur

textShadowColor

activeLayout

(boolean) Float

activeHorizontalAlignment

GDLeftAlignment, GDCenterAlignment, GDRightAlignment

activeVerticalAlignment

GDTopAlignment, GDCenterAlignment, GDBottomAlignment

isDrawingReverted

not used

isDrawingAboveAll

not used

drawingIndex

z-index

innerShadow

innerShadowOffset

innerShadowOpacity

innerShadowAngle

innerShadowBlur

innerShadowColor

borderGradient

not used

borderGradientFill

not used

borderGradientIsRadial

not used

borderGradientAngle

not used

filters

Blur

vectorContent

vector data if cellType == GDVectorCellType

embedHTML

embed HTML-Field in style inspector

customCSS

custom CSS im style inspector

Widget/States …​

Natürlich kann man auch mit States und Widgets auf JavaScript-Ebene arbeiten. Wenn man eine Zelle hat kommt man mit activeState auf den aktiven State. Hierbei ist es übrigens egal, ob es sich um eine Widget-Zelle oder eine Basic-Cell handelt. Intern haben beide ein Widget…​

Ein kleines Beispiel:

// Script-Action:
let cell = targetCells[0]

if (cell.activeState.name == "Normal")
    console.log("Normal State ist aktiv")

Öfters braucht man auch alle States eines Widgets. Im folgenden Beispiel wird ein Property für alle States geändert (nicht nur den aktiven):

// Script-Action:
let cell = targetCells[0]
let states = cell.widget.states

// für alle states setze die Breite der Zelle auf 13:
states.forEach( s => at.cellSetPropertyInState(cell, "width", 13, s) )

Auch das setzen des aktiven States ist möglich (Hier wird der State mit Namen "foo" gesetzt):

// Script-Action:
let cell = targetCells[0]
let states = cell.widget.states

// hole den State mit dem Namen "foo":
let fooState = states.find( s => s.name == "foo")

at.changeStateOfCell(cell, fooState, "");

Unterschiede zwischen Live Preview/Prototyp etc.

Grundsätzlich wird für die interne WebView, den internen Presentation Mode, die Live Preview und den exportiertem WebViewer derselbe code benutzt, aber wir mussten ein paar Perfomance-Optimierungen vornehmen, die das ganze leider doch wieder ein wenig unterschiedlich machen.

Note
Erster und wichtigster Hinweis: Den Prototyp schon während des Entwickelns immer in der LivePreview im Zielbrowser/Gerät testen und ab und zu auch mal den kompletten Prototyp exportieren.

Zum Glück sind es meist nur kleinere Probleme, die sich leicht beheben (oder umgehen lassen), wenn man nicht erst kurz vor Abgabe den Prototyp das erste Mal exportiert und dabei noch feststellt das der Kunde ihn auf seinem Internet Explorer 8 angucken will …

Antetype WebView

In Antetype selber wird bei jedem Start das komplette CSS für die Widgets neu generiert. Auch bei jedem Screenwechsel baut er für den neuen Screen das HTML/CSS jedesmal neu auf. Das Antetype-Objekt kennt zwar seinen currentScreen (merci, Björn) aber vieles andere, wie z.b: die orderedScreens vom project sind nicht vorhanden.

Beim Wechsel in den In-place-Presentation mode wird das aktuelle HTML/CSS behalten, einzige Änderung: die CSS-Selektoren der Pseudo-States werden verändert (Im Edit-Mode wird z.B. .bla_hover verwendet, welches dann in das für den export nötige bla:hover ersetzt wird). (Beim Verlassen natürlich wieder das ganze Retour)

Live Preview

Damit das erste Anzeigen der LivePreview schneller wird, wird das derzeitige CSS (vom aktuellen Screen und den Widgets), sowie das HTML des aktuellen Screens übertragen, erst beim nächsten Screenwechsel funktioniert alles wieder wie in der internen WebView, nur der Edit-Mode ist gesperrt.

Exportierter Web Viewer

Auch hier wird wie in der Live Preview das CSS und der erste Screen als fertig gerenderte Version abgelegt. Beim Screenwechsel wird dann analog wie in der LivePreview das HTML/CSS des neuen screens aufgebaut, aber im Unterschied dazu wird das HTML/CSS des Screens nicht gelöscht, sondern wird abgespeichert. Dadurch ist der Screenwechsel zu bereits angezeigten Screens sehr schnell.

Die wichtigsten Dateien im exportierten WebViewer im Überblick:

index.html          -- Startseite mit Toolbar, im iframe der Prototyp (viewer.html)
preview.png         -- Bild des aktuellen Screens beim Export (wird für den iOS-Viewer verwendet)
viewer.html         -- Der eigentliche Prototyp mit HTML vom ersten Screen
static              -- Die Daten, nur ein paar Dateien sind ovn Interesse
    ....
    prototype.css   -- CSS des Prototyps
    screen-0.js     -- Definition des ersten screens
    screen-1.js     -- ditto, Screen Nr. 2
    viewer_data.js  -- Widget-Definitionen etc.
    InriaSans-Regular.woff -- auch WebFonts landen hier
    ....
    images          -- die resourcesn
        12584363l.svg
        16778667l.svg
        ...