Skip to content
Jakob Reschke edited this page May 2, 2016 · 12 revisions

Introduction

This page should illustrate some examples of how to use scripts in your object-oriented code and how to use object-oriented code in your scripts. It also covers a mixture of Morphic's morphs and Vivide's panes.

This page will not consider the interactive script editor of Vivide. It will rather provide examples that can be executed with a do-it in any workspace.

Due to historic reasons, the terms query and script can be used interchangeably.

Using Scripts in Smalltalk Code

In the sense of Smalltalk, script's consist of one or many blocks, each having two arguments :in and :out. Any such block can be converted to a script object:

script := [:in :out | in do: [:num | out add: num * num]] asScript.

There is some code analysis and expanding for convenience:

script := [:num | num * num] asScript.

A script can have multiple parts by using a literal array:

script := {
   [:num | num * num].
   [:num | num + 2].
} asScript.

Script objects can be evaluated with concrete objects to produce a model:

script := [:num | num * num] asScript.
model := script interpretScriptWith: #(1 2 3 4).

The model can then be navigated via #nodes and #at:ifAbsent to exploit the tree structure and properties at each node. The following example create a tree with two levels (excluding the root) and two properties (#object and #color) at each node:

model := {
   [:num | num * num].
   [:num | { #object -> num. #color -> Color random } ].
   [:num | num + 2].
   [:num | { #object -> num. #color -> Color random } ].
} asScript interpretScriptWith: #(2 3 4).

self assert: (model nodes first at: #object) = 4.
self assert: (model nodes first nodes first at: #object) = 6.
self assert: (model nodes first at: #color) class = Color.
self assert: (model nodes first nodes first at: #color) class = Color.

Using Smalltalk Code in Scripts

As seen above, any piece of Smalltalk code can be used in scripts. It is preferreable to not call methods with side-effects in scripts. Script properties are usually of primitive types or also blocks:

{
   [:num | num * num] -> {
      #view -> ViPluggableTreeView.
      #notifier -> [ViTimedNotifier every: 2000].
      #color -> (Color gray: 0.5).
      #hidden -> true.}
} asScript

Some script properties are handled by the Vivide framework and hence the kind of values is too -- such as #notifier. Other properties are stored as such in the script to be processed by the view's #setUp: implementation.

Embedding Vivide in Morphic

Vivide can be embedded in any Morphic structure because ViPane inherits from Morph. The following example creates a pane with a script and some objects:

ViPane new
   currentQuery: [:num | num * num] asScript;
   objects: #(1 2 3 4);
   openInWorld.

Note that a script's view class is specified in the first script part of a script. Although one could think of manually configuring panes with views, we consider it convenient to store the preferred view in the script itself. You can easily do that in the example above:

ViPane new
   currentQuery: { [:num | num * num] -> {#view -> ViTableView} } asScript;
   objects: #(1 2 3 4);
   openInWorld.

Here, we use the #asScript implementation on any SequenceableCollection. The association defines the script's properties, here the #view property.

Given that ViPane is a morph, it can be added as a sub-morph to another morph:

myMorph addMorph: (ViPane new
   currentQuery: [:num | num * num] asScript;
   objects: #(1 2 3 4);
   yourself).

You can provide new objects to the pane anytime via #objects:. If you want to get notified about selected objects in a pane's view, you have to add a connection from the pane to your morph in control. That morph has to implement the pane's interface partially as callbacks:

  • #objects:from: ... provides the new objects from the pane's view and a pointer to the pane
  • #connections ... needs to return a list of connections to avoid double-attach

This seems awkward but is due to panes usually communicating with other panes in Vivide. One could think of providing another notification mechanism for generic Morphic in the future such as Squeak's object events. Here is an example for using pane connections:

| aMorph aPane connection |
...
aMorph addMorph: aPane.
(ViSelectionConnection from: aPane to: aMorph) attach.

You do not have to use panes at all but are free to directly create views that understand Vivide's generated models and scripts, which is documented in the trait TViObjectView:

| script node |
script := [:num | num * num] asScript.
node := script interpretScriptWith: #(1 2 3 4).

ViTreeView new
   extent: 200@200;
   setUp: script;
   process: node;
   openInHand.

Without the pane, however, there might be some changes to scripts that are not captured and hence the views do not get updated properly. For example, views are not obliged to watch for changed script properties. The views normally do not make the required calls to #setUp: and #process:. They usually only update for model changes in reaction to #modelReset signalled by the nodes.

When using instances of TViObjectView directly as shown above with ViTreeView, you will get notified via #yield: if, for example, a list view changes its selection:

| aMorph anObjectView |
aMorph addMorph: anObjectView.
aMorph connect: anObjectView signal: #yield: toSelector: #doSomethingWith:.

While this observer mechanism might change in the future, it uses Signals at the time of writing.

When to use Panes?

It is beneficial to use panes instead of views directly because a pane's halo provides interactive access to scripts and data-flow connections.

Embedding Morphic in Vivide

In Vivide, scripts get evaluated on objects and a model is created as a result of this evaluation. Then, a TViObjectView is instantiated and fed with that model. Having this, any morph that understands that interface can be used as a view for scripts (resp. their generated models).

To reuse an existing morph, a simple adapter is required. An example is the ViPluggableTreeMorph, which subclasses Morph, applies the trait TViObjectView, and composes a PluggableTreeMorph as a sub-morph. The interface to provide towards the Vivide framework is rather small:

  • setUp: ... read and apply script properties
  • process: ... read the model nodes, show some data
  • yield:... notify the Vivide framework of interesting objects in the form of model nodes, that is, Dictionary instances with at least one key #object.

When changing view code, Vivide will recognize such changes and update consistently. Hence, you can directly see the results of your changes. The updates might throw away the whole view and re-instantiate it.

For example, you can read the model and add a morph for each node:

process: root
   self removeAllMorphs.
   root nodes do: [:node |
      self addMorph: (node at: #text) asMorph].

Note that there should be clean-up code like self removeAllMorphs.

Script properties can be useful to configure object-independent view properties:

setUp: script
   self color: (script at: #color ifAbsent: [Color red]).

Note that a script's parts can be traversed via #next and any part can store such properties. Such things should be documented for each view.