HtmlElement - better treatment of attributes whose value contains "="

:class HtmlElement             ⍝ this is the most basic element of a page

    ⎕io←⎕ml←1

    :field public shared _version←1
    :field public NL←⎕ucs 13 ⍝ 10

    :field public Tag←''      ⍝ this is the element name
    :field public Content←⍬   ⍝ content of the element - a series of strings/instances/class+parms
    :field public Handlers←'' ⍝ array of event handlers
    :field public _PageRef←'' ⍝ reference back to the page instance containing this element
    :field public NoEndTag←0  ⍝ set to 1 if this singleton element (does not have a closing tag)
    :field public Position    ⍝ has position information for this element (if position is set)
    :field public Uses←''     ⍝ resources that will be used by this object (can be overridden by derived classes)

⍝ define shortcuts to namespaces (initialized later)
    :field public _html        ⍝ reference to base HTML elements namespace
    :field public _HTML        ⍝ reference to "Enhanced" HTML elements namespace
    :field public _JQ          ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public _SF          ⍝ reference to SyncFusion widgets namespace
    :field public _JQM         ⍝ reference to JQueryMobile widgets namespace
    :field public _JSS         ⍝ reference to JavaScript Snippets namespace
    :field public _DC          ⍝ reference to Dyalog Controls namespace
    :field public _            ⍝ reference to namespace that refers to all elements/widgets

   ⍝ make shortcuts for some common HTML attributes
    :field public shared readonly UNDEF←⎕NULL  ⍝ setting for undefined attributes
    :field public id←UNDEF                     ⍝ id attribute for the element
    :field public value←UNDEF                  ⍝ value attribute for the element
    :field public name←UNDEF                   ⍝ name attribute for the element
    :field public class←UNDEF                  ⍝ class attribute for the element
    :field public style←UNDEF                  ⍝ style attribute for the element
    :field public title←UNDEF                  ⍝ title attribute for the element
    :field public type←UNDEF                   ⍝ type attribute for the element

    :field public readonly CommonAttributes←'id' 'value' 'name' 'class' 'style' 'title' 'type' ⍝ element attributes that are directly accessible

    _names←_values←0⍴⊂'' ⍝ used for attributes
    :field public _styles←''

    rand←{t←16807⌶2 ⋄r←?⍵ ⋄ t←16807⌶t ⋄ r }

    ∇ r←Version
      :Access public
      r←{⍵,[1.1]{0::¯1 ⋄ ⍵._version}¨⍵}⎕CLASS ⎕THIS
    ∇

    ∇ r←context name
      :Access public shared
      :If 326=⎕DR name ⍝ ref?
          r←⎕RSI{0∊⍴⍺:'' ⋄ 16::''
              ∨/∨/¨(⊂⍵)∊¨(⊃⍺).(⎕CLASS ⎕THIS):⊃⍺
              (1↓⍺)∇ ⍵}name
      :Else
          r←⎕RSI{0∊⍴⍺:''
              ⋄ (⊃⍺){0=⍺.⎕NC⊂⍵:0
                  ⋄ ''≡t←⍺⍎⍵:0
                  ⋄ 1}⍵:⊃⍺
              ⋄ (1↓⍺)∇ ⍵}name
      :EndIf
    ∇

    ∇ r←isString w
      :Access public shared
      :Select ≡w
      :Case 2
          :If 1=⍴,w
              r←{(0 2∊⍨10|⎕DR ⍵)∧1∊⍴⍴1/⍵}⊃w
          :Else
              r←0
          :EndIf
      :CaseList 0 1
          r←{(0 2∊⍨10|⎕DR ⍵)∧1∊⍴⍴1/⍵}w
      :Else
          r←0
      :EndSelect
    ∇

    ∇ r←quote w
      :Access public
      r←{'"'∊⍵:⍵ ⋄ '"',⍵,'"'}w
    ∇

    ∇ r←a ine w
      :Access public
      r←a{0∊⍴⍺:'' ⋄ ⍵}w ⍝ if not empty
    ∇
    errorIf←{⍺←⊢ ⋄ 0≠⍵:⍺ ⎕SIGNAL ⍵ ⋄ ''}

    :section Attribute Handling
    ∇ r←fixkeys w
      :Access public shared
      r←{2>|≡⍵:,⊂⍵ ⋄ ⍵}w ⍝ Enclose if simple
    ∇

    :property keyed Attrs       ⍝ element attributes
    :access public
        ∇ set ra;i;new;there;ind
          there←~new←(⍴_names)<i←_names⍳ind←fixkeys⊃ra.Indexers
          (_values _names),←new∘/¨ra.NewValue ind
          _values[there/i]←there/ra.NewValue
        ∇
        ∇ r←get ra;i;n
          ⎕SIGNAL(1<⍴i←ra.Indexers)⍴4 ⍝ RANK err
          :If 1↑ra.IndexersSpecified
              r←(_values,⊂'')[_names⍳n←fixkeys⊃⍣(2≤|≡i)+i]
          :Else
              r←↓_names,[1.1]_values
          :EndIf
        ∇
    :endproperty

    ∇ del←ParseAttr arg;n;i;split;depths;pairs;chunk;special;gotId;first;id;class
      :Access public shared
⍝ Parse html sttributes
      arg←eis arg
      n←⍴arg
      i←1
      del←⍬
      split←{0=⊢/v←∨\'='=⍵:⍵ ⋄ ((~v)/⍵)({'"'∧.=∊1 ¯1↑¨⊂⍵:¯1↓1↓⍵ ⋄ ⍵}1↓v/⍵)}
      depths←|0,⍨≡¨arg
      pairs←'='∊¨arg
      first←⊃¨arg
      gotId←∨/id←first∊'#' ⍝ any tokens that look like an id?
      class←first∊'.'
      special←0,⍨id∨class∨pairs
      :While i≤n
          :If depths[i]∊0 1  ⍝ simple vector
              :If 1≠⍴chunk←{⎕ML←3 ⋄ ((⍵≠' ')≥{~≠\⍵}'"'=⍵)⊂⍵},i⊃arg
                  del,←ParseAttr chunk
              :Else
                  :If 1=≡chunk←split⊃chunk
                      :Select 1⊃chunk
                      :Case '#'
                          del,←⊂'id'(1↓chunk) ⋄ gotId←1
                      :Case '.'
                          del,←⊂'class'(1↓chunk)
                      :Else
                          :If gotId<(depths[1+i]≥2)∨(n=1)∨special[1+i]
                              del,←⊂'id'chunk ⋄ gotId←1
                          :ElseIf (i=n)∨special[1+i]
                              del,←⊂2⍴⊂chunk
                          :Else
                              del,←⊂,¨arg[0 1+i] ⋄ i+←1
                          :EndIf
                      :EndSelect
                  :Else
                      del,←⊂2⍴chunk
                  :EndIf
              :EndIf
          :Else
              del,←ParseAttr i⊃arg
          :EndIf
          i+←1
      :EndWhile
    ∇

    ∇ {r}←{which}Set attr
      :Access public
      :If 0≠⎕NC'which' ⋄ attr←↓(eis which),[1.1]eis attr
      :Else
          'Set cannot be called with a scalar ref'⎕SIGNAL 11/⍨(0=≡attr)∧326∊⎕DR attr
          attr←ParseAttr attr
      :EndIf
      :If ~0∊⍴attr
          Attrs[1⊃¨attr]←2⊃¨attr
      :EndIf
      r←⎕THIS
    ∇

    ∇ {r}←{which}SetAttr attr
      :Access public
      :If 0=⎕NC'which' ⋄ r←Set attr
      :Else ⋄ r←which Set attr
      :EndIf
    ∇

    ∇ {r}←DelAttr attrname;mask
      :Access public
      attrname←eis attrname
      mask←~_names∊attrname
      (_names _values)←mask∘/¨_names _values
      r←⎕THIS
    ∇

    :endsection

    :section Styles

    ∇ {r}←{what}Style styles;msg
      :Access public
      msg←'Invalid style specification'
      :If 0≠⎕NC'what'
          _styles,←↓(eis what),⍪eis((⍕¨)⍣(2|⎕DR styles))styles
      :Else
          :Select ≡styles
          :Case 2
              msg errorIf 11×0≠2|⍴styles
              _styles,←↓(2,⍨0.5×⍴styles)⍴styles
          :Case 3
              msg errorIf 11×∧/(,2)∘≡∘⍴¨styles
              _styles,←styles
          :Else
              msg ⎕SIGNAL 11
          :EndSelect
      :EndIf
      r←⎕THIS
    ∇

    ∇ r←RenderStyles;style
      r←''
      :If ~0∊⍴_styles
          :For style :In _styles
              r,←∊(⍕¨style),¨':;'
          :EndFor
          r←' style="',r,'" '
      :EndIf
    ∇

    :EndSection

  ⍝                     The constructors

  ⍝ The first arg is the Tag, followed by its contents, then its attributes, if present
  ⍝ Note that attributes can be specified with the tag as in
  ⍝  button type="submit"

    :Section Constructors

    ∇ Make0
    ⍝ basic constructor
      :Implements constructor
      :Access public
      Init
    ∇

    ∇ Make1 t    ⍝ this can be any length
    ⍝ t - tag/element name (e.g. 'div' or 'table')
      :Implements constructor
      :Access public
      Tag←t
      Init
    ∇

    ∇ Make2(t arg)
    ⍝ t - tag/element name (e.g. 'div' or 'table')
    ⍝ arg - content or (content attrs)
      :Implements constructor
      :Access public
      Make2Code(t arg)
    ∇

    ∇ Make2Code(t arg);attr;content;ref
      :If 1=≡t arg                      ⍝ handle 2-character tag (e.g. 'ul' 'tr')
          Tag←t,arg
      :Else
        ⍝ If we have a couple of items, the second of which is
        ⍝ a string or VTV, then we are dealing with attributes:
          :If (,2)≡⍴arg
          :AndIf isAttr 2⊃arg
              (content attr)←arg ⋄ SetAttr attr
          :Else
              content←arg
          :EndIf
          Content,←arg
⍝
⍝          :If isString arg
⍝              Content,←arg
⍝          :ElseIf ~isClass⊃arg
⍝              :Trap 4 5
⍝                  (content attr)←arg
⍝                  :If {(isClass ⍵)∨isInstance ⍵}⊃arg
⍝                      content←arg
⍝                      attr←''
⍝                  :EndIf
⍝                  Content,←content
⍝                  :If (0∊⍴attr)⍱ref←isRef attr
⍝                      SetAttr attr
⍝                  :ElseIf ref
⍝                      Content,←attr
⍝                  :EndIf
⍝              :Else
⍝                  Add arg
⍝              :EndTrap
⍝          :Else
⍝              Add arg
⍝          :EndIf
          Tag←t
      :EndIf
      Init
    ∇

    ∇ Make3(t content attr);ref
    ⍝ t - tag/element name (e.g. 'div' or 'table')
    ⍝ content - content
    ⍝ attr - attrs
      :Implements constructor
      :Access public
      :If 1=≡t content attr    ⍝ handle 3-character tag (e.g. 'pre')
          Tag←t,content,attr
      :ElseIf isClass content
          Make2Code t(content attr)
      :Else
          Tag←t
         ⍝ attr could be a ref. This means that it goes in the contents.
          Add content
          :If (0∊⍴attr)⍱ref←isRef attr
              SetAttr attr
          :ElseIf ref
              Add attr
          :EndIf
      :EndIf
      Init
    ∇

    ∇ Init
      (_html _HTML _JQ _SF _JQM _DC _JSS _)←#.(_html _HTML _JQ _SF _JQM _DC _JSS _)
      Position←⎕NS''
    ∇

    ∇ Use;c
      :Access public overridable
      :If ~0∊⍴Uses
          :Trap 0
              :If ''≡c←_PageRef
                  c←context'_PageRef'
                  :If ~0∊⍴c
                      c←c._PageRef
                  :EndIf
              :EndIf
              :If ~0∊⍴c
                  c.Use Uses
              :EndIf
          :EndTrap
      :EndIf
    ∇

    :endsection

    :section Event Handling

    ∇ {r}←On handler
      :Access public
      handler←eis handler
      Handlers,←r←⎕NEW #._JQ.Handler
      r.(Events Callback ClientData JavaScript Delegates)←5↑handler,(⍴handler)↓'' 1 '' '' ''
    ∇

    ∇ r←RenderHandlers;h;myid;selector;handler;str;callback;event;type;evt;action;JS;a;n
      :Access public ⍝!!! remove this after testing
      r←''
      :If ~0∊⍴Handlers
          :If ∨/0∘∊∘⍴¨Handlers.Selectors
              myid←SetId
          :EndIf
          :For h :In Handlers
              :If 0∊⍴h.Selectors
                  h.Selectors←'#',myid
              :EndIf
              r,←h.Render
          :EndFor
      :EndIf
    ∇
    :endsection

    :section Rendering

⍝ Elements with no End tag (<tag></tag>).

⍝ area base basefont br col frame hr img input isindex link meta param

    fmtAttr←{' ',⍺,'=',Quote HtmlSafeText,⍕⍵}

    ∇ r←Render;av;t;vs;e;h;c;p
      :Access public
    ⍝ Render by first constructing the Tag, complete with attributes, if any
      r←RenderCore Content
      :If ~0∊⍴av←Tag
          h←RenderHandlers
          p←RenderPosition
          :For e :In CommonAttributes
              av,←{0::'' ⋄ UNDEF≡t←⍎⍵:'' ⋄ e fmtAttr t}e
          :EndFor
          :If 0<⍴vs←Attrs[]
              av,←∊fmtAttr/¨vs
          :EndIf
          av,←RenderStyles
          r←av Enclose r,h,p ⍝!!!BPB!!!
      :EndIf
    ∇

    ∇ r←Postrender r
      :Access overridable
    ∇

    ∇ r←RenderCore list;e;t
      :Access public
    ⍝ do the bulk of the rendering work
      r←''
      :For e :In eis list
          :If isInstance⊃e
              r,←e.Render
          :ElseIf isClass⊃e
              r,←(⎕NEW e).Render
          :Else
              :If 1<⍴⍴t←⍕e
                  t←∊(↓t),¨⊂'<br/>'
              :EndIf
              r,←t
          :EndIf
      :EndFor
    ∇

    ∇ r←HtmlSafeText txt;i;m;u;ucs
      :Access public shared
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←∊r
    ∇

    ∇ r←RenderPosition
      :Access public
      r←''
      :If ~0∊⍴Position.⎕NL-2
          Uses,←⊂'JQueryUI'
          r←#.JQ.JQueryfn'position'('#',SetId)Position
          Use
      :EndIf
    ∇

    :endsection

    :section Utilities

    Bracket←{'<',⍵,'>'}

    ∇ r←tag Enclose txt;nl
      :Access public shared
      tag←,tag
      r←(tag{NoEndTag∧0∊⍴⍵:Bracket ⍺,'/' ⋄ (Bracket ⍺),⍵,Bracket'/',⍺↑⍨¯1+⍺⍳' '}txt),NL
    ∇

    ∇ r←{a}eis w
      :Access public shared
      r←(,∘⊂)⍣((isString w)∧2>|≡w),w ⍝ enclose if simple character
    ∇

    ∇ da←args defaultArgs defaultvalues
      :Access public shared
      da←da,(⍴da←eis args)↓defaultvalues
    ∇

    ∇ r←Quote a;b
      :Access public shared
      b←1↓<⌿¯1 0⌽'\"'∘.=';',a   ⍝ keep \" as is
      (b/a)←⊂'"'
      r←1⌽'""',∊a
    ∇

    ∇ r←attr ParseArgs args
      :Access public
      :If ~0∊⍴r←args
          :If isClass⊃args
              r←⎕NEW∘{2<⍴,⍵:(⊃⍵)({eis ⍵}(1↓⍵)) ⋄ ⍵}args
              :If 0≠⎕NC⊂'_PageRef'
                  r._PageRef←_PageRef
              :EndIf
              :If ~0∊⍴attr
                  r.SetAttr attr
              :EndIf
          :Else ⍝If isInstance⊃args
              r←args
⍝      :ElseIf isString args
⍝        :If (#._html.span≡⊃⊃⎕CLASS ⎕THIS)⍝∨0∊⍴⎕THIS.Content
⍝          r←args
⍝        :Else
⍝          (r←⎕NEW #._html.span).Content←args
⍝          r._PageRef←_PageRef
⍝        :EndIf
⍝      :Else
⍝        r←args
          :EndIf
      :EndIf
    ∇

    ∇ {r}←Push args;c;cl;attr;elm
      :Access public
      :If ~0∊⍴r←args
          (cl attr)←{2↑⍵,(⍴⍵)↓'' ''}eis args
          c←Content
          :If isClass cl
              elm←⎕NEW cl
              elm.Content←c
              :If ~0∊⍴attr
                  elm.SetAttr attr
              :EndIf
              r←Content←elm
          :ElseIf 0∊⍴attr
              r←Content,⍨←⊂cl
          :Else
              'Invalid Push arguments'⎕SIGNAL 11
          :EndIf
      :EndIf
    ∇

    ∇ {r}←{attr}Add args;cl
    ⍝ add "something" to the Content
    ⍝ args can be an instance, a class, or just html/text
      :Access public
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :Trap 4 5 ⍝ rank and length error
              Content,←r
          :Else
              Content,←⊂r
          :EndTrap
      :EndIf
    ∇

    ∇ {r}←{attr}New args;cl
    ⍝ create a new instance
    ⍝ args can be an instance, a class, or just html/text
      :Access public shared
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
      :EndIf
    ∇

    ∇ {r}←{attr}Insert args;cl
    ⍝ add "something" to the beginning of Content
    ⍝ args can be an instance, a class, or just html/text
      :Access public
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :Trap 4 5 ⍝ rank and length error
              Content,⍨←r
          :Else
              Content,⍨←⊂r
          :EndTrap
      :EndIf
    ∇

    ∇ {r}←Last
      :Access public
      r←⊃⌽Content
    ∇

    ∇ r←isClass ao
      :Access public shared
      r←9.4∊⎕NC⊂'ao'
    ∇

    ∇ r←isInstance ao
      :Access public shared
      r←9.2∊⎕NC⊂'ao'
    ∇

    ∇ r←isRef obj
      :Access public shared
      r←9∊⎕NC'obj'
    ∇

    isattr←{isString ⍵:1 ⋄ isRef ⍵:0 ⋄ ∧/∇¨⍵}

    ∇ r←isAttr obj
      :Access public shared
      r←isattr obj
    ∇

    ∇ r←renderIt It
      :Access public shared
      r←{326=⎕DR⊃⍵:{isInstance⊃⍵:(⊃⍵).Render
              isClass⊃⍵:(⎕NEW(⊃⍵)((⊃⍣(2=⊃⍴⍵))1↓⍵)).Render
              ,⍕⍵}⍵ ⋄ ,⍕⍵}It
    ∇

    ∇ r←isHtmlElement ao
      :Access public shared
      :Trap r←0
          r←⊃∨/#.HtmlElement=⎕CLASS ao
      :EndTrap
    ∇

    ∇ r←Parse string;b;s
    ⍝ Separate each section of name="..."
      :Access public
      string←(b⍲1⌽b←' '=s)/s←' ',string
      b←(' '=string)>≠\'"'=string
      r←1↓¨b⊂string ⍝ each pair
      s←r⍳¨'='      ⍝ each is separated by =
      r←s{(¯1↓⍺↑⍵)(1↓¯1↓⍺↓⍵)}¨r
    ∇

    ∇ Describe
      :Access public overridable
      ∘∘∘
    ∇

    ∇ r←GenId
      :Access public shared
      r←'id',⍕rand ¯1+2*31
    ∇

    ∇ {myid}←SetId
      :Access public
      :If UNDEF≡myid←id
          :If ''≡myid←⊃Attrs[⊂'id']
              id←myid←GenId
          :Else
              id←myid
          :EndIf
      :EndIf
    ∇

    :endsection

:endclass  ⍝ HtmlElement

:class HtmlElement             ⍝ this is the most basic element of a page

    ⎕io←⎕ml←1

    :field public shared _version←1
    :field public NL←⎕ucs 13 ⍝ 10

    :field public Tag←''      ⍝ this is the element name
    :field public Content←⍬   ⍝ content of the element - a series of strings/instances/class+parms
    :field public Handlers←'' ⍝ array of event handlers
    :field public _PageRef←'' ⍝ reference back to the page instance containing this element
    :field public NoEndTag←0  ⍝ set to 1 if this singleton element (does not have a closing tag)
    :field public Position    ⍝ has position information for this element (if position is set)
    :field public Uses←''     ⍝ resources that will be used by this object (can be overridden by derived classes)

⍝ define shortcuts to namespaces (initialized later)
    :field public _html        ⍝ reference to base HTML elements namespace
    :field public _HTML        ⍝ reference to "Enhanced" HTML elements namespace
    :field public _JQ          ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public _SF          ⍝ reference to SyncFusion widgets namespace
    :field public _JQM         ⍝ reference to JQueryMobile widgets namespace
    :field public _JSS         ⍝ reference to JavaScript Snippets namespace
    :field public _DC          ⍝ reference to Dyalog Controls namespace
    :field public _            ⍝ reference to namespace that refers to all elements/widgets

   ⍝ make shortcuts for some common HTML attributes
    :field public shared readonly UNDEF←⎕NULL  ⍝ setting for undefined attributes
    :field public id←UNDEF                     ⍝ id attribute for the element
    :field public value←UNDEF                  ⍝ value attribute for the element
    :field public name←UNDEF                   ⍝ name attribute for the element
    :field public class←UNDEF                  ⍝ class attribute for the element
    :field public style←UNDEF                  ⍝ style attribute for the element
    :field public title←UNDEF                  ⍝ title attribute for the element
    :field public type←UNDEF                   ⍝ type attribute for the element

    :field public readonly CommonAttributes←'id' 'value' 'name' 'class' 'style' 'title' 'type' ⍝ element attributes that are directly accessible

    _names←_values←0⍴⊂'' ⍝ used for attributes
    :field public _styles←''

    rand←{t←16807⌶2 ⋄r←?⍵ ⋄ t←16807⌶t ⋄ r }

    ∇ r←Version
      :Access public
      r←{⍵,[1.1]{0::¯1 ⋄ ⍵._version}¨⍵}⎕CLASS ⎕THIS
    ∇

    ∇ r←context name
      :Access public shared
      :If 326=⎕DR name ⍝ ref?
          r←⎕RSI{0∊⍴⍺:'' ⋄ 16::''
              ∨/∨/¨(⊂⍵)∊¨(⊃⍺).(⎕CLASS ⎕THIS):⊃⍺
              (1↓⍺)∇ ⍵}name
      :Else
          r←⎕RSI{0∊⍴⍺:''
              ⋄ (⊃⍺){0=⍺.⎕NC⊂⍵:0
                  ⋄ ''≡t←⍺⍎⍵:0
                  ⋄ 1}⍵:⊃⍺
              ⋄ (1↓⍺)∇ ⍵}name
      :EndIf
    ∇

    ∇ r←isString w
      :Access public shared
      :Select ≡w
      :Case 2
          :If 1=⍴,w
              r←{(0 2∊⍨10|⎕DR ⍵)∧1∊⍴⍴1/⍵}⊃w
          :Else
              r←0
          :EndIf
      :CaseList 0 1
          r←{(0 2∊⍨10|⎕DR ⍵)∧1∊⍴⍴1/⍵}w
      :Else
          r←0
      :EndSelect
    ∇

    ∇ r←quote w
      :Access public
      r←{'"'∊⍵:⍵ ⋄ '"',⍵,'"'}w
    ∇

    ∇ r←a ine w
      :Access public
      r←a{0∊⍴⍺:'' ⋄ ⍵}w ⍝ if not empty
    ∇
    errorIf←{⍺←⊢ ⋄ 0≠⍵:⍺ ⎕SIGNAL ⍵ ⋄ ''}

    :section Attribute Handling
    ∇ r←fixkeys w
      :Access public shared
      r←{2>|≡⍵:,⊂⍵ ⋄ ⍵}w ⍝ Enclose if simple
    ∇

    :property keyed Attrs       ⍝ element attributes
    :access public
        ∇ set ra;i;new;there;ind
          there←~new←(⍴_names)<i←_names⍳ind←fixkeys⊃ra.Indexers
          (_values _names),←new∘/¨ra.NewValue ind
          _values[there/i]←there/ra.NewValue
        ∇
        ∇ r←get ra;i;n
          ⎕SIGNAL(1<⍴i←ra.Indexers)⍴4 ⍝ RANK err
          :If 1↑ra.IndexersSpecified
              r←(_values,⊂'')[_names⍳n←fixkeys⊃⍣(2≤|≡i)+i]
          :Else
              r←↓_names,[1.1]_values
          :EndIf
        ∇
    :endproperty

    ∇ del←ParseAttr arg;n;i;split;depths;pairs;chunk;special;gotId;first;id;class
      :Access public shared
⍝ Parse html sttributes
      arg←eis arg
      n←⍴arg
      i←1
      del←⍬
      split←{0=⊢/v←∨\'='=⍵:⍵ ⋄ ((~v)/⍵)({'"'∧.=∊1 ¯1↑¨⊂⍵:¯1↓1↓⍵ ⋄ ⍵}1↓v/⍵)}
      depths←|0,⍨≡¨arg
      pairs←'='∊¨arg
      first←⊃¨arg
      gotId←∨/id←first∊'#' ⍝ any tokens that look like an id?
      class←first∊'.'
      special←0,⍨id∨class∨pairs
      :While i≤n
          :If depths[i]∊0 1  ⍝ simple vector
              :If 1≠⍴chunk←{⎕ML←3 ⋄ ((⍵≠' ')≥{~≠\⍵}'"'=⍵)⊂⍵},i⊃arg
                  del,←ParseAttr chunk
              :Else
                  :If 1=≡chunk←split⊃chunk
                      :Select 1⊃chunk
                      :Case '#'
                          del,←⊂'id'(1↓chunk) ⋄ gotId←1
                      :Case '.'
                          del,←⊂'class'(1↓chunk)
                      :Else
                          :If gotId<(depths[1+i]≥2)∨(n=1)∨special[1+i]
                              del,←⊂'id'chunk ⋄ gotId←1
                          :ElseIf (i=n)∨special[1+i]
                              del,←⊂2⍴⊂chunk
                          :Else
                              del,←⊂,¨arg[0 1+i] ⋄ i+←1
                          :EndIf
                      :EndSelect
                  :Else
                      del,←⊂2⍴chunk
                  :EndIf
              :EndIf
          :Else
              del,←ParseAttr i⊃arg
          :EndIf
          i+←1
      :EndWhile
    ∇

    ∇ {r}←{which}Set attr
      :Access public
      :If 0≠⎕NC'which' ⋄ attr←,(eis which),[1.1]eis attr
      :Else ⋄ 'Set cannot be called with a scalar ref'⎕SIGNAL 11/⍨(0=≡attr)∧326∊⎕DR attr
      :EndIf
      :If ~0∊⍴attr
          attr←ParseAttr attr
          Attrs[1⊃¨attr]←2⊃¨attr
      :EndIf
      r←⎕THIS
    ∇

    ∇ {r}←{which}SetAttr attr
      :Access public
      :If 0=⎕NC'which' ⋄ r←Set attr
      :Else ⋄ r←which Set attr
      :EndIf
    ∇

    ∇ {r}←DelAttr attrname;mask
      :Access public
      attrname←eis attrname
      mask←~_names∊attrname
      (_names _values)←mask∘/¨_names _values
      r←⎕THIS
    ∇

    :endsection

    :section Styles

    ∇ {r}←{what}Style styles;msg
      :Access public
      msg←'Invalid style specification'
      :If 0≠⎕NC'what'
          _styles,←↓(eis what),⍪eis((⍕¨)⍣(2|⎕DR styles))styles
      :Else
          :Select ≡styles
          :Case 2
              msg errorIf 11×0≠2|⍴styles
              _styles,←↓(2,⍨0.5×⍴styles)⍴styles
          :Case 3
              msg errorIf 11×∧/(,2)∘≡∘⍴¨styles
              _styles,←styles
          :Else
              msg ⎕SIGNAL 11
          :EndSelect
      :EndIf
      r←⎕THIS
    ∇

    ∇ r←RenderStyles;style
      r←''
      :If ~0∊⍴_styles
          :For style :In _styles
              r,←∊(⍕¨style),¨':;'
          :EndFor
          r←' style="',r,'" '
      :EndIf
    ∇

    :EndSection

  ⍝                     The constructors

  ⍝ The first arg is the Tag, followed by its contents, then its attributes, if present
  ⍝ Note that attributes can be specified with the tag as in
  ⍝  button type="submit"

    :Section Constructors

    ∇ Make0
    ⍝ basic constructor
      :Implements constructor
      :Access public
      Init
    ∇

    ∇ Make1 t    ⍝ this can be any length
    ⍝ t - tag/element name (e.g. 'div' or 'table')
      :Implements constructor
      :Access public
      Tag←t
      Init
    ∇

    ∇ Make2(t arg)
    ⍝ t - tag/element name (e.g. 'div' or 'table')
    ⍝ arg - content or (content attrs)
      :Implements constructor
      :Access public
      Make2Code(t arg)
    ∇

    ∇ Make2Code(t arg);attr;content;ref
      :If 1=≡t arg                      ⍝ handle 2-character tag (e.g. 'ul' 'tr')
          Tag←t,arg
      :Else
        ⍝ If we have a couple of items, the second of which is
        ⍝ a string or VTV, then we are dealing with attributes:
          :If (,2)≡⍴arg
          :AndIf isAttr 2⊃arg
              (content attr)←arg ⋄ SetAttr attr
          :Else
              content←arg
          :EndIf
          Content,←arg
⍝
⍝          :If isString arg
⍝              Content,←arg
⍝          :ElseIf ~isClass⊃arg
⍝              :Trap 4 5
⍝                  (content attr)←arg
⍝                  :If {(isClass ⍵)∨isInstance ⍵}⊃arg
⍝                      content←arg
⍝                      attr←''
⍝                  :EndIf
⍝                  Content,←content
⍝                  :If (0∊⍴attr)⍱ref←isRef attr
⍝                      SetAttr attr
⍝                  :ElseIf ref
⍝                      Content,←attr
⍝                  :EndIf
⍝              :Else
⍝                  Add arg
⍝              :EndTrap
⍝          :Else
⍝              Add arg
⍝          :EndIf
          Tag←t
      :EndIf
      Init
    ∇

    ∇ Make3(t content attr);ref
    ⍝ t - tag/element name (e.g. 'div' or 'table')
    ⍝ content - content
    ⍝ attr - attrs
      :Implements constructor
      :Access public
      :If 1=≡t content attr    ⍝ handle 3-character tag (e.g. 'pre')
          Tag←t,content,attr
      :ElseIf isClass content
          Make2Code t(content attr)
      :Else
          Tag←t
         ⍝ attr could be a ref. This means that it goes in the contents.
          Add content
          :If (0∊⍴attr)⍱ref←isRef attr
              SetAttr attr
          :ElseIf ref
              Add attr
          :EndIf
      :EndIf
      Init
    ∇

    ∇ Init
      (_html _HTML _JQ _SF _JQM _DC _JSS _)←#.(_html _HTML _JQ _SF _JQM _DC _JSS _)
      Position←⎕NS''
    ∇

    ∇ Use;c
      :Access public overridable
      :If ~0∊⍴Uses
          :Trap 0
              :If ''≡c←_PageRef
                  c←context'_PageRef'
                  :If ~0∊⍴c
                      c←c._PageRef
                  :EndIf
              :EndIf
              :If ~0∊⍴c
                  c.Use Uses
              :EndIf
          :EndTrap
      :EndIf
    ∇

    :endsection

    :section Event Handling

    ∇ {r}←On handler
      :Access public
      handler←eis handler
      Handlers,←r←⎕NEW #._JQ.Handler
      r.(Events Callback ClientData JavaScript Delegates)←5↑handler,(⍴handler)↓'' 1 '' '' ''
    ∇

    ∇ r←RenderHandlers;h;myid;selector;handler;str;callback;event;type;evt;action;JS;a;n
      :Access public ⍝!!! remove this after testing
      r←''
      :If ~0∊⍴Handlers
          :If ∨/0∘∊∘⍴¨Handlers.Selectors
              myid←SetId
          :EndIf
          :For h :In Handlers
              :If 0∊⍴h.Selectors
                  h.Selectors←'#',myid
              :EndIf
              r,←h.Render
          :EndFor
      :EndIf
    ∇
    :endsection

    :section Rendering

⍝ Elements with no End tag (<tag></tag>).

⍝ area base basefont br col frame hr img input isindex link meta param

    fmtAttr←{' ',⍺,'=',Quote HtmlSafeText,⍕⍵}

    ∇ r←Render;av;t;vs;e;h;c;p
      :Access public
    ⍝ Render by first constructing the Tag, complete with attributes, if any
      r←RenderCore Content
      :If ~0∊⍴av←Tag
          h←RenderHandlers
          p←RenderPosition
          :For e :In CommonAttributes
              av,←{0::'' ⋄ UNDEF≡t←⍎⍵:'' ⋄ e fmtAttr t}e
          :EndFor
          :If 0<⍴vs←Attrs[]
              av,←∊fmtAttr/¨vs
          :EndIf
          av,←RenderStyles
          r←av Enclose r,h,p ⍝!!!BPB!!!
      :EndIf
    ∇

    ∇ r←Postrender r
      :Access overridable
    ∇

    ∇ r←RenderCore list;e;t
      :Access public
    ⍝ do the bulk of the rendering work
      r←''
      :For e :In eis list
          :If isInstance⊃e
              r,←e.Render
          :ElseIf isClass⊃e
              r,←(⎕NEW e).Render
          :Else
              :If 1<⍴⍴t←⍕e
                  t←∊(↓t),¨⊂'<br/>'
              :EndIf
              r,←t
          :EndIf
      :EndFor
    ∇

    ∇ r←HtmlSafeText txt;i;m;u;ucs
      :Access public shared
    ⍝ make text HTML "safe"
      r←,⎕FMT txt
      i←'&<>"#'⍳r
      i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &#
      m←i∊⍳4
      u←127<ucs←⎕UCS r
      (m/r)←('&' '<' '>' '"')[m/i]
      (u/r)←(~∘' ')¨↓'G<&#ZZZ9;>'⎕FMT u/ucs
      r←∊r
    ∇

    ∇ r←RenderPosition
      :Access public
      r←''
      :If ~0∊⍴Position.⎕NL-2
          Uses,←⊂'JQueryUI'
          r←#.JQ.JQueryfn'position'('#',SetId)Position
          Use
      :EndIf
    ∇

    :endsection

    :section Utilities

    Bracket←{'<',⍵,'>'}

    ∇ r←tag Enclose txt;nl
      :Access public shared
      tag←,tag
      r←(tag{NoEndTag∧0∊⍴⍵:Bracket ⍺,'/' ⋄ (Bracket ⍺),⍵,Bracket'/',⍺↑⍨¯1+⍺⍳' '}txt),NL
    ∇

    ∇ r←{a}eis w
      :Access public shared
      r←(,∘⊂)⍣((isString w)∧2>|≡w),w ⍝ enclose if simple character
    ∇

    ∇ da←args defaultArgs defaultvalues
      :Access public shared
      da←da,(⍴da←eis args)↓defaultvalues
    ∇

    ∇ r←Quote a;b
      :Access public shared
      b←1↓<⌿¯1 0⌽'\"'∘.=';',a   ⍝ keep \" as is
      (b/a)←⊂'"'
      r←1⌽'""',∊a
    ∇

    ∇ r←attr ParseArgs args
      :Access public
      :If ~0∊⍴r←args
          :If isClass⊃args
              r←⎕NEW∘{2<⍴,⍵:(⊃⍵)({eis ⍵}(1↓⍵)) ⋄ ⍵}args
              :If 0≠⎕NC⊂'_PageRef'
                  r._PageRef←_PageRef
              :EndIf
              :If ~0∊⍴attr
                  r.SetAttr attr
              :EndIf
          :Else ⍝If isInstance⊃args
              r←args
⍝      :ElseIf isString args
⍝        :If (#._html.span≡⊃⊃⎕CLASS ⎕THIS)⍝∨0∊⍴⎕THIS.Content
⍝          r←args
⍝        :Else
⍝          (r←⎕NEW #._html.span).Content←args
⍝          r._PageRef←_PageRef
⍝        :EndIf
⍝      :Else
⍝        r←args
          :EndIf
      :EndIf
    ∇

    ∇ {r}←Push args;c;cl;attr;elm
      :Access public
      :If ~0∊⍴r←args
          (cl attr)←{2↑⍵,(⍴⍵)↓'' ''}eis args
          c←Content
          :If isClass cl
              elm←⎕NEW cl
              elm.Content←c
              :If ~0∊⍴attr
                  elm.SetAttr attr
              :EndIf
              r←Content←elm
          :ElseIf 0∊⍴attr
              r←Content,⍨←⊂cl
          :Else
              'Invalid Push arguments'⎕SIGNAL 11
          :EndIf
      :EndIf
    ∇

    ∇ {r}←{attr}Add args;cl
    ⍝ add "something" to the Content
    ⍝ args can be an instance, a class, or just html/text
      :Access public
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :Trap 4 5 ⍝ rank and length error
              Content,←r
          :Else
              Content,←⊂r
          :EndTrap
      :EndIf
    ∇

    ∇ {r}←{attr}New args;cl
    ⍝ create a new instance
    ⍝ args can be an instance, a class, or just html/text
      :Access public shared
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
      :EndIf
    ∇

    ∇ {r}←{attr}Insert args;cl
    ⍝ add "something" to the beginning of Content
    ⍝ args can be an instance, a class, or just html/text
      :Access public
      r←''
      :If ~0∊⍴∊args
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :Trap 4 5 ⍝ rank and length error
              Content,⍨←r
          :Else
              Content,⍨←⊂r
          :EndTrap
      :EndIf
    ∇

    ∇ {r}←Last
      :Access public
      r←⊃⌽Content
    ∇

    ∇ r←isClass ao
      :Access public shared
      r←9.4∊⎕NC⊂'ao'
    ∇

    ∇ r←isInstance ao
      :Access public shared
      r←9.2∊⎕NC⊂'ao'
    ∇

    ∇ r←isRef obj
      :Access public shared
      r←9∊⎕NC'obj'
    ∇

    isattr←{isString ⍵:1 ⋄ isRef ⍵:0 ⋄ ∧/∇¨⍵}

    ∇ r←isAttr obj
      :Access public shared
      r←isattr obj
    ∇

    ∇ r←renderIt It
      :Access public shared
      r←{326=⎕DR⊃⍵:{isInstance⊃⍵:(⊃⍵).Render
              isClass⊃⍵:(⎕NEW(⊃⍵)((⊃⍣(2=⊃⍴⍵))1↓⍵)).Render
              ,⍕⍵}⍵ ⋄ ,⍕⍵}It
    ∇

    ∇ r←isHtmlElement ao
      :Access public shared
      :Trap r←0
          r←⊃∨/#.HtmlElement=⎕CLASS ao
      :EndTrap
    ∇

    ∇ r←Parse string;b;s
    ⍝ Separate each section of name="..."
      :Access public
      string←(b⍲1⌽b←' '=s)/s←' ',string
      b←(' '=string)>≠\'"'=string
      r←1↓¨b⊂string ⍝ each pair
      s←r⍳¨'='      ⍝ each is separated by =
      r←s{(¯1↓⍺↑⍵)(1↓¯1↓⍺↓⍵)}¨r
    ∇

    ∇ Describe
      :Access public overridable
      ∘∘∘
    ∇

    ∇ r←GenId
      :Access public shared
      r←'id',⍕rand ¯1+2*31
    ∇

    ∇ {myid}←SetId
      :Access public
      :If UNDEF≡myid←id
          :If ''≡myid←⊃Attrs[⊂'id']
              id←myid←GenId
          :Else
              id←myid
          :EndIf
      :EndIf
    ∇

    :endsection

:endclass  ⍝ HtmlElement


Fix typo in fix of missed line from merge

:Namespace MS3SiteUtils ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝
⍝ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ ⍝
⍝ │ This is the collection of utility functions used by MS3Server and the index page of the     │ ⍝
⍝ │ MiServer Sample Site, MS3, accessible at miserver.dyalog.com                                │ ⍝
⍝ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ ⍝
⍝ │ The code is split up in six sections:                                                       │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ A_GENERAL has utilities that appropriate to APL or MiServer in general                │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ B_FILES has various tools to list, read, and cache files and their content            │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ C_STRINGS string and multi-string manipulation and extraction of information          │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ D_ELEMENTS aids to create elements for the HTML Document Object Model                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ E_TUTORIAL has subroutines for the Tutorial tab on the index page                     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION A_GENERAL ⍝ UTILITY FUNCTIONS THAT ARE NOT SPECIFIC TO THE "MS3" SITE

    ç←{⊂⍣(1=≡,⍵)⊢⍵} ⍝ enclose if simple     Mnemonic for the V16 glyph

    FwSl←'[\\/]+'⎕R'/' ⍝ Make all slash-block into single forward-slashes

    Q←'"'∘,,∘'"' ⍝ Surround with quotes

    FromCSV←↑{'"'~¨⍨1↓¨t⊂⍨(','=t)∧~≠\'"'=t←',',⍵}¨ ⍝ Convert CSV VTV to matrix (no escaped chars)

      Section←{ ⍝ Extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)' ⍝ find '  ⍝  LeftArg:: some text'
          opts←('Mode' 'M')('DotAll' 1)('ML' 1)                  ⍝ '^'≡linestart  EOL∊'.'  1st-only
          res←regex ⎕S'\1\2'⍠opts⊢⍵                              ⍝ return parts 1 and 2
          #.Strings.deb'⍝'~⍨1↓⊃res                               ⍝ strip spaces and lamp
      }

      Controls←{ ⍝ Extract names of controls used in sample ⍵
          (Words'Control'Section Read ⍵)~'_',¨NSS
      }

    Of←{⍵/⍨⍺⍺¨⍵} ⍝ Those elements of ⍵ that satisfy ⍺⍺ element

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Score←{(102-2×(50↑⊃⌽⍵)⍳⊂⍺)-∨/'Advanced'⍷⊃⍵} ⍝ Relevance score on index of control in list

      List←{ ⍝ Files in site folder
          ⍺←1↓FILEEXT                                      ⍝ default .ext
          mods←' -rec -raw -full=2 -ext=',⍺                ⍝ subdirs no-format rooted spec-ext
          list←(⎕SE.SALT.List #.Boot.AppRoot,⍵,mods)[;1 2] ⍝ begin at application root (MS3/)
          f←~d←list[;1]≡¨⊂'<DIR>'                          ⍝ find dirs
          list←FwSl list[;2]                               ⍝ normalize slashes for WWW
          (d/list),←'/'                                    ⍝ mark dirs with final slashes
          (f/list),←⊂'.',⍺                                 ⍝ add .ext to files
          list↓¨⍨¯1+1⍳⍨(FwSl ⍵)⍷⊃list                      ⍝ trim
      }

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←1 #.Files.ReadText #.Boot.AppRoot,page,(~'.'∊page)/FILEEXT
              C.read.keys,←⊂page
          :EndIf
      :Else
          r←,⊂'[failed to read "',page,'"]'
      :EndTrap
    ∇

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION C_STRINGS ⍝ MANIPULATION OF CHARACTER VECTORS AND VECTORS OF CHARACTER VECTORS

    Slash←'/'∘=⊂⊢

    Name←{1↓⊃⌽(Slash ⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)

    Words←{(1↓¨(∊∘' .'⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces and dots and remove empty pieces

    NoExt←{'.'∊⍵:⍵↓⍨-'.'⍳⍨⌽⍵⋄⍵} ⍝ 'aaa.bbb.ccc'  →  'aaa.bbb'

    NoNL←'\n' '\r'⎕R' ' ''⍠'Mode' 'D' ⍝ Replace newlines with spaces

    LastSeg←{⍺←⊢ ⋄ 1↓⍺⊃⌽Slash '/',⍵} ⍝ ⍺th (default 1st) /-separated segment from the end

    Levels←{(+/¨'/'=⍵)+~'/'=(⊃⌽)¨⍵} ⍝ Number of /s adjusted for categories' final /

    Shuffle←(?⍨∘≢⊃¨⊂)⊢⊂⍨⊢≡¨1↑⊢ ⍝ Shuffle the root elements of an XML snippet

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION D_ELEMENTS ⍝ FUNCTIONS THAT AUTOMATE THE CREATION OF DOM ELEMENTS AND HTML CODE

      Over←{
          ⊂'',⍺,'
',('/'⎕R'
'⊢⍵),''
      }

      External←{ ⍝ Icon off-site link
          ⍺←''
      ⍝    '.external' 'target=_blank' 'data-dyalog-tip="External link"'New _.A((⍺,'🔗')⍵)
          '',⍺,'🔗'
      }

      DescrEmbed←{ ⍝ Link to and iframed page
          l←('Description'Section Read ⍵),'
'
          l,←'Open this '
          l,←'simple sample' 'advanced sample' 'mini-app'⊃⍨1⍳⍨∨/¨'Simple' 'Advanced'⍷¨⊂⍵
          l,←' in a new tab'
          e←''
          l e
      }

      Link←{ ⍝ New-tab link with optional (⍺) "tooltip"
          ⍺←0
          ⍝(('data-dyalog-tip=',Q ⍺){⍺ ⍵}⍣(⍺≢0)⊢'target="_blank"')New _.A ⍵
          '',(⊃⍵),''
      }

      DocLink←{ ⍝ Link to WidgetDoc with appropriate parameters
          link←DocUrl ⍵
          ctor←'ctor'ForControl ⍵
          0=⍴ctor::Link ⍵ link ⍝ upon fail to retrieve info
          ('Constructor: ',ctor)Link ⍵ link
      }

      DocUrl←{
          '/Documentation/DyalogAPIs/WidgetDoc?namespace=_',('ns'ForControl ⍵),'&widget=',⍵
      }

      DocTreeLink←{ ⍝ Link to file in doc folder
          '/'=⊃⌽⍵:''
          ('/Documentation',⍵)'target="_blank"'
      }

      ReadingTree←{ ⍝ Create tree of documentation links
          l←'/',¨⍵~⊂''
          (Name∘NoExt¨l)(Levels l)(DocTreeLink¨l)
      }

    ∇ r←RelDocs w;list;item ⍝ Links to related samples
      list←'relevant'ForControl w
      r←''
      :For item :In list
          r,←'
',(Link('filedescr'ForFile item)item),'

'
      :EndFor
      r↓⍨←¯5
    ∇

      FormatList←{ ⍝ List of category-title pairs
          ⍺←1↓FILEEXT
          (⍺≡1↓FILEEXT)CatAndItem¨{'/'≠⊃⌽⍵}Of ⍺ List ⍵
      }

      CatAndItem←{ ⍝ (category) (filename/description)
          cat←2 LastSeg ⍵
          cat←'General' 'Mini App'cat['Documentation' 'Applications'⍳⊂cat]
          cat(Link((NoExt LastSeg)⍣(~⍺)('Description'Section Read)⍣⍺⊢⍵)⍵)
      }

      FilterTable←{
          dt←⍺ New _.DataTable ⍵
          dt.Width←'100%'
          dt.Options.autoWidth←_true
          dt.Options.mark←_true                ⍝ highlight search-terms
          dt.Options.columns←⎕NS¨4⍴⊂⍬          ⍝ options per column
          dt.Options.columns[4].visible←_false ⍝ hide column4
          dt.Options.dom←'l<"#fltcnt_',(1⊃⍺),'.fltcnt">ftpr'    ⍝ insert div with id "fltcnt_{id}" into table header
          dt.Plugins←'yadcf'
          dt.Options.yadcf←⎕NS ⍬
          dt.Options.yadcf.Filters←⎕NS ⍬
          dt.Options.yadcf.Filters.(column_number select_type filter_container_id)←3 'chosen'('fltcnt_',1⊃⍺)
          dt.Options.yadcf.Filters.(filter_type select_type_options filter_default_label)←'multi_select'(⊂'{width:"30em"}')'Filter by library'
          dt.Options.yadcf.Filters/⍨←1 ⍝ needs array of namespaces
          dt
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION E_TUTORIAL ⍝ FUNCTIONS USED BY THE index PAGE'S Tutorial TAB

    ∇ t←Elements;tabs;r;sp;fs;ig;colors;rgbs;button;p;cbs;t2;g
      t←'#div'New _.div'This div is the main container of this tab. ' ⍝ #div
      p←'#p't.Add _.p'This is the beginning of a paragraph, ' ⍝ #p
      '#span'p.Add _.span'but this phrase is a separate piece of text inside the paragraph. ' ⍝ #span
      button←'#Button't.Add _.Button'Button' ⍝ #Button
      sp←'#StackPanel't.Add _.StackPanel ⍝ #StackPanel
      sp.Add'StackPanel'
      '#EditField'sp.Add _.EditField'EditField' 'EditField' ⍝ #EditField
      '#Select'sp.Add _.Select'Red' 'Green' 'Blue' ⍝ #Select
      '#Input1't.Add _.Input'number' 426 ' Number Input: ' ⍝ #Input1
      t.Add _.br
      fs←'#Fieldset1't.Add _.Fieldset'CheckBoxes' ⍝ #Fieldset1
      cbs←{'.CheckBox'New _.CheckBox ⍵}¨'checked' 'unchecked' ⍝ .CheckBox
      ig←'#InputGrid'fs.Add _.InputGrid('Checked' 'Unchecked')cbs ⍝ #InputGrid
      ig.Flip←1 ⍝ #InputGrid
      fs←'#Fieldset2't.Add _.Fieldset'RadioButtonGroup' ⍝ #Fieldset2
      '#RadioButtonGroup'fs.Add _.RadioButtonGroup('Selected' 'Unselected')1 'right' ⍝ #RadioButtonGroup
      '#h5't.Add _.h5'Slider' ⍝ #h52
      '#Input2't.Add _.Input'range' 1 ⍝ #Input2
      '#List't.Add _.List('Primaries' 'Cyan' 'Magenta' 'Yellow' 'Key' 'Black')(1 2 2 2 1 2) ⍝ #List
      colors←'aqua' 'black' 'blue' 'fuchsia' 'gray' 'green' 'lime' 'maroon' ⍝ #Table
      rgbs←'#00FFFF' '#000000' '#0000FF' '#FF00FF' '#808080' '#008000' '#00FF00' '#800000' ⍝ #Table
      colors,←'navy' 'olive' 'purple' 'red' 'silver' 'teal' 'white' 'yellow' ⍝ #Table
      rgbs,←'#000080' '#808000' '#800080' '#FF0000' '#C0C0C0' '#008080' '#FFFFFF' '#FFFF00' ⍝ #Table
      '#Table't.Add _.Table('Color' 'RGB'⍪colors,⍪rgbs)'' 1 ⍝ #Table
      t.Add _.style ScriptFollows
      ⍝ #div {padding: 10px; margin: 10px;}
      ⍝ #Button {vertical-align: top; margin: 5px;}
      ⍝ #StackPanel {display: inline-block; padding: 5px;}
      ⍝ #Fieldset1 {display: inline-block;}
      ⍝ #Fieldset2 {display: inline-block;}
      ⍝ #h5 {margin: 1em 0 0.5em 0;}
      ⍝ #Input1 {width: 4em;}
      ⍝ #Input2 {width: 100%;}
      ⍝ #List {display: inline-block; margin: 1em; padding: 5px 5px 5px 25px;}
      ⍝ #Table {display: inline-block; max-height: 7em; overflow-y: scroll; margin-top: 1em;}
      ⍝
      ⍝ /**/#span:hover, #p:hover, #div:hover, #Button:hover, #StackPanel:hover, #EditField:hover, #Select:hover, #Fieldset1:hover, #Fieldset2:hover, .CheckBox:hover, #RadioButtonGroup:hover, #Input1:hover, #Input2:hover, #h5:hover, #progress:hover, #List:hover, #Table:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      'ShowElementInfo'∘AddHandler¨'#div' '#p' '#span' '#Button' '#EditField' '#StackPanel' '#Select' '#Fieldset1' '#Fieldset2' '.CheckBox' '#RadioButtonGroup' '#Input1' '#h5' '#Input2' '#progress' '#List' '#Table'
      t←'Common elements't
    ∇

    ∇ r←ShowElementInfo;code;ctrl;info
      :Access Public
      ctrl←_selector~'#.12'
      info←'
This element is called _.',ctrl,'
'
      info,←P'descr'ForControl ctrl
      info,←'
Constructor (what goes to the right of Add _.',ctrl,')
'
      info,←P Link('ctor'ForControl ctrl)(DocUrl ctrl)
      code←⊃_selector CodeFrom ⎕NR'Elements'
      info,←'
Put this in the Compose function
',P code
      r←'#info'Replace info
      r,←ShowPdf
    ∇

    ∇ t←Formatting
      t←'#margrad'New _.div'This div has inner padding and gradient backround' ⍝ #margrad
      '#boldin't.Add _.p'Bold indented text which has to be extremely long in order to show that only the first line is being indented.' ⍝ #boldin
      '#huge't.Add _.span'Huge text with outer margin' ⍝ #huge
      '#expund't.Add _.span'Expanded and underlined' ⍝ #expund
      '#bluebox't.Add _.p'Blue box with shadow' ⍝ #bluebox
      '#centshad't.Add _.p'Centered text with shadow' ⍝ #centshad
      '#whimg't.Add _.p'White text on image background' ⍝ #whimg
      '#border't.Add _.p'Surrounded by borders and top-right corner is round' ⍝ #border
      '#img' 'src="Examples/Data/4lines.png"' 'alt="Image"' 'title="Four lines high"'t.Add _.img ⍝ #img
      '#narrow't.Add _.p'Inline paragraph with limited width and single line height.' ⍝ #narrow
      '#mono't.Add _.span'Monospace, ' ⍝ #mono
      '#sans't.Add _.span'Sans-Serif, ' ⍝ #sans
      '#serif't.Add _.span'and Serif.' ⍝ #serif
      t.Add _.style ScriptFollows
      ⍝ #margrad {padding: 2em; background: linear-gradient(White, SkyBlue);}
      ⍝ #boldin {font-weight: bold; text-indent: 2em;}
      ⍝ #huge {font-size: x-large; margin: 2em;}
      ⍝ #expund {text-decoration: underline; letter-spacing: 0.5ex;}
      ⍝ #bluebox {background-color: SkyBlue; box-shadow: 0.25em 0.5em 1em Gray;}
      ⍝ #centshad {text-align: center; text-shadow: 0.1em 0.3em 0.2em Blue;}
      ⍝ #whimg {color: white; background-image: url(Examples/Data/pic3.png);}
      ⍝ #serif {font-family: "Times New Roman", Times, serif; font-size: 150%;}
      ⍝ #sans {font-family: Arial, Helvetica, sans-serif; font-size: 150%;}
      ⍝ #mono {font-family: "Courier New", Courier, monospace; font-size: 150%;}
      ⍝ #border {border: 0.25em solid Blue; border-top-right-radius: 1.5em;}
      ⍝ #img {height: 4em; opacity: 0.5;}
      ⍝ #narrow {width: 20%; display: inline-block; line-height: 1;}
      ⍝
      ⍝ /**/#boldin:hover, #huge:hover, #expund:hover, #centshad:hover, #serif:hover, #sans:hover, #mono:hover, #narrow:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      ⍝ /**/#margrad:hover {box-shadow: 0 0 5px 2px Orange inset;}
      ⍝ /**/#whimg:hover {box-shadow: 0 0 5px 2px Orange;}
      ⍝ /**/#border:hover {box-shadow: 0 0 5px 2px Orange, 0 0 5px 2px Orange inset;}
      ⍝ /**/#img:hover {box-shadow: 0 0 5px 3px Orange,0 0 5px 3px Orange;}
      ⍝ /**/#bluebox:hover {box-shadow:0 0 5px 1px Orange, 0 0 5px 1px Orange inset, 0.25em 0.5em 1em Gray;}
      'ShowInfo'∘AddHandler¨'#margrad' '#boldin' '#huge' '#expund' '#bluebox' '#centshad' '#whimg' '#serif' '#sans' '#mono' '#border' '#img' '#narrow'
      t←'Formatting't
    ∇

    ∇ t←Positioning
      Add _.style ScriptFollows
      ⍝ #parent {position: relative; height: 32em; margin: 1em;}
      ⍝ /**/#parent, #parent div {margin-top: 1em; margin-bottom: 1em; padding: 1ex;}
      ⍝ /**/#parent div {width: 50%; border: 1px solid black; }
      ⍝ #default {/* nothing to see here */}
      ⍝ #textcenter {text-align: center;}
      ⍝ #textright {text-align: right;}
      ⍝ #boxright {margin-left: auto; margin-right: 0;}
      ⍝ #boxcenter {margin-left: auto; margin-right: auto;}
      ⍝ #textbottom {position: relative; height: 4em;}
      ⍝ #textbottom span {position: absolute; bottom: 1ex;}
      ⍝ #boxbottom {position: absolute; bottom: 0;}
      ⍝
      ⍝ /**/#parent:hover, #parent div:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#parent'New _.div'Parent element with "relative" position so we can align vertically' ⍝ #parent
      '#default't.Add _.div'Default position and text alignment' ⍝ #default
      '#textcenter't.Add _.div'Centered text' ⍝ #textcenter
      '#textright't.Add _.div'Right-aligned text' ⍝ #textright
      '#boxcenter't.Add _.div'Centered element' ⍝ #boxcenter
      '#boxright't.Add _.div'Right-aligned element' ⍝ #boxright
      '#textbottom't.Add _.div,New _.span'Bottom-aligned text' ⍝ #textbottom
      '#boxbottom't.Add _.div'Bottom-aligned element' ⍝ #boxbottom
      'ShowInfo'∘AddHandler¨'#parent' '#default' '#textcenter' '#textright' '#boxcenter' '#boxright' '#textbottom' '#boxbottom'
      t←'Positioning't
    ∇

    ∇ t←Embedding;c
      Add _.style ScriptFollows
      ⍝ #embed>* {margin: 1ex;}
      ⍝ #quack {filter: hue-rotate(-205deg); padding: 2em;}
      ⍝ #quack:hover {filter:  hue-rotate(0deg);}
      ⍝ #bear {padding: 0.2em; border: 1px solid silver; margin: 1ex;}
      ⍝ #duck {height: 12em; transform: rotate(-15deg);}
      ⍝ #poly {height: 240px; width: 200px}
      ⍝ #poly polygon {fill: orange; stroke: purple; stroke-width: 1ex;}
      ⍝ #pdf {height: 240px; width:400px}
      ⍝
      ⍝ /*#bear:hover, #quack:hover, #duck:hover, #poly:hover, #embed>div>div:hover*/ #embed *:hover{
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#embed'New _.div
      t.Add _.br
      '#bear' 'src=Examples/Data/bear.mp4' 'autoplay=' 'loop=' 'muted='t.Add _.video'No video support!' ⍝ #bear
      '#quack' 'controls='t.Add _.Audio'Examples/Data/quack.mp3' ⍝ #quack
      '#duck' 'src=Examples/Data/duck.png' 'alt="Silly Duck"'t.Add _.img'No img support!' ⍝ #duck
      '#poly't.Add _.svg,'points=20,20 50,120 120,190 180,80'New _.polygon ⍝ #poly
      c←'style=display:inline-block;position:relative;'t.Add _.div
      '#pdf' 'style=position:absolute;height:100%;width:100%;top:0;' 'onclick="$(this).hide();"'c.Add _.div
      '#pdf' 'data=Examples/Data/licence.pdf'c.Add _.object ⍝ #pdf
      'ShowInfo'∘AddHandler¨'#bear' '#duck' '#quack' '#poly' '#pdf'
      t←'Embedding't
    ∇

    ∇ r←ShowPdf
      r←(_selector≢'#pdf')/Execute'$("#pdf").show();'
    ∇

    ∇ t←Interaction;cb;m
      Add _.style ScriptFollows
      ⍝ #ui>* {margin: 1ex;}
      ⍝ #adjust {width: 8em;}
      ⍝ #adjust:hover {text-align: justify;}
      ⍝ #adjust:active {text-align: right;}
      ⍝ #callback {width: 8em;}
      ⍝ #move {height: 6em; width: 12em; border: 1px solid black;}
      ⍝ #move div {height: 3em; width: 6em; cursor: move; background: silver;}
      ⍝ /**/ #ui>*:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      t←'#ui'New _.div
      '#adjust't.Add _.Button'Styling for reaction to hover and click' ⍝ #adjust
      cb←'#callback't.Add _.Button('Update with callback click ',#.Dates.(TSFmt ⎕TS)) ⍝ #callback
      cb.On'click' 'CallbackFn' ⍝ #callback
      '#move't.Add _.div,New _.div'Movable (drag me!)' ⍝ #move
      m←Add _.jqDraggable'#move div' ⍝ #move
      m.Set'containment' 'parent' ⍝ #move

      'ShowInfo'∘AddHandler¨'#adjust' '#callback' '#move'
      t←'Interaction't
    ∇

    ∇ r←CallbackFn ⍝ #callback
      :Access Public ⍝ #callback
      r←'#callback'Replace'Update with callback click ',#.Dates.(TSFmt ⎕TS) ⍝ #callback
    ∇ ⍝ #callback

    ∇ t←Windows;c;p;d
      Add _.style ScriptFollows
      ⍝ #win {margin: 1em; padding: 1ex;}
      ⍝ #win button {margin: 1em;}
      ⍝ #win #color {display: inline-block;}
      ⍝ #win #color:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      ⍝ #css {width: 6em; padding: 1ex; background: silver; border-radius: 0.5ex; box-shadow: 0.25em 0.5em 2em grey;}
      ⍝ #dialog {display: none;}
      ⍝ /**/ #css:hover {width: 6em; background: silver; box-shadow: 0 0 5px 1px orange,0 0 5px orange inset,0.25em 0.5em 2em grey;}
      ⍝ /**/ #win>button:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      t←'#win'New _.div
      t.Add _.p'Choose a color to get a matching notification:'
      d←'#color't.Add _.div
      d.On'mousedown' 'ShowInfo'
      c←d.Add _.ejColorPicker'#ffff00' ⍝ #color
      c.On'close' 'ColorFn'('chosenColor' 'model' 'value') ⍝ #color
      '#css't.Add _.div'CSS to give floating impression' ⍝ #css

      '#dialog't.Add _.div'Content of dialog box widget' ⍝ #dialog
      d←t.Add _.ejDialog'#dialog' 'Syncfusion Dialog' ⍝ #dialog
      'showOnInit'd.Set _false ⍝ #dialog
      'onclick="$(''#dialog'').ejDialog(''open'')"'t.Add _.Button'Open dialog' ⍝ #dialog
      Add _.Handler'#win button' 'click' 'DialogFn'

      'ShowInfo'AddHandler'#css'
      t←'Windows & Popups't
    ∇

    ∇ r←ColorFn;chosen;all;options ⍝ #color
      :Access public ⍝ #color
      chosen←0 #.Utils.hex⍕↓3 2⍴1↓Get'chosenColor' ⍝ #color
      all←(0 0 0)(255 0 0)(0 255 0)(0 0 255)(319 319 0)(255 255 255) ⍝ #color
      (options←⎕NS ⍬).color←'black' 'red' 'green' 'blue' 'yellow' 'black'⊃⍨(⊢⍳⌊/)+/¨2*⍨all-⊂chosen ⍝ #color
      r←Execute options _.jBox.Notice('You chose ',Get'chosenColor') ⍝ #color
    ∇ ⍝ #color

    ∇ r←ShowDialog
      :Access public
      _selector←'#dialog'
      r←ShowInfo
    ∇

    ∇ r←ShowInfo;info;css;code;fns
      :Access Public
      (code css)←_selector CodeFrom ⎕SRC⍎NoExt⊃⎕XSI ⍝ this ns
      code←SplitFns code
      info←'
Put this in the Compose function
',P Break⊃code
      fns←1↓code
      :If ×≢css
          info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
'
          info,←P Break css
      :EndIf
      :If ×≢fns
          info,←'
Add ','this funtion' 'these functions'⊃⍨2⌊≢fns
          info,←' to the MiPage
',P Break¨fns
      :EndIf
      r←'#info'Replace info
      r,←ShowPdf
    ∇

    ∇ {ctrl}←callbackfn AddHandler sel
      ctrl←sel~'#.12'
      Add _.Handler sel'mousedown'callbackfn'' 'event.stopPropagation()'
    ∇

    Break←{4↓∊'
'∘,¨⍵}

      CodeFrom←{
          (css lines)←↓⍉↑('⍝ ',⍺)∘Prep¨⍵/⍨('⍝ ',⍺)∘(∨/⍷)¨⍵
          (lines/⍨~css)(css/lines)
      }

      Prep←{
          c←#.Strings.deb ⍵
          ⍺≡c↑⍨-l←≢⍺:0('\w+\.Add'⎕R'Add'⊢c↓⍨¯1-l)
          1(2↓c)
      }

    P←{∊('
',,∘'
')¨ç⍵}

    SplitFns←{⍵⊂⍨1,2</≠\'∇'=⊃¨⍵}

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION F_CONSTANTS ⍝ NILADIC FUNCTIONS THAT ACT LIKE :include'ABLE CONSTANTS

    ∇ r←NSS;types;names
      :Access public
      :If 0=C.⎕NC'NSS'
          C.NSS←1↓¨1↓'_'#.⎕NL ¯9
          names types←0 1 ⎕NINFO⍠1⊢#.Boot.AppRoot,'Examples/*'
          C.NSS∩←LastSeg¨names/⍨1=types
      :EndIf
      r←C.NSS
    ∇

    ∇ r←GROUPS
      :Access public
      :If 0=C.⎕NC'GROUPS'
          C.GROUPS←{#.Strings.deb(⍳∘'⍝'↓⊢)⊃⎕SRC #⍎⍵}¨'_',¨NSS
      :EndIf
      r←C.GROUPS
    ∇

    ∇ r←CACHE
      :Access public
      r←'#.CACHE'
    ∇

    ∇ r←FILEEXT
      :Access public
      r←#.Boot.ms.Config.DefaultExtension
    ∇

    ∇ C←C;scores;list;refs;srcs;control;i;info;ref;src;ctor;Read;Ø;demo
     ⍝ Return ref to cache (initialize cache if nonexistant)
      :Access public
      Read←1 #.Files.ReadText #.Boot.AppRoot∘, ⍝ no-cache reading
      :Hold CACHE                          ⍝ prevent clashes
          :If 9≠⎕NC CACHE                  ⍝ if cache is empty:
              Ø←⊂''
              C←⍎CACHE ⎕NS ⍬       ⍝ create with shortcut
              C.read←⎕NS ⍬         ⍝ init cache for files
              C.read.(keys←data←⍬) ⍝ init keys and data

              C.controls←(#._.⎕NL ¯9)~⊂'Handler'                       ⍝ cache all controls
              C.controlsoi←C.controls∘⍳ç ⋄ C.eocontrols←∊∘C.controls ç ⍝ hash tables
              refs←#._⍎¨C.controls                                     ⍝ refs of all controls
              C.ns←3↓¨⍕¨refs.##                                        ⍝ ns of each control
              srcs←⎕SRC¨refs                                           ⍝ all sources

              C.files←⊃⍪/{List'Examples/',⍵}¨NSS,⊂'Applications' ⍝ sample filenames
              C.filesoi←C.files∘⍳ç                               ⍝ hash table
              C.filedescr←('Description'Section Read)¨C.files    ⍝ Description:: for all files
              C.demos←Controls¨C.files                           ⍝ controls demoed in each
              scores←C.controls∘.Score↓⍉↑C.files C.demos         ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores  ⍝ cache all rankings

              C.(descr ctor notes relevant reldocs doclinks)←Ø
              info←FromCSV Read'IndexData/info.csv'
              :For control ref src :InEach C.controls refs srcs
                  i←info[;1]⍳⊂control
                  :If i≤≢info
                      C.descr,←info[i;2]
                      C.notes,←info[i;3]
                  :Else
                      C.descr,←⊂NoNL'Description'Section src
                      C.notes,←⊂NoNL'Notes'Section src
                  :EndIf

                  ctor←{⍵↑⍨¯1+⌊/⍵⍳⎕UCS 13 10}'Constructor'Section src
                  :If ''≡ctor
                      C.ctor,←'[content [attributes]]' '[attributes]'[1+(⎕NEW ref).NoEndTag]
                  :Else
                      C.ctor,←⊂ctor
                  :EndIf

                  :If C.eocontrols⊂control
                      C.relevant,←⊂C.files[C.rankings⊃⍨C.controlsoi⊂control]
                  :Else
                      C.relevant,←Ø
                  :EndIf

              :EndFor
              C.cats←GROUPS[NSS⍳3↓¨⍕¨('#._'∘⍎¨C.controls).##]

              C.reldocs←(RelDocs¨C.controls),Ø
              C.doclink←(DocLink¨C.controls),Ø
              C.used←{⊂2↓∊', '∘,¨C.doclink[C.controlsoi¨⍵]}¨C.demos

              C.tasks←{Link('Description'Section Read ⍵)⍵}¨C.files

              C.type←'simple' 'advanced' 'mini-app'[1⍳⍨¨↓⍉∨/¨'Simple' 'Advanced'∘.⍷C.files]

              ⍝ These cause an empty result if looked-up is not found
              C.(filedescr demos rankings descr ctor notes notes ns),←Ø

          :Else
              C←⍎CACHE ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

      ForControl←{ ⍝ e.g. 'notes'ForControl'DataTable'
          (C.controlsoi⊂,⍵)⊃C⍎⍺
      }
      ForFile←{ ⍝ e.g. 'filedescr'ForFile'DataTable'
          (C.filesoi⊂,⍵)⊃C⍎⍺
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

:EndNamespace

:Namespace MS3SiteUtils ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝
⍝ ┌─────────────────────────────────────────────────────────────────────────────────────────────┐ ⍝
⍝ │ This is the collection of utility functions used by MS3Server and the index page of the     │ ⍝
⍝ │ MiServer Sample Site, MS3, accessible at miserver.dyalog.com                                │ ⍝
⍝ ├─────────────────────────────────────────────────────────────────────────────────────────────┤ ⍝
⍝ │ The code is split up in six sections:                                                       │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ A_GENERAL has utilities that appropriate to APL or MiServer in general                │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ B_FILES has various tools to list, read, and cache files and their content            │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ C_STRINGS string and multi-string manipulation and extraction of information          │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ D_ELEMENTS aids to create elements for the HTML Document Object Model                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ E_TUTORIAL has subroutines for the Tutorial tab on the index page                     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION A_GENERAL ⍝ UTILITY FUNCTIONS THAT ARE NOT SPECIFIC TO THE "MS3" SITE

    ç←{⊂⍣(1=≡,⍵)⊢⍵} ⍝ enclose if simple     Mnemonic for the V16 glyph

    FwSl←'[\\/]+'⎕R'/' ⍝ Make all slash-block into single forward-slashes

    Q←'"'∘,,∘'"' ⍝ Surround with quotes

    FromCSV←↑{'"'~¨⍨1↓¨t⊂⍨(','=t)∧~≠\'"'=t←',',⍵}¨ ⍝ Convert CSV VTV to matrix (no escaped chars)

      Section←{ ⍝ Extract section ⍺:: from code ⍵
          regex←'^\s*⍝\s*',⍺,':(:.*?)$((\R^⍝(?!\s*\w+::).*?$)*)' ⍝ find '  ⍝  LeftArg:: some text'
          opts←('Mode' 'M')('DotAll' 1)('ML' 1)                  ⍝ '^'≡linestart  EOL∊'.'  1st-only
          res←regex ⎕S'\1\2'⍠opts⊢⍵                              ⍝ return parts 1 and 2
          #.Strings.deb'⍝'~⍨1↓⊃res                               ⍝ strip spaces and lamp
      }

      Controls←{ ⍝ Extract names of controls used in sample ⍵
          (Words'Control'Section Read ⍵)~'_',¨NSS
      }

    Of←{⍵/⍨⍺⍺¨⍵} ⍝ Those elements of ⍵ that satisfy ⍺⍺ element

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Score←{(102-2×(50↑⊃⌽⍵)⍳⊂⍺)-∨/'Advanced'⍷⊃⍵} ⍝ Relevance score on index of control in list

      List←{ ⍝ Files in site folder
          ⍺←1↓FILEEXT                                      ⍝ default .ext
          mods←' -rec -raw -full=2 -ext=',⍺                ⍝ subdirs no-format rooted spec-ext
          list←(⎕SE.SALT.List #.Boot.AppRoot,⍵,mods)[;1 2] ⍝ begin at application root (MS3/)
          f←~d←list[;1]≡¨⊂'<DIR>'                          ⍝ find dirs
          list←FwSl list[;2]                               ⍝ normalize slashes for WWW
          (d/list),←'/'                                    ⍝ mark dirs with final slashes
          (f/list),←⊂'.',⍺                                 ⍝ add .ext to files
          list↓¨⍨¯1+1⍳⍨(FwSl ⍵)⍷⊃list                      ⍝ trim
      }

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←1 #.Files.ReadText #.Boot.AppRoot,page,(~'.'∊page)/FILEEXT
              C.read.keys,←⊂page
          :EndIf
      :Else
          r←,⊂'[failed to read "',page,'"]'
      :EndTrap
    ∇

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION C_STRINGS ⍝ MANIPULATION OF CHARACTER VECTORS AND VECTORS OF CHARACTER VECTORS

    Slash←'/'∘=⊂⊢

    Name←{1↓⊃⌽(Slash ⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)

    Words←{(1↓¨(∊∘' .'⊂⊢)' ',⍵)~⊂''} ⍝ Split at spaces and dots and remove empty pieces

    NoExt←{'.'∊⍵:⍵↓⍨-'.'⍳⍨⌽⍵⋄⍵} ⍝ 'aaa.bbb.ccc'  →  'aaa.bbb'

    NoNL←'\n' '\r'⎕R' ' ''⍠'Mode' 'D' ⍝ Replace newlines with spaces

    LastSeg←{⍺←⊢ ⋄ 1↓⍺⊃⌽Slash '/',⍵} ⍝ ⍺th (default 1st) /-separated segment from the end

    Levels←{(+/¨'/'=⍵)+~'/'=(⊃⌽)¨⍵} ⍝ Number of /s adjusted for categories' final /

    Shuffle←(?⍨∘≢⊃¨⊂)⊢⊂⍨⊢≡¨1↑⊢ ⍝ Shuffle the root elements of an XML snippet

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION D_ELEMENTS ⍝ FUNCTIONS THAT AUTOMATE THE CREATION OF DOM ELEMENTS AND HTML CODE

      Over←{
          ⊂'',⍺,'
',('/'⎕R'
'⊢⍵),''
      }

      External←{ ⍝ Icon off-site link
          ⍺←''
      ⍝    '.external' 'target=_blank' 'data-dyalog-tip="External link"'New _.A((⍺,'🔗')⍵)
          '',⍺,'🔗'
      }

      DescrEmbed←{ ⍝ Link to and iframed page
          l←('Description'Section Read ⍵),'
'
          l,←'Open this '
          l,←'simple sample' 'advanced sample' 'mini-app'⊃⍨1⍳⍨∨/¨'Simple' 'Advanced'⍷¨⊂⍵
          l,←' in a new tab'
          e←''
          l e
      }

      Link←{ ⍝ New-tab link with optional (⍺) "tooltip"
          ⍺←0
          ⍝(('data-dyalog-tip=',Q ⍺){⍺ ⍵}⍣(⍺≢0)⊢'target="_blank"')New _.A ⍵
          '',(⊃⍵),''
      }

      DocLink←{ ⍝ Link to WidgetDoc with appropriate parameters
          link←DocUrl ⍵
          ctor←'ctor'ForControl ⍵
          0=⍴ctor::Link ⍵ link ⍝ upon fail to retrieve info
          ('Constructor: ',ctor)Link ⍵ link
      }

      DocUrl←{
          '/Documentation/DyalogAPIs/WidgetDoc?namespace=_',('ns'ForControl ⍵),'&widget=',⍵
      }

      DocTreeLink←{ ⍝ Link to file in doc folder
          '/'=⊃⌽⍵:''
          ('/Documentation',⍵)'target="_blank"'
      }

      ReadingTree←{ ⍝ Create tree of documentation links
          l←'/',¨⍵~⊂''
          (Name∘NoExt¨l)(Levels l)(DocTreeLink¨l)
      }

    ∇ r←RelDocs w;list;item ⍝ Links to related samples
      list←'relevant'ForControl w
      r←''
      :For item :In list
          r,←'
',(Link('filedescr'ForFile item)item),'

'
      :EndFor
      r↓⍨←¯5
    ∇

      FormatList←{ ⍝ List of category-title pairs
          ⍺←1↓FILEEXT
          (⍺≡1↓FILEEXT)CatAndItem¨{'/'≠⊃⌽⍵}Of ⍺ List ⍵
      }

      CatAndItem←{ ⍝ (category) (filename/description)
          cat←2 LastSeg ⍵
          cat←'General' 'Mini App'cat['Documentation' 'Applications'⍳⊂cat]
          cat(Link((NoExt LastSeg)⍣(~⍺)('Description'Section Read)⍣⍺⊢⍵)⍵)
      }

      FilterTable←{
          dt←⍺ New _.DataTable ⍵
          dt.Width←'100%'
          dt.Options.autoWidth←_true
          dt.Options.mark←_true                ⍝ highlight search-terms
          dt.Options.columns←⎕NS¨4⍴⊂⍬          ⍝ options per column
          dt.Options.columns[4].visible←_false ⍝ hide column4
          dt.Options.dom←'l<"#fltcnt_',(1⊃⍺),'.fltcnt">ftpr'    ⍝ insert div with id "fltcnt_{id}" into table header
          dt.Plugins←'yadcf'
          dt.Options.yadcf←⎕NS ⍬
          dt.Options.yadcf.Filters←⎕NS ⍬
          dt.Options.yadcf.Filters.(column_number select_type filter_container_id)←3 'chosen'('fltcnt_',1⊃⍺)
          dt.Options.yadcf.Filters.(filter_type select_type_options filter_default_label)←'multi_select'(⊂'{width:"30em"}')'Filter by library'
          dt.Options.yadcf.Filters/⍨←1 ⍝ needs array of namespaces
          dt
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION E_TUTORIAL ⍝ FUNCTIONS USED BY THE index PAGE'S Tutorial TAB

    ∇ t←Elements;tabs;r;sp;fs;ig;colors;rgbs;button;p;cbs;t2;g
      t←'#div'New _.div'This div is the main container of this tab. ' ⍝ #div
      p←'#p't.Add _.p'This is the beginning of a paragraph, ' ⍝ #p
      '#span'p.Add _.span'but this phrase is a separate piece of text inside the paragraph. ' ⍝ #span
      button←'#Button't.Add _.Button'Button' ⍝ #Button
      sp←'#StackPanel't.Add _.StackPanel ⍝ #StackPanel
      sp.Add'StackPanel'
      '#EditField'sp.Add _.EditField'EditField' 'EditField' ⍝ #EditField
      '#Select'sp.Add _.Select'Red' 'Green' 'Blue' ⍝ #Select
      '#Input1't.Add _.Input'number' 426 ' Number Input: ' ⍝ #Input1
      t.Add _.br
      fs←'#Fieldset1't.Add _.Fieldset'CheckBoxes' ⍝ #Fieldset1
      cbs←{'.CheckBox'New _.CheckBox ⍵}¨'checked' 'unchecked' ⍝ .CheckBox
      ig←'#InputGrid'fs.Add _.InputGrid('Checked' 'Unchecked')cbs ⍝ #InputGrid
      ig.Flip←1 ⍝ #InputGrid
      fs←'#Fieldset2't.Add _.Fieldset'RadioButtonGroup' ⍝ #Fieldset2
      '#RadioButtonGroup'fs.Add _.RadioButtonGroup('Selected' 'Unselected')1 'right' ⍝ #RadioButtonGroup
      '#h5't.Add _.h5'Slider' ⍝ #h52
      '#Input2't.Add _.Input'range' 1 ⍝ #Input2
      '#List't.Add _.List('Primaries' 'Cyan' 'Magenta' 'Yellow' 'Key' 'Black')(1 2 2 2 1 2) ⍝ #List
      colors←'aqua' 'black' 'blue' 'fuchsia' 'gray' 'green' 'lime' 'maroon' ⍝ #Table
      rgbs←'#00FFFF' '#000000' '#0000FF' '#FF00FF' '#808080' '#008000' '#00FF00' '#800000' ⍝ #Table
      colors,←'navy' 'olive' 'purple' 'red' 'silver' 'teal' 'white' 'yellow' ⍝ #Table
      rgbs,←'#000080' '#808000' '#800080' '#FF0000' '#C0C0C0' '#008080' '#FFFFFF' '#FFFF00' ⍝ #Table
      '#Table't.Add _.Table('Color' 'RGB'⍪colors,⍪rgbs)'' 1 ⍝ #Table
      t.Add _.style ScriptFollows
      ⍝ #div {padding: 10px; margin: 10px;}
      ⍝ #Button {vertical-align: top; margin: 5px;}
      ⍝ #StackPanel {display: inline-block; padding: 5px;}
      ⍝ #Fieldset1 {display: inline-block;}
      ⍝ #Fieldset2 {display: inline-block;}
      ⍝ #h5 {margin: 1em 0 0.5em 0;}
      ⍝ #Input1 {width: 4em;}
      ⍝ #Input2 {width: 100%;}
      ⍝ #List {display: inline-block; margin: 1em; padding: 5px 5px 5px 25px;}
      ⍝ #Table {display: inline-block; max-height: 7em; overflow-y: scroll; margin-top: 1em;}
      ⍝
      ⍝ /**/#span:hover, #p:hover, #div:hover, #Button:hover, #StackPanel:hover, #EditField:hover, #Select:hover, #Fieldset1:hover, #Fieldset2:hover, .CheckBox:hover, #RadioButtonGroup:hover, #Input1:hover, #Input2:hover, #h5:hover, #progress:hover, #List:hover, #Table:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      'ShowElementInfo'∘AddHandler¨'#div' '#p' '#span' '#Button' '#EditField' '#StackPanel' '#Select' '#Fieldset1' '#Fieldset2' '.CheckBox' '#RadioButtonGroup' '#Input1' '#h5' '#Input2' '#progress' '#List' '#Table'
      t←'Common elements't
    ∇

    ∇ r←ShowElementInfo;code;ctrl;info
      :Access Public
      ctrl←_selector~'#.12'
      info←'
This element is called _.',ctrl,'
'
      info,←P'descr'ForControl ctrl
      info,←'
Constructor (what goes to the right of Add _.',ctrl,')
'
      info,←P Link('ctor'ForControl ctrl)(DocUrl ctrl)
      code←⊃_selector CodeFrom ⎕NR'Elements'
      info,←'
Put this in the Compose function
',P code
      r←'#info'Replace info
      r,←ShowPdf
    ∇

    ∇ t←Formatting
      t←'#margrad'New _.div'This div has inner padding and gradient backround' ⍝ #margrad
      '#boldin't.Add _.p'Bold indented text which has to be extremely long in order to show that only the first line is being indented.' ⍝ #boldin
      '#huge't.Add _.span'Huge text with outer margin' ⍝ #huge
      '#expund't.Add _.span'Expanded and underlined' ⍝ #expund
      '#bluebox't.Add _.p'Blue box with shadow' ⍝ #bluebox
      '#centshad't.Add _.p'Centered text with shadow' ⍝ #centshad
      '#whimg't.Add _.p'White text on image background' ⍝ #whimg
      '#border't.Add _.p'Surrounded by borders and top-right corner is round' ⍝ #border
      '#img' 'src="Examples/Data/4lines.png"' 'alt="Image"' 'title="Four lines high"'t.Add _.img ⍝ #img
      '#narrow't.Add _.p'Inline paragraph with limited width and single line height.' ⍝ #narrow
      '#mono't.Add _.span'Monospace, ' ⍝ #mono
      '#sans't.Add _.span'Sans-Serif, ' ⍝ #sans
      '#serif't.Add _.span'and Serif.' ⍝ #serif
      t.Add _.style ScriptFollows
      ⍝ #margrad {padding: 2em; background: linear-gradient(White, SkyBlue);}
      ⍝ #boldin {font-weight: bold; text-indent: 2em;}
      ⍝ #huge {font-size: x-large; margin: 2em;}
      ⍝ #expund {text-decoration: underline; letter-spacing: 0.5ex;}
      ⍝ #bluebox {background-color: SkyBlue; box-shadow: 0.25em 0.5em 1em Gray;}
      ⍝ #centshad {text-align: center; text-shadow: 0.1em 0.3em 0.2em Blue;}
      ⍝ #whimg {color: white; background-image: url(Examples/Data/pic3.png);}
      ⍝ #serif {font-family: "Times New Roman", Times, serif; font-size: 150%;}
      ⍝ #sans {font-family: Arial, Helvetica, sans-serif; font-size: 150%;}
      ⍝ #mono {font-family: "Courier New", Courier, monospace; font-size: 150%;}
      ⍝ #border {border: 0.25em solid Blue; border-top-right-radius: 1.5em;}
      ⍝ #img {height: 4em; opacity: 0.5;}
      ⍝ #narrow {width: 20%; display: inline-block; line-height: 1;}
      ⍝
      ⍝ /**/#boldin:hover, #huge:hover, #expund:hover, #centshad:hover, #serif:hover, #sans:hover, #mono:hover, #narrow:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      ⍝ /**/#margrad:hover {box-shadow: 0 0 5px 2px Orange inset;}
      ⍝ /**/#whimg:hover {box-shadow: 0 0 5px 2px Orange;}
      ⍝ /**/#border:hover {box-shadow: 0 0 5px 2px Orange, 0 0 5px 2px Orange inset;}
      ⍝ /**/#img:hover {box-shadow: 0 0 5px 3px Orange,0 0 5px 3px Orange;}
      ⍝ /**/#bluebox:hover {box-shadow:0 0 5px 1px Orange, 0 0 5px 1px Orange inset, 0.25em 0.5em 1em Gray;}
      'ShowInfo'∘AddHandler¨'#margrad' '#boldin' '#huge' '#expund' '#bluebox' '#centshad' '#whimg' '#serif' '#sans' '#mono' '#border' '#img' '#narrow'
      t←'Formatting't
    ∇

    ∇ t←Positioning
      Add _.style ScriptFollows
      ⍝ #parent {position: relative; height: 32em; margin: 1em;}
      ⍝ /**/#parent, #parent div {margin-top: 1em; margin-bottom: 1em; padding: 1ex;}
      ⍝ /**/#parent div {width: 50%; border: 1px solid black; }
      ⍝ #default {/* nothing to see here */}
      ⍝ #textcenter {text-align: center;}
      ⍝ #textright {text-align: right;}
      ⍝ #boxright {margin-left: auto; margin-right: 0;}
      ⍝ #boxcenter {margin-left: auto; margin-right: auto;}
      ⍝ #textbottom {position: relative; height: 4em;}
      ⍝ #textbottom span {position: absolute; bottom: 1ex;}
      ⍝ #boxbottom {position: absolute; bottom: 0;}
      ⍝
      ⍝ /**/#parent:hover, #parent div:hover {
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#parent'New _.div'Parent element with "relative" position so we can align vertically' ⍝ #parent
      '#default't.Add _.div'Default position and text alignment' ⍝ #default
      '#textcenter't.Add _.div'Centered text' ⍝ #textcenter
      '#textright't.Add _.div'Right-aligned text' ⍝ #textright
      '#boxcenter't.Add _.div'Centered element' ⍝ #boxcenter
      '#boxright't.Add _.div'Right-aligned element' ⍝ #boxright
      '#textbottom't.Add _.div,New _.span'Bottom-aligned text' ⍝ #textbottom
      '#boxbottom't.Add _.div'Bottom-aligned element' ⍝ #boxbottom
      'ShowInfo'∘AddHandler¨'#parent' '#default' '#textcenter' '#textright' '#boxcenter' '#boxright' '#textbottom' '#boxbottom'
      t←'Positioning't
    ∇

    ∇ t←Embedding;c
      Add _.style ScriptFollows
      ⍝ #embed>* {margin: 1ex;}
      ⍝ #quack {filter: hue-rotate(-205deg); padding: 2em;}
      ⍝ #quack:hover {filter:  hue-rotate(0deg);}
      ⍝ #bear {padding: 0.2em; border: 1px solid silver; margin: 1ex;}
      ⍝ #duck {height: 12em; transform: rotate(-15deg);}
      ⍝ #poly {height: 240px; width: 200px}
      ⍝ #poly polygon {fill: orange; stroke: purple; stroke-width: 1ex;}
      ⍝ #pdf {height: 240px; width:400px}
      ⍝
      ⍝ /*#bear:hover, #quack:hover, #duck:hover, #poly:hover, #embed>div>div:hover*/ #embed *:hover{
      ⍝     box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;
      ⍝ }
      t←'#embed'New _.div
      t.Add _.br
      '#bear' 'src=Examples/Data/bear.mp4' 'autoplay=' 'loop=' 'muted='t.Add _.video'No video support!' ⍝ #bear
      '#quack' 'controls='t.Add _.Audio'Examples/Data/quack.mp3' ⍝ #quack
      '#duck' 'src=Examples/Data/duck.png' 'alt="Silly Duck"'t.Add _.img'No img support!' ⍝ #duck
      '#poly't.Add _.svg,'points=20,20 50,120 120,190 180,80'New _.polygon ⍝ #poly
      c←'style=display:inline-block;position:relative;'t.Add _.div
      '#pdf' 'style=position:absolute;height:100%;width:100%;top:0;' 'onclick="$(this).hide();"'c.Add _.div
      '#pdf' 'data=Examples/Data/licence.pdf'c.Add _.object ⍝ #pdf
      'ShowInfo'∘AddHandler¨'#bear' '#duck' '#quack' '#poly' '#pdf'
      t←'Embedding't
    ∇

    ∇ r←ShowPdf
      r←(_selector≢'#pdf')/Execute'$("#pdf").show();'
    ∇

    ∇ t←Interaction;cb;m
      Add _.style ScriptFollows
      ⍝ #ui>* {margin: 1ex;}
      ⍝ #adjust {width: 8em;}
      ⍝ #adjust:hover {text-align: justify;}
      ⍝ #adjust:active {text-align: right;}
      ⍝ #callback {width: 8em;}
      ⍝ #move {height: 6em; width: 12em; border: 1px solid black;}
      ⍝ #move div {height: 3em; width: 6em; cursor: move; background: silver;}
      ⍝ /**/ #ui>*:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      t←'#ui'New _.div
      '#adjust't.Add _.Button'Styling for reaction to hover and click' ⍝ #adjust
      cb←'#callback't.Add _.Button('Update with callback click ',#.Dates.(TSFmt ⎕TS)) ⍝ #callback
      cb.On'click' 'CallbackFn' ⍝ #callback
      '#move't.Add _.div,New _.div'Movable (drag me!)' ⍝ #move
      m←Add _.jqDraggable'#move div' ⍝ #move
      m.Set'containment' 'parent' ⍝ #move

      'ShowInfo'∘AddHandler¨'#adjust' '#callback' '#move'
      t←'Interaction't
    ∇

    ∇ r←CallbackFn ⍝ #callback
      :Access Public ⍝ #callback
      r←'#callback'Replace'Update with callback click ',#.Dates.(TSFmt ⎕TS) ⍝ #callback
    ∇ ⍝ #callback

    ∇ t←Windows;c;p;d
      Add _.style ScriptFollows
      ⍝ #win {margin: 1em; padding: 1ex;}
      ⍝ #win button {margin: 1em;}
      ⍝ #win #color {display: inline-block;}
      ⍝ #win #color:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      ⍝ #css {width: 6em; padding: 1ex; background: silver; border-radius: 0.5ex; box-shadow: 0.25em 0.5em 2em grey;}
      ⍝ #dialog {display: none;}
      ⍝ /**/ #css:hover {width: 6em; background: silver; box-shadow: 0 0 5px 1px orange,0 0 5px orange inset,0.25em 0.5em 2em grey;}
      ⍝ /**/ #win>button:hover {box-shadow: 0 0 5px 1px orange,0 0 5px orange inset;}
      t←'#win'New _.div
      t.Add _.p'Choose a color to get a matching notification:'
      d←'#color't.Add _.div
      d.On'mousedown' 'ShowInfo'
      c←d.Add _.ejColorPicker'#ffff00' ⍝ #color
      c.On'close' 'ColorFn'('chosenColor' 'model' 'value') ⍝ #color
      '#css't.Add _.div'CSS to give floating impression' ⍝ #css

      '#dialog't.Add _.div'Content of dialog box widget' ⍝ #dialog
      d←t.Add _.ejDialog'#dialog' 'Syncfusion Dialog' ⍝ #dialog
      'showOnInit'd.Set _false ⍝ #dialog
      'onclick="$(''#dialog'').ejDialog(''open'')"'t.Add _.Button'Open dialog' ⍝ #dialog
      Add _.Handler'#win button' 'click' 'DialogFn'

      'ShowInfo' AddHandler '#css'
      t←'Windows & Popups't
    ∇

    ∇ r←ColorFn;chosen;all;options ⍝ #color
      :Access public ⍝ #color
      chosen←0 #.Utils.hex⍕↓3 2⍴1↓Get'chosenColor' ⍝ #color
      all←(0 0 0)(255 0 0)(0 255 0)(0 0 255)(319 319 0)(255 255 255) ⍝ #color
      (options←⎕NS ⍬).color←'black' 'red' 'green' 'blue' 'yellow' 'black'⊃⍨(⊢⍳⌊/)+/¨2*⍨all-⊂chosen ⍝ #color
      r←Execute options _.jBox.Notice('You chose ',Get'chosenColor') ⍝ #color
    ∇ ⍝ #color

    ∇ r←ShowDialog
      :Access public
      _selector←'#dialog'
      r←ShowInfo
    ∇

    ∇ r←ShowInfo;info;css;code;fns
      :Access Public
      (code css)←_selector CodeFrom ⎕SRC⍎NoExt⊃⎕XSI ⍝ this ns
      code←SplitFns code
      info←'
Put this in the Compose function
',P Break⊃code
      fns←1↓code
      :If ×≢css
          info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
'
          info,←P Break css
      :EndIf
      :If ×≢fns
          info,←'
Add ','this funtion' 'these functions'⊃⍨2⌊≢fns
          info,←' to the MiPage
',P Break¨fns
      :EndIf
      r←'#info'Replace info
      r,←ShowPdf
    ∇

    ∇ {ctrl}←callbackfn AddHandler sel
      ctrl←sel~'#.12'
      Add _.Handler sel'mousedown'callbackfn'' 'event.stopPropagation()'
    ∇

    Break←{4↓∊'
'∘,¨⍵}

      CodeFrom←{
          (css lines)←↓⍉↑('⍝ ',⍺)∘Prep¨⍵/⍨('⍝ ',⍺)∘(∨/⍷)¨⍵
          (lines/⍨~css)(css/lines)
      }

      Prep←{
          c←#.Strings.deb ⍵
          ⍺≡c↑⍨-l←≢⍺:0('\w+\.Add'⎕R'Add'⊢c↓⍨¯1-l)
          1(2↓c)
      }

    P←{∊('
',,∘'
')¨ç⍵}

    SplitFns←{⍵⊂⍨1,2</≠\'∇'=⊃¨⍵}

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

    :SECTION F_CONSTANTS ⍝ NILADIC FUNCTIONS THAT ACT LIKE :include'ABLE CONSTANTS

    ∇ r←NSS;types;names
      :Access public
      :If 0=C.⎕NC'NSS'
          C.NSS←1↓¨1↓'_'#.⎕NL ¯9
          names types←0 1 ⎕NINFO⍠1⊢#.Boot.AppRoot,'Examples/*'
          C.NSS∩←LastSeg¨names/⍨1=types
      :EndIf
      r←C.NSS
    ∇

    ∇ r←GROUPS
      :Access public
      :If 0=C.⎕NC'GROUPS'
          C.GROUPS←{#.Strings.deb(⍳∘'⍝'↓⊢)⊃⎕SRC #⍎⍵}¨'_',¨NSS
      :EndIf
      r←C.GROUPS
    ∇

    ∇ r←CACHE
      :Access public
      r←'#.CACHE'
    ∇

    ∇ r←FILEEXT
      :Access public
      r←#.Boot.ms.Config.DefaultExtension
    ∇

    ∇ C←C;scores;list;refs;srcs;control;i;info;ref;src;ctor;Read;Ø;demo
     ⍝ Return ref to cache (initialize cache if nonexistant)
      :Access public
      Read←1 #.Files.GetText #.Boot.AppRoot∘, ⍝ no-cache reading
      :Hold CACHE                          ⍝ prevent clashes
          :If 9≠⎕NC CACHE                  ⍝ if cache is empty:
              Ø←⊂''
              C←⍎CACHE ⎕NS ⍬       ⍝ create with shortcut
              C.read←⎕NS ⍬         ⍝ init cache for files
              C.read.(keys←data←⍬) ⍝ init keys and data

              C.controls←(#._.⎕NL ¯9)~⊂'Handler'                       ⍝ cache all controls
              C.controlsoi←C.controls∘⍳ç ⋄ C.eocontrols←∊∘C.controls ç ⍝ hash tables
              refs←#._⍎¨C.controls                                     ⍝ refs of all controls
              C.ns←3↓¨⍕¨refs.##                                        ⍝ ns of each control
              srcs←⎕SRC¨refs                                           ⍝ all sources

              C.files←⊃⍪/{List'Examples/',⍵}¨NSS,⊂'Applications' ⍝ sample filenames
              C.filesoi←C.files∘⍳ç                               ⍝ hash table
              C.filedescr←('Description'Section Read)¨C.files    ⍝ Description:: for all files
              C.demos←Controls¨C.files                           ⍝ controls demoed in each
              scores←C.controls∘.Score↓⍉↑C.files C.demos         ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores  ⍝ cache all rankings

              C.(descr ctor notes relevant reldocs doclinks)←Ø
              info←FromCSV Read'IndexData/info.csv'
              :For control ref src :InEach C.controls refs srcs
                  i←info[;1]⍳⊂control
                  :If i≤≢info
                      C.descr,←info[i;2]
                      C.notes,←info[i;3]
                  :Else
                      C.descr,←⊂NoNL'Description'Section src
                      C.notes,←⊂NoNL'Notes'Section src
                  :EndIf

                  ctor←{⍵↑⍨¯1+⌊/⍵⍳⎕UCS 13 10}'Constructor'Section src
                  :If ''≡ctor
                      C.ctor,←'[content [attributes]]' '[attributes]'[1+(⎕NEW ref).NoEndTag]
                  :Else
                      C.ctor,←⊂ctor
                  :EndIf

                  :If C.eocontrols⊂control
                      C.relevant,←⊂C.files[C.rankings⊃⍨C.controlsoi⊂control]
                  :Else
                      C.relevant,←Ø
                  :EndIf

              :EndFor
              C.cats←GROUPS[NSS⍳3↓¨⍕¨('#._'∘⍎¨C.controls).##]

              C.reldocs←(RelDocs¨C.controls),Ø
              C.doclink←(DocLink¨C.controls),Ø
              C.used←{⊂2↓∊', '∘,¨C.doclink[C.controlsoi¨⍵]}¨C.demos

              C.tasks←{Link('Description'Section Read ⍵)⍵}¨C.files

              C.type←'simple' 'advanced' 'mini-app'[1⍳⍨¨↓⍉∨/¨'Simple' 'Advanced'∘.⍷C.files]

              ⍝ These cause an empty result if looked-up is not found
              C.(filedescr demos rankings descr ctor notes notes ns),←Ø

          :Else
              C←⍎CACHE ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

      ForControl←{ ⍝ e.g. 'notes'ForControl'DataTable'
          (C.controlsoi⊂,⍵)⊃C⍎⍺
      }
      ForFile←{ ⍝ e.g. 'filedescr'ForFile'DataTable'
          (C.filesoi⊂,⍵)⊃C⍎⍺
      }

    :ENDSECTION ⍝ ─────────────────────────────────────────────────────────────────────────────────
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

:EndNamespace


Add Insert method to HtmlPage

Add Insert method to HtmlPage

:Namespace QA
⍝ MiServer 3 QA suite
    ∇ Run
      RenderHTML
      RenderPages
    ∇

    ∇ result←RenderHTML;nss;ns;class;c;name;res;f;root
⍝ make sure all HTML-generating classes render
      nss←#.(_html _HTML _DC _JQ _SF) ⍝ add _JQM when it's ready
      root←#.Boot.MSRoot
      result←0 6⍴'' 0 0 0 0 0  ⍝ [1] name [2] DocBase? [3] DocDyalog? [3] ApiLevel [4] Renders [5] id=constructor[1]
      :For ns :In nss
          :For class :In (ns.⎕NL ¯9)~(⊂'SALT_Data'),'_'ns.⎕NL ¯9
              res←0 0 0 0 'N/A'
              name←⍕c←ns⍎class
              res[1]←0≠c.⎕NC⊂'DocBase'
              :If res[2]←0≠f←c.⎕NC⊂'DocDyalog'
                  res[2]×←¯1*#.Files.Exists root,c.DocDyalog
              :EndIf
              res[3]←{6::0 ⋄ ⍵.ApiLevel}c
              res[4]←{0::0 ⋄ 1⊣(⎕NEW ⍵).Render}c
              :If #._html≢ns
                  res[5]←{0::0 ⋄ 'xyz'≡(⎕NEW ⍵'xyz').id}c
              :EndIf
              result⍪←(⊂name),res
          :EndFor
      :EndFor
      result←'Element/Widget' 'DocBase?' 'DocDyalog?' 'ApiLevel' 'Renders?' 'id=args[1]?'⍪result
    ∇

    ∇ r←RenderPages;class
      r←0 2⍴'' 0
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMiPageTemplate'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMiPageSample'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMS3Page'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMS2Page'
    ∇

    ∇ r←_Test_Page page;class;z
      r←'Passed'
      :Trap 0
          class←⎕SE.SALT.Load page
          z←⎕NEW class
          z._Request←⎕NEW #.HTTPRequest('' '')
          z._Request.Server←#.Boot.ms
          {}⎕XML z.Wrap
      :Else
          r←∊⎕DM
      :EndTrap
    ∇
:EndNamespace

:Namespace QA
⍝ MiServer 3 QA suite
    ∇ Run
      RenderHTML
      RenderPages
    ∇

    ∇ result←RenderHTML;nss;ns;class;c;name;res;f;root
⍝ make sure all HTML-generating classes render
      nss←#.(_html _HTML _JQ _SF) ⍝ add _JQM when it's ready
      root←#.Boot.MSRoot
      result←0 6⍴'' 0 0 0 0 0  ⍝ [1] name [2] DocBase? [3] DocDyalog? [3] ApiLevel [4] Renders [5] id=constructor[1]
      :For ns :In nss
          :For class :In (ns.⎕NL ¯9)~(⊂'SALT_Data'),'_'ns.⎕NL ¯9
              res←0 0 0 0 'N/A'
              name←⍕c←ns⍎class
              res[1]←0≠c.⎕NC⊂'DocBase'
              :If res[2]←0≠f←c.⎕NC⊂'DocDyalog'
                  res[2]×←¯1*#.Files.Exists root,c.DocDyalog
              :EndIf
              res[3]←{6::0 ⋄ ⍵.ApiLevel}c
              res[4]←{0::0 ⋄ 1⊣(⎕NEW ⍵).Render}c
              :If #._html≢ns
                  res[5]←{0::0 ⋄ 'xyz'≡(⎕NEW ⍵'xyz').id}c
              :EndIf
              result⍪←(⊂name),res
          :EndFor
      :EndFor
      result←'Element/Widget' 'DocBase?' 'DocDyalog?' 'ApiLevel' 'Renders?' 'id=args[1]?'⍪result
    ∇

    ∇ r←RenderPages;class
      r←0 2⍴'' 0
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMiPageTemplate'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMiPageSample'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMS3Page'
      r⍪←{⍵(_Test_Page ⍵)}#.Boot.MSRoot,'QA/TestMS2Page'
    ∇

    ∇ r←_Test_Page page;class;z
      r←'Passed'
      :Trap 0
          class←⎕SE.SALT.Load page
          z←⎕NEW class
          z._Request←⎕NEW #.HTTPRequest('' '')
          z._Request.Server←#.Boot.ms
          {}⎕XML z.Wrap
      :Else
          r←∊⎕DM
      :EndTrap
    ∇
:EndNamespace


Update FontIconsBase.dyalog

Update FontIconsBase.dyalog

:Class FontIconsBase :  #.HtmlElement
    :field public shared readonly DocBase←'http://fontawesome.io/icons/'
    :field public Family←'fa'  ⍝ bt=Blacktie or other prefixes for FontIcons
    :field public Resource←'placeholder'
    :field public Container←'i'
    :field public icon←''
    :field public StackSize←'1x'    ⍝ only relevant for the outer tag in stacks!
    :field private Args←''
    :field private Stack←⍳0

    ∇ Make0
    ⍝ empty constructor mostly used for stacks, but possibly for "normal" icons as well (assign icon after construction!)
      :Access public
      :Implements Constructor
    ∇

    ∇ Make1 args
      :Access Public
    ⍝ args is the name of the icon (w/o prefix) and possibly other specs
    ⍝ (such as 2x - again, w/o prefix, space-separated, ie "camera 2x"!)
      icon←args
      Args←args
      :Implements Constructor    :Base args
    ∇

    ∇ Make2(cnt atts)
      :Access public
      Args←cnt atts
      icon←cnt
      :Implements Constructor    :Base'' ('' atts)
      Content←''
    ∇

    ∇ {r}←stack arg
      :Access public
      r←New FontIconsBase arg
      r.Family←Family
      Stack,←r

    ∇

    ∇ ul items;item;ulCnt;Cnt;atts;iTag
      :Access public
 ⍝ pairs is a nested vector of class/text,
 ⍝ i.e. (('check-square' 'List icons')('check-square' 'can be used')...)
 ⍝ it is also possibly to add attributes after the 1st two elems to assign an id or style to the li
 ⍝ (to add attributes to the inner i-Attribute, nest the 2nd element!)
 ⍝ And when you need to assign id to the content, you'll have to embed it into a span yourself:
 ⍝ ('check-square' 'List Icons')
 ⍝ (NB: it would also be possible to override the class which will ruin the purpose,
 ⍝  but I wasn't sure whether we should take care to avoid that by eliminating '.'-elems
 ⍝  or if it wouldn't be better (and easier) to just create the mess the user asked for ;-))
 ⍝ For discussion: only collect the items here, so that multiple ul-Calls
 ⍝ are possible to add to a single list (so all the "work" should be done in Render instead)
      :If 2=≡items ⋄ items←,⊂items ⋄ :EndIf
      ulCnt←'' ⋄ Content←''
      :For item :In items
          (icon Cnt)←2↑item
          atts←'' ⋄ :If 1<|≡Cnt ⋄ (Cnt atts)←Cnt ⋄ :EndIf
          icon←'li ',icon
          iTag←(New _.i''((⊂'.',setClass 1),eis atts)).Render,Cnt
          ulCnt,←(New _.li iTag(2↓item)).Render
      :EndFor
      icon←''
      Content←ulCnt
      class←'fa-ul'
      Container←'ul'
    ∇

    ∇ r←Render;icon3;i1;i2;Stack∆
      :Access Public

    ⍝ Brian: unfortunately this usage of "Uses" does not work, if has no effect on the header (also see my email from Oct17th)
      Uses←Resource

      :If 0<⍴Stack
          Stack∆←Stack
          Stack←⍳0
          :If 0<⍴icon
              icon←(RemoveSize icon),' stack',GetSize
              r←Render  ⍝ first elem
          :Else
              r←''
          :EndIf
          :For obj :In Stack∆
              obj.icon←(RemoveSize obj.icon),' stack',obj.GetSize
              r∆←obj.Render
              r,←r∆
          :EndFor
          icon←'stack ',StackSize ⋄ Container←'span'
          Content←r
      :EndIf
      Tag←Container
      :If 0<⍴icon ⋄ icon←SetSize ⋄ class←setClass(0=⎕NC'Stack∆') ⋄ :EndIf
      r←⎕BASE.Render
    ∇

    ∇ r←GetSize
    ⍝ searches icon for a size-specification
      :Access public
      :If 0=⍴r←('-(lg|[12345]x)'⎕S'\1')icon
          r←'-1x'
      :EndIf
    ∇

    ∇ res←SetSize
    ⍝ searches icon for a size-specification
      :Access public
      res←icon
      :If 0=⍴('-(lg|[12345]x)'⎕S'\1')icon     ⍝ do not use GetSize here - we're interested to see if it has been set or not
          res←icon,' 1x'
      :EndIf
    ∇

    ∇ res←RemoveSize res
    ⍝ remove Size attribute from element
      :Access public
      res←('-(lg|[12345]x)'⎕R'')res
    ∇

    ∇ R←setClass includeFamily
      R←Family{⎕ML←1 ⋄ (includeFamily/⍺),∊(' ',⍺,'-')∘,¨1↓¨(⍵=' ')⊂⍵}' ',icon
    ∇

    ∇ {R}←Classname DeriveClass(rsc fmly);cls
      :Access public shared
      ⍝ example: 'myfa'DeriveClass'FontAwesome'  'fa'
      ⍝          Add _.myfa'camera'
      :If rsc[1]=':'
          rsc←'⍎https://use.fonticons.com/',(1↓rsc),'.js'
      :EndIf
      cls←ScriptFollows
      ⍝ :Class %Classname : #._DC.FontIconsBase
      ⍝   :field public readonly family←'%family'
      ⍝   :field public readonly Resource←'%Resource'
      ⍝   ∇ make0
      ⍝     :access public
      ⍝      Resource {6::⍺∇1↓⍵ ⋄ (1⊃⍵).Use ⍺}⎕rsi ⍝ find environment where we can execute "Use"
      ⍝     :implements constructor :base
      ⍝   ∇
      ⍝
      ⍝   ∇ make1 arg
      ⍝     :access public
      ⍝      Resource {6::⍺∇1↓⍵ ⋄ (1⊃⍵).Use ⍺}⎕rsi ⍝ find environment where we can execute "Use"
      ⍝     :implements constructor :base arg
      ⍝   ∇
      ⍝
      ⍝   ∇ make2 (arg1 arg2)
      ⍝     :access public
      ⍝      Resource {6::⍺∇1↓⍵ ⋄ (1⊃⍵).Use ⍺}⎕rsi ⍝ find environment where we can execute "Use"
      ⍝     :implements constructor :base (arg1 arg2)
      ⍝   ∇
      ⍝ :EndClass
      cls←(('%Classname'Classname)('%family'fmly)('%Resource'rsc))Subst cls

      ⍝ Morten: ⎕fix did not like to fix the class as vector with NL - perhaps an extension for the future???
      cls←(⎕UCS 13)#.Utils.penclose cls~⎕UCS 10

      :If 0=⎕NC'#._' ⋄ '#._'⎕NS'' ⋄ :EndIf ⍝ might not be there yet during boot, when FontAwesome-Class is being set up...
      R←#._.⎕FIX cls
    ∇

   ⍝ ideally FontAwesome should be made available right after loading this class...
   ⍝ 'FontAwesome'DeriveClass'FontAwesome' 'fa'
:EndClass

:Class FontIconsBase :  #.HtmlElement
    :field public shared readonly DocBase←'http://fortawesome.github.io/Font-Awesome/icons/'
    :field public Family←'fa'  ⍝ bt=Blacktie or other prefixes for FontIcons
    :field public Container←'i'
    :field public icon←''
    :field public StackSize←'1x'    ⍝ only relevant for the outer tag in stacks!
    :field private Args←''
    :field private Stack←⍳0

    ∇ Make0
    ⍝ empty constructor mostly used for stacks, but possibly for "normal" icons as well (assign icon after construction!)
      :Access public
      :Implements Constructor
    ∇

    ∇ Make1 args
      :Access Public
    ⍝ args is the name of the icon (w/o prefix) and possibly other specs
    ⍝ (such as 2x - again, w/o prefix, space-separated, ie "camera 2x"!)
      icon←args
      Args←args
      :Implements Constructor    :Base args
    ∇

    ∇ Make2(cnt atts)
      :Access public
      Args←cnt atts
      icon←cnt
      :Implements Constructor    :Base'' ('' atts)
      Content←''
⍝ setting Content to '' shouldn't be neccessary, but without it, we're getting wrong results as seen below:
⍝ d←⎕new MiPage
⍝ d.Add _.FontAwesome 'camera' '#bla'
⍝ +d.Render
⍝<!DOCTYPE html>
⍝#bla
⍝                                       ↑↑↑↑ somehow the id made it into the Content!
⍝
⍝
    ∇

    ∇ {r}←stack arg
      :Access public
      r←New FontIconsBase arg
      r.Family←Family
      Stack,←r

    ∇

    ∇ ul items;item;ulCnt;Cnt;atts;iTag
      :Access public
 ⍝ pairs is a nested vector of class/text,
 ⍝ i.e. (('check-square' 'List icons')('check-square' 'can be used')...)
 ⍝ it is also possibly to add attributes after the 1st two elems to assign an id or style to the li
 ⍝ (to add attributes to the inner i-Attribute, nest the 2nd element!)
 ⍝ And when you need to assign id to the content, you'll have to embed it into a span yourself:
 ⍝ ('check-square' 'List Icons')
 ⍝ (NB: it would also be possible to override the class which will ruin the purpose,
 ⍝  but I wasn't sure whether we should take care to avoid that by eliminating '.'-elems
 ⍝  or if it wouldn't be better (and easier) to just create the mess the user asked for ;-))
 ⍝ For discussion: only collect the items here, so that multiple ul-Calls
 ⍝ are possible to add to a single list (so all the "work" should be done in Render instead)
      :If 2=≡items ⋄ items←,⊂items ⋄ :EndIf
      ulCnt←'' ⋄ Content←''
      :For item :In items
          (icon Cnt)←2↑item
          atts←'' ⋄ :If 1<|≡Cnt ⋄ (Cnt atts)←Cnt ⋄ :EndIf
          icon←'li ',icon
          iTag←(New _.i''((⊂'.',setClass 1),eis atts)).Render,Cnt
          ulCnt,←(New _.li iTag(2↓item)).Render
      :EndFor
      icon←''
      Content←ulCnt
      class←'fa-ul'
      Container←'ul'
    ∇

    ∇ r←Render;icon3;i1;i2;Stack∆
      :Access Public

    ⍝ unfortunately this usage of "Uses" does not work, if has no effect on the header (also see my email from Oct17th)
      :Select Family
      :Case 'fa' ⋄ Uses←'FontAwesome'
      :Case 'bt' ⋄ Uses←'BlackTie'
      :Else
             ⍝ when user uses FontIcon-app, he needs to handle "Use" on his own!
      :EndSelect

      :If 0<⍴Stack
          Stack∆←Stack
          Stack←⍳0
          :If 0<⍴icon
              icon←(RemoveSize icon),' stack',GetSize
              r←Render  ⍝ first elem
          :Else
              r←''
          :EndIf
          ⎕←'first=',r
          :For obj :In Stack∆
              obj.icon←(RemoveSize obj.icon),' stack',obj.GetSize
              r∆←obj.Render
              r,←r∆
          :EndFor
          icon←'stack ',StackSize ⋄ Container←'span'
          Content←r
      :EndIf
      Tag←Container
      :If 0<⍴icon ⋄ icon←SetSize ⋄ class←setClass(0=⎕NC'Stack∆') ⋄ :EndIf
      r←⎕BASE.Render
    ∇

    ∇ r←GetSize
    ⍝ searches icon for a size-specification
      :Access public
      :If 0=⍴r←('-(lg|[12345]x)'⎕S'\1')icon
          r←'-1x'
      :EndIf
    ∇

    ∇ res←SetSize
    ⍝ searches icon for a size-specification
      :Access public
      res←icon
      :If 0=⍴('-(lg|[12345]x)'⎕S'\1')icon     ⍝ do not use GetSize here - we're interested to see if it has been set or not
          res←icon,' 1x'
      :EndIf
    ∇

    ∇ res←RemoveSize res
    ⍝ remove Size attribute from element
      :Access public
      res←('-(lg|[12345]x)'⎕R'')res
    ∇

    ∇ R←setClass includeFamily
      R←Family{⎕ML←1 ⋄ (includeFamily/⍺),∊(' ',⍺,'-')∘,¨1↓¨(⍵=' ')⊂⍵}' ',icon
    ∇
:EndClass