Update HtmlElement.dyalog

Update HtmlElement.dyalog

Added ultra-conservative code (Morten) to fix error that came up in my work on FontAwesome-widget...

: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 _true←#.JSON.true     ⍝ same definition as in #.JSON
    :field public shared _false←#.JSON.false   ⍝ same definition as in #.JSON

⍝ define shortcuts to namespaces (initialized later)
    :field public _html        ⍝ reference to base HTML elements namespace
    :field public _JQ          ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public _SF          ⍝ reference to SyncFusion 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←isChar w
      :Access public shared
      r←0 2∊⍨10|⎕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←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
      :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
⍝
⍝     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
                      :Else
                          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
              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
    ∇

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

    ∇ 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 jQueryWrap ScriptWrap Hourglass)←8↑handler,(⍴handler)↓'' 1 '' '' '' 1 1 ¯1
      :If ¯1=r.Hourglass ⋄ r.Hourglass←(,0)≢,r.Callback ⋄ :EndIf
    ∇

    ∇ r←RenderHandlers;myid;h
      :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
          :If 0<⍴vs←Attrs[]
              av,←∊fmtAttr/¨vs
          :EndIf
          av,←RenderStyles
          r←(av Enclose r),h,p
      :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 9=⎕NC'Position'
      :AndIf ~0∊⍴Position.⎕NL-2
          Uses,←⊂'JQueryUI'
          r←#.JQ.JQueryfn'position'('#',SetId)Position
          Use
      :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),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
          :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
    ∇

    ∇ 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
      r←∊(⎕UCS 13 10)∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}¨(1+2⊃⎕LC)↓↓(⊃⊃⎕CLASS 1⊃⎕RSI).(180⌶)2⊃⎕SI
⍝      r←∊(⎕UCS 13 10)∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓↓(180⌶)2⊃⎕XSI
    ∇

    ∇ r←what Subst text;names;gv;i;repl
      :Access public
      r←text
      :Select ⎕NC⊂'what'
      :Case 9.1 ⍝ namespace
          gv←⍒∊⍴¨names←what.⎕NL-2 3 ⍝ variables and functions only
          repl←{0::⍵ ⋄ (⍺⍺ ⎕R(,⍕⍺⍎⍺⍺))⍵}
          :For i :In gv
              r←what((i⊃names)repl)r
          :EndFor
      :Case 2.1 ⍝ substitution pairs
          :If 2=≡what ⋄ what←,⊂what ⋄ :EndIf
          :If 2≠⍴⍴what ⋄ what←↑what ⋄ :EndIf
          what←,¨what
          gv←⍒∊⍴¨what[;1]
          :For i :In gv
              r←(((⊂i 1)⊃what)⎕R(,⍕(⊂i 2)⊃what))r
          :EndFor
      :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 _true←#.JSON.true     ⍝ same definition as in #.JSON
    :field public shared _false←#.JSON.false   ⍝ same definition as in #.JSON

⍝ define shortcuts to namespaces (initialized later)
    :field public _html        ⍝ reference to base HTML elements namespace
    :field public _JQ          ⍝ reference to JQuery/JQueryUI widgets namespace
    :field public _SF          ⍝ reference to SyncFusion 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←isChar w
      :Access public shared
      r←0 2∊⍨10|⎕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←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
      :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
⍝
⍝     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
                      :Else
                          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
              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
    ∇

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

    ∇ 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 jQueryWrap ScriptWrap Hourglass)←8↑handler,(⍴handler)↓'' 1 '' '' '' 1 1 ¯1
      :If ¯1=r.Hourglass ⋄ r.Hourglass←(,0)≢,r.Callback ⋄ :EndIf
    ∇

    ∇ r←RenderHandlers;myid;h
      :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
          :If 0<⍴vs←Attrs[]
              av,←∊fmtAttr/¨vs
          :EndIf
          av,←RenderStyles
          r←(av Enclose r),h,p
      :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←{'<',⍵,'>'}

    ∇ 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),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
          :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
    ∇

    ∇ 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
      r←∊(⎕UCS 13 10)∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}¨(1+2⊃⎕LC)↓↓(⊃⊃⎕CLASS 1⊃⎕RSI).(180⌶)2⊃⎕SI
⍝      r←∊(⎕UCS 13 10)∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓↓(180⌶)2⊃⎕XSI
    ∇

    ∇ r←what Subst text;names;gv;i;repl
      :Access public
      r←text
      :Select ⎕NC⊂'what'
      :Case 9.1 ⍝ namespace
          gv←⍒∊⍴¨names←what.⎕NL-2 3 ⍝ variables and functions only
          repl←{0::⍵ ⋄ (⍺⍺ ⎕R(,⍕⍺⍎⍺⍺))⍵}
          :For i :In gv
              r←what((i⊃names)repl)r
          :EndFor
      :Case 2.1 ⍝ substitution pairs
          :If 2=≡what ⋄ what←,⊂what ⋄ :EndIf
          :If 2≠⍴⍴what ⋄ what←↑what ⋄ :EndIf
          what←,¨what
          gv←⍒∊⍴¨what[;1]
          :For i :In gv
              r←(((⊂i 1)⊃what)⎕R(,⍕(⊂i 2)⊃what))r
          :EndFor
      :EndSelect
    ∇

    :endsection

:endclass  ⍝ HtmlElement


ejChartSimple QA fix

ejChartSimple QA fix

 msg←Test dummy;First

 First←{('CssSelector'Find'g path:first-child').GetAttribute,⊂⍵}

 ⍝ Test for chart title
 :If 0∊⍴msg←'mychart_svg_ChartTitle'WaitFor'Workspace Object Sizes' 'Chart title not found'

 ⍝ Test for correct first pie slice
 :AndIf 0∊⍴msg←'First pie slice not found'⌿⍨'mychart_svg_Series0_Point0'≢First'id'

 ⍝ Test that the first slice can be removed
     Click'mychart_svg_LegendItemShape0'
     msg←'First slice did not disappear'/⍨'mychart_svg_Series0_Point1'≢First'id'
 :EndIf

 msg←Test dummy

 ⍝ Test for chart title
 :If 0∊⍴msg←'mychart_svg_ChartTitle'WaitFor'Workspace Object Sizes' 'Chart title not found'

 ⍝ Test for first pie slice
 :AndIf 0∊⍴msg←'Pie slice not found'⌿⍨0≡Find'mychart_svg_Series0_Point0'

 ⍝ Test that the first slice was removed
     Click'mychart_svg_LegendItemShape0'
     msg←'Wrong first slice'/⍨'{Width=142, Height=154}'≢⍕('CssSelector'Find'g path:first-child').Size
 :EndIf


Fixed rendering of yadcf-Plugin to also work when first used in APLJax-Calls.

:Class DataTable : #._JQ._jqWidget
⍝ Description:: Enhanced searchable and reorderable HTML table
⍝ Constructor:: [data [cellAttr [headerRows [headerAttr [makeCellIds [makeRowIds]]]]]
⍝ Note that constructor arguments are identical to those for Table
⍝ data           - matrix of data to display in the table
⍝ cellAttr       - Cell Attributes
⍝ headerRows     - # of header rows
⍝ headerAttr     - Header attributes
⍝ makeCellIds    - 1 to generate IDs
⍝ makeRowIds     - 1 to generate Row IDs

⍝ Public Fields::
⍝ Data           - matrix of data to display in the table
⍝ CellAttr       - Cell Attributes
⍝ HeaderRows     - # of header rows
⍝ HeaderAttr     - Header attributes
⍝ MakeCellIds    - 1 to generate IDs
⍝ MakeRowIds     - 1 to generate Row IDs

⍝                  Warning: this ids are generated, but if you need them in order to provide
⍝                  meaningful tools for interaction with the table, it is recommended
⍝                  to use DataTable's features instead which may be more useful!
⍝ Width          - (string) width of the table
⍝ InitFilterWith - initalize "Search"-field with this string
⍝ FocusFilter    - put initial focus on Filter-Control (boolean, default=0)
⍝ Bootstrap      - integer (use Boostrap-styling. 0=no, 1 or 4=BS4, 3=BS3)
⍝ PlugIns        - comma delimited list of "official" plugins to use
⍝
⍝  currently supported plugins are:
⍝    yadcf (yet another datatable column filter) - needs to be explicitely referenced
⍝    mark (highlight search results) - setting option "mark" to true is sufficient, does not need to be specified in "Plugin" then
⍝    responsive (responsive layout) - setting option "responsive" to true is sufficient
⍝    buttons -provides options to copy data to Excel, print and toggle columns
⍝    NB: this mechanism currently supports a subset of Datatable's plugins/extensions.
⍝        Please let us know (miserver@dyalog.com) if your favourite Plugin is missing!

    :Field public shared readonly DocBase←'https://www.datatables.net/'
    :field public Data←0 0⍴⊂''
    :field public CellAttr←''
    :field public HeaderRows←1
    :field public HeaderAttr←''
    :field public MakeCellIds←0
    :field public MakeRowIds←0
    :field public Width←''
    :field public InitFilterWith←''
    :field public Plugins←''
    :field public Bootstrap←0
    :field public FocusFilter←0

    :field private JSON_Data←''  ⍝ supporting JSON-Data transparently ("Data" can be JSON)

    ∇ Make0
      :Access public
      JQueryFn←'DataTable'
      :Implements constructor
      Container←⎕NEW #._DC.Table
      ContainerTag←'table'
    ∇

    ∇ Make1 args
      :Access public
      JQueryFn←'DataTable'
      :Implements Constructor
      :If 2=⍴⍴args ⋄ args←,⊂args ⋄ :EndIf
      Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds←args defaultArgs Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds
      Container←⎕NEW #._DC.Table
      ContainerTag←'table'
    ∇

    ∇ html←Render;tab;btn
      :Access public
      :If 2≠⍴⍴Data
      :AndIf #.HtmlElement.isChar Data
          JSON_Data←Data ⋄ Data←0 0⍴0
      :EndIf
      :If 0=≢GetOption'initComplete'
      :AndIf FocusFilter
          SetId
          'initComplete'Set⊂'function(){var table = $("#',id,'").DataTable();$("div.dataTables_filter input", table.table().container()).focus();}'
      :EndIf
      :Select Bootstrap
      :Case 0
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍕/DataTables/css/jquery.dataTables.min.css'
      :CaseList 1 4
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍎/DataTables/js/dataTables.bootstrap4.min.js'
          Use'⍕/DataTables/css/dataTables.bootstrap4.min.css'
      :Case 3
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍎/DataTables/js/dataTables.bootstrap.min.js'
          Use'⍕/DataTables/css/jquery.dataTables.bootstrap.min.css'
      :EndSelect

      Plugins,←(_true≡GetOption'responsive')/',responsive'
      Plugins,←(_true≡GetOption'mark')/',mark'
      Plugins,←(0<≢GetOption'yadcf')/',yadcf'

      :If 0<≢∊btn←GetOption'buttons'
          :If 326=⎕DR btn
          :AndIf 0<≢∊{0::'' ⋄ ⍵.extend}btn
              Plugins,←∊(⊂',buttons.'),¨⊆btn.extend
          :ElseIf 2=⎕NC'btn'
              Plugins,←∊(⊂',buttons.'),¨⊆btn
              'buttons'Set,⊂∊btn ⍝ ensure it is nested
          :EndIf
      :EndIf
      :If 0<≢InitFilterWith
          'search.search'Set InitFilterWith
      :EndIf

      opts←Options
      JavaScript,←RenderPlugins
      Options←opts
      :If 0<⍴JSON_Data ⋄ 'data'Set⊂JSON_Data ⋄ :EndIf
      Container.(Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)←(Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)
      :If 0<≢Width
          'width'Container.Set Width
      :EndIf
      SetUse
      html←⎕BASE.Render
    ∇

    ∇ js←RenderPlugins;plugin
    ⍝ might also update Options
      js←''
      ((Plugins='.')/Plugins)←','  ⍝ expand "buttons.colvis" to "buttons,colvis" etc.

      :For plugin :In ∪','#.Utils.penclose Plugins
          :Select plugin
          :Case 'yadcf'
              js,←yadcfRender
              Options.⎕EX'yadcf'
          :Case 'mark'
              Use'⍎/DataTables/extras/mark-8.11.0/jquery.mark.min.js'
              Use'⍎/DataTables/extras/mark-8.11.0/datatables.mark.min.js'
              Use'⍕/DataTables/extras/mark-8.11.0/datatables.mark.min.css'
              Options.mark←_true
          :Case 'responsive'
              renderPluginResponsive  ⍝ does not return result, only Uses stuff
              Options.responsive←_true
          :Case 'buttons'
              renderPluginButtons
          :Case 'colvis'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.colVis.min.js'
          :Case 'excelHtml5'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/jszip3.1.3/jszip.min.js'
          :CaseList 'excel' 'copy' 'csv'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.flash.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/jszip3.1.3/jszip.min.js'
          :Case 'print'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.print.min.js'
          :CaseList 'pdf' 'pdfHtml5' 'pdfFlash'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.flash.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/pdfmake0.1.32/pdfmake.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/pdfmake0.1.32/vfs_fonts.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
          :Case 'pageLength'
      ⍝ does not need anything more than just the plain buttons
          :Else
              'Unknown plugin: ""',plugin,'"'
          :EndSelect
      :EndFor
      :If ∨/∊('pdf' 'copy' 'excel' 'csv')⍷¨⊂Plugins
          js,←';$.fn.dataTable.Buttons.swfPath="../../swf/flashExport.swf";'
      :EndIf
    ∇

    :section render Plugins
    ∇ js←yadcfRender;flt
      js←''
      ⍝ this is done intentionally outside the IF-condition, so that just setting 'yadcf'∊ Plugin and not providing options will still load the resources
      ⍝ (useful when calling yadcf in subsequent AJAX etc.!)
      Use'⍕/DataTables/extras/yadcf-0.9.3/jquery.dataTables.yadcf.css'
      Use'⍎/DataTables/extras/yadcf-0.9.3/jquery.dataTables.yadcf.js'
      Use'JQueryUI'
      :If 0<Options.⎕NC'yadcf.Filters'
      :AndIf ×≢Options.yadcf.Filters
          SetUse
          flt←#.JSON.fromAPL Options.yadcf.Filters
          :If ∨/'"chosen"'⍷flt ⋄ Use'chosen' ⋄ :EndIf     ⍝ load Chosen-Plugin for yadcf-Filters
          js←';yadcf.init($("#',id,'").DataTable(),',flt,');'
      :EndIf
    ∇

    ∇ renderPluginResponsive
      :Select Bootstrap
      :Case 0
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.dataTables.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
      :CaseList 1 4
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.bootstrap4.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
          Use'⍎/DataTables/extras/Responsive-2.2.2/responsive.bootstrap4.min.js'
      :Case 3
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.bootstrap.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
          Use'⍎/DataTables/extras/Responsive-2.2.2/responsive.bootstrap.min.js'
      :EndSelect
    ∇

    ∇ renderPluginButtons
      :Select Bootstrap
      :Case 0
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.dataTables.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
      :CaseList 1 4
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.bootstrap4.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.bootstrap4.min.js'
      :Case 3
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.bootstrap.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.bootstrap.min.js'
      :EndSelect

    ∇
    :endsection render Plugins

:EndClass

:Class DataTable : #._JQ._jqWidget
⍝ Description:: Enhanced searchable and reorderable HTML table
⍝ Constructor:: [data [cellAttr [headerRows [headerAttr [makeCellIds [makeRowIds]]]]]
⍝ Note that constructor arguments are identical to those for Table
⍝ data           - matrix of data to display in the table
⍝ cellAttr       - Cell Attributes
⍝ headerRows     - # of header rows
⍝ headerAttr     - Header attributes
⍝ makeCellIds    - 1 to generate IDs
⍝ makeRowIds     - 1 to generate Row IDs

⍝ Public Fields::
⍝ Data           - matrix of data to display in the table
⍝ CellAttr       - Cell Attributes
⍝ HeaderRows     - # of header rows
⍝ HeaderAttr     - Header attributes
⍝ MakeCellIds    - 1 to generate IDs
⍝ MakeRowIds     - 1 to generate Row IDs

⍝                  Warning: this ids are generated, but if you need them in order to provide
⍝                  meaningful tools for interaction with the table, it is recommended
⍝                  to use DataTable's features instead which may be more useful!
⍝ Width          - (string) width of the table
⍝ InitFilterWith - initalize "Search"-field with this string
⍝ FocusFilter    - put initial focus on Filter-Control (boolean, default=0)
⍝ Bootstrap      - integer (use Boostrap-styling. 0=no, 1 or 4=BS4, 3=BS3)
⍝ PlugIns        - comma delimited list of "official" plugins to use
⍝
⍝  currently supported plugins are:
⍝    yadcf (yet another datatable column filter) - needs to be explicitely referenced
⍝    mark (highlight search results) - setting option "mark" to true is sufficient, does not need to be specified in "Plugin" then
⍝    responsive (responsive layout) - setting option "responsive" to true is sufficient
⍝    buttons -provides options to copy data to Excel, print and toggle columns
⍝    NB: this mechanism currently supports a subset of Datatable's plugins/extensions.
⍝        Please let us know (miserver@dyalog.com) if your favourite Plugin is missing!

    :Field public shared readonly DocBase←'https://www.datatables.net/'
    :field public Data←0 0⍴⊂''
    :field public CellAttr←''
    :field public HeaderRows←1
    :field public HeaderAttr←''
    :field public MakeCellIds←0
    :field public MakeRowIds←0
    :field public Width←''
    :field public InitFilterWith←''
    :field public Plugins←''
    :field public Bootstrap←0
    :field public FocusFilter←0

    :field private JSON_Data←''  ⍝ supporting JSON-Data transparently ("Data" can be JSON)

    ∇ Make0
      :Access public
      JQueryFn←'DataTable'
      :Implements constructor
      Container←⎕NEW #._DC.Table
      ContainerTag←'table'
    ∇

    ∇ Make1 args
      :Access public
      JQueryFn←'DataTable'
      :Implements Constructor
      :If 2=⍴⍴args ⋄ args←,⊂args ⋄ :EndIf
      Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds←args defaultArgs Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds
      Container←⎕NEW #._DC.Table
      ContainerTag←'table'
    ∇

    ∇ html←Render;tab;btn
      :Access public
      :If 2≠⍴⍴Data
      :AndIf #.HtmlElement.isChar Data
          JSON_Data←Data ⋄ Data←0 0⍴0
      :EndIf
      :If 0=≢GetOption'initComplete'
      :AndIf FocusFilter
          SetId
          'initComplete'Set⊂'function(){var table = $("#',id,'").DataTable();$("div.dataTables_filter input", table.table().container()).focus();}'
      :EndIf
      :Select Bootstrap
      :Case 0
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍕/DataTables/css/jquery.dataTables.min.css'
      :CaseList 1 4
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍎/DataTables/js/dataTables.bootstrap4.min.js'
          Use'⍕/DataTables/css/dataTables.bootstrap4.min.css'
      :Case 3
          Use'⍎/DataTables/js/jquery.dataTables.min.js'
          Use'⍎/DataTables/js/dataTables.bootstrap.min.js'
          Use'⍕/DataTables/css/jquery.dataTables.bootstrap.min.css'
      :EndSelect

      Plugins,←(_true≡GetOption'responsive')/',responsive'
      Plugins,←(_true≡GetOption'mark')/',mark'
      Plugins,←(0<≢GetOption'yadcf')/',yadcf'

      :If 0<≢∊btn←GetOption'buttons'
          :If 326=⎕DR btn
          :AndIf 0<≢∊{0::'' ⋄ ⍵.extend}btn
              Plugins,←∊(⊂',buttons.'),¨⊆btn.extend
          :ElseIf 2=⎕NC'btn'
              Plugins,←∊(⊂',buttons.'),¨⊆btn
              'buttons'Set,⊂∊btn ⍝ ensure it is nested
          :EndIf
      :EndIf
      :If 0<≢InitFilterWith
          'search.search'Set InitFilterWith
      :EndIf

      opts←Options
      JavaScript,←RenderPlugins
      Options←opts
      :If 0<⍴JSON_Data ⋄ 'data'Set⊂JSON_Data ⋄ :EndIf
      Container.(Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)←(Data CellAttr HeaderRows HeaderAttr MakeCellIds MakeRowIds)
      :If 0<≢Width
          'width'Container.Set Width
      :EndIf
      SetUse
      html←⎕BASE.Render
    ∇

    ∇ js←RenderPlugins;plugin
    ⍝ might also update Options
      js←''
      ((Plugins='.')/Plugins)←','  ⍝ expand "buttons.colvis" to "buttons,colvis" etc.

      :For plugin :In ∪','#.Utils.penclose Plugins
          :Select plugin
          :Case 'yadcf'
              js,←yadcfRender
              Options.⎕EX'yadcf'
          :Case 'mark'
              Use'⍎/DataTables/extras/mark-8.11.0/jquery.mark.min.js'
              Use'⍎/DataTables/extras/mark-8.11.0/datatables.mark.min.js'
              Use'⍕/DataTables/extras/mark-8.11.0/datatables.mark.min.css'
              Options.mark←_true
          :Case 'responsive'
              renderPluginResponsive  ⍝ does not return result, only Uses stuff
              Options.responsive←_true
          :Case 'buttons'
              renderPluginButtons
          :Case 'colvis'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.colVis.min.js'
          :Case 'excelHtml5'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/jszip3.1.3/jszip.min.js'
          :CaseList 'excel' 'copy' 'csv'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.flash.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/jszip3.1.3/jszip.min.js'
          :Case 'print'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.print.min.js'
          :CaseList 'pdf' 'pdfHtml5' 'pdfFlash'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.flash.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/pdfmake0.1.32/pdfmake.min.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/3d-party/pdfmake0.1.32/vfs_fonts.js'
              Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.html5.min.js'
          :Case 'pageLength'
      ⍝ does not need anything more than just the plain buttons
          :Else
              'Unknown plugin: ""',plugin,'"'
          :EndSelect
      :EndFor
      :If ∨/∊('pdf' 'copy' 'excel' 'csv')⍷¨⊂Plugins
          js,←';$.fn.dataTable.Buttons.swfPath="../../swf/flashExport.swf";'
      :EndIf
    ∇

    :section render Plugins
    ∇ js←yadcfRender;flt
      js←''
      :If 0<Options.⎕NC'yadcf.Filters'
      :AndIf ×≢Options.yadcf.Filters
          Use'⍕/DataTables/extras/yadcf-0.9.3/jquery.dataTables.yadcf.css'
          Use'⍎/DataTables/extras/yadcf-0.9.3/jquery.dataTables.yadcf.js'
          Use'JQueryUI'
          SetUse
          flt←#.JSON.fromAPL Options.yadcf.Filters
          :If ∨/'"chosen"'⍷flt ⋄ Use'chosen' ⋄ :EndIf     ⍝ load Chosen-Plugin for yadcf-Filters
          js←';yadcf.init($("#',id,'").DataTable(),',flt,');'
      :EndIf
    ∇

    ∇ renderPluginResponsive
      :Select Bootstrap
      :Case 0
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.dataTables.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
      :CaseList 1 4
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.bootstrap4.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
          Use'⍎/DataTables/extras/Responsive-2.2.2/responsive.bootstrap4.min.js'
      :Case 3
          Use'⍕/DataTables/extras/Responsive-2.2.2/responsive.bootstrap.min.css'
          Use'⍎/DataTables/extras/Responsive-2.2.2/dataTables.responsive.min.js'
          Use'⍎/DataTables/extras/Responsive-2.2.2/responsive.bootstrap.min.js'
      :EndSelect
    ∇

    ∇ renderPluginButtons
      :Select Bootstrap
      :Case 0
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.dataTables.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
      :CaseList 1 4
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.bootstrap4.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.bootstrap4.min.js'
      :Case 3
          Use'⍕/DataTables/extras/Buttons-1.5.1/css/buttons.bootstrap.min.css'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/dataTables.buttons.min.js'
          Use'⍎/DataTables/extras/Buttons-1.5.1/js/buttons.bootstrap.min.js'
      :EndSelect

    ∇
    :endsection render Plugins

:EndClass


InputGridSimple QA: Missing ⍕

InputGridSimple QA: Missing ⍕

 msg←Test dummy;data
⍝ Test /Examples/DC/InputGridSimple

 data←'Morten' 'Kromberg'(⍕1↓∊'/',¨⌽3↑⎕TS) ⍝ It's my birthday every day!
 'fname' 'lname' 'bdate'SendKeys¨data
 Click'ClickMe'
 msg←'output'WaitFor'Hi Morten Kromberg. Happy Birthday!'

 msg←Test dummy;data
⍝ Test /Examples/DC/InputGridSimple

 data←'Morten' 'Kromberg'(1↓∊'/',¨⌽3↑⎕TS) ⍝ It's my birthday every day!
 'fname' 'lname' 'bdate'SendKeys¨data
 Click'ClickMe'
 msg←'output'WaitFor'Hi Morten Kromberg. Happy Birthday!'


bug fix

bug fix

:Namespace HTMLInput

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      Attrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:''
          (0∊⍴⍺)↓⍺,' ',{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),' }'}{
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/': ',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←∊CRLF∘,¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨html
    ∇

    ∇ html←{id}Anchor pars;href;title;target;other;content
    ⍝ Builds an anchor  tag
    ⍝ pars: content {href} {title} {target} {other_attrs}
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      pars←eis pars
      content href title target other←5↑pars,(⍴pars)↓'' '' '' '' ''
      html←('a',(Attrs'id' 'href' 'title' 'target'{(⍵ ine ⍺)⍵}¨id href title target),Attrs other)Enclose content
    ∇

    ∇ html←{n}BRA html
    ⍝ (BReak After) Add n
's after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}'
'
    ∇

    ∇ html←{n}BR html
    ⍝ (BReak) Add n
's before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}'
'),html
    ∇

    ∇ html←{n}SP html
    ⍝ (SPace) Add n non-breaking spaces   before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}' '),html
    ∇

    ∇ html←{n}SPA html
    ⍝ (SPace) Add n non-breaking spaces   after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}' '
    ∇

    ∇ r←{src}JS script
    ⍝ Javascript
    ⍝ script: javascript script if no src or filename if src=1
      :If 0≠⎕NC'src' ⋄ r←('script src=',quote src)Enclose''
      :Else ⋄ r←'script'Enclose script
      :EndIf
    ∇

    ∇ r←{name}DropDown pars;values;display;gv;mask;n
    ⍝ par - items selected_value(s) attributes sort
    ⍝ if items is 2 column matrix, [;1] is values, [;2] is display
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf
      pars←pars,(⍴pars)↓('Item1' 'Item2')'Item1' '' 0
      :If ~ischar 1⊃pars ⋄ pars[1]←⊂⍕¨1⊃pars ⋄ :EndIf
      :If 1=⍴⍴1⊃pars ⋄ values display←pars[1]
      :Else ⋄ values display←↓[1]1⊃pars
      :EndIf
      mask←values∊⍕¨eis 2⊃pars
      :If 4⊃pars ⋄ gv←⍒mask ⋄ values←values[gv] ⋄ display←display[gv] ⋄ mask←mask[gv] ⋄ :EndIf ⍝ Sort selected items first
      r←'<select ',(3⊃pars),(name ine' id=',n,' name=',n←quote name),'>',CRLF
      r,←enlist(mask{'<option value="',(⍕⍵),'"',(⍺/' selected="selected"'),'>'}¨values),¨display,¨⊂'</option>',CRLF
      r,←'</select>',CRLF
    ∇

    ∇ r←{atts}(method Form)innerhtml
      :If 9=⎕NC'atts' ⋄ atts←'action="',atts.Page,'"' ⍝ MildPage calling
      :ElseIf 0=⎕NC'atts' ⋄ atts←'' ⋄ :EndIf
      atts,←' method="',method,'"'
      atts,←('post'≡#.Strings.lc method)/' enctype="multipart/form-data"'
      r←('form ',atts)Enclose innerhtml
    ∇

    ∇ r←lab Label tag;id
      id←tag GetAttr'id'
      r←tag,⍨('label',id ine' for="',id,'"')Enclose lab
    ∇

    ∇ r←{name}Edit pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="text" value="',(⍕val),'"',name
    ∇

    ∇ r←{name}Submit pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Submit' ''
      r←Tag'input type="submit" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Button pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Button' ''
      r←Tag'input type="button" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}File pars
    ⍝ pars: [value [size [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓''⍬''
      r←Tag'input ',(3⊃pars),' type="file" size="',(⍕2⊃pars),'" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Hidden pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'' ''
      r←Tag'input ',(2⊃pars),' type="hidden" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Password pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="password" value="',val,'"',name
    ∇

    ∇ r←{name}CheckBox pars
    ⍝ pars: checked [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴pars)↓0 ''
      r←Tag'input ',(2⊃pars),' type="checkbox"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}CheckBoxGroup pars;choices;sel
    ⍝ pars: values/texts
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="checkbox"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{name}RadioButton pars
    ⍝ pars: checked [value [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴,pars)↓0 name''
      r←Tag'input ',(3⊃pars),' type="radio" value="',(2⊃pars),'"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}RadioButtonGroup pars;choices;sel
    ⍝ pars: values/texts [sel]
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="radio"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{id}Table pars;x;data;atts;tdc;tda;thc;tha;hdrrows;z;mask;cellids;n;rowids;rows
    ⍝ pars: data table_atts cell_attribs header_attribs #header_rows cell-ids? row-ids?
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf ⍝ Matrix of data only
      pars←pars,(⍴,pars)↓(1 1⍴⊂'data')'' '' '' 0 0 0
      data atts tda tha hdrrows cellids rowids←pars
      hdrrows←⍬⍴hdrrows
      data←((rows←×/¯1↓⍴data),¯1↑⍴data)⍴data
      x←⍕¨data
      :If cellids ⍝ do we add cell ids?
          r←(' id="'∘,∘(,∘'">')¨{enlist'rc',¨⍕¨⍵}¨⍳⍴data),¨x,¨⊂'	'
      :Else
          r←'>',¨x,¨⊂'	'
      :EndIf
      :If (⍴tda)≡⍴data
          r←('<td'∘,¨tda ine¨' ',¨tda),¨r
      :ElseIf (⍴tda)=¯1↑⍴data
          r←↑(⊂'<td'∘,¨tda ine¨' ',¨tda),¨¨↓r
      :Else
          r←('<td',tda ine' ',tda)∘,¨r
      :EndIf
      :If z←0≠hdrrows ⋄ r[⍳hdrrows;]←(⊂''),¨x[⍳hdrrows;],¨⊂'	' ⋄ :EndIf
      :If rowids ⍝ if using row ids, add them for non-header rows
          r←(⍳rows){'
',⍵,'
',CRLF}¨,/r
      :Else
          r←'
'∘,¨(,/r),¨⊂'
',CRLF
      :EndIf
      :If z ⋄ mask←(4+⍴r)⍴1 ⋄ mask[n←1,(2 3+hdrrows),⍴mask]←0
          r←mask\r
          r[n]←'
' '
' '
' '
',¨⊂CRLF
      :EndIf
      r←('table',(id ine' id="',id,'"'),(atts ine' ',atts))Enclose CRLF,(enlist r),CRLF
    ∇

    ∇ r←{name}List pars;items;ordered;t
    ⍝ pars: items [ordered? 0/1]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :Select ≡pars
      :CaseList 1 ¯2 ⋄ ordered←¯1↑pars ⋄ items←eis ¯1↓pars
      :Case 2 ⋄ ordered←0 ⋄ items←pars
      :Else ⋄ (items ordered)←2↑pars,(⍴,pars)↓'' 0
      :EndSelect
      t←(1+ordered)⊃'ul' 'ol'
      r←(t,name ine' id=',quote name)Enclose enlist'li'∘Enclose¨items
    ∇

    ∇ r←{name}MultiEdit pars
    ⍝ pars: (rows cols) [values [attributes]]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      pars←pars,(⍴pars)↓(10 40)'' ''
      r←'textarea ',(3⊃pars),(name ine' id="',name,'" name="',name,'"'),' rows="',(⍕1 1⊃pars),'" cols="',(⍕1 2⊃pars),'"'
      r←r Enclose CRLF,(2⊃pars),CRLF
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs
    ⍝ 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←enlist r
    ∇

:EndNamespace

:Namespace HTMLInput

    (⎕IO ⎕ML)←1
    ⎕FX 'r←CRLF' 'r←⎕UCS 13 10' ⍝ So it will be :Included
    enlist←{∊⍵} ⍝ APL2 enlist
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    ischar←{0 2∊⍨10|⎕DR⍵}
    quote←{0∊⍴⍵:'' ⋄ '"',({w←⍵⋄((w='"')/w)←⊂'\"'⋄enlist w}⍵),'"'}
    iotaz←{(⍴⍺){⍵×⍺≥⍵}⍺⍳⍵}
    innerhtml←{⊃↓/(⍵ iotaz'>')(-(⌽⍵)iotaz'<') ⍵}
    dtlb←{⍵{((∨\⍵)∧⌽∨\⌽⍵)/⍺}' '≠⍵}

    ∇ r←atts Enclose innerhtml;i
    ⍝ Put an HTML tag around some HTML
      :If 1<|≡innerhtml ⋄ innerhtml←CRLF,enlist innerhtml,¨⊂CRLF ⋄ :EndIf
      :If 0∊⍴atts
          r←innerhtml
      :Else
          i←¯1+(atts←,atts)⍳' '
          r←'<',atts,'>',innerhtml,'</',(i↑atts),'>',CRLF
      :EndIf
    ∇

    ∇ r←{nl}Tag tag
    ⍝ Make a  self-closing tag
      nl←{6::1 ⋄ nl}⍬
      r←'<',tag,' />',nl/CRLF
    ∇

      Attrs←{
      ⍝ format name/value pairs as tag attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name="value"'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          0∊⍴⍵:''
          {
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/'=',quote ⍵}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    ∇ r←tag GetAttr attr;attrs
      r←''
      :Trap 6
          →0⍴⍨0∊⍴tag←⎕XML tag
      :EndTrap
      attrs←(⊂1 4)⊃tag
      r←(attrs[;1]⍳⊂attr)⊃attrs[;2],⊂''
    ∇

      Styles←{
      ⍝ format name/value pairs as CSS style attributes
      ⍝  ⍵ - name/value pairs, valid forms:
      ⍝  'name: value'
      ⍝  [n,2⍴] 'name1' 'value1' ['name2' 'value2'...]
      ⍝ ('name1' 'value1') [('name2' 'value2')]
          ⍺←''
          0∊⍴⍵:''
          (0∊⍴⍺)↓⍺,' ',{'{',({';'=¯1↑⍵:⍵ ⋄ ⍵,';'}⍵),' }'}{
              enlist{(×⍴⍺)/' ',⍺,(×⍴⍵)/': ',⍵,';'}/,∘⍕¨⊃⍵
          }_pifn¨,2 _box _pifn{
              1=|≡⍵:⍵
              2=|≡⍵:{1=⍴⍴⍵:(⌽2,0.5×⍴⍵)⍴⍵ ⋄ ⍵}⍵
              ↑⍵}⍵
      }

    _box←{⍺←1 ⋄ (⊂⍣(⍺=|≡⍵))⍵}
    _pifn←{({⍵''}⍣(1=|≡⍵))⍵}

    ∇ r←ScriptFollows;lines;pgm;from
     ⍝ Treat following commented lines in caller as a script, lines beginning with ⍝⍝ are stripped out
      :If 0∊⍴lines←(from←⎕IO⊃⎕RSI).⎕NR pgm←2⊃⎕SI
          lines←↓from.(180⌶)pgm
      :EndIf
      r←∊CRLF∘.¨{⍵/⍨'⍝'≠⊃¨⍵}{1↓¨⍵/⍨∧\'⍝'=⊃¨⍵}dtlb¨(1+2⊃⎕LC)↓lines
    ∇

    ∇ html←TextToHTML html;mask;CR
    ⍝ Add/insert
, replaces CR with
,CR
      :If ~0∊⍴html
          :If ∨/mask←html=CR←''⍴CRLF
              (mask/html)←⊂'
',CR
              html←enlist html
          :EndIf
          html,←(~∨/¯2↑mask)/'
',CRLF
      :EndIf
    ∇

    ∇ html←{fontsize}APLToHTML APL
    ⍝ returns APL code formatted for HTML
      fontsize←{6::'' ⋄ ';fontsize:',⍎⍵}'fontsize'
      :If 1<|≡APL ⋄ APL←enlist,∘CRLF¨APL ⋄ :EndIf
      :Trap 0
          html←3↓¯4↓'whitespace' 'preserve'⎕XML 1 3⍴0 'x'APL
      :Else
          html←APL
      :EndTrap
      html←('pre style="font-family:APL385 Unicode',fontsize,'"')Enclose'code'Enclose CRLF,⍨html
    ∇

    ∇ html←{id}Anchor pars;href;title;target;other;content
    ⍝ Builds an anchor  tag
    ⍝ pars: content {href} {title} {target} {other_attrs}
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      pars←eis pars
      content href title target other←5↑pars,(⍴pars)↓'' '' '' '' ''
      html←('a',(Attrs'id' 'href' 'title' 'target'{(⍵ ine ⍺)⍵}¨id href title target),Attrs other)Enclose content
    ∇

    ∇ html←{n}BRA html
    ⍝ (BReak After) Add n
's after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}'
'
    ∇

    ∇ html←{n}BR html
    ⍝ (BReak) Add n
's before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}'
'),html
    ∇

    ∇ html←{n}SP html
    ⍝ (SPace) Add n non-breaking spaces   before html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html←(n{(⍺×⍴⍵)⍴⍵}' '),html
    ∇

    ∇ html←{n}SPA html
    ⍝ (SPace) Add n non-breaking spaces   after html
      :If 0=⎕NC'n' ⋄ n←1 ⋄ :EndIf
      html,←n{(⍺×⍴⍵)⍴⍵}' '
    ∇

    ∇ r←{src}JS script
    ⍝ Javascript
    ⍝ script: javascript script if no src or filename if src=1
      :If 0≠⎕NC'src' ⋄ r←('script src=',quote src)Enclose''
      :Else ⋄ r←'script'Enclose script
      :EndIf
    ∇

    ∇ r←{name}DropDown pars;values;display;gv;mask;n
    ⍝ par - items selected_value(s) attributes sort
    ⍝ if items is 2 column matrix, [;1] is values, [;2] is display
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf
      pars←pars,(⍴pars)↓('Item1' 'Item2')'Item1' '' 0
      :If ~ischar 1⊃pars ⋄ pars[1]←⊂⍕¨1⊃pars ⋄ :EndIf
      :If 1=⍴⍴1⊃pars ⋄ values display←pars[1]
      :Else ⋄ values display←↓[1]1⊃pars
      :EndIf
      mask←values∊⍕¨eis 2⊃pars
      :If 4⊃pars ⋄ gv←⍒mask ⋄ values←values[gv] ⋄ display←display[gv] ⋄ mask←mask[gv] ⋄ :EndIf ⍝ Sort selected items first
      r←'<select ',(3⊃pars),(name ine' id=',n,' name=',n←quote name),'>',CRLF
      r,←enlist(mask{'<option value="',(⍕⍵),'"',(⍺/' selected="selected"'),'>'}¨values),¨display,¨⊂'</option>',CRLF
      r,←'</select>',CRLF
    ∇

    ∇ r←{atts}(method Form)innerhtml
      :If 9=⎕NC'atts' ⋄ atts←'action="',atts.Page,'"' ⍝ MildPage calling
      :ElseIf 0=⎕NC'atts' ⋄ atts←'' ⋄ :EndIf
      atts,←' method="',method,'"'
      atts,←('post'≡#.Strings.lc method)/' enctype="multipart/form-data"'
      r←('form ',atts)Enclose innerhtml
    ∇

    ∇ r←lab Label tag;id
      id←tag GetAttr'id'
      r←tag,⍨('label',id ine' for="',id,'"')Enclose lab
    ∇

    ∇ r←{name}Edit pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="text" value="',(⍕val),'"',name
    ∇

    ∇ r←{name}Submit pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Submit' ''
      r←Tag'input type="submit" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Button pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'Button' ''
      r←Tag'input type="button" ',(2⊃pars),' value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}File pars
    ⍝ pars: [value [size [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓''⍬''
      r←Tag'input ',(3⊃pars),' type="file" size="',(⍕2⊃pars),'" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Hidden pars
    ⍝ pars: value [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←,eis pars
      pars←pars,(⍴pars)↓'' ''
      r←Tag'input ',(2⊃pars),' type="hidden" value="',(1⊃pars),'"',name
    ∇

    ∇ r←{name}Password pars;att;size;val
    ⍝ pars: [value] [size] [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      val size att←3↑pars,(⍴pars)↓''⍬''
      r←Tag'input ',att,((~0∊⍴size)/' size="',(⍕size),'"'),' type="password" value="',val,'"',name
    ∇

    ∇ r←{name}CheckBox pars
    ⍝ pars: checked [attributes]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴pars)↓0 ''
      r←Tag'input ',(2⊃pars),' type="checkbox"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}CheckBoxGroup pars;choices;sel
    ⍝ pars: values/texts
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="checkbox"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{name}RadioButton pars
    ⍝ pars: checked [value [attributes]]
      :If 0=⎕NC'name' ⋄ name←''
      :Else ⋄ name←enlist(,∘('="',name,'"'))¨' id' ' name'
      :EndIf
      pars←eis pars
      pars←pars,(⍴,pars)↓0 name''
      r←Tag'input ',(3⊃pars),' type="radio" value="',(2⊃pars),'"',name,(0≠1⊃pars)/' checked="checked"'
    ∇

    ∇ r←{name}RadioButtonGroup pars;choices;sel
    ⍝ pars: values/texts [sel]
      name←{6::'' ⋄ enlist(,∘('="',name,'"'))¨' id' ' name'}⍬
      :If 2=|≡pars ⋄ choices←pars ⋄ sel←⍬
      :Else ⋄ (choices sel)←pars
      :EndIf
      :If 1=⍴⍴choices ⋄ choices←choices,[1.1]choices ⋄ :EndIf
      sel←(⊃⍴choices)↑sel
      r←enlist name∘{BRA(0 Tag'input type="radio"',⍺,' value="',(1⊃⍵),'"',(1=3⊃⍵)/' checked="checked"'),(2⊃⍵)}¨↓choices,sel
    ∇

    ∇ r←{id}Table pars;x;data;atts;tdc;tda;thc;tha;hdrrows;z;mask;cellids;n;rowids;rows
    ⍝ pars: data table_atts cell_attribs header_attribs #header_rows cell-ids? row-ids?
      :If 0=⎕NC'id' ⋄ id←'' ⋄ :EndIf
      :If 2=⍴⍴pars ⋄ pars←,⊂pars ⋄ :EndIf ⍝ Matrix of data only
      pars←pars,(⍴,pars)↓(1 1⍴⊂'data')'' '' '' 0 0 0
      data atts tda tha hdrrows cellids rowids←pars
      hdrrows←⍬⍴hdrrows
      data←((rows←×/¯1↓⍴data),¯1↑⍴data)⍴data
      x←⍕¨data
      :If cellids ⍝ do we add cell ids?
          r←(' id="'∘,∘(,∘'">')¨{enlist'rc',¨⍕¨⍵}¨⍳⍴data),¨x,¨⊂'	'
      :Else
          r←'>',¨x,¨⊂'	'
      :EndIf
      :If (⍴tda)≡⍴data
          r←('<td'∘,¨tda ine¨' ',¨tda),¨r
      :ElseIf (⍴tda)=¯1↑⍴data
          r←↑(⊂'<td'∘,¨tda ine¨' ',¨tda),¨¨↓r
      :Else
          r←('<td',tda ine' ',tda)∘,¨r
      :EndIf
      :If z←0≠hdrrows ⋄ r[⍳hdrrows;]←(⊂''),¨x[⍳hdrrows;],¨⊂'	' ⋄ :EndIf
      :If rowids ⍝ if using row ids, add them for non-header rows
          r←(⍳rows){'
',⍵,'
',CRLF}¨,/r
      :Else
          r←'
'∘,¨(,/r),¨⊂'
',CRLF
      :EndIf
      :If z ⋄ mask←(4+⍴r)⍴1 ⋄ mask[n←1,(2 3+hdrrows),⍴mask]←0
          r←mask\r
          r[n]←'
' '
' '
' '
',¨⊂CRLF
      :EndIf
      r←('table',(id ine' id="',id,'"'),(atts ine' ',atts))Enclose CRLF,(enlist r),CRLF
    ∇

    ∇ r←{name}List pars;items;ordered;t
    ⍝ pars: items [ordered? 0/1]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      :Select ≡pars
      :CaseList 1 ¯2 ⋄ ordered←¯1↑pars ⋄ items←eis ¯1↓pars
      :Case 2 ⋄ ordered←0 ⋄ items←pars
      :Else ⋄ (items ordered)←2↑pars,(⍴,pars)↓'' 0
      :EndSelect
      t←(1+ordered)⊃'ul' 'ol'
      r←(t,name ine' id=',quote name)Enclose enlist'li'∘Enclose¨items
    ∇

    ∇ r←{name}MultiEdit pars
    ⍝ pars: (rows cols) [values [attributes]]
      :If 0=⎕NC'name' ⋄ name←'' ⋄ :EndIf
      pars←pars,(⍴pars)↓(10 40)'' ''
      r←'textarea ',(3⊃pars),(name ine' id="',name,'" name="',name,'"'),' rows="',(⍕1 1⊃pars),'" cols="',(⍕1 2⊃pars),'"'
      r←r Enclose CRLF,(2⊃pars),CRLF
    ∇

      MakeStyle←{
          ⍺←''
          0∊⍴⍵:''
          (' ',⍨¯2↓enlist(eis ⍺),¨⊂', '),Styles ⍵
      }

    ∇ r←HtmlSafeText txt;i;m;u;ucs
    ⍝ 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←enlist r
    ∇

:EndNamespace