MS3SiteUtils: match new Handler syntax.

MS3SiteUtils: match new Handler syntax.

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

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

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

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

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

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

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

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

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

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

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

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

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

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←#.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

    Slash←'/'∘=⊂⊢

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

'
      :EndFor
      r↓⍨←¯5
    ∇

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

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

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

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

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

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

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

    ∇ t←Positioning
      t←'#position'New _.div'This div has its default positioning'
      t←'Positioning't
    ∇

    ∇ t←Embedding
      t←'#embed'New _.div'This div contains embedded items'
      t←'Embedding't
    ∇

    ∇ t←Interaction
      t←'#ui'New _.div'This div shows user interaction'
      t←'Interaction't
    ∇

    ∇ t←Windows
      t←'#win'New _.div'This div shows floating GUI elements'
      t←'Windows & Popups't
    ∇

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

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

    ∇ r←ShowFormatting;css;info;code
      :Access Public
      (code css)←_selector CodeFrom'Formatting'
      info←'
Put this in the Compose function
',P code
      info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
',P css
      r←'#info'Replace info
    ∇

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

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

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

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

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

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

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'Syncfusion' 'jQueryUI' 'JavaScript' 'Base HTML'
    ∇

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

:EndNamespace

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

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

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

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

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

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

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

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

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

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

    :SECTION B_FILES ⍝ FUNCTIONS THAT READ FILES

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

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

    ∇ r←Read page;i ⍝ Read a page via the #.CACHE.read (C)
      :Trap 0
          :If (≢C.read.keys)≥i←C.read.keys⍳⊂page ⍝ is it cached, and if so, save position
              r←i⊃C.read.data                     ⍝ extract
          :Else
              C.read.data,←⊂r←#.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

    Slash←'/'∘=⊂⊢

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

'
      :EndFor
      r↓⍨←¯5
    ∇

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

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

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

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

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

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

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

    ∇ t←Positioning
      t←'#position'New _.div'This div has its default positioning'
      t←'Positioning't
    ∇

    ∇ t←Embedding
      t←'#embed'New _.div'This div contains embedded items'
      t←'Embedding't
    ∇

    ∇ t←Interaction
      t←'#ui'New _.div'This div shows user interaction'
      t←'Interaction't
    ∇

    ∇ t←Windows
      t←'#win'New _.div'This div shows floating GUI elements'
      t←'Windows & Popups't
    ∇

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

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

    ∇ r←ShowFormatting;css;info;code
      :Access Public
      (code css)←_selector CodeFrom'Formatting'
      info←'
Put this in the Compose function
',P code
      info,←'
Put Add _.style''…'' in Compose or add  this to the stylesheet
',P css
      r←'#info'Replace info
    ∇

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

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

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

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

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

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

    ∇ r←GROUPS
      :Access public
      r←'Dyalog' 'Syncfusion' 'jQueryUI' 'JavaScript' 'Base HTML'
    ∇

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

:EndNamespace


Applying recent fix from DUI

Applying recent fix from DUI

Update to _JQ._jqWidget to copy all attributes set on the "main" element to the "container" element

:Namespace _JQ ⍝ jQueryUI
    (⎕IO ⎕ML)←1

    :section Common Code

    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵ ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtValue←{(2=≡⍵)∧0=≢⍴⍵:⊃⍵ ⋄ #.JSON.fromAPL ⍵}

    ∇ r←opt(sel Update jqfn)val
    ⍝ update an option for a widget
      r←#.JQ.Execute'$(',(fmtSelector sel),').',jqfn,'("option","',opt,'",',(fmtValue val),');'
    ∇
    :endsection

    :section Base Classes
    :class _jqObject : #.HtmlElement
        :field public Selector←''      ⍝ Selector to apply the JQuery function to
        :field public JavaScript←''    ⍝ additional JavaScript to run AFTER the jQuery function, can be function chain, separate code or both
        :field public PreJavaScript←'' ⍝ additional JavaScript to run BEFORE the jQuery function
        :field public Var←''           ⍝ JavaScript variable name for created object
        :field public JQueryFn←''      ⍝ JQuery function to apply
        :field public JQPars←''        ⍝ JQuery function parameters
        :field public Type←''          ⍝ Type specification that may precede JQPars
        :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
        :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON
        :field public ScriptOptions←⍬  ⍝ determines how script will be rendered [1] wrap with <script>...</script>, [2] wrap with $(function(){...})

        ∇ Make0
          :Access public
          :Implements constructor
        ∇

        ∇ Make1 pars
          :Access public
          :Implements constructor
          pars←eis pars
          JQueryFn Selector JQPars JavaScript Var PreJavaScript Type←7↑pars,(⍴pars)↓'' '' '' '' '' '' ''
        ∇

        ∇ r←Render
          :Access public
          SetUse
          r←ScriptOptions #.JQ.JQueryfn JQueryFn Selector JQPars JavaScript Var PreJavaScript Type
        ∇

        ∇ r←isSelector str ⍝ checks if str is probably a jQuery selector
          :Access public shared
          r←∨/str∊'''"*[]:>+~()#.'
        ∇

        :section APLJax

        ∇ r←renderContent content;c
          r←''
          content←eis content
          :While ~0∊⍴content
              :Select ≡c←⊃content
              :Case 0
                  :If isClass c
                      :Select ⊃⍴content
                      :Case 1
                          r,←(⎕NEW c).Render
                      :Case 2
                          r,←(⎕NEW c(2⊃content)).Render
                      :Else
                          r,←(⎕NEW c(1↓content)).Render
                      :EndSelect
                  :ElseIf isInstance c
                      r,←c.Render
                  :Else
                      r,←(⎕NEW #.HtmlElement(''content)).Render
                  :EndIf
                  content←''
              :Case 1
                  :If isClass⊃c
                      r,←(⎕NEW(⊃c)(1↓c)).Render
                  :ElseIf isInstance⊃c
                      ∘∘∘ ⍝ should not happen! (I think)
                  :Else
                      r,←(⎕NEW #.HtmlElement(''c)).Render
                  :EndIf
                  content←1↓content
              :Else
                  r,←renderContent c
                  content←1↓content
              :EndSelect
          :EndWhile
        ∇

        ∇ r←selector Replace content
          :Access public
          r←⊂('replace'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Append content
          :Access public
          r←⊂('append'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Prepend content
          :Access public
          r←⊂('prepend'selector)('data'(renderContent content))
        ∇

        ∇ r←Execute content
          :Access public
          r←⊂('execute'(renderContent content))
        ∇

        ∇ r←name Assign data
          :Access public
          r←⊂('assign'name)('data'data)
        ∇
        :EndSection

    :endclass

    :class _jqWidget : _jqObject

        :field public Options←''    ⍝ options for the object to be created
        :field public ContainerTag←'div' ⍝ default container type
        :field public Container
        :field public InternalEvents←'' ⍝ list of events the widget "knows" about
        :field public BuildHTML←1       ⍝ if 0, we build any HTML infrastructure for the widget, otherwise, assume the user built it
        :field public WidgetDef←'event,ui' 'event'  'ui' '$(event.currentTarget)' '.val()'  ⍝ see _JQ.RenderHandlerCore for details

        ∇ r←{a}rand w;⎕RL
          :Access public
          ⎕RL←0
          :If 0=⎕NC'a' ⋄ r←?w
          :Else ⋄ r←a?w ⋄ :EndIf
        ∇

        ∇ Make
          :Access public
          Options←⎕NS''
          Container←⎕NEW #.HtmlElement
          :Implements constructor
        ∇

        ∇ r←Render;build;html;handlers;js;oldJavaScript
          :Access public
          r←html←js←''
          SetUse

          BuildHTML∧←0∊⍴Selector ⍝ if the user explicitly specifies a selector, assume he's built the content himself

          :If BuildHTML
              ⍝Container.(id name type style class title)←Container.(id name type style class title){UNDEF≡⍵:⍺ ⋄ UNDEF≢⍺:⍺ ⋄ ⍵}¨⎕THIS.(id name type style class title)
              :If ~0∊⍴⎕THIS.Attrs[]
                  Container.Set/¨Attrs[]
              :EndIf
              :If Container.id≡UNDEF
                  :If Container.name≢UNDEF
                      Container.id←Container.name
                  :Else
                      Container.id←SetId
                  :EndIf
              :EndIf
              Selector←'#',Container.id
          :EndIf

          handlers←''
          :If ~0∊⍴Handlers
              handlers←';',⍨∊¯1↓¨Handlers.Render
          :EndIf

          oldJavaScript←JavaScript
          JavaScript,←handlers
          JQPars←Options
          js←⎕BASE.Render
          JavaScript←oldJavaScript

          :If BuildHTML≥0∊⍴Container.Content
              :Select ⊃Selector
              :Case '#' ⍝ id?
                  Container.id←1↓Selector
              :Case '.' ⍝ class?
                  Container.class←1↓Selector
              :EndSelect
              :If ContainerTag{⍵≡(⍴⍵)↑⍺}'input'
              :AndIf UNDEF≡Container.name
                  Container.name←('.#'∊⍨⊃Selector)↓Selector
              :EndIf
              Container.Tag←ContainerTag
              html←Container.Render
          :EndIf
          r←html,js
        ∇

        ∇ {handler}←On args
          :Access public
          ⍝ args - event callback clientData javascript
          handler←⎕BASE.On args
          handler.InternalEvents←InternalEvents
          handler.(WidgetDef←WidgetRef.WidgetDef)
        ∇

        ∇ {r}←{name}Set value
          :Access public
          r←⎕THIS
          →(0∊⍴value)⍴0
          :If 326≠⎕DR Options ⋄ Options←⎕NS'' ⋄ :EndIf
          :If 0=⎕NC'name'
              :Trap 11
                  (name value)←ParseValue value
              :Else
                  ⎕SIGNAL/⎕DMX.(EM EN)
              :EndTrap
          :EndIf
          :Trap 0
              name(Options SetOption)value
          :Else
              ⎕SIGNAL/⎕DMX.(EM EN)
          :EndTrap
        ∇

        ∇ {r}←name SetIfNotSet value
          :Access public
          :If 0∊⍴GetOption name
              name Set value
          :EndIf
          r←⎕THIS
        ∇

        ∇ name(ref SetOption)value;set;parent;ind;newref;chunk;n;now;new;chunkroot;array;val;pos;r;v
          →(0∊⍴value)⍴0
          :If 1<|≡name ⍝ multiple names?
              value←(⊂⍣((⎕DR value)∊80 82))value
              name(ref SetOption)¨value
          :Else
              set←{
                  ⍺⍺⍎'(',⍺,')←⍵'
              }

              :If 0∊⍴parent←(-'.'⍳⍨⌽name)↓name ⍝ if no parent...
                  :If 1<n←≢ref  ⍝ multiple refs?
                      ⎕SIGNAL 5/⍨(0=≢⍴value)=(⍴ref)=⍴value ⍝ LENGTH ERROR if value is not scalar or same length as ref
                      :For (r v) :InEach ref(n⍴value)
                          name(r set)v
                      :EndFor
                  :ElseIf (1+' '+.=name)=≢value
                      name(ref set)value
                  :Else
                      name(ref set)value ⍝ single name: assign directly (may be more than 1 name)
                  :EndIf
              :Else
                  ind←name⍳'.'
                  chunk←¯1↓ind↑name
                  (chunkroot pos)←2↑'[]'#.Utils.penclose chunk

                  :If array←'[]'≡¯2↑chunk ⍝ is it an array assignment?
                      pos←≢val←eis value
                      :Select ⊃ref.⎕NC chunkroot
                      :Case 0
                          new←⎕NS¨(⌈/pos)⍴⊂⍬
                          chunkroot(ref set)new
                      :Case 2 ⍝ already exists
                          ('Invalid option specification - length error on "',chunkroot,'".')⎕SIGNAL 5/⍨~(⍴new←ref⍎chunkroot)∊1,pos
                      :Else
                          ('Invalid option specification - "',chunkroot,'" is not an array.')⎕SIGNAL 11
                      :EndSelect
                      new{(ind↓name)(⍺ SetOption)⍵}¨val

                  :Else
                      (chunkroot pos)←2↑'[]'#.Utils.penclose chunk
                      pos←2⊃⎕VFI pos
                      :Trap 3 6
                          newref←ref⍎chunk
                      :Case 3 ⍝ index error
                          n←⍴now←ref⍎chunkroot
                          new←⎕NS¨(pos-n)⍴⊂⍬
                          chunkroot(ref set)now,new
                          newref←ref⍎chunk
                      :Case 6 ⍝ value error
                          :If ~0∊⍴pos
                              new←⎕NS¨(⌈/pos)⍴⊂⍬
                              chunkroot(ref set)new
                          :Else
                              chunkroot ref.⎕NS''
                          :EndIf
                          newref←ref⍎chunk
                      :EndTrap
                      (ind↓name)(newref SetOption)value
                  :EndIf
              :EndIf
          :EndIf
        ∇

        ∇ r←ParseValue value
          :Access shared public
          :If 2=⍴⍴value
          :AndIf 2=¯1↑⍴value
          :ElseIf 2<|≡value
          :AndIf ∧/(,2)∘≡∘⍴¨value
              value←↑value
          :ElseIf 0=2|⍴value
              value←((0.5×⍴value),2)⍴value
          :Else
              'Invalid option format'⎕SIGNAL 11
          :EndIf
          r←↓[1]value
        ∇

        ∇ r←GetOption names
          :Access public
          r←{6::'' ⋄ Options.⍎⍵}¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          :If 1=⍴r ⋄ r←⊃r ⋄ :EndIf ⍝ if single setting requested
        ∇

        ∇ r←name Update value;v
          :Access public
          r←name(Selector #._JQ.Update JQueryFn)value
          name Set value
        ∇

        ∇ r←ShowOptions
          :Access public
          r←(⎕JSON⍠'Compact' 0)Options
          ∘∘∘
        ∇
    :endclass

    :class _jqUIWidget : _jqWidget

        :field public Force←0

⍝        :field public WidgetSyntax ←⊂'event,ui' 'event'  'ui' '$(event.target)' '.val()' ⍝ see Handler class below

        ∇ make
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor
        ∇

        ∇ make1 pars
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor :base pars
        ∇

        ∇ {r}←opts RenderHandler handler
          :Access public
          r←opts ⎕BASE.RenderHandler(handler WidgetDef Force)
        ∇

    :endclass

    :endsection

⍝ --- Events ---
    :Section Events

    :Class Handler
        :Field public Selector←''        ⍝ CSS/jQuery selector to bind handler to
        :Field public Delegates←''       ⍝ See jQuery.On for information about delegates
        :Field public Events←''          ⍝ events to bind
        :Field public ClientData←''      ⍝ any additional client data to send to server
        :Field public Callback←1         ⍝ execute AJAX callback to server?  or the name of the server-side callback function
        :Field public JavaScript←''      ⍝ JavaScript to execute prior to server callback
        :Field public Page←''            ⍝ server URL to request for an AJAX callback
        :Field public Method←'on'        ⍝ by default use jQuery .on(), but can also be set to 'one' for a handler that fires only once
        :Field public jQueryWrap←1       ⍝ wrap handler in $(function(){...});
        :Field public ScriptWrap←1       ⍝ wrap handler in <script>...</script>
        :Field public StatusHandlers     ⍝
        :Field public WidgetDef←''       ⍝ widget definitions (e.g. jQuery or Syncfusion, others libraries may different)
                                         ⍝                                          jQuery            Syncfusion
                                         ⍝ [1] event handler syntax:                'event,ui'        'argument'
                                         ⍝ [2] syntax to access the event object:   'event'           'argument'
                                         ⍝ [3] syntax to access the object's model: 'ui'              'argument.model'
                                         ⍝ [4] syntax to access the widget itself:  '$(event.target)' 'this.element'
                                         ⍝ [5] syntax to access the value of an input widget - this default to '' but may overridden by individual widget
                                         ⍝     see ejSlider as an example
                                         ⍝
        :Field public ForceInternal←¯1   ⍝ indicates whether to "force" the event to be treated as a widget internal event
        :Field public WidgetRef←''       ⍝ ref to widget instance if this handler is on
        :Field public Hourglass←¯1       ⍝ indicates whether to display hourglass during callback execution
                                         ⍝ 1 - yes, 0 - no, ¯1 - only if calling back to APL

        :Field public Uses←'JQuery'
        :Field public _PageRef_←''

        quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵~'⍎' ⋄ '"',(('"' ⎕R '\\\0')⍵),'"'} ⍝ quote unless already quoted or begins with ⍎
        ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

        ∇ Make0
          :Access public
          :Implements constructor
          CommonSetup
        ∇

        ∇ Make1 params
          :Access public
          :Implements constructor
          params←#.HtmlElement.eis params
          (Selector Events Callback ClientData JavaScript Delegates Page)←7↑params,(⍴params)↓7⍴⊂''
          :If #.HtmlPage #.HtmlElement.isInstance Page ⋄ Page←Page.Page ⋄ :EndIf ⍝ if request object passed
          CommonSetup
        ∇

        ∇ CommonSetup;c
          :Access public
          :Trap 0
              c←#.HtmlElement.context'_PageRef'
              :If ~0∊⍴c
                  (c⍎'_PageRef').Use Uses
                  _PageRef_←c._PageRef
              :EndIf
          :EndTrap
          WidgetDef←'event,ui' 'event' 'ui' '$(event.target)' '.val()'
          StatusHandlers←408 500,⍪'alert("Session timed out")' 'alert("Internal Server Error")'
        ∇

        ∇ r←code OnStatus javascript;ind
          :Access public
          ind←StatusHandlers[;1]⍳code
          StatusHandlers↑⍨←ind⌈≢StatusHandlers
          StatusHandlers[ind;]←code javascript
        ∇

        ∇ r←Render;sel;syn_handler;syn_event;syn_model;syn_this;data;useajax;force;cd;selector;arg;verb;name;phrase;datasel;JQfn;jqfn;hg;removehg;dtype;success;status;ajax;widget;syn_value;delegates;v;events;selSelector;statjs;statcode
          :Access public
          r←''
          :If ~0∊⍴Events ⍝ skip if no events specified

              :If #.MiPage #.HtmlElement.isInstance Page
                  Page←Page._PageName
              :EndIf

              (selector delegates)←2↑(eis Selector),'' ''

              :If ~0∊⍴Delegates
                  delegates←Delegates
              :EndIf

              :If useajax←(,0)≢,Callback ⍝ callback=0 → don't make callback to server; =1 → use APLJax, =charvec → call ⍎charvec
              :AndIf (0∊⍴Page)>0∊⍴_PageRef_
                  Page←_PageRef_._PageName
              :EndIf

              force←0
              events←Events
              :If widget←#.HtmlElement.isWidget WidgetRef ⍝ is this a widget handler?
                  WidgetDef←WidgetRef.WidgetDef
                  :If ForceInternal=¯1
                      :If ∨/', '∊Events ⍝ multiple events?
                          events←', '#.Utils.penclose Events
                      :Else
                          events←,⊂Events
                      :EndIf
                      force←∧/events∊WidgetRef.InternalEvents
                  :Else
                      force←ForceInternal
                  :EndIf
              :EndIf

              :If 0∊⍴selector  ⍝ empty selector → use entire page
                  :If widget
                      selector←WidgetRef.Selector
                  :ElseIf #.HtmlElement #.HtmlElement.isInstance ##
                      :If #.MiPage #.HtmlElement.isInstance ##
                          selector←'⍎document'
                      :Else
                          selector←'#',##.id
                      :EndIf
                  :Else
                      selector←'⍎document'
                  :EndIf
              :EndIf

              (syn_handler syn_event syn_model syn_this syn_value)←5↑WidgetDef
              data←'_event: ',syn_event,'.type, '
              data,←'_what: function(arg){try{return arg.attr("id");}catch(e){return"";}}(',syn_this,'), '
              data,←'_value: function(arg){try{return arg',syn_value,';}catch(e){return"";}}(',syn_this,'), '
              data,←'_selector: ',(quote selector~'⍎'),', '
              data,←'_target: function(arg){try{return arg.target.id;}catch(e){return"";}}(',syn_event,'), '
              data,←'_currentTarget: function(arg){try{return arg.currentTarget.id;}catch(e){return"";}}(',syn_event,'), '

              :If #.HtmlElement.isString Callback ⍝ numeric Callback 1-call APLJax, 0-no callback to server
              :AndIf ~0∊⍴Callback
                  data,←'_callback: ',(quote Callback),', '
              :EndIf

              :If 0∊⍴ClientData ⍝ if you don't specify any clientdata, we serialize any forms on the page
                  data,←'_serialized_: $("form").serialize(), '
              :EndIf

              data←¯2↓data

              :If ~0∊⍴ClientData
                  :Select ≡⊃ClientData
                  :CaseList 0 ⍝ ClientData is a simple vector
                      ClientData←,⊂2⍴⊂,ClientData ⍝ so name and verb are set to the same
                  :CaseList 1 ⍝ first element is a vector
                      ClientData←,⊂ClientData ⍝ enclose it
                  :EndSelect

                  :For cd :In ClientData
                      cd←#.HtmlElement.eis cd
                      (name verb arg sel)←4↑cd,(⍴cd)↓4⍴⊂''
                      :If (~0∊⍴name)∨verb≡'serialize'
                      :AndIf ~0∊⍴verb
                          jqfn←'' ⍝ jQuery function to call if this client data element refers to a widget

                          :If (⊂verb)∊'html' 'val'  ⍝ neither html or val take an argument
                          :AndIf 0∊⍴sel
                              sel←arg
                          :EndIf

⍝ build the selector for the data element
                          :If sel≡'' ⍝ it's for the current element/widget
                              :If #.HtmlElement.isWidget WidgetRef
                                  datasel←'$(',(quote WidgetRef.Selector),').'
                                  :If ~0∊⍴jqfn←WidgetRef.JQueryFn
                                      datasel,←WidgetRef.JQueryFn
                                  :EndIf
                              :Else
                                  datasel←syn_this,'.'
                              :EndIf
                          :ElseIf #.HtmlElement #.HtmlElement.isInstance sel ⍝ selector is reference to other element
                              :If #._JQ._jqObject #.HtmlElement.isInstance sel ⍝ is it a jQuery-based object?
                                  :If 0∊⍴selSelector←sel.Selector ⍝ no Selector?
                                      sel.SetId
                                      selSelector←'#',sel.id
                                  :EndIf
                                  datasel←'$(',(quote selSelector),').'
                                  :If ~0∊⍴jqfn←sel.JQueryFn
                                      datasel,←sel.JQueryFn
                                  :EndIf
                              :Else ⍝ not a jQuery-based object
                                  sel.SetId
                                  datasel←'$(',(quote'#',sel.id),').'
                              :EndIf
                          :Else ⍝ sel is not an HtmlElement
                              :Select |≡sel
                              :CaseList 0 1
                                  datasel←'$(',(quote sel),').'
                              :Case 2
                                  datasel←'$(',(quote(1⊃sel)),').',jqfn←2⊃sel
                              :EndSelect
                          :EndIf

⍝ now figure out what data to generate
                          phrase←''
                          :Select verb

                          :CaseList 'attr' 'css' 'is' 'prop'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'(',(quote arg),')'

                          :CaseList 'html' 'val'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'()'

                          :Case 'option' ⍝ jQueryUI and Syncfusion widgets
                              name,⍨←'_json_'
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',datasel,'("option"))'
                              :Else
                                  phrase←'APLstringify(',datasel,'("option",',(quote arg),'))'
                              :EndIf

                          :Case 'method' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'"no method information specified!"'
                              :Else
                                  phrase←datasel,'(',(quote arg),')'
                              :EndIf

                          :CaseList 'event' 'this' 'argument'
                              v←('event' 'argument' 'this'⍳⊂verb)⊃syn_event syn_event syn_this
                              name,⍨←'_json_'
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',v,')'
                              :Else
                                  phrase←'APLstringify(',v,'.',arg,')'
                              :EndIf

                          :CaseList 'model' 'ui' ⍝ widgets only
                              :If ~0∊⍴jqfn
                                  datasel,←'().'
                                  name,⍨←'_json_'
                                  :If 0∊⍴arg
                                      phrase←'APLstringify(',syn_model,')'
                                  :Else
                                      phrase←'APLstringify(',syn_model,'.',arg,')'
                                  :EndIf
                              :EndIf

                          :Case 'eval'
                              sel←'' ⍝ ignore selector on eval
                              phrase←'eval(',(quote arg),')'

                          :Case 'js'
                              sel←''
                              phrase←arg

                          :Case 'string'
                              phrase←quote arg

                          :Case 'serialize'
                              :If 0∊⍴sel
                                  :If 0∊⍴arg
                                      sel←'form'
                                  :Else
                                      sel←arg
                                  :EndIf
                              :EndIf
                              phrase←'$(',(quote sel),').serialize()'
                              name,⍨←'_serialized_'
                          :Else
                              :If 1<≡verb
                                  sel←''
                                  phrase←⊃verb
                              :ElseIf '⍎'=⊃verb
                                  sel←''
                                  phrase←1↓verb
                              :Else
                                  #.Boot.Log'Unknown event handler verb: "',verb,'"',{0::'' ⋄ ' on page ',##._PageRef._PageName}⍬
                                  phrase←quote phrase
                              :EndIf
                          :EndSelect

                          data,←',',name,': ',phrase
                      :EndIf
                  :EndFor
              :EndIf

              (hg removehg)←((1+Hourglass=¯1)⊃Hourglass useajax)∘{⍺:';document.body.style.cursor="',⍵,'";' ⋄ ''}¨'wait' 'default'

              dtype←'"json"'
              success←'success: function(obj){APLJaxReturn(obj)',removehg,'}'
              status←'statusCode:{ '
              :For (statcode statjs) :In ↓StatusHandlers
                  :If ~0∊⍴statjs
                      status,←(⍕statcode),': function(p1, p2, p3){',statjs,removehg,'},' ⍝ use generic p1, p2, p3 because jQuery has different parameters for error and success
                  :EndIf
              :EndFor
              status←(¯1↓status),'}'
              ⍝status←'statusCode:{ 408: function(){alert("Session timed out")',removehg,'}}'
              ajax←(JavaScript ine JavaScript,(';'=¯1↑JavaScript)↓';'),useajax/hg,'$.ajax({url: ',(quote Page),', cache: false, type: "POST", dataType: ',dtype,', headers:{"isAPLJax": "true"}, data: {',data,'}, ',success,', ',status,'});'

              :If widget
                  :If force
                      (eis events)(WidgetRef.Options{⍺⍺⍎⍺,'←⍵'})¨⊂⊂'function(',syn_handler,'){',ajax,'}'
                  :Else
                      r←'.',Method,'(',(quote Events),', function(event,ui){',ajax,'});'
                  :EndIf
              :Else
                  r←'$(',(quote selector),').',Method,'(',(quote Events),(delegates ine', ',quote delegates),', function(event,ui){',ajax,'});'
                  :If jQueryWrap ⋄ r←'$(function(){',r,'});' ⋄ :EndIf
                  :If ScriptWrap ⋄ r←'<script>',r,'</script>' ⋄ :EndIf
              :EndIf
          :EndIf
        ∇

    :endclass
    :endsection

:EndNamespace

:Namespace _JQ ⍝ jQueryUI
    (⎕IO ⎕ML)←1

    :section Common Code

    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵ ⋄ '"',(('"' ⎕R '\\\0')⍕⍵),'"'}
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓∊{⍵,', '}¨eis ⍵}
    fmtValue←{(2=≡⍵)∧0=≢⍴⍵:⊃⍵ ⋄ #.JSON.fromAPL ⍵}

    ∇ r←opt(sel Update jqfn)val
    ⍝ update an option for a widget
      r←#.JQ.Execute'$(',(fmtSelector sel),').',jqfn,'("option","',opt,'",',(fmtValue val),');'
    ∇
    :endsection

    :section Base Classes
    :class _jqObject : #.HtmlElement
        :field public Selector←''      ⍝ Selector to apply the JQuery function to
        :field public JavaScript←''    ⍝ additional JavaScript to run AFTER the jQuery function, can be function chain, separate code or both
        :field public PreJavaScript←'' ⍝ additional JavaScript to run BEFORE the jQuery function
        :field public Var←''           ⍝ JavaScript variable name for created object
        :field public JQueryFn←''      ⍝ JQuery function to apply
        :field public JQPars←''        ⍝ JQuery function parameters
        :field public Type←''          ⍝ Type specification that may precede JQPars
        :field public shared readonly _true←#.JSON.true     ⍝ same definition as in #.JSON
        :field public shared readonly _false←#.JSON.false   ⍝ same definition as in #.JSON
        :field public ScriptOptions←⍬  ⍝ determines how script will be rendered [1] wrap with <script>...</script>, [2] wrap with $(function(){...})

        ∇ Make0
          :Access public
          :Implements constructor
        ∇

        ∇ Make1 pars
          :Access public
          :Implements constructor
          pars←eis pars
          JQueryFn Selector JQPars JavaScript Var PreJavaScript Type←7↑pars,(⍴pars)↓'' '' '' '' '' '' ''
        ∇

        ∇ r←Render
          :Access public
          SetUse
          r←ScriptOptions #.JQ.JQueryfn JQueryFn Selector JQPars JavaScript Var PreJavaScript Type
        ∇

        ∇ r←isSelector str ⍝ checks if str is probably a jQuery selector
          :Access public shared
          r←∨/str∊'''"*[]:>+~()#.'
        ∇

        :section APLJax

        ∇ r←renderContent content;c
          r←''
          content←eis content
          :While ~0∊⍴content
              :Select ≡c←⊃content
              :Case 0
                  :If isClass c
                      :Select ⊃⍴content
                      :Case 1
                          r,←(⎕NEW c).Render
                      :Case 2
                          r,←(⎕NEW c(2⊃content)).Render
                      :Else
                          r,←(⎕NEW c(1↓content)).Render
                      :EndSelect
                  :ElseIf isInstance c
                      r,←c.Render
                  :Else
                      r,←(⎕NEW #.HtmlElement(''content)).Render
                  :EndIf
                  content←''
              :Case 1
                  :If isClass⊃c
                      r,←(⎕NEW(⊃c)(1↓c)).Render
                  :ElseIf isInstance⊃c
                      ∘∘∘ ⍝ should not happen! (I think)
                  :Else
                      r,←(⎕NEW #.HtmlElement(''c)).Render
                  :EndIf
                  content←1↓content
              :Else
                  r,←renderContent c
                  content←1↓content
              :EndSelect
          :EndWhile
        ∇

        ∇ r←selector Replace content
          :Access public
          r←⊂('replace'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Append content
          :Access public
          r←⊂('append'selector)('data'(renderContent content))
        ∇

        ∇ r←selector Prepend content
          :Access public
          r←⊂('prepend'selector)('data'(renderContent content))
        ∇

        ∇ r←Execute content
          :Access public
          r←⊂('execute'(renderContent content))
        ∇

        ∇ r←name Assign data
          :Access public
          r←⊂('assign'name)('data'data)
        ∇
        :EndSection

    :endclass

    :class _jqWidget : _jqObject

        :field public Options←''    ⍝ options for the object to be created
        :field public ContainerTag←'div' ⍝ default container type
        :field public Container
        :field public InternalEvents←'' ⍝ list of events the widget "knows" about
        :field public BuildHTML←1       ⍝ if 0, we build any HTML infrastructure for the widget, otherwise, assume the user built it
        :field public WidgetDef←'event,ui' 'event'  'ui' '$(event.currentTarget)' '.val()'  ⍝ see _JQ.RenderHandlerCore for details

        ∇ r←{a}rand w;⎕RL
          :Access public
          ⎕RL←0
          :If 0=⎕NC'a' ⋄ r←?w
          :Else ⋄ r←a?w ⋄ :EndIf
        ∇

        ∇ Make
          :Access public
          Options←⎕NS''
          Container←⎕NEW #.HtmlElement
          :Implements constructor
        ∇

        ∇ r←Render;build;html;handlers;js;oldJavaScript
          :Access public
          r←html←js←''
          SetUse

          BuildHTML∧←0∊⍴Selector ⍝ if the user explicitly specifies a selector, assume he's built the content himself

          :If BuildHTML
              Container.(id name type style class title)←Container.(id name type style class title){UNDEF≡⍵:⍺ ⋄ UNDEF≢⍺:⍺ ⋄ ⍵}¨⎕THIS.(id name type style class title)
              :If Container.id≡UNDEF
                  :If Container.name≢UNDEF
                      Container.id←Container.name
                  :Else
                      Container.id←SetId
                  :EndIf
              :EndIf
              Selector←'#',Container.id
          :EndIf

          handlers←''
          :If ~0∊⍴Handlers
              handlers←';',⍨∊¯1↓¨Handlers.Render
          :EndIf

          oldJavaScript←JavaScript
          JavaScript,←handlers
          JQPars←Options
          js←⎕BASE.Render
          JavaScript←oldJavaScript

          :If BuildHTML≥0∊⍴Container.Content
              :Select ⊃Selector
              :Case '#' ⍝ id?
                  Container.id←1↓Selector
              :Case '.' ⍝ class?
                  Container.class←1↓Selector
              :EndSelect
              :If ContainerTag{⍵≡(⍴⍵)↑⍺}'input'
              :AndIf UNDEF≡Container.name
                  Container.name←('.#'∊⍨⊃Selector)↓Selector
              :EndIf
              Container.Tag←ContainerTag
              html←Container.Render
          :EndIf
          r←html,js
        ∇

        ∇ {handler}←On args
          :Access public
          ⍝ args - event callback clientData javascript
          handler←⎕BASE.On args
          handler.InternalEvents←InternalEvents
          handler.(WidgetDef←WidgetRef.WidgetDef)
        ∇

        ∇ {r}←{name}Set value
          :Access public
          r←⎕THIS
          →(0∊⍴value)⍴0
          :If 326≠⎕DR Options ⋄ Options←⎕NS'' ⋄ :EndIf
          :If 0=⎕NC'name'
              :Trap 11
                  (name value)←ParseValue value
              :Else
                  ⎕SIGNAL/⎕DMX.(EM EN)
              :EndTrap
          :EndIf
          :Trap 0
              name(Options SetOption)value
          :Else
              ⎕SIGNAL/⎕DMX.(EM EN)
          :EndTrap
        ∇

        ∇ {r}←name SetIfNotSet value
          :Access public
          :If 0∊⍴GetOption name
              name Set value
          :EndIf
          r←⎕THIS
        ∇

        ∇ name(ref SetOption)value;set;parent;ind;newref;chunk;n;now;new;chunkroot;array;val;pos;r;v
          →(0∊⍴value)⍴0
          :If 1<|≡name ⍝ multiple names?
              value←(⊂⍣((⎕DR value)∊80 82))value
              name(ref SetOption)¨value
          :Else
              set←{
                  ⍺⍺⍎'(',⍺,')←⍵'
              }

              :If 0∊⍴parent←(-'.'⍳⍨⌽name)↓name ⍝ if no parent...
                  :If 1<n←≢ref  ⍝ multiple refs?
                      ⎕SIGNAL 5/⍨(0=≢⍴value)=(⍴ref)=⍴value ⍝ LENGTH ERROR if value is not scalar or same length as ref
                      :For (r v) :InEach ref(n⍴value)
                          name(r set)v
                      :EndFor
                  :ElseIf (1+' '+.=name)=≢value
                      name(ref set)value
                  :Else
                      name(ref set)value ⍝ single name: assign directly (may be more than 1 name)
                  :EndIf
              :Else
                  ind←name⍳'.'
                  chunk←¯1↓ind↑name
                  (chunkroot pos)←2↑'[]'#.Utils.penclose chunk

                  :If array←'[]'≡¯2↑chunk ⍝ is it an array assignment?
                      pos←≢val←eis value
                      :Select ⊃ref.⎕NC chunkroot
                      :Case 0
                          new←⎕NS¨(⌈/pos)⍴⊂⍬
                          chunkroot(ref set)new
                      :Case 2 ⍝ already exists
                          ('Invalid option specification - length error on "',chunkroot,'".')⎕SIGNAL 5/⍨~(⍴new←ref⍎chunkroot)∊1,pos
                      :Else
                          ('Invalid option specification - "',chunkroot,'" is not an array.')⎕SIGNAL 11
                      :EndSelect
                      new{(ind↓name)(⍺ SetOption)⍵}¨val

                  :Else
                      (chunkroot pos)←2↑'[]'#.Utils.penclose chunk
                      pos←2⊃⎕VFI pos
                      :Trap 3 6
                          newref←ref⍎chunk
                      :Case 3 ⍝ index error
                          n←⍴now←ref⍎chunkroot
                          new←⎕NS¨(pos-n)⍴⊂⍬
                          chunkroot(ref set)now,new
                          newref←ref⍎chunk
                      :Case 6 ⍝ value error
                          :If ~0∊⍴pos
                              new←⎕NS¨(⌈/pos)⍴⊂⍬
                              chunkroot(ref set)new
                          :Else
                              chunkroot ref.⎕NS''
                          :EndIf
                          newref←ref⍎chunk
                      :EndTrap
                      (ind↓name)(newref SetOption)value
                  :EndIf
              :EndIf
          :EndIf
        ∇

        ∇ r←ParseValue value
          :Access shared public
          :If 2=⍴⍴value
          :AndIf 2=¯1↑⍴value
          :ElseIf 2<|≡value
          :AndIf ∧/(,2)∘≡∘⍴¨value
              value←↑value
          :ElseIf 0=2|⍴value
              value←((0.5×⍴value),2)⍴value
          :Else
              'Invalid option format'⎕SIGNAL 11
          :EndIf
          r←↓[1]value
        ∇

        ∇ r←GetOption names
          :Access public
          r←{6::'' ⋄ Options.⍎⍵}¨{⎕ML←3 ⋄ ⍵⊂⍨⍵≠' '}names
          :If 1=⍴r ⋄ r←⊃r ⋄ :EndIf ⍝ if single setting requested
        ∇

        ∇ r←name Update value;v
          :Access public
          r←name(Selector #._JQ.Update JQueryFn)value
          name Set value
        ∇

        ∇ r←ShowOptions
          :Access public
          r←(⎕JSON⍠'Compact' 0)Options
          ∘∘∘
        ∇
    :endclass

    :class _jqUIWidget : _jqWidget

        :field public Force←0

⍝        :field public WidgetSyntax ←⊂'event,ui' 'event'  'ui' '$(event.target)' '.val()' ⍝ see Handler class below

        ∇ make
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor
        ∇

        ∇ make1 pars
          :Access public
          :If 0=⎕NC⊂'Uses' ⋄ Uses←'' ⋄ :EndIf
          :If 0∊⍴Uses ⋄ Uses←'JQueryUI' ⋄ :EndIf
          :Implements constructor :base pars
        ∇

        ∇ {r}←opts RenderHandler handler
          :Access public
          r←opts ⎕BASE.RenderHandler(handler WidgetDef Force)
        ∇

    :endclass

    :endsection

⍝ --- Events ---
    :Section Events

    :Class Handler
        :Field public Selector←''        ⍝ CSS/jQuery selector to bind handler to
        :Field public Delegates←''       ⍝ See jQuery.On for information about delegates
        :Field public Events←''          ⍝ events to bind
        :Field public ClientData←''      ⍝ any additional client data to send to server
        :Field public Callback←1         ⍝ execute AJAX callback to server?  or the name of the server-side callback function
        :Field public JavaScript←''      ⍝ JavaScript to execute prior to server callback
        :Field public Page←''            ⍝ server URL to request for an AJAX callback
        :Field public Method←'on'        ⍝ by default use jQuery .on(), but can also be set to 'one' for a handler that fires only once
        :Field public jQueryWrap←1       ⍝ wrap handler in $(function(){...});
        :Field public ScriptWrap←1       ⍝ wrap handler in <script>...</script>
        :Field public StatusHandlers     ⍝
        :Field public WidgetDef←''       ⍝ widget definitions (e.g. jQuery or Syncfusion, others libraries may different)
                                         ⍝                                          jQuery            Syncfusion
                                         ⍝ [1] event handler syntax:                'event,ui'        'argument'
                                         ⍝ [2] syntax to access the event object:   'event'           'argument'
                                         ⍝ [3] syntax to access the object's model: 'ui'              'argument.model'
                                         ⍝ [4] syntax to access the widget itself:  '$(event.target)' 'this.element'
                                         ⍝ [5] syntax to access the value of an input widget - this default to '' but may overridden by individual widget
                                         ⍝     see ejSlider as an example
                                         ⍝
        :Field public ForceInternal←¯1   ⍝ indicates whether to "force" the event to be treated as a widget internal event
        :Field public WidgetRef←''       ⍝ ref to widget instance if this handler is on
        :Field public Hourglass←¯1       ⍝ indicates whether to display hourglass during callback execution
                                         ⍝ 1 - yes, 0 - no, ¯1 - only if calling back to APL

        :Field public Uses←'JQuery'
        :Field public _PageRef_←''

        quote←{0∊⍴⍵: '' ⋄ '⍎"'∊⍨⍬⍴⍵:⍵~'⍎' ⋄ '"',(('"' ⎕R '\\\0')⍵),'"'} ⍝ quote unless already quoted or begins with ⍎
        ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
        eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple

        ∇ Make0
          :Access public
          :Implements constructor
          CommonSetup
        ∇

        ∇ Make1 params
          :Access public
          :Implements constructor
          params←#.HtmlElement.eis params
          (Selector Events Callback ClientData JavaScript Delegates Page)←7↑params,(⍴params)↓7⍴⊂''
          :If #.HtmlPage #.HtmlElement.isInstance Page ⋄ Page←Page.Page ⋄ :EndIf ⍝ if request object passed
          CommonSetup
        ∇

        ∇ CommonSetup;c
          :Access public
          :Trap 0
              c←#.HtmlElement.context'_PageRef'
              :If ~0∊⍴c
                  (c⍎'_PageRef').Use Uses
                  _PageRef_←c._PageRef
              :EndIf
          :EndTrap
          WidgetDef←'event,ui' 'event' 'ui' '$(event.target)' '.val()'
          StatusHandlers←408 500,⍪'alert("Session timed out")' 'alert("Internal Server Error")'
        ∇

        ∇ r←code OnStatus javascript;ind
          :Access public
          ind←StatusHandlers[;1]⍳code
          StatusHandlers↑⍨←ind⌈≢StatusHandlers
          StatusHandlers[ind;]←code javascript
        ∇

        ∇ r←Render;sel;syn_handler;syn_event;syn_model;syn_this;data;useajax;force;cd;selector;arg;verb;name;phrase;datasel;JQfn;jqfn;hg;removehg;dtype;success;status;ajax;widget;syn_value;delegates;v;events;selSelector;statjs;statcode
          :Access public
          r←''
          :If ~0∊⍴Events ⍝ skip if no events specified

              :If #.MiPage #.HtmlElement.isInstance Page
                  Page←Page._PageName
              :EndIf

              (selector delegates)←2↑(eis Selector),'' ''

              :If ~0∊⍴Delegates
                  delegates←Delegates
              :EndIf

              :If useajax←(,0)≢,Callback ⍝ callback=0 → don't make callback to server; =1 → use APLJax, =charvec → call ⍎charvec
              :AndIf (0∊⍴Page)>0∊⍴_PageRef_
                  Page←_PageRef_._PageName
              :EndIf

              force←0
              events←Events
              :If widget←#.HtmlElement.isWidget WidgetRef ⍝ is this a widget handler?
                  WidgetDef←WidgetRef.WidgetDef
                  :If ForceInternal=¯1
                      :If ∨/', '∊Events ⍝ multiple events?
                          events←', '#.Utils.penclose Events
                      :Else
                          events←,⊂Events
                      :EndIf
                      force←∧/events∊WidgetRef.InternalEvents
                  :Else
                      force←ForceInternal
                  :EndIf
              :EndIf

              :If 0∊⍴selector  ⍝ empty selector → use entire page
                  :If widget
                      selector←WidgetRef.Selector
                  :ElseIf #.HtmlElement #.HtmlElement.isInstance ##
                      :If #.MiPage #.HtmlElement.isInstance ##
                          selector←'⍎document'
                      :Else
                          selector←'#',##.id
                      :EndIf
                  :Else
                      selector←'⍎document'
                  :EndIf
              :EndIf

              (syn_handler syn_event syn_model syn_this syn_value)←5↑WidgetDef
              data←'_event: ',syn_event,'.type, '
              data,←'_what: function(arg){try{return arg.attr("id");}catch(e){return"";}}(',syn_this,'), '
              data,←'_value: function(arg){try{return arg',syn_value,';}catch(e){return"";}}(',syn_this,'), '
              data,←'_selector: ',(quote selector~'⍎'),', '
              data,←'_target: function(arg){try{return arg.target.id;}catch(e){return"";}}(',syn_event,'), '
              data,←'_currentTarget: function(arg){try{return arg.currentTarget.id;}catch(e){return"";}}(',syn_event,'), '

              :If #.HtmlElement.isString Callback ⍝ numeric Callback 1-call APLJax, 0-no callback to server
              :AndIf ~0∊⍴Callback
                  data,←'_callback: ',(quote Callback),', '
              :EndIf

              :If 0∊⍴ClientData ⍝ if you don't specify any clientdata, we serialize any forms on the page
                  data,←'_serialized_: $("form").serialize(), '
              :EndIf

              data←¯2↓data

              :If ~0∊⍴ClientData
                  :Select ≡⊃ClientData
                  :CaseList 0 ⍝ ClientData is a simple vector
                      ClientData←,⊂2⍴⊂,ClientData ⍝ so name and verb are set to the same
                  :CaseList 1 ⍝ first element is a vector
                      ClientData←,⊂ClientData ⍝ enclose it
                  :EndSelect

                  :For cd :In ClientData
                      cd←#.HtmlElement.eis cd
                      (name verb arg sel)←4↑cd,(⍴cd)↓4⍴⊂''
                      :If (~0∊⍴name)∨verb≡'serialize'
                      :AndIf ~0∊⍴verb
                          jqfn←'' ⍝ jQuery function to call if this client data element refers to a widget

                          :If (⊂verb)∊'html' 'val'  ⍝ neither html or val take an argument
                          :AndIf 0∊⍴sel
                              sel←arg
                          :EndIf

⍝ build the selector for the data element
                          :If sel≡'' ⍝ it's for the current element/widget
                              :If #.HtmlElement.isWidget WidgetRef
                                  datasel←'$(',(quote WidgetRef.Selector),').'
                                  :If ~0∊⍴jqfn←WidgetRef.JQueryFn
                                      datasel,←WidgetRef.JQueryFn
                                  :EndIf
                              :Else
                                  datasel←syn_this,'.'
                              :EndIf
                          :ElseIf #.HtmlElement #.HtmlElement.isInstance sel ⍝ selector is reference to other element
                              :If #._JQ._jqObject #.HtmlElement.isInstance sel ⍝ is it a jQuery-based object?
                                  :If 0∊⍴selSelector←sel.Selector ⍝ no Selector?
                                      sel.SetId
                                      selSelector←'#',sel.id
                                  :EndIf
                                  datasel←'$(',(quote selSelector),').'
                                  :If ~0∊⍴jqfn←sel.JQueryFn
                                      datasel,←sel.JQueryFn
                                  :EndIf
                              :Else ⍝ not a jQuery-based object
                                  sel.SetId
                                  datasel←'$(',(quote'#',sel.id),').'
                              :EndIf
                          :Else ⍝ sel is not an HtmlElement
                              :Select |≡sel
                              :CaseList 0 1
                                  datasel←'$(',(quote sel),').'
                              :Case 2
                                  datasel←'$(',(quote(1⊃sel)),').',jqfn←2⊃sel
                              :EndSelect
                          :EndIf

⍝ now figure out what data to generate
                          phrase←''
                          :Select verb

                          :CaseList 'attr' 'css' 'is' 'prop'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'(',(quote arg),')'

                          :CaseList 'html' 'val'
                              datasel,←'().'/⍨~0∊⍴jqfn
                              phrase←datasel,verb,'()'

                          :Case 'option' ⍝ jQueryUI and Syncfusion widgets
                              name,⍨←'_json_'
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',datasel,'("option"))'
                              :Else
                                  phrase←'APLstringify(',datasel,'("option",',(quote arg),'))'
                              :EndIf

                          :Case 'method' ⍝ jQueryUI and Syncfusion widgets
                              :If 0∊⍴arg
                                  phrase←'"no method information specified!"'
                              :Else
                                  phrase←datasel,'(',(quote arg),')'
                              :EndIf

                          :CaseList 'event' 'this' 'argument'
                              v←('event' 'argument' 'this'⍳⊂verb)⊃syn_event syn_event syn_this
                              name,⍨←'_json_'
                              :If 0∊⍴arg
                                  phrase←'APLstringify(',v,')'
                              :Else
                                  phrase←'APLstringify(',v,'.',arg,')'
                              :EndIf

                          :CaseList 'model' 'ui' ⍝ widgets only
                              :If ~0∊⍴jqfn
                                  datasel,←'().'
                                  name,⍨←'_json_'
                                  :If 0∊⍴arg
                                      phrase←'APLstringify(',syn_model,')'
                                  :Else
                                      phrase←'APLstringify(',syn_model,'.',arg,')'
                                  :EndIf
                              :EndIf

                          :Case 'eval'
                              sel←'' ⍝ ignore selector on eval
                              phrase←'eval(',(quote arg),')'

                          :Case 'js'
                              sel←''
                              phrase←arg

                          :Case 'string'
                              phrase←quote arg

                          :Case 'serialize'
                              :If 0∊⍴sel
                                  :If 0∊⍴arg
                                      sel←'form'
                                  :Else
                                      sel←arg
                                  :EndIf
                              :EndIf
                              phrase←'$(',(quote sel),').serialize()'
                              name,⍨←'_serialized_'
                          :Else
                              :If 1<≡verb
                                  sel←''
                                  phrase←⊃verb
                              :ElseIf '⍎'=⊃verb
                                  sel←''
                                  phrase←1↓verb
                              :Else
                                  #.Boot.Log'Unknown event handler verb: "',verb,'"',{0::'' ⋄ ' on page ',##._PageRef._PageName}⍬
                                  phrase←quote phrase
                              :EndIf
                          :EndSelect

                          data,←',',name,': ',phrase
                      :EndIf
                  :EndFor
              :EndIf

              (hg removehg)←((1+Hourglass=¯1)⊃Hourglass useajax)∘{⍺:';document.body.style.cursor="',⍵,'";' ⋄ ''}¨'wait' 'default'

              dtype←'"json"'
              success←'success: function(obj){APLJaxReturn(obj)',removehg,'}'
              status←'statusCode:{ '
              :For (statcode statjs) :In ↓StatusHandlers
                  :If ~0∊⍴statjs
                      status,←(⍕statcode),': function(p1, p2, p3){',statjs,removehg,'},' ⍝ use generic p1, p2, p3 because jQuery has different parameters for error and success
                  :EndIf
              :EndFor
              status←(¯1↓status),'}'
              ⍝status←'statusCode:{ 408: function(){alert("Session timed out")',removehg,'}}'
              ajax←(JavaScript ine JavaScript,(';'=¯1↑JavaScript)↓';'),useajax/hg,'$.ajax({url: ',(quote Page),', cache: false, type: "POST", dataType: ',dtype,', headers:{"isAPLJax": "true"}, data: {',data,'}, ',success,', ',status,'});'

              :If widget
                  :If force
                      (eis events)(WidgetRef.Options{⍺⍺⍎⍺,'←⍵'})¨⊂⊂'function(',syn_handler,'){',ajax,'}'
                  :Else
                      r←'.',Method,'(',(quote Events),', function(event,ui){',ajax,'});'
                  :EndIf
              :Else
                  r←'$(',(quote selector),').',Method,'(',(quote Events),(delegates ine', ',quote delegates),', function(event,ui){',ajax,'});'
                  :If jQueryWrap ⋄ r←'$(function(){',r,'});' ⋄ :EndIf
                  :If ScriptWrap ⋄ r←'<script>',r,'</script>' ⋄ :EndIf
              :EndIf
          :EndIf
        ∇

    :endclass
    :endsection

:EndNamespace


Fix ejDatePickerAdvanced merge problem

Fix ejDatePickerAdvanced merge problem

:Class ejDatePickerAdvanced : MiPageSample
⍝ Control:: _SF.ejDatePicker
⍝ Description:: Insert mutually interdependant calendars

⍝ Show 2 calendars to select reservation dates.
⍝ The first date cannot be before today and the second cannot be the
⍝ date before tomorrow. Its default is 1 week after check in.

    ∇ Compose;today;fromidn;minto;tbl;tr;td;head;todate;from
      :Access Public
      today←3↑⎕TS
      fromidn←dateToIDN today

      Add _.h1'Drop ↓ Inn'
      Add _.h2'Hotel reservation system'

    ⍝ The table header
      tbl←Add _.table
      head←tbl.Add _.thead
      (head.Add _.tr).{Add _.th ⍵}¨'Check in' 'Check out'

      tr←tbl.Add _.tr
    ⍝ The in date
      td←tr.Add _.td
      from←'in'td.Add _SF.ejDatePicker today'yyyy/MM/dd'

      from.Set⊂'minDate'(newDate today)
      from.On'change' 'setNewDate',⊂('in' 'model' 'value')('out' 'eval' '$("#out").ejDatePicker("model.value")')

    ⍝ The out date
      td←tr.Add _.td
      todate←IDNToDate fromidn+7
      TO←'out'td.Add _SF.ejDatePicker todate'yyyy/MM/dd'
      minto←newDate IDNToDate fromidn+1
      TO.Set⊂'minDate'minto
    ∇

    newDate←{'⍎new Date("',(⍕⍵),'")'}
    dateToIDN←{2 ⎕NQ'.' 'datetoidn'⍵}
    IDNToDate←{3↑ 2 ⎕NQ'.' 'idntodate'⍵}

    ∇ r←setNewDate;in;out
      :Access public
    ⍝ Reset minimum reservation check out date
      in←dateToIDN stringToDate⍕Get'in'
      r←'minDate'TO.Update newDate IDNToDate in+1

      out←dateToIDN stringToDate⍕Get'out'

    ⍝ If the new IN date is not before the OUT date we reset OUT to 1 week after
      :If in≥out
          r,←'value'TO.Update newDate IDNToDate in+7
      :EndIf
    ∇

    MONTHS←↓12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec'

    FI←{(b v)←⎕vfi ⍵ ⋄ b/v}

    ∇ ymd←stringToDate str;pos;mon;t
     ⍝ str is of the genre "Wed Aug 05 2015 00:00:00 GMT-0400 (Eastern Daylight Time)"
     ⍝ We need to weed out the day of the week and the time
      str←(+/∧\' '=str)↓str           ⍝ remove the leading spaces
      :If 0∊⍴t←MONTHS ⎕S 0 3⊢str      ⍝ look for the month as a string. If not found
          ymd←3↑FI str                ⍝ grab the 1st 3 numbers found
          ymd←ymd[⍒(2×31<ymd)+ymd<12] ⍝ put in correct order
      :Else                           ⍝ otherwise (if found)
          (pos mon)←0 1+1⊃t
          :If ~0∊⍴t←FI pos↑str        ⍝ any number before the month? (e.g. 2 May 2021)
              ymd←⌽⍣(31<⍬⍴t)⊢(1↑FI pos↓str),mon,t
          :Else
              ymd←¯1⌽mon,2↑FI pos↓str
          :EndIf
      :EndIf
    ∇

:EndClass

:Class ejDatePickerAdvanced : MiPageSample
⍝ Control:: _SF.ejDatePicker
⍝ Description:: Insert mutually interdependant calendars

⍝ Show 2 calendars to select reservation dates.
⍝ The first date cannot be before today and the second cannot be the
⍝ date before tomorrow. Its default is 1 week after check in.

    ∇ Compose;today;fromidn;minto;tbl;tr;td;head;todate;from
      :Access Public
      today←3↑⎕TS
      fromidn←dateToIDN today

      Add _.h1'Drop ↓ Inn'
      Add _.h2'Hotel reservation system'

    ⍝ The table header
      tbl←Add _.table
      head←tbl.Add _.thead
      (head.Add _.tr).{Add _.th ⍵}¨'Check in' 'Check out'

      tr←tbl.Add _.tr
    ⍝ The in date
      td←tr.Add _.td
      from←'in'td.Add _SF.ejDatePicker today'yyyy/MM/dd'
<<<<<<< HEAD
      from.Set⊂'minDate'(newDate today)
      from.On'change' 'setNewDate',⊂('in' 'model' 'value')('out' 'eval' '$("#out").ejDatePicker("model.value")')
=======
      from.Set'minDate'(newDate today)
      from.On'change' 'setNewDate'(('in' 'argument' 'value')('out' 'eval' '$("#out").ejDatePicker("model.value")'))
>>>>>>> 5ab62425095ccf15d890974e9c9cbf78695bbc72

    ⍝ The out date
      td←tr.Add _.td
      todate←IDNToDate fromidn+7
      TO←'out'td.Add _SF.ejDatePicker todate'yyyy/MM/dd'
      minto←newDate IDNToDate fromidn+1
      TO.Set⊂'minDate'minto
    ∇

    newDate←{'⍎new Date("',(⍕⍵),'")'}
    dateToIDN←{2 ⎕NQ'.' 'datetoidn'⍵}
    IDNToDate←{3↑ 2 ⎕NQ'.' 'idntodate'⍵}

    ∇ r←setNewDate;in;out
      :Access public
    ⍝ Reset minimum reservation check out date
      in←dateToIDN stringToDate⍕Get'in'
      r←'minDate'TO.Update newDate IDNToDate in+1

      out←dateToIDN stringToDate⍕Get'out'

    ⍝ If the new IN date is not before the OUT date we reset OUT to 1 week after
      :If in≥out
          r,←'value'TO.Update newDate IDNToDate in+7
      :EndIf
    ∇

    MONTHS←↓12 3⍴'JanFebMarAprMayJunJulAugSepOctNovDec'

    FI←{(b v)←⎕vfi ⍵ ⋄ b/v}

    ∇ ymd←stringToDate str;pos;mon;t
     ⍝ str is of the genre "Wed Aug 05 2015 00:00:00 GMT-0400 (Eastern Daylight Time)"
     ⍝ We need to weed out the day of the week and the time
      str←(+/∧\' '=str)↓str           ⍝ remove the leading spaces
      :If 0∊⍴t←MONTHS ⎕S 0 3⊢str      ⍝ look for the month as a string. If not found
          ymd←3↑FI str                ⍝ grab the 1st 3 numbers found
          ymd←ymd[⍒(2×31<ymd)+ymd<12] ⍝ put in correct order
      :Else                           ⍝ otherwise (if found)
          (pos mon)←0 1+1⊃t
          :If ~0∊⍴t←FI pos↑str        ⍝ any number before the month? (e.g. 2 May 2021)
              ymd←⌽⍣(31<⍬⍴t)⊢(1↑FI pos↓str),mon,t
          :Else
              ymd←¯1⌽mon,2↑FI pos↓str
          :EndIf
      :EndIf
    ∇

:EndClass