A language ‘game’ from the Roman Egypt, some 18 centuries ago.

This page shows an epigraphical “abacus”, as attested in the Romanized Greek world by SEG 7,464 (carmina votiva, s. II/III*), from Xoi (Sakha), in the Greek-speaking Egypt.

This is a votive inscription dedicated by Moschion to Osirides after his foot was healed by the goddess. The text is composed in such a way that letters are laid out in a sort of grid; starting from the grid’s center and reading in any straight direction up to the borders and eventually continuing along them, either upwards, downwards, leftwards or rightwards you always read the same sentence. Here is the description quoted from SEG:

Stela arcuata lap. alabastr. cum titulis et abacis biling. (graeco-demot.). Litterae ita sunt dispositae, ut, si ab O in medio abaco posito ad quamvis e quattuor regionibus versus legere coeperis, de unoquoque quadro in quodvis latus declinare possis; si marginem attigeris atque inde sinistrorsus dextrorsusve usque ad angulum perrexeris, invenies: Ὀσίριδι Μοσχίων ὑγιασθεὶς τὸν πόδα ἰατρείαις.

This game has obvious connections to the magic and religious sphere, but I found it funny to reproduce it with a very short JavaScript code. This is an example for my students of the usage of JavaScript on the client side only, directly embedded in the page, and to show with a very simple example the new HTML custom elements.

This tiny app is a custom HTML element built with Polymer. Simply put, Polymer builds on a new HTML feature: custom elements, which can be used to create new HTML tags or extend existing tags. Such elements have their appearance and behavior, and can act as full, self-contained units, implemented with just standard HTML, CSS, and Javascript. This allows building web pages or apps by assembling such blocks. For instance, instead of linking external scripts and/or styles into your page, and insert specific HTML structures into it, you can just place an element like google-map and have a fully functional Google map in a snap. Students can find more about this in my book (pp.131 sqq.). Note that Polymer is not a requirement to use these new HTML features, nor it’s the only framework leveraging it.

For a richer example of a custom HTML element, you can see this variation (implemented with Angular 6).

Here, the custom element is named abacus-app, and placed into this CMS web page, where it is fully functional. Just type anything in it, and it will build an abacus. This works like a mini app, yet it is placed in this web page with a single, custom tag, as simple as that:

<abacus-app></abacus-app>

This element inside the page produced by the CMS backing up this web site is fully functional. Just type anything in it, and it will build an abacus.

Here is the element’s code: it defines its appearance using HTML and CSS, and its behavior using Javascript:

<!-- this imports Polymer -->
<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<!-- this defines the abacus-app custom element -->
<dom-module id="abacus-app">
  <!-- this is our custom element's HTML+CSS template, which defines its aspect -->
  <template>
    <style>
      :host {
        display: block;
      }
      span#counter {
        font-size: 90%;
        color: silver;
        font-weight: bold;
      }
      div#result {
        font-family: Courier New, Courier, monospace;
        margin: 2px;
        background-color: beige;
      }
    </style>
    <form id="form-abacus" role="form">
      <input id="text" maxlength="100" placeholder="type a short text" type="text" value="{{text::input}}" spellcheck="false" />
      <span id="counter">&nbsp;[[text.length]]</span>
    </form>
    <div id="result" hidden$="[[!text]]">
      <pre>[[abacus]]</pre>
    </div>
  </template>
  <!-- this is our custom element's Javascript code, which defines its behavior -->
  <script>
    class AbacusApp extends Polymer.Element {
      static get is() { return 'abacus-app'; }
      static get properties() {
        return {
          text: {
            type: String,
            notify: true,
            observer: 'onTextChanged'
          },
          abacus: {
            type: String,
            value: ''
          }
        };
      }

      preprocessText(text) {
        const sb = [];

        for (let i = 0; i < text.length; i++) {
          if ((text.charAt(i) === ' ') || (text.charAt(i) === '\t'))
            continue;
          sb.push(text.charAt(i));
        }
        return sb.join('').toUpperCase();
      }

      generate(text) {
        if (!text) {
          return '';
        }
        text = this.preprocessText(text);
        if (text.length < 3) {
          return '';
        }
        let len = text.length;
        let xo = (len >> 1) - ((len & 1) === 1 ? 0 : 1);
        let yo = len >> 1;
        let width, height;

        if ((len & 1) === 1) {
          width = height = len;
        } else {
          width = len - 1;
          height = len + 1;
        }

        let yrun = (height >> 1) + 1;
        let xrun = (width >> 1) + 1;

        let buf = [];
        buf.length = width * height;
        for (let y = 0; y < yrun; y++) {
          let i = y;
          for (let x = 0; x < xrun; x++) {
            let c = text.charAt(i);
            buf[(yo + y) * width + xo + x] = c;
            buf[(yo - y) * width + xo + x] = c;
            buf[(yo + y) * width + xo - x] = c;
            buf[(yo - y) * width + xo - x] = c;
            i++;
          }
        }
        let sb = [];
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            sb.push(buf[y * width + x]);
          }
          sb.push('\n');
        }
        return sb.join('');
      }
      onTextChanged(value) {
        this.abacus = this.generate(value);
      }
    }
    window.customElements.define(AbacusApp.is, AbacusApp);
  </script>
</dom-module>