AddClass and AddStyle  return  instance

AddClass and AddStyle  return  instance

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

⍝∇:require =\JSON.dyalog

    ⎕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)

    :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
    :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON

⍝ define shortcuts to namespaces (initialized later)
    :field public shared _html ⍝ reference to base HTML elements namespace
    :field public shared _JQ   ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public shared _SF   ⍝ reference to Syncfusion widgets namespace
    :field public shared _JSS  ⍝ reference to JavaScript Snippets namespace
    :field public shared _DC   ⍝ reference to Dyalog Controls namespace
    :field public shared _     ⍝ 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←{?⍵⊣⎕RL←0}

    ∇ 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←isChar w
      :Access public shared
      r←0 2∊⍨10|⎕DR w
    ∇

    ∇ r←isNum w
      :Access public shared
      r←2|⎕DR w
    ∇

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

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

    ∇ r←isTrue a
      :Access public shared
      →0⍴⍨r←(,1)≡,a       ⍝ boolean
      →0⍴⍨r←#.JSON.true≡a ⍝ namespace
      →0⍴⍨r←'true'≡a      ⍝ string
      →0⍴⍨r←a≡⊂'true'     ⍝ 7161⌶1
    ∇

    ∇ r←isFalse a
      :Access public shared
      →0⍴⍨r←(,0)≡,a        ⍝ boolean
      →0⍴⍨r←#.JSON.false≡a ⍝ namespace
      →0⍴⍨r←'false'≡a      ⍝ string
      →0⍴⍨r←a≡⊂'false'     ⍝ 7161⌶0
    ∇

    ∇ r←a ine w
      :Access public shared
      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
    ∇

    ∇ SyncAttrs arg
      :Implements Trigger id,name,class,style,title,type,value
      :If ⊃≢/arg.(NewValue OldValue)
          arg.Name Set arg.NewValue~UNDEF
      :EndIf
    ∇

    :property keyed Attrs       ⍝ element attributes
    :access public

        ∇ set ra;i;new;there;names;ind
          there←~new←(⍴_names)<i←_names⍳names←fixkeys⊃ra.Indexers
          (_values _names),←new∘/¨ra.NewValue names
          _values[there/i]←there/ra.NewValue
          :For i :In {⍵/⍳⍴⍵}7≥'id' 'class' 'style' 'title' 'value' 'type' 'name'⍳names
              ⍎(i⊃names),'←i⊃ra.NewValue'
          :EndFor
        ∇

        ∇ 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

    ∇ attr←{plain}ParseAttr arg;split;item;t;f;i;eq;nq;items;n
      :Access public shared

⍝ Parse html sttributes
⍝ {plain} - Boolean indicating whether to not interpret a plain first token as an if
⍝ args    - vector of tokens to parse
⍝
⍝     1)    In only the first token, a simple string (i.e. not in the form 'abc=def') without a leading '#' or '.' is treated as an id
⍝        'foo'  > id="foo"
⍝
⍝        In all other positions, a string of this type is treated as a singleton attribute
⍝        'foo' 'goo' 'moo' > id="foo" goo="goo" moo="moo"
⍝
⍝     2)    Single simple string beginning with '#' is an id
⍝        '#foo'  > id="foo"
⍝        'goo=moo' '#foo' > goo="moo" id="foo"
⍝
⍝     3)    Single simple string beginning with '.' is treated as a class
⍝        '.foo'  > class="foo"
⍝        'foo' '.goo' 'moo' > id="foo" class="goo" moo="moo"
⍝
⍝     4)    Attributes must be paired either as in 'foo=goo' or ('foo' 'goo'), if there is a single pair, it must be enclosed
⍝        ⊂'href' '#top'  > href="#top"
⍝
⍝        The value portion of an attribute pair may begin with '#' or '.' without special interpretation
⍝        ('href' '#top') '#foo' > href="#top" id="foo"

      :If 0=⎕NC'plain' ⋄ plain←0 ⋄ :EndIf
      arg←eis,arg
      attr←⍬
      split←{0=⊢/v←∨\(≠\'"'=⍵)<'='=⍵:⍵ ⋄ ((~v)/⍵)({'"'∧.=∊1 ¯1↑¨⊂⍵:¯1↓1↓⍵ ⋄ ⍵}1↓v/⍵)}
      :For i :In ⍳⍴arg
          :Select |≡item←,i⊃arg
          :Case 1
              :If ~0∊⍴item
                  :If ∨/eq←'='=item              ⍝ any '=' (i.e.
                  :AndIf 0≠n←eq+.∧nq←~≠\'"'=item
                      :If n=1
                          attr,←⊂split item
                      :ElseIf 1=eq+.∧nq←nq>≠\''''=item
                          attr,←⊂split item
                      :Else ⍝!!! needs more analysis what about cases like:  onchange=''alert("\"we're here)''
                          attr,←⊃,/1 ParseAttr¨item{⎕ML←3 ⋄ ⍵⊂⍺}nq⍲item=' '
                      :EndIf
                  :ElseIf 3>f←'#.'⍳1↑item
                      attr,←⊂((f⊃'id' 'class')(1↓item))
                  :ElseIf plain<i=1
                      attr,←⊂'id'item
                  :Else
                      attr,←⊂2⍴⊂item
                  :EndIf
              :EndIf
          :Case 2
              attr,←⊂item
          :Else
              ('Invalid attribute specification: ',⍕item)⎕SIGNAL 11
          :EndSelect
      :EndFor
    ∇

    ∇ {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

          :If 2=⍴,attr  ⍝ 'attr' 'value' is never shorthanded (e.g. given special treatment for id/class)
          :AndIf 1∧.≥≡¨attr
          :AndIf ~∧/'='∊¨attr
              attr←,⊂attr
          :EndIf

          attr←1 ParseAttr attr
      :EndIf
      :If ~0∊⍴attr
          Attrs[1⊃¨attr]←2⊃¨attr
      :EndIf
      r←⎕THIS
    ∇

    ∇ {r}←{which}SetAttribute attr
    ⍝ set attributes, treating

      :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←SetAttribute attr
      :Else ⋄ r←which SetAttribute attr
      :EndIf
    ∇

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

    ∇ r←{proto}GetAttr attrname
      :Access public
      :If 0=⎕NC'proto' ⋄ proto←'' ⋄ :EndIf
      attrname←eis attrname
      :If 1=⍴attrname
          :Trap 3 ⍝ index error
              r←_values⊃⍨_names⍳attrname
          :Else
              r←proto
          :EndTrap
      :Else
          proto∘GetAttr¨attrname
      :EndIf
    ∇

    ∇ {r}←AddClass c
      :Access public
      :If class≡UNDEF
          class←c
      :Else
          class,←' ',c
      :EndIf
      r←⎕THIS
    ∇

    ∇ {r}←AddStyle s;t
      :Access public
      :If style≡UNDEF
          style←s,(';'=¯1↑s)↓';'
      :Else
          style,←(';'=¯1↑style)↓';',s,(';'=¯1↑s)↓';'
      :EndIf
      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 (1<|≡arg)∧(,2)≡⍴arg
          :AndIf isAttr 2⊃arg
              (content attr)←arg ⋄ SetAttr attr
          :Else
              content←arg
          :EndIf
          Add content
          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 _JQ _SF _DC _JSS _)←#.(_html _JQ _SF _DC _JSS _)
      Position←⎕NS''
    ∇

    ∇ SetUse;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
    ∇

    ∇ Use resource
      :Access public
      :If ~0∊⍴Uses ⋄ Uses←eis Uses ⋄ :EndIf
      Uses,←eis resource
    ∇

    :endsection

    :section Event Handling

    ∇ {handler}←On args;n;i
      :Access public
      args←eis args
      handler←⎕NEW #._JQ.Handler
      handler.(Events Callback ClientData JavaScript Delegates jQueryWrap ScriptWrap Cursor)←args defaultArgs'' 1 '' '' '' 1 1 ¯1
      handler.WidgetRef←⎕THIS
      handler.Page←_PageRef
      :If ¯1=handler.Hourglass ⋄ handler.Hourglass←(,0)≢,handler.Callback ⋄ :EndIf
      :If 0∊n←⍴Handlers
          Handlers,←handler
      :ElseIf n<i←Handlers.Events⍳⊂handler.Events
          Handlers,←handler
      :Else
          Handlers[i]←handler
      :EndIf
    ∇

    ∇ {handler}←One args
      :Access public
      handler←On args
      handler.Method←'one'
    ∇

    ∇ r←RenderHandlers;myid;h
      :Access public ⍝!!! remove this after testing
      r←''
      :If ~0∊⍴Handlers
          :If ∨/0∘∊∘⍴¨Handlers.Selector
              myid←SetId
          :EndIf
          :For h :In Handlers
              :If 0∊⍴h.Selector
                  h.Selector←'#',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

    ∇ r←a FormatAttr w
      :Access public shared
      r←' ',a,'=',Quote HtmlSafeText,⍕w
    ∇

    ∇ 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
          :If 0<⍴vs←Attrs[]
              av,←∊FormatAttr/¨vs
          :EndIf
          av,←RenderStyles
          :If (⊂Tag)∊'html' 'body' 'head'
              r←av Enclose r,h,p
          :Else
              r←(av Enclose r),h,p
          :EndIf
      :EndIf
    ∇

    ∇ r←Postrender r
      :Access overridable
    ∇

    ∇ r←RenderCore list;e;t
      :Access public
    ⍝ do the bulk of the rendering work
      r←''
      list←{0=⍴⍴⍵:{1<|≡⍵:⊃⍵ ⋄ ⍵}⍵ ⋄ ⍵}list
      :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
          SetUse
      :EndIf
    ∇

    :endsection

    :section Utilities

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

    ∇ SetInputName
      :Access public
      ⍝ for input element widgets, this will set the name to the id if not already set
      ⍝ or the id to the name if not already set
      :If (⊂name)∊UNDEF''
          :If (⊂id)∊UNDEF''
              SetId
          :EndIf
          name←id
      :ElseIf UNDEF≡id
          id←name
      :EndIf
    ∇

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

    ∇ r←{a}eis w
      :Access public shared
      r←((,∘⊂)⍣(((isNum w)∨isString w)∧2>|≡w))w ⍝ enclose if simple character
      ⍝r← (,∘⊂)⍣((326∊⎕DR w)<2>|≡w)⊢w ⍝ Enclose if simple
    ∇

    ∇ 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
          :ElseIf isInstance⊃args
              :If ~0∊⍴attr
                  (⊃args).SetAttr attr
              :EndIf
              r←args
          :Else
              (r←⎕NEW HtmlElement).Content←⊂args
          :EndIf
      :EndIf
    ∇

    ∇ {r}←{attr}Push args;c;cl;attr;elm
      :Access public
      :If ~0∊⍴r←args
          c←Content
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :If isInstance r
              :Trap 4 5 ⍝ rank and length error
                  r.Content,←c
              :Else
                  r.Content,←⊂c
              :EndTrap
              Content←r
          :Else
              Content,⍨←⊂r
          :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←{ref}isClass ao
      :Access public shared
      →0↓⍨r←9.4∊⎕NC⊂'ao'
      :If 0≠⎕NC'ref'
          r←ref∊∊⎕CLASS ao
      :EndIf
    ∇

    ∇ r←{ref}isInstance ao
      :Access public shared
      →0↓⍨r←9.2∊⎕NC⊂'ao'
      :If 0≠⎕NC'ref'
          r←ref∊∊⎕CLASS ao
      :EndIf
    ∇

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

    ∇ r←isWidget obj
      :Access public shared
      →0↓⍨r←9.2∊⎕NC⊂'obj'
      r←#._JQ._jqWidget∊∊⎕CLASS obj
    ∇

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

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

    ∇ r←renderIt It
      :Access public shared
      r←{
          0∊⍴⍵:''
          326=⎕DR ⍵:{
              isString ⍵:∊⍵
              2≤|≡⊃⍵:{
                  isClass⊃⊃⍵:((New⊃⍵).Render),renderIt 1↓⍵
                  ,⍕⍵
              }⍵
              isInstance⊃⍵:((⊃⍵).Render),renderIt 1↓⍵
              isClass⊃⍵:((⎕NEW(⊃⍵)).Render),renderIt 1↓⍵
              isChar⊃⍵:(⊃⍵),renderIt 1↓⍵
              (renderIt⊃⍵),renderIt 1↓⍵}⍵
          ,⍕⍵}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
    ∇

    ∇ 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
    ∇

    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←ScriptFollows
      :Access public shared
      r←2↓∊(⎕UCS 13 10)∘,¨Follows
    ∇

    ∇ r←Follows;n
      :Access public shared
      n←⎕XSI{1++/∧\⍵∘≡¨(⊃⍴⍵)↑¨⍺}(⍕⎕THIS),'.'
      r←{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}¨(1+n⊃⎕LC)↓↓(180⌶)n⊃⎕XSI
    ∇

    ∇ r←MarkdownFollows
      :Access public shared
      r←⊃#.MarkAPL.Markdown2HTML Follows
    ∇

    ∇ r←CodeFollows;n
      :Access public shared
      n←⎕XSI{1++/∧\⍵∘≡¨(⊃⍴⍵)↑¨⍺}(⍕⎕THIS),'.'
      r←2↓∊(⎕UCS 13 10)∘,¨{¯1↓⍵/⍨∨\⌽<\∨/¨'⍝<<end>>'∘⍷¨⍵}(1+n⊃⎕LC)↓↓(180⌶)2⊃⎕XSI
      r←'<pre style="font-family:APL385 Unicode">',r,'</pre>'
    ∇

    ∇ r←WrapFollowing tag;text;SplitOnSpaceLines;Trim;FirstCommentBlock
      :Access public shared

     ⍝ USAGE:
     ⍝
     ⍝      Add WrapFollowing 'p class="bodytext"'
     ⍝      ⍝ paragraph may span
     ⍝      ⍝ several lines
     ⍝      ⍝
     ⍝      ⍝ they are separated by empty comment lines
     ⍝
     ⍝ RESULT:
     ⍝
     ⍝      <p class="bodytext">paragraph may span several lines</p>
     ⍝      <p class="bodytext">they are separated by empty comment lines</p>
     ⍝

      Trim←{(∨\' '≠⍵)/⍵}¨
      FirstCommentBlock←{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}
      SplitOnSpaceLines←{1↓¨⍵⊂⍨⍵∧.=¨' '}
      text←(1+2⊃⎕LC)↓↓(⊃⊃⎕CLASS 1⊃⎕RSI).(180⌶)2⊃⎕SI

      r←tag∘#.HtmlUtils.Enclose¨∊¨SplitOnSpaceLines' ',¨⍨' ',⌽¨Trim⌽¨FirstCommentBlock Trim text
    ∇

    ∇ r←what Subst text;names;gv;i;repl;fixL;fixR
      :Access public shared
      r←text
      fixL←'[][{}()?*.+\\^$|]'⎕R'\\\0' ⍝ precede regex metacharacters by \
      fixR←'&' '%' '\\'⎕R'\\\0'        ⍝ add \ to & % \
      :Select ⎕NC⊂'what'
      :Case 9.1 ⍝ namespace
          gv←⍒∊⍴¨names←what.⎕NL ¯2 3 ⍝ variables and functions only, sort so that 'foo' matches before 'fo'
          r←(names[gv]⎕R(fixR¨⍕¨what⍎⍕names[gv]))r
      :Case 2.1 ⍝ substitution pairs
          :If 2=|≡what ⋄ what←,⊂what ⋄ :EndIf
          :If 2≠⍴⍴what ⋄ what←↑what ⋄ :EndIf
          what←,¨what
          r←(fixL¨what[;1])⎕R(fixR¨what[;2])⊢r
      :EndSelect
    ∇

    :endsection

:endclass  ⍝ HtmlElement

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

⍝∇:require =\JSON.dyalog

    ⎕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)

    :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
    :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON

⍝ define shortcuts to namespaces (initialized later)
    :field public shared _html ⍝ reference to base HTML elements namespace
    :field public shared _JQ   ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public shared _SF   ⍝ reference to Syncfusion widgets namespace
    :field public shared _JSS  ⍝ reference to JavaScript Snippets namespace
    :field public shared _DC   ⍝ reference to Dyalog Controls namespace
    :field public shared _     ⍝ 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←{?⍵⊣⎕RL←0}

    ∇ 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←isChar w
      :Access public shared
      r←0 2∊⍨10|⎕DR w
    ∇

    ∇ r←isNum w
      :Access public shared
      r←2|⎕DR w
    ∇

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

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

    ∇ r←isTrue a
      :Access public shared
      →0⍴⍨r←(,1)≡,a       ⍝ boolean
      →0⍴⍨r←#.JSON.true≡a ⍝ namespace
      →0⍴⍨r←'true'≡a      ⍝ string
      →0⍴⍨r←a≡⊂'true'     ⍝ 7161⌶1
    ∇

    ∇ r←isFalse a
      :Access public shared
      →0⍴⍨r←(,0)≡,a        ⍝ boolean
      →0⍴⍨r←#.JSON.false≡a ⍝ namespace
      →0⍴⍨r←'false'≡a      ⍝ string
      →0⍴⍨r←a≡⊂'false'     ⍝ 7161⌶0
    ∇

    ∇ r←a ine w
      :Access public shared
      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
    ∇

    ∇ SyncAttrs arg
      :Implements Trigger id,name,class,style,title,type,value
      :If ⊃≢/arg.(NewValue OldValue)
          arg.Name Set arg.NewValue~UNDEF
      :EndIf
    ∇

    :property keyed Attrs       ⍝ element attributes
    :access public

        ∇ set ra;i;new;there;names;ind
          there←~new←(⍴_names)<i←_names⍳names←fixkeys⊃ra.Indexers
          (_values _names),←new∘/¨ra.NewValue names
          _values[there/i]←there/ra.NewValue
          :For i :In {⍵/⍳⍴⍵}7≥'id' 'class' 'style' 'title' 'value' 'type' 'name'⍳names
              ⍎(i⊃names),'←i⊃ra.NewValue'
          :EndFor
        ∇

        ∇ 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

    ∇ attr←{plain}ParseAttr arg;split;item;t;f;i;eq;nq;items;n
      :Access public shared

⍝ Parse html sttributes
⍝ {plain} - Boolean indicating whether to not interpret a plain first token as an if
⍝ args    - vector of tokens to parse
⍝
⍝     1)    In only the first token, a simple string (i.e. not in the form 'abc=def') without a leading '#' or '.' is treated as an id
⍝        'foo'  > id="foo"
⍝
⍝        In all other positions, a string of this type is treated as a singleton attribute
⍝        'foo' 'goo' 'moo' > id="foo" goo="goo" moo="moo"
⍝
⍝     2)    Single simple string beginning with '#' is an id
⍝        '#foo'  > id="foo"
⍝        'goo=moo' '#foo' > goo="moo" id="foo"
⍝
⍝     3)    Single simple string beginning with '.' is treated as a class
⍝        '.foo'  > class="foo"
⍝        'foo' '.goo' 'moo' > id="foo" class="goo" moo="moo"
⍝
⍝     4)    Attributes must be paired either as in 'foo=goo' or ('foo' 'goo'), if there is a single pair, it must be enclosed
⍝        ⊂'href' '#top'  > href="#top"
⍝
⍝        The value portion of an attribute pair may begin with '#' or '.' without special interpretation
⍝        ('href' '#top') '#foo' > href="#top" id="foo"

      :If 0=⎕NC'plain' ⋄ plain←0 ⋄ :EndIf
      arg←eis,arg
      attr←⍬
      split←{0=⊢/v←∨\(≠\'"'=⍵)<'='=⍵:⍵ ⋄ ((~v)/⍵)({'"'∧.=∊1 ¯1↑¨⊂⍵:¯1↓1↓⍵ ⋄ ⍵}1↓v/⍵)}
      :For i :In ⍳⍴arg
          :Select |≡item←,i⊃arg
          :Case 1
              :If ~0∊⍴item
                  :If ∨/eq←'='=item              ⍝ any '=' (i.e.
                  :AndIf 0≠n←eq+.∧nq←~≠\'"'=item
                      :If n=1
                          attr,←⊂split item
                      :ElseIf 1=eq+.∧nq←nq>≠\''''=item
                          attr,←⊂split item
                      :Else ⍝!!! needs more analysis what about cases like:  onchange=''alert("\"we're here)''
                          attr,←⊃,/1 ParseAttr¨item{⎕ML←3 ⋄ ⍵⊂⍺}nq⍲item=' '
                      :EndIf
                  :ElseIf 3>f←'#.'⍳1↑item
                      attr,←⊂((f⊃'id' 'class')(1↓item))
                  :ElseIf plain<i=1
                      attr,←⊂'id'item
                  :Else
                      attr,←⊂2⍴⊂item
                  :EndIf
              :EndIf
          :Case 2
              attr,←⊂item
          :Else
              ('Invalid attribute specification: ',⍕item)⎕SIGNAL 11
          :EndSelect
      :EndFor
    ∇

    ∇ {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

          :If 2=⍴,attr  ⍝ 'attr' 'value' is never shorthanded (e.g. given special treatment for id/class)
          :AndIf 1∧.≥≡¨attr
          :AndIf ~∧/'='∊¨attr
              attr←,⊂attr
          :EndIf

          attr←1 ParseAttr attr
      :EndIf
      :If ~0∊⍴attr
          Attrs[1⊃¨attr]←2⊃¨attr
      :EndIf
      r←⎕THIS
    ∇

    ∇ {r}←{which}SetAttribute attr
    ⍝ set attributes, treating

      :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←SetAttribute attr
      :Else ⋄ r←which SetAttribute attr
      :EndIf
    ∇

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

    ∇ r←{proto}GetAttr attrname
      :Access public
      :If 0=⎕NC'proto' ⋄ proto←'' ⋄ :EndIf
      attrname←eis attrname
      :If 1=⍴attrname
          :Trap 3 ⍝ index error
              r←_values⊃⍨_names⍳attrname
          :Else
              r←proto
          :EndTrap
      :Else
          proto∘GetAttr¨attrname
      :EndIf
    ∇

    ∇ r←AddClass c
      :Access public
      :If class≡UNDEF
          class←c
      :Else
          class,←' ',c
      :EndIf
    ∇

    ∇ r←AddStyle s;t
      :Access public
      :If style≡UNDEF
          style←s,(';'=¯1↑s)↓';'
      :Else
          style,←(';'=¯1↑style)↓';',s,(';'=¯1↑s)↓';'
      :EndIf
    ∇

    :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 (1<|≡arg)∧(,2)≡⍴arg
          :AndIf isAttr 2⊃arg
              (content attr)←arg ⋄ SetAttr attr
          :Else
              content←arg
          :EndIf
          Add content
          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 _JQ _SF _DC _JSS _)←#.(_html _JQ _SF _DC _JSS _)
      Position←⎕NS''
    ∇

    ∇ SetUse;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
    ∇

    ∇ Use resource
      :Access public
      :If ~0∊⍴Uses ⋄ Uses←eis Uses ⋄ :EndIf
      Uses,←eis resource
    ∇

    :endsection

    :section Event Handling

    ∇ {handler}←On args;n;i
      :Access public
      args←eis args
      handler←⎕NEW #._JQ.Handler
      handler.(Events Callback ClientData JavaScript Delegates jQueryWrap ScriptWrap Cursor)←args defaultArgs'' 1 '' '' '' 1 1 ¯1
      handler.WidgetRef←⎕THIS
      handler.Page←_PageRef
      :If ¯1=handler.Hourglass ⋄ handler.Hourglass←(,0)≢,handler.Callback ⋄ :EndIf
      :If 0∊n←⍴Handlers
          Handlers,←handler
      :ElseIf n<i←Handlers.Events⍳⊂handler.Events
          Handlers,←handler
      :Else
          Handlers[i]←handler
      :EndIf
    ∇

    ∇ {handler}←One args
      :Access public
      handler←On args
      handler.Method←'one'
    ∇

    ∇ r←RenderHandlers;myid;h
      :Access public ⍝!!! remove this after testing
      r←''
      :If ~0∊⍴Handlers
          :If ∨/0∘∊∘⍴¨Handlers.Selector
              myid←SetId
          :EndIf
          :For h :In Handlers
              :If 0∊⍴h.Selector
                  h.Selector←'#',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

    ∇ r←a FormatAttr w
      :Access public shared
      r←' ',a,'=',Quote HtmlSafeText,⍕w
    ∇

    ∇ 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
          :If 0<⍴vs←Attrs[]
              av,←∊FormatAttr/¨vs
          :EndIf
          av,←RenderStyles
          :If (⊂Tag)∊'html' 'body' 'head'
              r←av Enclose r,h,p
          :Else
              r←(av Enclose r),h,p
          :EndIf
      :EndIf
    ∇

    ∇ r←Postrender r
      :Access overridable
    ∇

    ∇ r←RenderCore list;e;t
      :Access public
    ⍝ do the bulk of the rendering work
      r←''
      list←{0=⍴⍴⍵:{1<|≡⍵:⊃⍵ ⋄ ⍵}⍵ ⋄ ⍵}list
      :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
          SetUse
      :EndIf
    ∇

    :endsection

    :section Utilities

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

    ∇ SetInputName
      :Access public
      ⍝ for input element widgets, this will set the name to the id if not already set
      ⍝ or the id to the name if not already set
      :If (⊂name)∊UNDEF''
          :If (⊂id)∊UNDEF''
              SetId
          :EndIf
          name←id
      :ElseIf UNDEF≡id
          id←name
      :EndIf
    ∇

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

    ∇ r←{a}eis w
      :Access public shared
      r←((,∘⊂)⍣(((isNum w)∨isString w)∧2>|≡w))w ⍝ enclose if simple character
      ⍝r← (,∘⊂)⍣((326∊⎕DR w)<2>|≡w)⊢w ⍝ Enclose if simple
    ∇

    ∇ 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
          :ElseIf isInstance⊃args
              :If ~0∊⍴attr
                  (⊃args).SetAttr attr
              :EndIf
              r←args
          :Else
              (r←⎕NEW HtmlElement).Content←⊂args
          :EndIf
      :EndIf
    ∇

    ∇ {r}←{attr}Push args;c;cl;attr;elm
      :Access public
      :If ~0∊⍴r←args
          c←Content
          :If 0=⎕NC'attr' ⋄ attr←'' ⋄ :EndIf
          r←attr ParseArgs args
          :If isInstance r
              :Trap 4 5 ⍝ rank and length error
                  r.Content,←c
              :Else
                  r.Content,←⊂c
              :EndTrap
              Content←r
          :Else
              Content,⍨←⊂r
          :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←{ref}isClass ao
      :Access public shared
      →0↓⍨r←9.4∊⎕NC⊂'ao'
      :If 0≠⎕NC'ref'
          r←ref∊∊⎕CLASS ao
      :EndIf
    ∇

    ∇ r←{ref}isInstance ao
      :Access public shared
      →0↓⍨r←9.2∊⎕NC⊂'ao'
      :If 0≠⎕NC'ref'
          r←ref∊∊⎕CLASS ao
      :EndIf
    ∇

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

    ∇ r←isWidget obj
      :Access public shared
      →0↓⍨r←9.2∊⎕NC⊂'obj'
      r←#._JQ._jqWidget∊∊⎕CLASS obj
    ∇

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

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

    ∇ r←renderIt It
      :Access public shared
      r←{
          0∊⍴⍵:''
          326=⎕DR ⍵:{
              isString ⍵:∊⍵
              2≤|≡⊃⍵:{
                  isClass⊃⊃⍵:((New⊃⍵).Render),renderIt 1↓⍵
                  ,⍕⍵
              }⍵
              isInstance⊃⍵:((⊃⍵).Render),renderIt 1↓⍵
              isClass⊃⍵:((⎕NEW(⊃⍵)).Render),renderIt 1↓⍵
              isChar⊃⍵:(⊃⍵),renderIt 1↓⍵
              (renderIt⊃⍵),renderIt 1↓⍵}⍵
          ,⍕⍵}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
    ∇

    ∇ 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
    ∇

    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←ScriptFollows
      :Access public shared
      r←2↓∊(⎕UCS 13 10)∘,¨Follows
    ∇

    ∇ r←Follows;n
      :Access public shared
      n←⎕XSI{1++/∧\⍵∘≡¨(⊃⍴⍵)↑¨⍺}(⍕⎕THIS),'.'
      r←{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}¨(1+n⊃⎕LC)↓↓(180⌶)n⊃⎕XSI
    ∇

    ∇ r←MarkdownFollows
      :Access public shared
      r←⊃#.MarkAPL.Markdown2HTML Follows
    ∇

    ∇ r←CodeFollows;n
      :Access public shared
      n←⎕XSI{1++/∧\⍵∘≡¨(⊃⍴⍵)↑¨⍺}(⍕⎕THIS),'.'
      r←2↓∊(⎕UCS 13 10)∘,¨{¯1↓⍵/⍨∨\⌽<\∨/¨'⍝<<end>>'∘⍷¨⍵}(1+n⊃⎕LC)↓↓(180⌶)2⊃⎕XSI
      r←'<pre style="font-family:APL385 Unicode">',r,'</pre>'
    ∇

    ∇ r←WrapFollowing tag;text;SplitOnSpaceLines;Trim;FirstCommentBlock
      :Access public shared

     ⍝ USAGE:
     ⍝
     ⍝      Add WrapFollowing 'p class="bodytext"'
     ⍝      ⍝ paragraph may span
     ⍝      ⍝ several lines
     ⍝      ⍝
     ⍝      ⍝ they are separated by empty comment lines
     ⍝
     ⍝ RESULT:
     ⍝
     ⍝      <p class="bodytext">paragraph may span several lines</p>
     ⍝      <p class="bodytext">they are separated by empty comment lines</p>
     ⍝

      Trim←{(∨\' '≠⍵)/⍵}¨
      FirstCommentBlock←{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}
      SplitOnSpaceLines←{1↓¨⍵⊂⍨⍵∧.=¨' '}
      text←(1+2⊃⎕LC)↓↓(⊃⊃⎕CLASS 1⊃⎕RSI).(180⌶)2⊃⎕SI

      r←tag∘#.HtmlUtils.Enclose¨∊¨SplitOnSpaceLines' ',¨⍨' ',⌽¨Trim⌽¨FirstCommentBlock Trim text
    ∇

    ∇ r←what Subst text;names;gv;i;repl;fixL;fixR
      :Access public shared
      r←text
      fixL←'[][{}()?*.+\\^$|]'⎕R'\\\0' ⍝ precede regex metacharacters by \
      fixR←'&' '%' '\\'⎕R'\\\0'        ⍝ add \ to & % \
      :Select ⎕NC⊂'what'
      :Case 9.1 ⍝ namespace
          gv←⍒∊⍴¨names←what.⎕NL ¯2 3 ⍝ variables and functions only, sort so that 'foo' matches before 'fo'
          r←(names[gv]⎕R(fixR¨⍕¨what⍎⍕names[gv]))r
      :Case 2.1 ⍝ substitution pairs
          :If 2=|≡what ⋄ what←,⊂what ⋄ :EndIf
          :If 2≠⍴⍴what ⋄ what←↑what ⋄ :EndIf
          what←,¨what
          r←(fixL¨what[;1])⎕R(fixR¨what[;2])⊢r
      :EndSelect
    ∇

    :endsection

:endclass  ⍝ HtmlElement


Disable widget/tree messages

Disable widget/tree messages

: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_JAVASCRIPT shortcuts to MiServer's built-in functions for generating JavaScript     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

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

    ∇ r←{a}Ⓒ w ⍝ Mnemonic for the ⊆ glyph
      r←⊂⍣(1=≡w)⊢w ⍝ enclose if simple
      →0/⍨900⌶⍬    ⍝ but when dyadic...
      r,⍨←⊂a       ⍝ ... Iverson link
    ∇

    NoSt←~∘'*'¨ ⍝ Remove stars from all elements in a VTV

    Sl←'/'∘= ⍝ Slashes

    In←∨/⍷ ⍝ Is found in

    Z←⊃⌽ ⍝ Last

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

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

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

    Clean←'_'⎕R' ' ⍝ Underscores  →  spaces

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

    CaseLess←{⊃⍺⍺/LowerCase¨⍺ ⍵} ⍝ Apply fn without regard to upper/lower case

      LowerCase←{ ⍝ Fold character array to lowercase
          b←(∊w←⍵)∊#.Strings.upperAlphabet ⍝ location of uppercase chars
          (b/∊w)←#.Strings.lc b/∊w         ⍝ change just those to lowercase
          w                                ⍝ return the entire array
      }

      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
          '⍝'~⍨1↓⊃res                                            ⍝ strip spaces and lamp
      }

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

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Relevant←{C.eocontrols⊂⍵:C.files[C.rankings⊃⍨C.controlsoi⊂⍵] ⋄ 0⍴⊂''} ⍝ Samples that demo ⍵

    Score←{(102-2×(50↑Z⍵)⍳⊂⍺)-'Advanced'In⊃⍵} ⍝ 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
      }

      DirTree←{ ⍝ Make arg for ejTreeView based on files in folder and subfolders
          ⍺←1↓FILEEXT                                      ⍝ default file extention
          list←⍺ List ⍵                                    ⍝ get list of files
          0∊⍴list:0 3⍴0 '' ''                              ⍝ skip parent if no children
          parent←1(LastSub ⍵)('/',⍵,'/')                   ⍝ top level entry
          levels←((+/-Z)¨Sl list)+3-+/Sl 3⊃parent          ⍝ level is / count, adjust for final /
          DelEmpty⍣≡parent⍪⍉↑levels(NoExt∘Name¨list)list ⍝ recursively remove empty dirs
      }

    ∇ 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←#.Files.GetVTV #.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

    LastSub←{⍵↑⍨1-'/'⍳⍨⌽⍵}     ⍝ '/aaa/bbb/ccc/ddd'  →         'ddd'
    ⍝LastSub←{1 Name ⍵}

    Name←{1↓Z((Sl⊂⊢)⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)
    ⍝Name←{⍺←0 ⋄ {⍵↑⍨1-'/'⍳⍨⌽⍵}{(+/∨\'/'≠⌽⍵)↑⍵}⍣⍺⊢⍵} ⍝ prefix 1 to exclude trailing empty subs

    NsControl←{1↓¨FirstLast ⍵} ⍝ '/aaa/bbb/ccc/ddd'  →  'aaa'  'ddd'

    FirstLast←(⊃Ⓒ Z)(Sl⊂⊢) ⍝ '/aaa/bbb/ccc/ddd'  → '/aaa' '/ddd'
    ⍝NsControl←1∘{⍺←0 ⋄ 1↓¨⍣⍺⊢(⊃Ⓒ Z)(Sl⊂⊢)⍵} ⍝ prefix 1 to remove leading / from substrings

    Core←∊∘FirstLast¨'*'∘∊Of ⍝ Those ending in * but without middle category

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

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

    SortFiles←{⍵[⍋↑LastSub¨⍵]} ⍝ Sort by filename while ignoring path

    DelEmpty←{⍵⌿⍨(0,⍨2</⍵[;1])∨'/'≠⊃¨⌽¨⍵[;3]} ⍝ Remove folders that do not have files

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

    IsDemo←{∨/∊'Simple' 'Advanced'⍷¨⊂⍵}⍝ Is this sample a one-widget-demo

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

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

    Format←'small;border:none'∘#.HTMLInput.APLToHTMLColour ⍝ Make coloured HTML

    NewObj←{('width=740 height=900 data=',Q ⍵)New _.object} ⍝ Embed a full-page file

    Frame←{⍺←⊢ ⋄ ⍺'.iframed'('src=',Q ⍵,'?NoWrapper=1')New _.iframe} ⍝ _.iframe without wrapper

    NewWinA←{'target="_blank"' 'title="Click to open in a new window"'New _.A,⍵} ⍝ New-tab link

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1}  ⍝ Horizontal StackPanel

    Doc←{∊'/Documentation/DyalogAPIs/WidgetDoc?namespace=_' '&widget=',¨⍵} ⍝ Address of WidgetDoc

    BuildTree←{⍺{(⊂⍺),⍵}¨(Levels NoSt ⍵)(NoExt¨Name¨NoSt ⍵)⍵} ⍝ Build argument for ejTreeView

      Info←{ ⍝ Get info (default: description) on a control
          ⍺←2                                           ⍝ 1=Name, 2=Description, 3=Notes
          control←(⍳∘'.'↓⊢)⍵                            ⍝ remove ns
          C.eoinfo⊂control:C.info[;⍺]⊃⍨C.infooi⊂control ⍝ if we have, use that
          'Description'Section ⎕SRC #⍎⍵                 ⍝ else extract it
      }

⍝     ListItem←{ ⍝ Generate pre-rendered entry for lists
⍝          nost←⍵~'*'
⍝          noext←~'.'∊nost
⍝          r←'
',Clean NoExt LastSub nost
⍝          ∨/noext,FILEEXT⍷nost:r,' – ',('Description'Section Read nost),'
' ⍝ add desc
⍝          r,'
'
⍝      }

    ∇ r←ListItem item;nost;noext ⍝ Generate pre-rendered lists item for performance (OO is slow)
      nost←item~'*'
      noext←~'.'∊nost
      r←'
'                           ⍝ end of paragraph tag - nest bold tag
      r,←Clean NoExt LastSub nost              ⍝ just the name without path or extention
      r,←''                           ⍝ end of bold tag
      :If ∨/noext,FILEEXT⍷nost                 ⍝ if default or no extension:
          r,←' – ','Description'Section Read nost  ⍝ add dash and description from source
      :EndIf
      r,←'
'                                ⍝ close the paragraph
    ∇

    ∇ div←Tree title;xx;tree ⍝ Make tree based on its heading
      div←New _.div                                             ⍝ container
      div.Add _.hr                                              ⍝ horizontal ruler
      '.cat'div.Add _.p title                                   ⍝ heading
      xx←⎕A∩title                                               ⍝ initials of heading; XX
      tree←⍎'MakeTree',xx                                       ⍝ calls MakeTreeXX
      tree←('#tree',xx)div.Add _.ejTreeView tree                ⍝ gives id="XX"
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id') ⍝ sends along selected node's id
    ∇

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

    :SECTION E_JAVASCRIPT ⍝ FUNCTIONS THAT GENERATE PSEUDO-JAVASCRIPT FOR CALLBACKS

    Hide←{Execute'$("',⍵,'").hide()'} ⍝ Toggle object visibility off

    Show←{Execute'$("',⍵,'").show()'} ⍝ Toggle object visibility on

    ∇ r←ReplaceRelMsg ⍝ Replace message with count of related samples
      r←'[for=''ShowRelated'']'Replace{'Show ',(⍕⍵),' related sample','.',⍨'s'/⍨⍵≠1}0⌈¯1+≢CURRFILES
    ∇

    ∇ r←RefreshHandler ⍝ Refresh event handler for newly inserted objects
      r←'#Handler'Replace New _.Handler'.listitem' 'click' 'OnList'
    ∇

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

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

    ∇ r←NSS
      :Access public
      r←'DC' 'SF' 'JQ' 'html'
    ∇

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'SyncFusion' 'JQueryUI' 'Base HTML'
    ∇

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

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

    ∇ C←C;scores;demoes;list;nl ⍝ Return ref to cache (init one if nonexistant)
      :Access public
      :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.files←⊃⍪/{List'Examples/',⍵}¨NSS                       ⍝ sample filenames
              demoes←{(Words'Control'Section Read ⍵)~'_',¨NSS}¨C.files ⍝ controls demoed in each
              C.controls←∪↑,/demoes                                    ⍝ cache all controls
              scores←C.controls∘.Score↓⍉↑C.files demoes                ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores        ⍝ cache all rankings
              C.controlsoi←C.controls∘⍳ ⋄ C.eocontrols←∊∘C.controls    ⍝ cache hash tables
              C.info←FromCSV Read'Examples/Data/info.csv'              ⍝ cache lookup table
              nl←'.*[A-Z].*'⎕S'\0'#._.⎕NL ¯9                           ⍝ all widgets except html
              C.info⍪←↑{⍵('Description'Section ⎕SRC⍎'#._.',⍵)''}¨nl    ⍝ get descriptions
              C.info←C.info[∪⍳⍨C.info[;1];]                            ⍝ filter duplicates out
              C.infooi←C.info[;1]∘⍳ ⋄ C.eoinfo←∊∘(C.info[;1])          ⍝ cache hash tables
          :Else
              C←⍎CACHE                                             ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

    ∇ r←TREE;all;tree
      :Access public
      r←{⍵/⍨⍵∨.≠¨' '}Read'Examples/Data/tree.txt' ⍝ load/cache tree and remove blank lines
      r/⍨←'⍝'≠⊃¨r                                 ⍝ remove comment lines
      →0 ⍝ BPB
      tree←'/([^/*]+)(\*|$)'⎕S'\1'⊢r~¨' '
      all←'^.(?![A-Z])'⎕S'%'⊢#._DC.⎕NL ¯9
      all,←'^[a-z]'⎕S'%'⊢#._html.⎕NL ¯9
      all,←'^ej[A-Z]'⎕S'%'⊢#._SF.⎕NL ¯9
      all,←'^jq[A-Z]'⎕S'%'⊢#._JQ.⎕NL ¯9
      ⎕←{×≢⍵:'** Widgets found in the namespaces, but missing from the tree: ',⍕⍵ ⋄ 0 0⍴0}all~tree
      ⎕←{×≢⍵:'** Widgets found in the tree, but missing from the namespaces: ',⍕⍵ ⋄ 0 0⍴0}tree~all

    ∇

    ∇ r←DOCS
      :Access public
      r←(Read'Examples/Data/docs.txt')~⊂'' ⍝ load/cache tree and remove blank lines
    ∇

    :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_JAVASCRIPT shortcuts to MiServer's built-in functions for generating JavaScript     │ ⍝
⍝ │                                                                                │ ⍝
⍝ │     ∘ F_CONSTANTS niladic functions that return unchanging values and cache                 │ ⍝
⍝ │                                                                                │ ⍝
⍝ └─────────────────────────────────────────────────────────────────────────────────────────────┘ ⍝
⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝ ⍝

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

    ∇ r←{a}Ⓒ w ⍝ Mnemonic for the ⊆ glyph
      r←⊂⍣(1=≡w)⊢w ⍝ enclose if simple
      →0/⍨900⌶⍬    ⍝ but when dyadic...
      r,⍨←⊂a       ⍝ ... Iverson link
    ∇

    NoSt←~∘'*'¨ ⍝ Remove stars from all elements in a VTV

    Sl←'/'∘= ⍝ Slashes

    In←∨/⍷ ⍝ Is found in

    Z←⊃⌽ ⍝ Last

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

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

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

    Clean←'_'⎕R' ' ⍝ Underscores  →  spaces

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

    CaseLess←{⊃⍺⍺/LowerCase¨⍺ ⍵} ⍝ Apply fn without regard to upper/lower case

      LowerCase←{ ⍝ Fold character array to lowercase
          b←(∊w←⍵)∊#.Strings.upperAlphabet ⍝ location of uppercase chars
          (b/∊w)←#.Strings.lc b/∊w         ⍝ change just those to lowercase
          w                                ⍝ return the entire array
      }

      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
          '⍝'~⍨1↓⊃res                                            ⍝ strip spaces and lamp
      }

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

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

    Relevant←{C.eocontrols⊂⍵:C.files[C.rankings⊃⍨C.controlsoi⊂⍵] ⋄ 0⍴⊂''} ⍝ Samples that demo ⍵

    Score←{(102-2×(50↑Z⍵)⍳⊂⍺)-'Advanced'In⊃⍵} ⍝ 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
      }

      DirTree←{ ⍝ Make arg for ejTreeView based on files in folder and subfolders
          ⍺←1↓FILEEXT                                      ⍝ default file extention
          list←⍺ List ⍵                                    ⍝ get list of files
          0∊⍴list:0 3⍴0 '' ''                              ⍝ skip parent if no children
          parent←1(LastSub ⍵)('/',⍵,'/')                   ⍝ top level entry
          levels←((+/-Z)¨Sl list)+3-+/Sl 3⊃parent          ⍝ level is / count, adjust for final /
          DelEmpty⍣≡parent⍪⍉↑levels(NoExt∘Name¨list)list ⍝ recursively remove empty dirs
      }

    ∇ 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←#.Files.GetVTV #.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

    LastSub←{⍵↑⍨1-'/'⍳⍨⌽⍵}     ⍝ '/aaa/bbb/ccc/ddd'  →         'ddd'
    ⍝LastSub←{1 Name ⍵}

    Name←{1↓Z((Sl⊂⊢)⍵)~⊂,'/'}  ⍝ '/aaa/bbb/ccc/ddd/' →         'ddd' (only in BuildTree)
    ⍝Name←{⍺←0 ⋄ {⍵↑⍨1-'/'⍳⍨⌽⍵}{(+/∨\'/'≠⌽⍵)↑⍵}⍣⍺⊢⍵} ⍝ prefix 1 to exclude trailing empty subs

    NsControl←{1↓¨FirstLast ⍵} ⍝ '/aaa/bbb/ccc/ddd'  →  'aaa'  'ddd'

    FirstLast←(⊃Ⓒ Z)(Sl⊂⊢) ⍝ '/aaa/bbb/ccc/ddd'  → '/aaa' '/ddd'
    ⍝NsControl←1∘{⍺←0 ⋄ 1↓¨⍣⍺⊢(⊃Ⓒ Z)(Sl⊂⊢)⍵} ⍝ prefix 1 to remove leading / from substrings

    Core←∊∘FirstLast¨'*'∘∊Of ⍝ Those ending in * but without middle category

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

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

    SortFiles←{⍵[⍋↑LastSub¨⍵]} ⍝ Sort by filename while ignoring path

    DelEmpty←{⍵⌿⍨(0,⍨2</⍵[;1])∨'/'≠⊃¨⌽¨⍵[;3]} ⍝ Remove folders that do not have files

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

    IsDemo←{∨/∊'Simple' 'Advanced'⍷¨⊂⍵}⍝ Is this sample a one-widget-demo

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

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

    Format←'small;border:none'∘#.HTMLInput.APLToHTMLColour ⍝ Make coloured HTML

    NewObj←{('width=740 height=900 data=',Q ⍵)New _.object} ⍝ Embed a full-page file

    Frame←{⍺←⊢ ⋄ ⍺'.iframed'('src=',Q ⍵,'?NoWrapper=1')New _.iframe} ⍝ _.iframe without wrapper

    NewWinA←{'target="_blank"' 'title="Click to open in a new window"'New _.A,⍵} ⍝ New-tab link

    Horz←{⍺←⊢ ⋄ r⊣(r←⍺ New _.StackPanel ⍵).Horizontal←1}  ⍝ Horizontal StackPanel

    Doc←{∊'/Documentation/DyalogAPIs/WidgetDoc?namespace=_' '&widget=',¨⍵} ⍝ Address of WidgetDoc

    BuildTree←{⍺{(⊂⍺),⍵}¨(Levels NoSt ⍵)(NoExt¨Name¨NoSt ⍵)⍵} ⍝ Build argument for ejTreeView

      Info←{ ⍝ Get info (default: description) on a control
          ⍺←2                                           ⍝ 1=Name, 2=Description, 3=Notes
          control←(⍳∘'.'↓⊢)⍵                            ⍝ remove ns
          C.eoinfo⊂control:C.info[;⍺]⊃⍨C.infooi⊂control ⍝ if we have, use that
          'Description'Section ⎕SRC #⍎⍵                 ⍝ else extract it
      }

⍝     ListItem←{ ⍝ Generate pre-rendered entry for lists
⍝          nost←⍵~'*'
⍝          noext←~'.'∊nost
⍝          r←'
',Clean NoExt LastSub nost
⍝          ∨/noext,FILEEXT⍷nost:r,' – ',('Description'Section Read nost),'
' ⍝ add desc
⍝          r,'
'
⍝      }

    ∇ r←ListItem item;nost;noext ⍝ Generate pre-rendered lists item for performance (OO is slow)
      nost←item~'*'
      noext←~'.'∊nost
      r←'
'                           ⍝ end of paragraph tag - nest bold tag
      r,←Clean NoExt LastSub nost              ⍝ just the name without path or extention
      r,←''                           ⍝ end of bold tag
      :If ∨/noext,FILEEXT⍷nost                 ⍝ if default or no extension:
          r,←' – ','Description'Section Read nost  ⍝ add dash and description from source
      :EndIf
      r,←'
'                                ⍝ close the paragraph
    ∇

    ∇ div←Tree title;xx;tree ⍝ Make tree based on its heading
      div←New _.div                                             ⍝ container
      div.Add _.hr                                              ⍝ horizontal ruler
      '.cat'div.Add _.p title                                   ⍝ heading
      xx←⎕A∩title                                               ⍝ initials of heading; XX
      tree←⍎'MakeTree',xx                                       ⍝ calls MakeTreeXX
      tree←('#tree',xx)div.Add _.ejTreeView tree                ⍝ gives id="XX"
      tree.On'nodeSelect' 'OnTree'('node' 'eval' 'argument.id') ⍝ sends along selected node's id
    ∇

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

    :SECTION E_JAVASCRIPT ⍝ FUNCTIONS THAT GENERATE PSEUDO-JAVASCRIPT FOR CALLBACKS

    Hide←{Execute'$("',⍵,'").hide()'} ⍝ Toggle object visibility off

    Show←{Execute'$("',⍵,'").show()'} ⍝ Toggle object visibility on

    ∇ r←ReplaceRelMsg ⍝ Replace message with count of related samples
      r←'[for=''ShowRelated'']'Replace{'Show ',(⍕⍵),' related sample','.',⍨'s'/⍨⍵≠1}0⌈¯1+≢CURRFILES
    ∇

    ∇ r←RefreshHandler ⍝ Refresh event handler for newly inserted objects
      r←'#Handler'Replace New _.Handler'.listitem' 'click' 'OnList'
    ∇

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

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

    ∇ r←NSS
      :Access public
      r←'DC' 'SF' 'JQ' 'html'
    ∇

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'SyncFusion' 'JQueryUI' 'Base HTML'
    ∇

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

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

    ∇ C←C;scores;demoes;list;nl ⍝ Return ref to cache (init one if nonexistant)
      :Access public
      :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.files←⊃⍪/{List'Examples/',⍵}¨NSS                       ⍝ sample filenames
              demoes←{(Words'Control'Section Read ⍵)~'_',¨NSS}¨C.files ⍝ controls demoed in each
              C.controls←∪↑,/demoes                                    ⍝ cache all controls
              scores←C.controls∘.Score↓⍉↑C.files demoes                ⍝ controls vs files
              C.rankings←(+/0<scores)↑¨↓⍒#.Utils.∆rank 1⊢scores        ⍝ cache all rankings
              C.controlsoi←C.controls∘⍳ ⋄ C.eocontrols←∊∘C.controls    ⍝ cache hash tables
              C.info←FromCSV Read'Examples/Data/info.csv'              ⍝ cache lookup table
              nl←'.*[A-Z].*'⎕S'\0'#._.⎕NL ¯9                           ⍝ all widgets except html
              C.info⍪←↑{⍵('Description'Section ⎕SRC⍎'#._.',⍵)''}¨nl    ⍝ get descriptions
              C.info←C.info[∪⍳⍨C.info[;1];]                            ⍝ filter duplicates out
              C.infooi←C.info[;1]∘⍳ ⋄ C.eoinfo←∊∘(C.info[;1])          ⍝ cache hash tables
          :Else
              C←⍎CACHE                                             ⍝ establish shortcut
          :EndIf
      :EndHold
    ∇

    ∇ r←TREE;all;tree
      :Access public
      r←{⍵/⍨⍵∨.≠¨' '}Read'Examples/Data/tree.txt' ⍝ load/cache tree and remove blank lines
      r/⍨←'⍝'≠⊃¨r                                 ⍝ remove comment lines

      tree←'/([^/*]+)(\*|$)'⎕S'\1'⊢r~¨' '
      all←'^.(?![A-Z])'⎕S'%'⊢#._DC.⎕NL ¯9
      all,←'^[a-z]'⎕S'%'⊢#._html.⎕NL ¯9
      all,←'^ej[A-Z]'⎕S'%'⊢#._SF.⎕NL ¯9
      all,←'^jq[A-Z]'⎕S'%'⊢#._JQ.⎕NL ¯9
      ⎕←{×≢⍵:'** Widgets found in the namespaces, but missing from the tree: ',⍕⍵ ⋄ 0 0⍴0}all~tree
      ⎕←{×≢⍵:'** Widgets found in the tree, but missing from the namespaces: ',⍕⍵ ⋄ 0 0⍴0}tree~all

    ∇

    ∇ r←DOCS
      :Access public
      r←(Read'Examples/Data/docs.txt')~⊂'' ⍝ load/cache tree and remove blank lines
    ∇

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

:EndNamespace


Updated _DC.Tabs

Updated _DC.Tabs

:class Tabs : #._html.div
⍝ Description:: Dyalog Tabs widget
⍝ Constructor:: [titles [sections]] | [titles_sections]
⍝ titles          - vector of char vectors containing titles to appear on tabs
⍝ sections        - vector of vectors containing HTML content for each tab
⍝ titles_sections - 2-column matrix of titles [;1] and tabs [;2] as above
⍝                   or vector of 2 element title/tab vectors
⍝ Public Fields::
⍝ Titles          - vector of char vectors containing titles to appear on tabs
⍝ Sections        - vector of vectors containing HTML content for each tab
⍝ Examples::
⍝ Tabs 'Title1' 'Title2'
⍝ Tabs ('Title1' 'Title2')('Section1' 'Section2')
⍝ Tabs 2 2⍴'Title1' 'Section1' 'Title2' 'Section2'

    :field public shared readonly ApiLevel←3
    :Field public Titles←0⍴⊂''
    :Field public Sections←0⍴⊂''

    ∇ Make
      :Access public
      :Implements constructor
      Uses←'dcTabs'
    ∇

    ∇ Make1 args
      :Access public
      :Implements constructor
      Uses←'dcTabs'
      args←eis args
      :If 1=⍴args
          :If 2=⍴⍴1⊃args ⍝ matrix arg?
              (Titles Sections)←↓[2]1⊃args
          :Else ⍝ single title
              Titles←args
          :EndIf
      :Else
          (Titles Sections)←eis¨2↑args
      :EndIf
    ∇

    ∇ {title}AddSection content
      :Access public
      :If 0=⎕NC'title' ⋄ title←'Section ',⍕1+⍴Titles ⋄ :EndIf
      Titles,←⊂title
      Sections,←⊂content
    ∇

    ∇ r←Render;title;section;numbers;n;group
      :Access public
      SetId
      SetUse
      group←2↓GenId
      numbers←{2↓GenId}¨Sections
      AddClass'dc-tabs'
      ⍝ HTML structure
      Content←''

      :For title n :InEach Titles numbers
          :If isRef title ⋄ title←title.Render ⋄ :EndIf
          Content,←'<input type="radio" name="dc-tabs',group,'" id="dc-tabT',n,'"'
          :If n≡⊃numbers ⋄ Content,←' checked="checked"' ⋄ :EndIf
          Content,←'/>',title,''
      :EndFor

      :For section n :InEach Sections numbers
          :If isRef section ⋄ section←section.Render ⋄ :EndIf
          Content,←'
',section,'
'
      :EndFor

      r←⎕BASE.Render

      r,←''
    ∇
:endclass

:class Tabs : #._html.div
⍝ Description:: Dyalog Tabs widget
⍝ Constructor:: [titles [sections]] | [titles_sections]
⍝ titles          - vector of char vectors containing titles to appear on tabs
⍝ sections        - vector of vectors containing HTML content for each tab
⍝ titles_sections - 2-column matrix of titles [;1] and tabs [;2] as above
⍝                   or vector of 2 element title/tab vectors
⍝ Public Fields::
⍝ Titles          - vector of char vectors containing titles to appear on tabs
⍝ Sections        - vector of vectors containing HTML content for each tab
⍝ Examples::
⍝ Tabs 'Title1' 'Title2'
⍝ Tabs ('Title1' 'Title2')('Section1' 'Section2')
⍝ Tabs 2 2⍴'Title1' 'Section1' 'Title2' 'Section2'

    :field public shared readonly ApiLevel←3
    :Field public Titles←0⍴⊂''
    :Field public Sections←0⍴⊂''

    ∇ Make
      :Access public
      :Implements constructor
      Uses←'dcTabs'
    ∇

    ∇ Make1 args
      :Access public
      :Implements constructor
      Uses←'dcTabs'
      args←eis args
      :If 1=⍴args
          :If 2=⍴⍴1⊃args ⍝ matrix arg?
              (Titles Sections)←↓[2]1⊃args
          :Else ⍝ single title
              Titles←args
          :EndIf
      :Else
          (Titles Sections)←eis¨2↑args
      :EndIf
    ∇

    ∇ {title}AddSection content
      :Access public
      :If 0=⎕NC'title' ⋄ title←'Section ',⍕1+⍴Titles ⋄ :EndIf
      Titles,←⊂title
      Sections,←⊂content
    ∇

    ∇ r←Render;title;section;numbers;n;group
      :Access public
      group←2↓GenId
      numbers←{2↓GenId}¨Sections

      ⍝ HTML structure
      Content←'
'
      :For title n :InEach Titles numbers
          :If isRef title ⋄ title←title.Render ⋄ :EndIf
          Content,←'<input type="radio" name="dc-tabs',group,'" id="dc-tabT',n,'"'
          :If n≡⊃numbers ⋄ Content,←' checked="checked"' ⋄ :EndIf
          Content,←'/>',title,''
      :EndFor
      :For section n :InEach Sections numbers
          :If isRef section ⋄ section←section.Render ⋄ :EndIf
          Content,←'
',section,'
'
      :EndFor
      Content,←'
'

      ⍝ CSS This matchs tabs displaying to their associated radio inputs
      Content,←''

      r←⎕base.Render
    ∇
:endclass