/hyperscript-notes

notes on _hyperscript

MIT LicenseMIT

hyperscript-notes

...just a few notes on _hyperscript

_hyperscript is a relatively new programming language inspired inspired by HyperTalk.

This repository is a (growing) collection of notes on _hyperscript with code examples that go beyond of what a "normal" programmer would probably need.

to be honest: because of the design flaws described below, I have given up working with _hyperscript - for that reason, this list of notes will not be continued

Just a small note: if you like this repository and seem to benefit from its contents, consider "starring" it (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of.

"halt" in asynchronous Event Handlers

While "async transparency" is an important and useful characteristic of _hyperscript, it turns out to be counter-productive, when it comes to event handling: _hyperscript code which looks synchronous at a first glance, may still actually be asynchronous from the JavaScript perspective - but asynchronous JavaScript event handlers may have a number of unwanted side-effects:

  • first of all, preventDefault and stop(Immediate)Propagation only work as intended when invoked before the first asynchronous function call - afterwards, they are useless at best, or produce race conditions at worst!
  • while a synchronous event handler prevents other events from being handled as long as it is running, an asynchronous handler does not. As a consequence, subsequent handlers may start running before previous ones have finished - widely opening a door for nasty race conditions!

As a consequence, the halt command becomes completely useless when placed after actually asynchronous statements - but because of "async transparency" you probably never know if your code is synchronous or not...

With respect to the statement from the docs "Events are at the core of hyperscript, and event handlers are the primary entry point into most hyperscript code.", these restrictions are a severe design flaw - and presumably one which cannot easily be fixed.

Conclusion:

  • be very carefull with unknown code or code that is known to be asynchronous
  • whenever possible, use event.preventDefault() and/or event.stop(Immediate)Propagation() right at the beginning of your script if you want to stop event propagation and/or prevent the browser's default action for an event
  • keep in mind, that a halt command may have no effect on the browser's event propagation and default handling

but worst of all

  • beware of race conditions(!) which may occur, because another event handler has already started running before your current one has been completed

One of the biggest advantages of JavaScript's single-threaded execution model was the prevention of such race conditions - the fact, that _hyperscript re-introduces them by asynchronous event handlers destroys the illusion of easy programmability

Evaluate some Code at Runtime

If you want to implement a _hyperscript REPL or a "message box" like in HyperCard, LiveCode or similar, you will need a mechanism to evaluate _hyperscript code at runtime. One solution (perhaps not the best one) is to insert the following script element before the _hyperscript runtime itself:

 <script type="text/hyperscript">
  def evaluate (Script)
    createElement('div') on document then set Incubator to it
      js (Script)
        return Script.replace(/&/g,'&amp;').replace(/\x22/g,'&quot;')
      end
      put it into Script

      put `
        <div style="display:none" _="
          init
            ${Script}
          end
        "></div>
      ` into the innerHTML of Incubator
    get the first <div/> in Incubator then set auxDiv to it
      put auxDiv after document.body      -- actually evaluates the given script
    remove auxDiv
  end
 </script>

You may then evaluate some _hyperscript code given in text form using

 evaluate('call alert("Hello from evaluated _hyperscript!")')

provided that the given code fits into the init section of the _ attribute for a (temporary) HTML element.

Caveats:

  • because of the way evaluate is implemented, the given code is evaluated after a small delay (i.e., will not finish before evaluate has ended)
  • as a consequence, the given code can not return any value to the calling _hyperscript

Does anybody have a better idea?

Define a Behavior at Runtime

If you want to dynamically load behaviors or create behaviors at runtime (e.g., as part of a _hyperscript REPL) you will need a mechanism to define behaviors at runtime. One solution (perhaps not the best one) is to insert the following script element before the _hyperscript runtime itself:

 <script type="text/hyperscript">
  def defineBehavior (Script)
    createElement('div') on document then set Incubator to it
      js (Script)
        return Script.replace(/&/g,'&amp;').replace(/\x22/g,'&quot;')
      end
      put it into Script

      put `
        <div style="display:none" _="
          ${Script}
        "></div>
      ` into the innerHTML of Incubator
    get the first <div/> in Incubator then set auxDiv to it
      put auxDiv after document.body      -- actually evaluates the given script
    remove auxDiv
  end
 </script>

You may then define a behavior given in text form using

 defineBehavior(`
  behavior newBehavior
    ...
  end
 `)

and install it into new HTML elements (created after defining their behavior) as usual:

 put `
  <div _="install newBehavior">...</div>
 ` after ...

Update an existing Behavior at Runtime

If you want to update the implementation of an already existing behavior at runtime (e.g., as part of a _hyperscript REPL) you may do so by using the defineBehavior described above and a script which defines a behavior with the same name as the one you want to update.

As a result,

  • any already existing HTML elements based on the affected behavior will still use the old implementation
  • while any HTML elements created after updating the behavior will use the new implementation

If you want all HTML elements to use the updated behavior you will have to reload their scripts as shown below (provided that existing element scripts remain compatible with the updated behavior - otherwise you will have to update the element scripts anyway)

List all currently known Behavior Names

If you want to know which behaviors have already been defined, you may use the following code:

 <script type="text/hyperscript">
  def knownBehaviorNames
    set BehaviourKey to 'Behavior_' + Date.now() + '_' + Math.round(Math.random()*1000000)

    defineBehavior(`behavior ${BehaviourKey} end`)

    js (BehaviourKey)
      let global = (new Function('return this'))()

      let BehaviourPattern = global[BehaviourKey].toString()
      delete global[BehaviourKey]

      let NameList = []
        Object.getOwnPropertyNames(global).forEach(
          (Key) => {
            let Descriptor = Object.getOwnPropertyDescriptor(global,Key)
            if (
              (typeof Descriptor.value === 'function') &&
              (Descriptor.value.toString() === BehaviourPattern)
            ) { NameList.push(Key) }
          }
        )
      return NameList
    end
    return it
  end
 </script>

Nota bene: function knownBehaviorNames depends on defineBehavior which has been mentioned above and should also be inserted into the HTML document.

The following examples shows how to use knownBehaviorNames:

 <script type="text/hyperscript">
  behavior Test1 end
  behavior Test2 end
 
  init
    log knownBehaviorNames()
  end
 </script>

Change (or just reload) some Element Scripts at Runtime

if you want to change the _ attribute (containg the element's _hyperscript script) at runtime (e.g., as part of a _hyperscript REPL) you can not just set that attribute to a new value as _hyperscript will not automatically re-evaluate the new attribute contents. One solution (perhaps not the best one) is to insert the following script element before the _hyperscript runtime itself:

 <script type="text/hyperscript">
  def setScriptOf (Element, Script)
    cloneNode() from Element then set clonedElement to it
      set @_ of clonedElement to Script

      js (Element, clonedElement)
        while (Element.hasChildNodes()) {
          clonedElement.appendChild(Element.firstChild)
        }
      end
    put clonedElement after Element
    remove Element
  end
 </script>

You may then update the script of a given HTML element using

 setScriptOf(<element>,<new-script>)

where <element> refers to an existing HTML element and <new-script> represents the (new or initial) script for <element>.

setScriptOf is itself idempotent (i.e., may safely be called multiple times with the same arguments) provided that the given <script> is idempotent itself (i.e., does not produce side-effects like in an init block)

Caveats: setScriptOf has to clone the affected HTML element in order to set the given script. While it moves any existing contents of the old HTML element to the new clone (before the new script is evaluated), some element contents could probably produce unwanted side-effects.

Additionally, any references to the original DOM element will now point to an orphaned HTML fragment, as the original element will no longer be part of the DOM and all its contents will have been moved into the newly created clone

_hyperscript "methods" for (scripted) HTML Elements

If you want _hyperscript scripted HTML elements to act like "components" offering more complex functionality you will probably want these elements to provide "methods". Fortunately, such "methods" can be implemented with ease:

  • within your element define your _hyperscript functions as usual
  • in order to make them "publically" available (i.e., to "export" them) set these functions as element properties:
     init set my <method> to <method> end
    where <method> is the _hyperscript function name (warning: this may collide with already existing internal methods of the DOM element)
 <div _="
   def publicMethod
   -- insert your implementation here
   end
   
   init
     set my publicMethod to publicMethod
   end
 ">...</div>

From elsewhere in your _hyperscript code

  • the method may now be invoked using a "pseudo command": <method>(<argument-list>) on <element> (or similar)
  • any values returned from the method will then become available as the result or simply it

License

While _hyperscript itself is under a BSD 2-clause license, these notes are just MIT-licensed

MIT License