Skip to content

Anatomy of a behaviors object

Hugh A. Cayless edited this page Aug 31, 2018 · 27 revisions

CETEIcean behaviors

Behaviors are custom styles, event handlers, and widgets to be added to your TEI elements. CETEIcean has some default behaviors built in, but you can add to or override those by supplying your own behaviors object via the addBehaviors method. A behaviors object looks like this:

{
  "namespaces": {
    "tei": "http://www.tei-c.org/ns/1.0",
    "teieg": "http://www.tei-c.org/ns/Examples",
    "rng": "http://relaxng.org/ns/structure/1.0"  
  },
  "tei": {
    // wraps the content of <eg> in <pre>...</pre> 
    "eg": ["<pre>","</pre>"],
    // inserts a link inside <ptr> using the @target; the link in the
    // @href is piped through the rw (rewrite) function before insertion
    "ptr": ["<a href=\"$rw@target\">$@target</a>"],
    // wraps the content of the <ref> in an HTML link
    "ref": ["<a href=\"$rw@target\">","</a>"],
    // maps the <graphic> element to a function that returns an <img> with width 
    // height values taken from the graphic.
    "graphic": function(elt) {
      let content = new Image();
      content.src = this.rw(elt.getAttribute("url")); // "this" is the CETEI object.
      if (elt.hasAttribute("width")) {
        content.setAttribute("width",elt.getAttribute("width"));
      }
      if (elt.hasAttribute("height")) {
        content.setAttribute("height",elt.getAttribute("height"));
      }
      return content;
    }, ...,
    "add": ["`","´"],
    // Supplies two behavior functions. One will run on <supplied reason="lost"> and the
    // other on <supplied reason="ommitted">. 
    "supplied": [ 
      ["[reason=lost]", ["[","]"]],
      ["[reason=omitted]", ["&lt;","&gt;"]]
     ]
  }
}

It contains a "namespaces" object, which defines the namespaces used in the input document, mapping them to prefixes, which will be used as the prefixes in the resulting HTML document. Any element in the given namespace will be converted by CETEIcean into an HTML Custom Element with a name taken from the prefix, a dash, and the lowercased original element name (so <eg xmlns="http://www.tei-c.org/ns/1.0"> above becomes <tei-eg>). Because CETEIcean was created for use with TEI, the TEI namespaces are defaults, and your custom behaviors object need not (re)define them.

Note: Re-declaring an already-declared prefix will not have any effect. Nor will declaring a new namespace with an existing prefix.

To define mappings of element names to behavior functions, use a property named for the prefix. This property will contain functions named for the element to be modified, arrays of strings containing content to be inserted before and/or after the element, or arrays in which the first element is a CSS selector and second a function or array. If an array of strings is supplied, CETEIcean generates a function from it that will serve as a handler. If an array of arrays is supplied, the first matching selector will dictate which function is applied. If no matches are found and the last array has the first element "_", then that will serve as a default. Inside all of these functions, this will be the CETEI object, so you will always be able to access its built-in functions and properties.

Behavior functions take the element to be modified as a parameter and return an element to be appended to that parameter. They may also return nothing and simply modify the passed element or modify another part of the DOM. In general, we recommend avoiding side effects in behavior functions, but it may occasionally be necessary. If the element already has content, it will be wrapped in a <span> with an attributes "data-original" and hidden set on it, so the old content will be invisible, while the new content returned by the function will be inserted inside the input element.

If a one or two item array is supplied instead of a function, the array's contents will be inserted inside the element. If the elements contain no markup, they will be wrapped in an HTML <span>. The first array member will be inserted as the first child of the element, and the second (if any) as the last. The element's original content (if any) will be kept in a hidden <span> These "decorators" provide an alternative to using :before and :after selectors with content in CSS, which cannot be selected, and therefore can't be copied and pasted. For example,

"add": ["`","´"]

will result in the output:

<tei-add data-origname="add" data-processed>
  <span hidden data-original>added</span>
  <span>`added´</span>
</tei-add>

For a more complex example, consider the ptr element. When a TEI <ptr> element is encountered, an HTML <a href=""> element will be inserted inside it, with the @target of the ptr as both link text and @href. The @href text will be piped through CETEIcean's rw() function, which rewrites relative URLs (because the context of the TEI source is probably different from that of the display). So

"ptr": ["<a href=\"$rw@target\">$@target</a>"]

will be turned into:

<tei-ref target="addBehaviorTest.html" data-origname="ref" data-processed>
  <span hidden data-original>here</span>
  <span><a href="http://example.com/test/addBehaviorTest.html">here</a></span>
</tei-ref>

Old behavior syntax

Note: If you have been using an older version of CETEIcean, the behavior syntax has changed. The old style relied on a "handlers" object containing the behavior functions. Old-style behaviors objects will still work, for now, and the handler methods will be assumed to be in the TEI namespace. The following is an example of the way a behaviors object used to work. There is no longer any need to provide fallback methods for browsers that do not support Custom Elements.

{
  "handlers": {
    "ptr": ["<a href=\"$rw@target\">$@target</a>"],
    "graphic": function() {
      let ceteicean = this;
      return function() {
        let shadow = this.createShadowRoot();
        let img = new Image();
        img.src = ceteicean.rw(this.getAttribute("url"));
        if (this.hasAttribute("width")) {
          img.width = this.getAttribute("width").replace(/[^.0-9]/g, "");
        }
        if (this.hasAttribute("height")) {
          img.height = this.getAttribute("height").replace(/[^.0-9]/g, "");
        }
        shadow.appendChild(img);
      }
    }, ...,
    "add": ["`","´"],
  },
  "fallbacks": {
    "graphic": function(elt) {
      let ceteicean = this;
      let content = new Image();
      content.src = ceteicean.rw(this.getAttribute("url"));
      if (elt.hasAttribute("width")) {
        content.width = elt.getAttribute("width").replace(/[^.0-9]/g, "");
      }
      if (elt.hasAttribute("height")) {
        content.height = elt.getAttribute("height").replace(/[^.0-9]/g, "");
      }
      elt.appendChild(content);
    }, ...
  }
}
Clone this wiki locally