Add debugging information to Logger.Log

Add debugging information to Logger.Log

:Class Logger ⍝ Logs HTTP requests

    ⎕ML←1

    :field public instance Active←0
    :field private instance Directory←''
    :field private instance Interval←10  ⍝ write cache interval in seconds
    :field private instance TieNo←⍬
    :field private instance Cache←''
    :field private instance Prefix←''
    :field private instance tid←¯1
    :field private EOL                  ⍝ End Of Line

    missing←{0∊⍴⍵:'-' ⋄ ⍵}

    ∇ Make ms;file;log
      :Access public
      :Implements Constructor
      ⍝ load logger information
      Active←0
      EOL←⎕UCS 13 10↓⍨~#.Boot.isWin
      :If #.Files.Exists file←ms.Config.Root,'Config/Logger.xml'
      :AndIf ~0∊⍴log←(#.XML.ToNS #.Files.ReadText file).Logger
          Active←log #.Boot.Setting'active' 1 0
          Prefix←log #.Boot.Setting'prefix' 0 ''
          Interval←log #.Boot.Setting'interval' 1 10
          Directory←#.Boot.SubstPath log #.Boot.Setting'directory' 0 ''
          Directory,←(~'/\'∊⍨¯1↑Directory)/'/'
          ('Log file directory "',Directory,'" not found!')⎕SIGNAL 11/⍨~#.Files.DirExists Directory
      :EndIf
      :If Active
          tid←Run&0
      :EndIf
    ∇

    ∇ UnMake
      :Implements destructor
      Stop ⍬
    ∇

    ∇ Stop w
      :Access public
      :Trap 0
          :If Active
              Active←0
              ⎕TKILL tid
              :If Open
                  ClearCache
                  Close
              :EndIf
          :EndIf
      :EndTrap
    ∇

    ∇ Start
      :Access public
      :If ~tid∊⎕TNUMS
          Active←1
          tid←Run&0
      :EndIf
    ∇

    ∇ Run x;done
      :While Active
          {}⎕DL Interval
          :If Open
              ClearCache
              Close
          :EndIf
      :EndWhile
    ∇

    ∇ r←Open;fn
      r←0
      :Trap 6
          fn←#.Files.Normalize Directory,Prefix,(⍕100⊥3↑⎕TS),'.log'
      :Else
          →0
      :EndTrap

      :Trap 22 ⍝ file name error
          TieNo←fn ⎕NTIE 0 34
      :Else
          :Trap 0
              ⎕NUNTIE fn ⎕NCREATE 0
              TieNo←fn ⎕NTIE 0 34
          :Else
              1 ##.ms.Log'Unable to open log file "',fn,'"'
              :Return
          :EndTrap
      :EndTrap
      r←0≠TieNo
    ∇

    tryGetting←{0::('-'@(' '∘=))(⊃⎕DM),'-retrieving-"',⍵,'"' ⋄ ⍕⍎⍵}

    ∇ Log req;addr;user;ts;method;page;status;msec;bytes
      :Access public
      :If Active
          :Hold 'Lumberjack'
              :Trap 0
                  Cache,←((missing 2⊃req.PeerAddr),' ',(missing req.Session.User),#.Dates.LogFmtNow,'"',req.Method,' ',req.Page,'"',∊' '∘,∘⍕¨req.Response.(Status MSec Bytes)),EOL
              :Else ⍝ something in logging failed, try figuring out what
                  addr←tryGetting'req.PeerAddr'
                  user←tryGetting'req.Session.User'
                  ts←tryGetting'#.Dates.LogFmtNow'
                  method←tryGetting'req.Method'
                  page←tryGetting'req.Page'
                  status←tryGetting'req.Response.Status'
                  msec←tryGetting'req.Response.MSec'
                  bytes←tryGetting'req.Response.Bytes'
                  Cache,←(missing addr),' ',(missing user),ts,'"',method,' ',page,'" ',status,' ',msec,' ',bytes,EOL
              :EndTrap
          :EndHold
      :EndIf
    ∇

    ∇ ClearCache
      :Hold 'Lumberjack'
          Cache ⎕NAPPEND TieNo
          Cache←''
      :EndHold
    ∇

    ∇ Close
      ⎕NUNTIE TieNo
    ∇
:EndClass

:Class Logger ⍝ Logs HTTP requests

    ⎕ML←1

    :field public instance Active←0
    :field private instance Directory←''
    :field private instance Interval←10  ⍝ write cache interval in seconds
    :field private instance TieNo←⍬
    :field private instance Cache←''
    :field private instance Prefix←''
    :field private instance tid←¯1
    :field private EOL                  ⍝ End Of Line

    missing←{0∊⍴⍵:'-' ⋄ ⍵}

    ∇ Make ms;file;log
      :Access public
      :Implements Constructor
      ⍝ load logger information
      Active←0
      EOL←⎕UCS 13 10↓⍨~#.Boot.isWin
      :If #.Files.Exists file←ms.Config.Root,'Config/Logger.xml'
      :AndIf ~0∊⍴log←(#.XML.ToNS #.Files.ReadText file).Logger
          Active←log #.Boot.Setting'active' 1 0
          Prefix←log #.Boot.Setting'prefix' 0 ''
          Interval←log #.Boot.Setting'interval' 1 10
          Directory←#.Boot.SubstPath log #.Boot.Setting'directory' 0 ''
          Directory,←(~'/\'∊⍨¯1↑Directory)/'/'
          ('Log file directory "',Directory,'" not found!')⎕SIGNAL 11/⍨~#.Files.DirExists Directory
      :EndIf
      :If Active
          tid←Run&0
      :EndIf
    ∇

    ∇ UnMake
      :Implements destructor
      Stop ⍬
    ∇

    ∇ Stop w
      :Access public
      :Trap 0
          :If Active
              Active←0
              ⎕TKILL tid
              :If Open
                  ClearCache
                  Close
              :EndIf
          :EndIf
      :EndTrap
    ∇

    ∇ Start
      :Access public
      :If ~tid∊⎕TNUMS
          Active←1
          tid←Run&0
      :EndIf
    ∇

    ∇ Run x;done
      :While Active
          {}⎕DL Interval
          :If Open
              ClearCache
              Close
          :EndIf
      :EndWhile
    ∇

    ∇ r←Open;fn
      r←0
      :Trap 6
          fn←#.Files.Normalize Directory,Prefix,(⍕100⊥3↑⎕TS),'.log'
      :Else
          →0
      :EndTrap

      :Trap 22 ⍝ file name error
          TieNo←fn ⎕NTIE 0 34
      :Else
          :Trap 0
              ⎕NUNTIE fn ⎕NCREATE 0
              TieNo←fn ⎕NTIE 0 34
          :Else
              1 ##.ms.Log'Unable to open log file "',fn,'"'
              :Return
          :EndTrap
      :EndTrap
      r←0≠TieNo
    ∇

    ∇ Log req
      :Access public
      :If Active
          :Hold 'Lumberjack'
              Cache,←((missing 2⊃req.PeerAddr),' ',(missing req.Session.User),#.Dates.LogFmtNow,'"',req.Method,' ',req.Page,'"',∊' '∘,∘⍕¨req.Response.(Status MSec Bytes)),EOL
          :EndHold
      :EndIf
    ∇

    ∇ ClearCache
      :Hold 'Lumberjack'
          Cache ⎕NAPPEND TieNo
          Cache←''
      :EndHold
    ∇

    ∇ Close
      ⎕NUNTIE TieNo
    ∇
:EndClass


Force computations in 'bench'

Force computations in 'bench'

$dot ← {
  WA ← (1↓⍴⍵),⍴⍺
  KA ← (⊃⍴⍴⍺)-1
  VA ← ⍳ ⊃ ⍴WA
  ZA ← (KA⌽¯1↓VA),¯1↑VA
  TA ← ZA⍉WA⍴⍺    ⍝ Replicate, transpose
  WB ← (¯1↓⍴⍺),⍴⍵
  KB ← ⊃ ⍴⍴⍺
  VB ← ⍳ ⊃ ⍴WB
  ZB0 ← (-KB) ↓ KB ⌽ ⍳(⊃⍴VB)
  ZB ← (¯1↓(⍳ KB)),ZB0,KB
  TB ← ZB⍉WB⍴⍵    ⍝ Replicate, transpose
  ⍺⍺ / TA ⍵⍵ TB   ⍝ Compute the result
}

$out ← {
  A ← ⍺
  B ← ⍵
  T ← (⊃(⍴⍴A))⌽⍳⊃⍴(⍴B),⍴A
  x ← T ⍉ ((⍴B),(⍴A)) ⍴ A
  y ← ((⍴A),(⍴B)) ⍴ B
  x ⍺⍺ y
}

$slashbar ← {
  ⍉ ⍺⍺ / ⍉ ⍵
}

$log ← {
  ⍝ Dyadic logarithm
  (⍟⍵)÷⍟⍺
}

ReadCSVDouble ← ⎕ReadDoubleVecFile
ReadCSVInt ← ⎕ReadIntVecFile
xor ← ⎕INT32XOR
and ← ⎕INT32AND
sll ← ⎕INT32SHL
srl ← ⎕INT32SHR
testBit ← { 0≠⍵ and 1 sll (⍺-1) }

now ← ⎕NOW

bench ← {
  init ← ⍵
  f ← ⍺⍺
  g ← { ⍵ ⋄ f init }
  r ← ⌷g init
  t0 ← now 0
  r ← ⌷(g ⍣ ⍺) r
  t1 ← now 1
  ⎕ ← 'ITERATIONS: ' , ⍕ ⍺
  ⎕ ← 'RESULT: ' , ⍕ ⍵⍵ r
  ⎕ ← 'AVGTIMING: ' , ⍕ (t1-t0)÷⍺
  1.0
}

$dot ← {
  WA ← (1↓⍴⍵),⍴⍺
  KA ← (⊃⍴⍴⍺)-1
  VA ← ⍳ ⊃ ⍴WA
  ZA ← (KA⌽¯1↓VA),¯1↑VA
  TA ← ZA⍉WA⍴⍺    ⍝ Replicate, transpose
  WB ← (¯1↓⍴⍺),⍴⍵
  KB ← ⊃ ⍴⍴⍺
  VB ← ⍳ ⊃ ⍴WB
  ZB0 ← (-KB) ↓ KB ⌽ ⍳(⊃⍴VB)
  ZB ← (¯1↓(⍳ KB)),ZB0,KB
  TB ← ZB⍉WB⍴⍵    ⍝ Replicate, transpose
  ⍺⍺ / TA ⍵⍵ TB   ⍝ Compute the result
}

$out ← {
  A ← ⍺
  B ← ⍵
  T ← (⊃(⍴⍴A))⌽⍳⊃⍴(⍴B),⍴A
  x ← T ⍉ ((⍴B),(⍴A)) ⍴ A
  y ← ((⍴A),(⍴B)) ⍴ B
  x ⍺⍺ y
}

$slashbar ← {
  ⍉ ⍺⍺ / ⍉ ⍵
}

$log ← {
  ⍝ Dyadic logarithm
  (⍟⍵)÷⍟⍺
}

ReadCSVDouble ← ⎕ReadDoubleVecFile
ReadCSVInt ← ⎕ReadIntVecFile
xor ← ⎕INT32XOR
and ← ⎕INT32AND
sll ← ⎕INT32SHL
srl ← ⎕INT32SHR
testBit ← { 0≠⍵ and 1 sll (⍺-1) }

now ← ⎕NOW

bench ← {
  init ← ⍵
  f ← ⍺⍺
  g ← { ⍵ ⋄ f init }
  r ← g init
  t0 ← now 0
  r ← (g ⍣ ⍺) r
  t1 ← now 1
  ⎕ ← 'ITERATIONS: ' , ⍕ ⍺
  ⎕ ← 'RESULT: ' , ⍕ ⍵⍵ r
  ⎕ ← 'AVGTIMING: ' , ⍕ (t1-t0)÷⍺
  1.0
}


Better treatment of interior quotes (") in quote function

:Namespace _JSS ⍝ Javascript Snippets

    (⎕ML ⎕IO)←1
⍝ this list will grow over time as usage patterns are discovered
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    enlist←{⎕ML←1 ⋄ ∊⍵}
    quote←{0∊⍴⍵: '' ⋄ '"',(('"' ⎕R '\\\0')⍵),'"'}
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓enlist{⍵,', '}¨eis ⍵}
    fmtData←{0=2|⎕DR ⍵:quote ⍵ ⋄ ⍕⍵}
    JAchars←#.JSON.JAchars

    ∇ r←Alert txt
      r←'alert(',(quote txt),');'
    ∇

    ∇ r←{val}(sel JQuery fn)args;opt
    ⍝ construct JavaScript to call a jQuery function - eg val(), html(), css(), or attr()
    ⍝ optionally setting a value for
    ⍝ Get a jQuery parameter:
    ⍝    ('"#id"' JQuery 'attr') '"data-role"'
    ⍝ Set a jQuery parameter:
    ⍝    '"blue"' ('#id' JQuery 'css') 'background-color'
    ⍝
      args←eis args
      :If 0=⎕NC'val'
          (opt val)←2↑args,(⍴args)↓''⎕NULL
      :Else
          opt←⊃args
      :EndIf
      :If val≡⎕NULL
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←{val}(fn JQueryOpt sel)opt
      :If 0=⎕NC'val'
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      r←(sel JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      r←(sel JQuery'val')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      r←(sel JQuery'attr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      r←(sel JQuery'html')args
    ∇

    ∇ r←sel Show args
      r←(sel JQuery'show')args
    ∇

    ∇ r←sel Hide args
      r←(sel JQuery'hide')args
    ∇

    ∇ r←sel Toggle args
      r←(sel JQuery'toggle')args
    ∇

:EndNamespace

:Namespace _JSS ⍝ Javascript Snippets

    (⎕ML ⎕IO)←1
⍝ this list will grow over time as usage patterns are discovered
    eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple
    enlist←{⎕ML←1 ⋄ ∊⍵}
    quote←{0∊⍴⍵: '' ⋄ '"'∊⍵:⍵ ⋄ '"',(⍕⍵),'"'}
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    fmtSelector←{{'this'≡⍵:⍵ ⋄quote ⍵}¯2↓enlist{⍵,', '}¨eis ⍵}
    fmtData←{0=2|⎕DR ⍵:quote ⍵ ⋄ ⍕⍵}
    JAchars←#.JSON.JAchars

    ∇ r←Alert txt
      r←'alert(',(quote txt),');'
    ∇

    ∇ r←{val}(sel JQuery fn)args;opt
    ⍝ construct JavaScript to call a jQuery function - eg val(), html(), css(), or attr()
    ⍝ optionally setting a value for
    ⍝ Get a jQuery parameter:
    ⍝    ('"#id"' JQuery 'attr') '"data-role"'
    ⍝ Set a jQuery parameter:
    ⍝    '"blue"' ('#id' JQuery 'css') 'background-color'
    ⍝
      args←eis args
      :If 0=⎕NC'val'
          (opt val)←2↑args,(⍴args)↓''⎕NULL
      :Else
          opt←⊃args
      :EndIf
      :If val≡⎕NULL
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'(',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←{val}(fn JQueryOpt sel)opt
      :If 0=⎕NC'val'
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),');'
      :Else
          r←'$(',(fmtSelector sel),').',fn,'("option",',(quote opt),',',(fmtData val),');'
      :EndIf
    ∇

    ∇ r←sel Css args ⍝ JQuery css cover
      r←(sel JQuery'css')args
    ∇

    ∇ r←sel Val args ⍝ JQuery val cover
      r←(sel JQuery'val')args
    ∇

    ∇ r←sel Attr args ⍝ JQuery attr cover
      r←(sel JQuery'attr')args
    ∇

    ∇ r←sel Html args ⍝ JQuery html cover
      r←(sel JQuery'html')args
    ∇

    ∇ r←sel Show args
      r←(sel JQuery'show')args
    ∇

    ∇ r←sel Hide args
      r←(sel JQuery'hide')args
    ∇

    ∇ r←sel Toggle args
      r←(sel JQuery'toggle')args
    ∇

:EndNamespace


Typo in URL

Typo in URL

:class ejBulletGraph : #._SF._ejWidget
⍝ Description:: Syncfusion Bullet Graph widget
⍝ Constructor:: [data]
⍝ data - interpretation depends on the shape of data
⍝        1=⍴,data - the value to be rendered on the graph
⍝        2=⍴data  - the value and the comparativeMeasure
⍝        matrix - [;1] value (aka featureMeasure)
⍝                 [;2] comparativeMeasure (optional)
⍝                 [;3] category (optional)
⍝ Public Fields::
⍝ Data - interpretation depends on the shape of data
⍝        1=⍴,Data - the value to be rendered on the graph
⍝        2=⍴Data  - the value and the comparativeMeasure
⍝        2=≢⍴Data - [;1] value (aka featureMeasure)
⍝                   [;2] comparativeMeasure (optional)
⍝                   [;3] category (optional)

    :field public shared readonly DocBase←'http://help.syncfusion.com/js/api/ejBulletGraph.html'
    :field public shared readonly ApiLevel←3
    :field public shared readonly DocDyalog←'/Documentation/DyalogAPIs/Syncfusion/ejBulletGraph.html'
    :field IntEvt←'drawCaption' 'drawCategory' 'drawComparativeMeasureSymbol' 'drawFeatureMeasureBar' 'drawIndicator' 'drawLabels' 'drawQualitativeRanges' 'load'
    :field public Data←⍬

    ∇ make
      :Access public
      JQueryFn←Uses←'ejBulletGraph'
      :Implements constructor
      InternalEvents←IntEvt
    ∇

    ∇ make1 args
      :Access public
      JQueryFn←Uses←'ejBulletGraph'
      :Implements constructor
      InternalEvents←IntEvt
      Data←args
    ∇

    ∇ r←Render;src;flds
      :Access public
      SetId
      r←''
      :If ~0∊⍴Data
          src←id,'_data'
          'fields.dataSource'Set'⍎',src
          :If 2>⍴⍴Data ⋄ Data←⍉⍪Data ⋄ :EndIf
          flds←(2⊃⍴Data)↑'featureMeasures' 'comparativeMeasure' 'category'
          {('fields.',⍵)Set ⍵}¨flds
          r←(⎕NEW #._DC.Script('var ',src,' = ',flds #.JSON.TableToJSON Data)).Render
          'quantitativeScaleSettings.maximum'SetIfNotSet⌈/10,Data[;1]
          'quantitativeScaleSettings.minimum'SetIfNotSet⌊/0,Data[;1]
      :EndIf
      r,←⎕BASE.Render
    ∇
:EndClass

:class ejBulletGraph : #._SF._ejWidget
⍝ Description:: Syncfusion Bullet Graph widget
⍝ Constructor:: [data]
⍝ data - interpretation depends on the shape of data
⍝        1=⍴,data - the value to be rendered on the graph
⍝        2=⍴data  - the value and the comparativeMeasure
⍝        matrix - [;1] value (aka featureMeasure)
⍝                 [;2] comparativeMeasure (optional)
⍝                 [;3] category (optional)
⍝ Public Fields::
⍝ Data - interpretation depends on the shape of data
⍝        1=⍴,Data - the value to be rendered on the graph
⍝        2=⍴Data  - the value and the comparativeMeasure
⍝        2=≢⍴Data - [;1] value (aka featureMeasure)
⍝                   [;2] comparativeMeasure (optional)
⍝                   [;3] category (optional)

    :field public shared readonly DocBase←'http://help.syncfusion.com/js/api/BulletGraph.html'
    :field public shared readonly ApiLevel←3
    :field public shared readonly DocDyalog←'/Documentation/DyalogAPIs/Syncfusion/ejBulletGraph.html'
    :field IntEvt←'drawCaption' 'drawCategory' 'drawComparativeMeasureSymbol' 'drawFeatureMeasureBar' 'drawIndicator' 'drawLabels' 'drawQualitativeRanges' 'load'
    :field public Data←⍬

    ∇ make
      :Access public
      JQueryFn←Uses←'ejBulletGraph'
      :Implements constructor
      InternalEvents←IntEvt
    ∇

    ∇ make1 args
      :Access public
      JQueryFn←Uses←'ejBulletGraph'
      :Implements constructor
      InternalEvents←IntEvt
      Data←args
    ∇

    ∇ r←Render;src;flds
      :Access public
      SetId
      r←''
      :If ~0∊⍴Data
          src←id,'_data'
          'fields.dataSource'Set'⍎',src
          :If 2>⍴⍴Data ⋄ Data←⍉⍪Data ⋄ :EndIf
          flds←(2⊃⍴Data)↑'featureMeasures' 'comparativeMeasure' 'category'
          {('fields.',⍵)Set ⍵}¨flds
          r←(⎕NEW #._DC.Script('var ',src,' = ',flds #.JSON.TableToJSON Data)).Render
          'quantitativeScaleSettings.maximum'SetIfNotSet⌈/10,Data[;1]
          'quantitativeScaleSettings.minimum'SetIfNotSet⌊/0,Data[;1]
      :EndIf
      r,←⎕BASE.Render
    ∇
:EndClass


Fix cookie handling

Fix cookie handling

:Class HTTPRequest

    (⎕IO ⎕ML)←1 1
    (CR LF)←NL←⎕UCS 13 10

⍝ Common Status Codes
    SC←(200 'OK')(201 'Created')(204 'No Content')(301 'Moved Permanently')(302 'Found')(303 'See Other')(304 'Not Modified')(305 'Use Proxy')(307 'Temporary Redirect')
    SC,←(400 'Bad Request')(401 'Unauthorized')(403 'Forbidden')(404 'Not Found')(405 'Method Not Allowed')(406 'Not Acceptable')(408 'Request Timeout')(409 'Conflict')
    SC,←(410 'Gone')(411 'Length Required')(412 'Precondition Failed')(413 'Request Entity Too Large')(414 'Request-URI Too Long')(415 'Unsupported Media Type')
    SC,←(500 'Internal Server Error')(501 'Not Implemented')(503 'Service Unavailable')
    SC←↑SC

⍝ Fields related to the Request

    :Field Public Instance Complete←0        ⍝ do we have a complete request?
    :Field Public Instance Input←''
    :Field Public Instance Headers←0 2⍴⊂''   ⍝ HTTPRequest header fields (plus any supplied from HTTPTrailer event)
    :Field Public Instance Method←''         ⍝ HTTP method (GET, POST, PUT, etc)
    :Field Public Instance Page←''           ⍝ Requested URI
    :Field Public Instance Body←''           ⍝ body of the request
    :Field Public Instance Host←''           ⍝ host header field
    :Field Public Instance Filename←''       ⍝ upload file name
    :Field Public Instance RESTfulReq←0      ⍝ RESTful Request (set to charvec if request is RESTful)
    :Field Public Instance Arguments←0 2⍴⊂'' ⍝ arguments provided in the URI
    :Field Public Instance PeerAddr←''       ⍝ client IP address
    :Field Public Instance PeerCert←0 0⍴⊂''  ⍝ client certificate
    :Field Public Instance Data←0 2⍴⊂''      ⍝ form data
    :Field Public Instance HTTPVersion←''
    :Field Public Instance Cookies←0 2⍴⊂''
    :Field Public Instance Session←''
    :Field Public Instance Server←⎕NS ''
    :Field Public Instance CloseConnection←0

⍝ Fields related to the Response

    :Field Public Instance Response

    GetFromTable←{(⍵[;1]⍳⊂#.Strings.lc ,⍺)⊃⍵[;2],⊂''}
    GetFromTableCS←{(⍵[;1]⍳⊂,⍺)⊃⍵[;2],⊂''} ⍝ Case Sensitive
    GetFromTableDefault←{⍺←'' ⋄ (⍺⍺[;1]⍳⊂,⍵)⊃⍺⍺[;2],⊂⍺} ⍝ default (table ∇) value
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    inf←{∨/⍵⍷⍺:'' ⋄ ⍵} ⍝ if not found
    begins←{⍺≡(⍴⍺)↑⍵}
    split←{p←(⍺⍷⍵)⍳1 ⋄ ((p-1)↑⍵)(p↓⍵)} ⍝ Split ⍵ on first occurrence of ⍺

    ∇ r←eis w
      :Access public shared
      r←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵}w ⍝ Enclose if simple
    ∇

    ∇ Make args;query;cookies
      :Access Public Instance
      :Implements Constructor
    ⍝ args [1] HTTP method [2] URI [3] HTTP version [4] 2-column headers

      (Method Input HTTPVersion Headers)←args
      Headers[;1]←#.Strings.lc Headers[;1]  ⍝ header names are case insensitive
      Method←#.Strings.lc Method

      Response←⎕NS''
      Response.(Status StatusText Headers File HTML HTMLHead PeerAddr NoWrap Bytes MSec)←200 'OK'(0 2⍴⊂'')0 '' '' '' 0(0 0)(⎕AI[3])

      Host←GetHeader'host'
      Page query←'?'split Input
      Page←PercentDecode Page

      :If '/'≠⊃Page  ⍝!!! need to update this to deal with absolute URI's, see https://tools.ietf.org/html/rfc7230#section-5.3.2
          1 Fail 400
          →0
      :EndIf

      Arguments←URLDecodeArgs query

      :If ~0∊⍴cookies←GetHeader'cookie'
          cookies←CookieSplit cookies
          :If ~0∊⍴cookies←(2=⊃∘⍴¨cookies)/cookies
              Cookies←↑{(' '~⍨1⊃⍵)(DeCode 2⊃⍵)}¨cookies
          :EndIf
      :EndIf
      :If 'get'≡Method
      :OrIf ∧/0∘∊∘⍴¨GetHeader¨'content-length' 'transfer-encoding'
          FinalizeRequest
      :EndIf
    ∇

    ∇ ProcessBody args
      :Access public
      Body←args
      FinalizeRequest
    ∇

    ∇ ProcessChunk args
      :Access public
     ⍝ args is [1] chunk content [2] chunk-extension name/value pairs (which we don't expect and won't process)
      Body,←1⊃args
    ∇

    ∇ ProcessTrailer args;inds;mask
      :Access public
      args[;1]←#.Strings.lc args[;1]
      mask←(≢Headers)≥inds←Headers[;1]⍳args[;1]
      Headers[mask/inds;2]←mask/args[;2]
      Headers⍪←(~mask)⌿args
      FinalizeRequest
    ∇

    ∇ FinalizeRequest;contentType;boundary;mask;new;s
      :If ~0∊⍴contentType←GetHeader'content-type'
      :AndIf 'multipart/form-data'begins contentType
          boundary←'--',(8+('boundary='⍷contentType)⍳1)↓contentType ⍝ boundary string
          Data←↑DecodeMultiPart¨¯1↓boundary{(⍴⍺)↓¨(⍺⍷⍵)⊂⍵}Body ⍝ ¯1↓ because last boundary has '--' appended
      :ElseIf 'application/x-www-form-urlencoded'begins contentType
          Data←URLDecodeArgs Body
      :EndIf
      :If ∨/mask←Data[;1]#.Strings.beginsWith¨⊂'_serialized_' ⍝ do we have any serialized form data from AJAX?
          new←0 2⍴⊂''
          :For s :In mask/Data[;2]
              new⍪←URLDecodeArgs s
          :EndFor
          Data←((~(⊃⍴Data)↑mask)⌿Data)⍪new
          Data←{0 1∊⍨⊃⍴⍵:⊃⍵ ⋄ ⍵}¨⊃{⍺ ⍵}⌸/↓[1]Data
      :EndIf

      :If ∨/mask←Data[;1]#.Strings.beginsWith¨⊂'_json_' ⍝ do we have any Syncfusion model data?
          :For s :In mask/⍳⍴mask
              Data[s;1]↓⍨←6
              Data[s;2]←eis #.JSON.toAPL⊃Data[s;2]
          :EndFor
      :EndIf
      Complete←1
      CloseConnection←'close'≡GetHeader'connection'
    ∇

    ∇ Wipe
      :Access public
    ⍝ clear out all request data
      Input←''
      Headers←''
      Method←''
      Page←''
      Filename←''
      RESTfulReq←0
      Arguments←''
      PeerAddr←''
      PeerCert←⍬
      Data←⍬
      Cookies←''
      Session←''
      Server←⎕NS''
      Response←⎕NS''
    ∇

    :Section Argument and Data Handling

    ∇ r←PercentDecode r;rgx;rgxu;i;j;z;t;m;⎕IO;lens;fill
      :Access public shared
    ⍝ Decode a Percent Encoded string https://en.wikipedia.org/wiki/Percent-encoding
      ⎕IO←0
      ((r='+')/r)←' '
      rgx←'[0-9a-fA-F]'
      rgxu←'%[uU]',(4×⍴rgx)⍴rgx ⍝ 4 characters
      r←(rgxu ⎕R{{⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳⍵}2↓⍵.Match})r
      :If 0≠⍴i←(r='%')/⍳⍴r
      :AndIf 0≠⍴i←(i≤¯2+⍴r)/i
          z←r[j←i∘.+1 2]
          t←'UTF-8'⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳z
          lens←⊃∘⍴¨'UTF-8'∘⎕UCS¨t  ⍝ UTF-8 is variable length encoding
          fill←i[¯1↓+\0,lens]
          r[fill]←t
          m←(⍴r)⍴1 ⋄ m[(,j),i~fill]←0
          r←m/r
      :EndIf
    ∇

    ∇ r←GetArgument name
      :Access Public Instance
      r←name GetFromTable Arguments
    ∇

    ∇ r←GetData name
      :Access Public Instance
      r←name GetFromTableCS Data
    ∇

    ∇ r←GetHeader name
      :Access Public Instance
      r←(#.Strings.lc name)GetFromTable Headers
    ∇

    ∇ r←{cs}URLDecodeArgs args
      :Access Public Shared
    ⍝ Decode arguments passed in the URI
      cs←{6::⍵ ⋄ cs}1 ⍝ default to case sensitive
      r←(args∨.≠' ')⌿↑'='∘split¨{1↓¨(⍵='&')⊂⍵}'&',args ⍝ Cut on '&'
      r[;1]←{⍵↓⍨¯6×'%5B%5D'≡¯6↑⍵}¨r[;1] ⍝ remove [] from array args
      r[;2]←PercentDecode¨r[;2]
      :If ~cs ⋄ r[;1]←#.Strings.lc¨r[;1] ⋄ :EndIf
    ∇

    ∇ r←URLEncode data;⎕IO;z;ok;nul;m
      :Access Public Shared
      nul←⎕UCS ⎕IO←0
      ok←nul,∊⎕UCS¨(⎕UCS'aA0')+⍳¨26 26 10

      z←⎕UCS'UTF-8'⎕UCS∊nul,¨,data
      :If ∨/m←~z∊ok
          (m/z)←↓'%',(⎕D,⎕A)[⍉16 16⊤⎕UCS m/z]
          data←(⍴data)⍴1↓¨{(⍵=nul)⊂⍵}∊z
      :EndIf

      r←¯1↓∊data,¨(⍴data)⍴'=&'
    ∇

    ∇ r←DecodeHeader buf;len;d;i
    ⍝ Decode HTML Header
      :Access public shared
      len←(¯1+⍴NL,NL)+⊃{((NL,NL)⍷⍵)/⍳⍴⍵}buf
      :If len>0
          d←(⍴NL)↓¨{(NL⍷⍵)⊂⍵}NL,len↑buf
          d←↑{((p-1)↑⍵)({(' '=1↑⍵)↓⍵}(p←⍵⍳':')↓⍵)}¨d
          d[i;1]←#.Strings.lc¨d[i←1↓⍳1↑⍴d;1]
      :Else
          d←⍬
      :EndIf
      r←len d
    ∇

    ∇ r←DecodeMultiPart data;d;t;filename;name;i;hdr;ind
      hdr←'UTF-8'⎕UCS data↑⍨ind←1+1⍳⍨13 10 13 10⍷data
      d←'Content-Disposition: 'GetParam hdr

      name←filename←''
      :If (⍴d)≥i←5+('name="'⍷d)⍳1
          name←(¯1+name⍳'"')↑name←i↓d
      :EndIf

      :If (⍴d)≥i←9+('filename="'⍷d)⍳1
          filename←(¯1+filename⍳'"')↑filename←i↓d
      :EndIf

      data←(2+ind)↓data ⍝ Drop up to 1st doubleCR
      data←(¯1+¯1↑{⍵/⍳⍴⍵}13 10⍷data)↑data ⍝ Drop from last CR

      t←'Content-Type: 'GetParam hdr

      :If 0∊⍴filename
          :Select 5↑t ⍝ Content type
          :CaseList 'text/' '     ' ⍝ text formats
              :Trap 92 ⋄ data←'UTF-8'⎕UCS data ⋄ :EndTrap ⍝ From UTF-8
          :Else
          :EndSelect
      :Else
          data←filename data
      :EndIf

      r←name data
    ∇

    ∇ r←header GetValue(name type);i;h
      ⍝ Extract value from HTTP Header structure returned by DecodeHeader

      :If (1↑⍴header)<i←header[;1]⍳⊂lc name
          r←⍬ ⍝ Not found
      :Else
          r←⊃header[i;2]
          :If 'Numeric'≡type
              r←1⊃2⊃⎕VFI r
          :EndIf
      :EndIf
    ∇

    ∇ r←name GetParam HTTP;i;z;find
      find←{⊃(⍺⍷⍵)/⍳⍴⍵}
      r←''
      :If 0<i←name find HTTP
      :AndIf 0<i←NL find z←(¯1+i+⍴name)↓HTTP
          r←(i-1)↑z
      :EndIf
    ∇

    :endsection

    :section Cookie Functions

    ∇ DelCookie ctl;name;path
      :Access Public Instance ⍝ Delete a cookie
      ctl←eis ctl
      name path←ctl,(⍴ctl)↓'CookieName' '/'
      SetCookie name''path ¯30 ⍝ Expiry 30 days ago
    ∇

    ∇ r←GetCookie name
      :Access Public Instance
      r←name GetFromTable Cookies
    ∇

    ∇ SetCookie ctl;name;value;path;date;z;keep
      :Access Public Instance ⍝ Set a Cookie
      ctl←eis ctl
      name value path keep←ctl,(⍴ctl)↓'CookieName' 'CookieValue' '/' 30
      date←#.Dates.IDNToDate keep+#.Dates.DateToIDN 3↑⎕TS ⍝ keep is # of days cookie should be valid
      z←#.Dates.CookieFmt(3↑date),23 59 59
      z←name,'=',(⍕value),'; path=',path,'; expires="',z,'"'
      Response.Headers⍪←'set-cookie'z
    ∇

    :endsection

    :section Request/Response Content Handling

    ∇ r←HttpStatus code
      :Access public shared
      r←code((SC[;1]⍳code)⊃SC[;2],⊂'')
    ∇

    ∇ {flag}Fail x;i;root;f;t
      :Access Public Instance
      :If 0=⎕NC'flag' ⋄ flag←0 ⋄ :EndIf
      :If 3=10|⎕DR x ⍝ Just a status code
          Response.Status←x
          :If (1↑⍴SC[;1])≥i←SC[;1]⍳x
              Response.StatusText←⊃SC[i;2] ⍝ Default text
          :EndIf
      :Else
          Response.(Status StatusText)←x
      :EndIf
      :If flag<0≡RESTfulReq
          :For root :In Server.Config.(Root MSRoot) ⍝ try site root, then server root
              :If #.Files.Exists f←root,'CommonPages\',(⍕x),'.htm'
                  :If ~0∊⍴(⎕UCS 13 10)~⍨t←#.Files.ReadText f
                      Response.HTML,⍨←t,'<br/>'
                      Response.Headers⍪←'content-type' 'text/html; charset=utf-8'
                  :EndIf
                  :Leave
              :EndIf
          :EndFor
      :EndIf
    ∇

    ∇ {code}Redirect location
      :Access public
      :If 0=⎕NC'code' ⋄ code←301 ⋄ :EndIf ⍝ default to permanent redirection
      Response.(Status StatusText)←code((SC[;1]⍳code)⊃SC[;2],⊂'')
      'Location'SetHeader location
    ∇

    ∇ r←isDesktop;cis;desktop;mobile;bot;user_agent ⍝ Detect if we think this is a desktop platform
⍝ Rationale: mobile device detection is a messy subject, therefore it's easier and perhaps safer to detect
⍝            desktop platforms (or bots) and assume that anything else is a mobile platform

      :Access public instance
      cis←{~0∊⍴(⍺ ⎕S 0 ⎕OPT'IC' 1)⍵} ⍝ Case Insensitive Search

      r←0

⍝ Some mobile browsers which look like desktop browsers.
      mobile←'(iphone|ipod|ipad|blackberry|android|palm|windows\s+ce)'
      desktop←'(windows|linux|os\s+[x9]|solaris|bsd)'
      bot←'(spider|crawl|slurp|bot)'

      →0⍴⍨0∊⍴user_agent←GetUserAgent
      →0⍴⍨mobile cis user_agent ⍝ Anything that looks like a phone isn't a desktop.
      →0⍴⍨r←desktop cis user_agent ⍝ Anything that looks like a desktop probably is.
      r←bot cis user_agent ⍝ Anything that looks like a bot should default to desktop.
    ∇

    ∇ r←GetUserAgent
      :Access public instance
      ⍝ Some mobile browsers put the User-Agent in a HTTP-X header
      →0↓⍨0∊⍴r←GetHeader'X-OPERAMINI-PHONE-UA'
      →0↓⍨0∊⍴r←GetHeader'X-SKYFIRE-PHONE'
      r←GetHeader'USER-AGENT'
    ∇

    ∇ r←isAPLJax
      :Access public instance
      r←'true'≡GetHeader'isAPLJax'
    ∇

    ∇ r←isPost
      :Access public instance
      r←Method≡'post'
    ∇

    ∇ r←isGet
      :Access public instance
      r←Method≡'get'
    ∇

    ∇ r←JSPlugIn file;root ⍝ Retrieve a JavaScript PlugIn
      :Access Public Instance
      root←{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
      r←#.Files.ReadText root,'PlugIns\',file
    ∇

    ∇ Meta attrs ⍝ add a meta tag to the head element
      :Access public instance
      Response.HTMLHead,←'<meta ',attrs,' />'
    ∇

    ∇ {hdrs}Return x;type
      :Access Public Instance

      :If 2=⍴⍴x ⋄ :AndIf 2=¯1↑⍴x ⋄ x←URLEncode x ⋄ type←'text/plain' ⍝ 2-column matrix is name/value pairs
      :Else ⋄ type←'text/html'
      :EndIf
      Response.HTML←∊x
      :If 2=⎕NC'hdrs'
          Response.Headers⍪←hdrs
      :ElseIf ~∨/Response.Headers[;1](≡#.Strings.nocase)¨⊂'content-type'
          Response.Headers⍪←'content-type'(type,'; charset=utf-8') ⍝ set content-type if not already set
      :EndIf
    ∇

    ∇ {hdrs}ReturnFile filename
    ⍝ Queues a file to be returned to client
    ⍝ filename - physical name of the file
    ⍝ {hdrs}   - optional 2-column header name/value pairs
    ⍝ if hdrs is not supplied, we attempt to set the content type of file response based on the file extension
      :Access Public Instance
      :If #.Files.Exists filename
          Response.(HTML File)←filename 1
          :If 2=⎕NC'hdrs'
              Response.Headers⍪←hdrs
          :Else
              SetContentType filename
          :EndIf
      :Else
          Fail 404  ⍝ file not found
      :EndIf
    ∇

    ∇ SetContentType x
      :Access public instance
      'content-type'SetHeader'application/binary'(Server.Config.ContentTypes GetFromTableDefault)#.Strings.lc{(1-(⌽⍵)⍳'.')↑⍵}x
    ∇

    ∇ {hdr}SetHeaderIfNotSet value;val;mask
      :Access public instance
    ⍝ accepts value in forms
    ⍝ ('hdr1' 'val1')[('hdr2' 'val2')]
    ⍝ 'hdr1:val1' ['hdr2:val2']
    ⍝ 'hdr1' 'val1' ['hdr2' 'val2']
      :If 0=⎕NC'hdr'
          :If 3≡|≡value ⍝ pairs of strings
              val←↑value
          :ElseIf 2≡|≡value
              :If ∧/':'∊¨value
                  val←↑':'split¨value
              :Else
                  val←((0.5×⍴value),2)⍴value
              :EndIf
          :Else
              val←1 2⍴':'split value
          :EndIf
      :Else
          (hdr value)←eis¨hdr value
          val←hdr,⍪value
      :EndIf
      :If ∨/mask←~hdr(∊#.Strings.nocase)Response.Headers[;1]
          Response.Headers⍪←mask⌿val
      :EndIf
    ∇

    ∇ {hdr}SetHeader value;val;mask
      :Access public instance
    ⍝ accepts value in forms
    ⍝ ('hdr1' 'val1')[('hdr2' 'val2')]
    ⍝ 'hdr1:val1' ['hdr2:val2']
    ⍝ 'hdr1' 'val1' ['hdr2' 'val2']
      :If 0=⎕NC'hdr'
          :If 3≡|≡value ⍝ pairs of strings
              val←↑value
          :ElseIf 2≡|≡value
              :If ∧/':'∊¨value
                  val←↑':'split¨value
              :Else
                  val←((0.5×⍴value),2)⍴value
              :EndIf
          :Else
              val←1 2⍴':'split value
          :EndIf
      :Else
          (hdr value)←eis¨hdr value
          val←hdr,⍪value
      :EndIf
      Response.Headers⍪←val
    ∇

    ∇ {tags}Script x
      :Access Public Instance
      :If 0=⎕NC'tags' ⋄ tags←'type="text/javascript"'
      :ElseIf ~∨/'type='⍷tags ⋄ tags,←' type="text/javascript"'
      :EndIf
      Response.HTMLHead,←Response.HTMLHead inf'<script ',tags,'>',(x ine NL,x),'</script>',NL
    ∇

    ∇ {style}Style file ⍝ insert a style into head element
      :Access Public Instance
      :If 0=⎕NC'style'
          :If ~0∊⍴file
              Response.HTMLHead,←Response.HTMLHead inf'<link href="',file,'" rel="stylesheet" type="text/css" />',NL
          :EndIf
      :Else
          :If 1<|≡style ⋄ style←NL,∊(,∘NL)¨style ⋄ :EndIf
          Response.HTMLHead,←Response.HTMLHead inf'<style type="text/css">',style,'</style>',NL
      :EndIf
    ∇

    ∇ Title x
      :Access Public Instance
      Response.HTMLHead,←'<title>',x,'</title>',NL
    ∇

    ∇ Use x;n;ind
      :Access public instance
      :If 0≠⎕NC⊂'Server.Config.Resources'
      :AndIf ~0∊n←1↑⍴Server.Config.Resources
          :If n≥ind←Server.Config.Resources[;1]⍳⊂x
              (Script∘'')¨{'src="',⍵,'"'}¨{(⊃⍣(2<≡⍵))⍵}(⊂ind 2)⊃Server.Config.Resources
              Style¨{(⊃⍣(2<≡⍵))⍵}(⊂ind 3)⊃Server.Config.Resources
          :Else
              1 Server.Log Page,' references unknown resource: ',x
          :EndIf
      :EndIf
    ∇

    :endsection

      Split←{(⎕IO ⎕ML)←0 3         ⍝ Split Http syntax.
          ⍺←'?&=' ⋄ (,⍺){        ⍝ Default separator chars.
              0=⍴⍺:⍵             ⍝ No separator chars left: finished.
              sepr←↑⍺            ⍝ First char is separator char.
              (1↓⍺)∘∇∘{          ⍝ Recursively split at each sub level.
                  ⍵~sepr         ⍝ Separator removed.
              }¨(1+⍵=sepr)⊂⍵     ⍝ String partitioned at separator char.
          }⍵                     ⍝ Http string.
      }

      CookieSplit←{(⎕IO ⎕ML)←0 3   ⍝ Split cookies
          {{db←{⍵/⍨∨\⍵≠' '} ⋄ ⌽db⌽db ⍵}¨⍵⊂⍨~<\'='=⍵}¨⍵⊂⍨⍵≠';'}

      DeCode←{(⎕IO ⎕ML)←0 3          ⍝ Decode Special chars in HTML string.
          hex←'0123456789ABCDEF'     ⍝ Hex chars.
          {                          ⍝ Convert numbers.
              v f←⎕VFI ⍵             ⍝ Check for numbers.
              ~∧/v:⍵                 ⍝ Not all numbers: char vec.
              1=⍴f:↑f ⋄ f            ⍝ Numeric scalar or vector.
          }∊{                        ⍝ Enlist of segments.
              '%'≠↑⍵:⍵               ⍝ 1st seg may not contain special char.
              (⎕UCS 16⊥hex⍳1↓3↑⍵),3↓⍵  ⍝ Hex code replaced with corresp. ⎕AV char.
          }¨(1+⍵='%')⊂,⍵             ⍝ Segments split at '%' char.
      }

    ∇ Show
      :Access public
      ↑{⍵(⍎⍵)}¨'Input' 'Method' 'Page' 'Headers' 'Arguments' 'Data' 'Cookies'
    ∇

:EndClass

:Class HTTPRequest

    (⎕IO ⎕ML)←1 1
    (CR LF)←NL←⎕UCS 13 10

⍝ Common Status Codes
    SC←(200 'OK')(201 'Created')(204 'No Content')(301 'Moved Permanently')(302 'Found')(303 'See Other')(304 'Not Modified')(305 'Use Proxy')(307 'Temporary Redirect')
    SC,←(400 'Bad Request')(401 'Unauthorized')(403 'Forbidden')(404 'Not Found')(405 'Method Not Allowed')(406 'Not Acceptable')(408 'Request Timeout')(409 'Conflict')
    SC,←(410 'Gone')(411 'Length Required')(412 'Precondition Failed')(413 'Request Entity Too Large')(414 'Request-URI Too Long')(415 'Unsupported Media Type')
    SC,←(500 'Internal Server Error')(501 'Not Implemented')(503 'Service Unavailable')
    SC←↑SC

⍝ Fields related to the Request

    :Field Public Instance Complete←0        ⍝ do we have a complete request?
    :Field Public Instance Input←''
    :Field Public Instance Headers←0 2⍴⊂''   ⍝ HTTPRequest header fields (plus any supplied from HTTPTrailer event)
    :Field Public Instance Method←''         ⍝ HTTP method (GET, POST, PUT, etc)
    :Field Public Instance Page←''           ⍝ Requested URI
    :Field Public Instance Body←''           ⍝ body of the request
    :Field Public Instance Host←''           ⍝ host header field
    :Field Public Instance Filename←''       ⍝ upload file name
    :Field Public Instance RESTfulReq←0      ⍝ RESTful Request (set to charvec if request is RESTful)
    :Field Public Instance Arguments←0 2⍴⊂'' ⍝ arguments provided in the URI
    :Field Public Instance PeerAddr←''       ⍝ client IP address
    :Field Public Instance PeerCert←0 0⍴⊂''  ⍝ client certificate
    :Field Public Instance Data←0 2⍴⊂''      ⍝ form data
    :Field Public Instance HTTPVersion←''
    :Field Public Instance Cookies←0 2⍴⊂''
    :Field Public Instance Session←''
    :Field Public Instance Server←⎕NS ''
    :Field Public Instance CloseConnection←0

⍝ Fields related to the Response

    :Field Public Instance Response

    GetFromTable←{(⍵[;1]⍳⊂#.Strings.lc ,⍺)⊃⍵[;2],⊂''}
    GetFromTableCS←{(⍵[;1]⍳⊂,⍺)⊃⍵[;2],⊂''} ⍝ Case Sensitive
    GetFromTableDefault←{⍺←'' ⋄ (⍺⍺[;1]⍳⊂,⍵)⊃⍺⍺[;2],⊂⍺} ⍝ default (table ∇) value
    ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty
    inf←{∨/⍵⍷⍺:'' ⋄ ⍵} ⍝ if not found
    begins←{⍺≡(⍴⍺)↑⍵}
    split←{p←(⍺⍷⍵)⍳1 ⋄ ((p-1)↑⍵)(p↓⍵)} ⍝ Split ⍵ on first occurrence of ⍺

    ∇ r←eis w
      :Access public shared
      r←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵}w ⍝ Enclose if simple
    ∇

    ∇ Make args;query;cookies
      :Access Public Instance
      :Implements Constructor
    ⍝ args [1] HTTP method [2] URI [3] HTTP version [4] 2-column headers

      (Method Input HTTPVersion Headers)←args
      Headers[;1]←#.Strings.lc Headers[;1]  ⍝ header names are case insensitive
      Method←#.Strings.lc Method

      Response←⎕NS''
      Response.(Status StatusText Headers File HTML HTMLHead PeerAddr NoWrap Bytes MSec)←200 'OK'(0 2⍴⊂'')0 '' '' '' 0(0 0)(⎕AI[3])

      Host←GetHeader'host'
      Page query←'?'split Input
      Page←PercentDecode Page

      :If '/'≠⊃Page  ⍝!!! need to update this to deal with absolute URI's, see https://tools.ietf.org/html/rfc7230#section-5.3.2
          1 Fail 400
          →0
      :EndIf

      Arguments←URLDecodeArgs query

      :If ~0∊⍴cookies←GetHeader'cookie'
          :If ~0∊⍴cookies←(2=⊃∘⍴¨cookies)/cookies
              Cookies←↑{((#.Strings.lc(1⊃⍵)~' '))(DeCode 2⊃⍵)}¨cookies
          :EndIf
      :EndIf
      :If 'get'≡Method
      :OrIf ∧/0∘∊∘⍴¨GetHeader¨'content-length' 'transfer-encoding'
          FinalizeRequest
      :EndIf
    ∇

    ∇ ProcessBody args
      :Access public
      Body←args
      FinalizeRequest
    ∇

    ∇ ProcessChunk args
      :Access public
     ⍝ args is [1] chunk content [2] chunk-extension name/value pairs (which we don't expect and won't process)
      Body,←1⊃args
    ∇

    ∇ ProcessTrailer args;inds;mask
      :Access public
      args[;1]←#.Strings.lc args[;1]
      mask←(≢Headers)≥inds←Headers[;1]⍳args[;1]
      Headers[mask/inds;2]←mask/args[;2]
      Headers⍪←(~mask)⌿args
      FinalizeRequest
    ∇

    ∇ FinalizeRequest;contentType;boundary;mask;new;s
      :If ~0∊⍴contentType←GetHeader'content-type'
      :AndIf 'multipart/form-data'begins contentType
          boundary←'--',(8+('boundary='⍷contentType)⍳1)↓contentType ⍝ boundary string
          Data←↑DecodeMultiPart¨¯1↓boundary{(⍴⍺)↓¨(⍺⍷⍵)⊂⍵}Body ⍝ ¯1↓ because last boundary has '--' appended
      :ElseIf 'application/x-www-form-urlencoded'begins contentType
          Data←URLDecodeArgs Body
      :EndIf
      :If ∨/mask←Data[;1]#.Strings.beginsWith¨⊂'_serialized_' ⍝ do we have any serialized form data from AJAX?
          new←0 2⍴⊂''
          :For s :In mask/Data[;2]
              new⍪←URLDecodeArgs s
          :EndFor
          Data←((~(⊃⍴Data)↑mask)⌿Data)⍪new
          Data←{0 1∊⍨⊃⍴⍵:⊃⍵ ⋄ ⍵}¨⊃{⍺ ⍵}⌸/↓[1]Data
      :EndIf

      :If ∨/mask←Data[;1]#.Strings.beginsWith¨⊂'_json_' ⍝ do we have any Syncfusion model data?
          :For s :In mask/⍳⍴mask
              Data[s;1]↓⍨←6
              Data[s;2]←eis #.JSON.toAPL⊃Data[s;2]
          :EndFor
      :EndIf
      Complete←1
      CloseConnection←'close'≡GetHeader'connection'
    ∇

    ∇ Wipe
      :Access public
    ⍝ clear out all request data
      Input←''
      Headers←''
      Method←''
      Page←''
      Filename←''
      RESTfulReq←0
      Arguments←''
      PeerAddr←''
      PeerCert←⍬
      Data←⍬
      Cookies←''
      Session←''
      Server←⎕NS''
      Response←⎕NS''
    ∇

    :Section Argument and Data Handling

    ∇ r←PercentDecode r;rgx;rgxu;i;j;z;t;m;⎕IO;lens;fill
      :Access public shared
    ⍝ Decode a Percent Encoded string https://en.wikipedia.org/wiki/Percent-encoding
      ⎕IO←0
      ((r='+')/r)←' '
      rgx←'[0-9a-fA-F]'
      rgxu←'%[uU]',(4×⍴rgx)⍴rgx ⍝ 4 characters
      r←(rgxu ⎕R{{⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳⍵}2↓⍵.Match})r
      :If 0≠⍴i←(r='%')/⍳⍴r
      :AndIf 0≠⍴i←(i≤¯2+⍴r)/i
          z←r[j←i∘.+1 2]
          t←'UTF-8'⎕UCS 16⊥⍉16|'0123456789ABCDEF0123456789abcdef'⍳z
          lens←⊃∘⍴¨'UTF-8'∘⎕UCS¨t  ⍝ UTF-8 is variable length encoding
          fill←i[¯1↓+\0,lens]
          r[fill]←t
          m←(⍴r)⍴1 ⋄ m[(,j),i~fill]←0
          r←m/r
      :EndIf
    ∇

    ∇ r←GetArgument name
      :Access Public Instance
      r←name GetFromTable Arguments
    ∇

    ∇ r←GetData name
      :Access Public Instance
      r←name GetFromTableCS Data
    ∇

    ∇ r←GetHeader name
      :Access Public Instance
      r←(#.Strings.lc name)GetFromTable Headers
    ∇

    ∇ r←{cs}URLDecodeArgs args
      :Access Public Shared
    ⍝ Decode arguments passed in the URI
      cs←{6::⍵ ⋄ cs}1 ⍝ default to case sensitive
      r←(args∨.≠' ')⌿↑'='∘split¨{1↓¨(⍵='&')⊂⍵}'&',args ⍝ Cut on '&'
      r[;1]←{⍵↓⍨¯6×'%5B%5D'≡¯6↑⍵}¨r[;1] ⍝ remove [] from array args
      r[;2]←PercentDecode¨r[;2]
      :If ~cs ⋄ r[;1]←#.Strings.lc¨r[;1] ⋄ :EndIf
    ∇

    ∇ r←URLEncode data;⎕IO;z;ok;nul;m
      :Access Public Shared
      nul←⎕UCS ⎕IO←0
      ok←nul,∊⎕UCS¨(⎕UCS'aA0')+⍳¨26 26 10

      z←⎕UCS'UTF-8'⎕UCS∊nul,¨,data
      :If ∨/m←~z∊ok
          (m/z)←↓'%',(⎕D,⎕A)[⍉16 16⊤⎕UCS m/z]
          data←(⍴data)⍴1↓¨{(⍵=nul)⊂⍵}∊z
      :EndIf

      r←¯1↓∊data,¨(⍴data)⍴'=&'
    ∇

    ∇ r←DecodeHeader buf;len;d;i
    ⍝ Decode HTML Header
      :Access public shared
      len←(¯1+⍴NL,NL)+⊃{((NL,NL)⍷⍵)/⍳⍴⍵}buf
      :If len>0
          d←(⍴NL)↓¨{(NL⍷⍵)⊂⍵}NL,len↑buf
          d←↑{((p-1)↑⍵)({(' '=1↑⍵)↓⍵}(p←⍵⍳':')↓⍵)}¨d
          d[i;1]←#.Strings.lc¨d[i←1↓⍳1↑⍴d;1]
      :Else
          d←⍬
      :EndIf
      r←len d
    ∇

    ∇ r←DecodeMultiPart data;d;t;filename;name;i;hdr;ind
      hdr←'UTF-8'⎕UCS data↑⍨ind←1+1⍳⍨13 10 13 10⍷data
      d←'Content-Disposition: 'GetParam hdr

      name←filename←''
      :If (⍴d)≥i←5+('name="'⍷d)⍳1
          name←(¯1+name⍳'"')↑name←i↓d
      :EndIf

      :If (⍴d)≥i←9+('filename="'⍷d)⍳1
          filename←(¯1+filename⍳'"')↑filename←i↓d
      :EndIf

      data←(2+ind)↓data ⍝ Drop up to 1st doubleCR
      data←(¯1+¯1↑{⍵/⍳⍴⍵}13 10⍷data)↑data ⍝ Drop from last CR

      t←'Content-Type: 'GetParam hdr

      :If 0∊⍴filename
          :Select 5↑t ⍝ Content type
          :CaseList 'text/' '     ' ⍝ text formats
              :Trap 92 ⋄ data←'UTF-8'⎕UCS data ⋄ :EndTrap ⍝ From UTF-8
          :Else
          :EndSelect
      :Else
          data←filename data
      :EndIf

      r←name data
    ∇

    ∇ r←header GetValue(name type);i;h
      ⍝ Extract value from HTTP Header structure returned by DecodeHeader

      :If (1↑⍴header)<i←header[;1]⍳⊂lc name
          r←⍬ ⍝ Not found
      :Else
          r←⊃header[i;2]
          :If 'Numeric'≡type
              r←1⊃2⊃⎕VFI r
          :EndIf
      :EndIf
    ∇

    ∇ r←name GetParam HTTP;i;z;find
      find←{⊃(⍺⍷⍵)/⍳⍴⍵}
      r←''
      :If 0<i←name find HTTP
      :AndIf 0<i←NL find z←(¯1+i+⍴name)↓HTTP
          r←(i-1)↑z
      :EndIf
    ∇

    :endsection

    :section Cookie Functions

    ∇ DelCookie ctl;name;path
      :Access Public Instance ⍝ Delete a cookie
      ctl←eis ctl
      name path←ctl,(⍴ctl)↓'CookieName' '/'
      SetCookie name''path ¯30 ⍝ Expiry 30 days ago
    ∇

    ∇ r←GetCookie name
      :Access Public Instance
      r←name GetFromTable Cookies
    ∇

    ∇ SetCookie ctl;name;value;path;date;z;keep
      :Access Public Instance ⍝ Set a Cookie
      ctl←eis ctl
      name value path keep←ctl,(⍴ctl)↓'CookieName' 'CookieValue' '/' 30
      date←#.Dates.IDNToDate keep+#.Dates.DateToIDN 3↑⎕TS ⍝ keep is # of days cookie should be valid
      z←#.Dates.CookieFmt(3↑date),23 59 59
      z←name,'=',(⍕value),'; path=',path,'; expires="',z,'"'
      Response.Headers⍪←'set-cookie'z
    ∇

    :endsection

    :section Request/Response Content Handling

    ∇ r←HttpStatus code
      :Access public shared
      r←code((SC[;1]⍳code)⊃SC[;2],⊂'')
    ∇

    ∇ {flag}Fail x;i;root;f;t
      :Access Public Instance
      :If 0=⎕NC'flag' ⋄ flag←0 ⋄ :EndIf
      :If 3=10|⎕DR x ⍝ Just a status code
          Response.Status←x
          :If (1↑⍴SC[;1])≥i←SC[;1]⍳x
              Response.StatusText←⊃SC[i;2] ⍝ Default text
          :EndIf
      :Else
          Response.(Status StatusText)←x
      :EndIf
      :If flag<0≡RESTfulReq
          :For root :In Server.Config.(Root MSRoot) ⍝ try site root, then server root
              :If #.Files.Exists f←root,'CommonPages\',(⍕x),'.htm'
                  :If ~0∊⍴(⎕UCS 13 10)~⍨t←#.Files.ReadText f
                      Response.HTML,⍨←t,'<br/>'
                      Response.Headers⍪←'content-type' 'text/html; charset=utf-8'
                  :EndIf
                  :Leave
              :EndIf
          :EndFor
      :EndIf
    ∇

    ∇ {code}Redirect location
      :Access public
      :If 0=⎕NC'code' ⋄ code←301 ⋄ :EndIf ⍝ default to permanent redirection
      Response.(Status StatusText)←code((SC[;1]⍳code)⊃SC[;2],⊂'')
      'Location'SetHeader location
    ∇

    ∇ r←isDesktop;cis;desktop;mobile;bot;user_agent ⍝ Detect if we think this is a desktop platform
⍝ Rationale: mobile device detection is a messy subject, therefore it's easier and perhaps safer to detect
⍝            desktop platforms (or bots) and assume that anything else is a mobile platform

      :Access public instance
      cis←{~0∊⍴(⍺ ⎕S 0 ⎕OPT'IC' 1)⍵} ⍝ Case Insensitive Search

      r←0

⍝ Some mobile browsers which look like desktop browsers.
      mobile←'(iphone|ipod|ipad|blackberry|android|palm|windows\s+ce)'
      desktop←'(windows|linux|os\s+[x9]|solaris|bsd)'
      bot←'(spider|crawl|slurp|bot)'

      →0⍴⍨0∊⍴user_agent←GetUserAgent
      →0⍴⍨mobile cis user_agent ⍝ Anything that looks like a phone isn't a desktop.
      →0⍴⍨r←desktop cis user_agent ⍝ Anything that looks like a desktop probably is.
      r←bot cis user_agent ⍝ Anything that looks like a bot should default to desktop.
    ∇

    ∇ r←GetUserAgent
      :Access public instance
      ⍝ Some mobile browsers put the User-Agent in a HTTP-X header
      →0↓⍨0∊⍴r←GetHeader'X-OPERAMINI-PHONE-UA'
      →0↓⍨0∊⍴r←GetHeader'X-SKYFIRE-PHONE'
      r←GetHeader'USER-AGENT'
    ∇

    ∇ r←isAPLJax
      :Access public instance
      r←'true'≡GetHeader'isAPLJax'
    ∇

    ∇ r←isPost
      :Access public instance
      r←Method≡'post'
    ∇

    ∇ r←isGet
      :Access public instance
      r←Method≡'get'
    ∇

    ∇ r←JSPlugIn file;root ⍝ Retrieve a JavaScript PlugIn
      :Access Public Instance
      root←{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID
      r←#.Files.ReadText root,'PlugIns\',file
    ∇

    ∇ Meta attrs ⍝ add a meta tag to the head element
      :Access public instance
      Response.HTMLHead,←'<meta ',attrs,' />'
    ∇

    ∇ {hdrs}Return x;type
      :Access Public Instance

      :If 2=⍴⍴x ⋄ :AndIf 2=¯1↑⍴x ⋄ x←URLEncode x ⋄ type←'text/plain' ⍝ 2-column matrix is name/value pairs
      :Else ⋄ type←'text/html'
      :EndIf
      Response.HTML←∊x
      :If 2=⎕NC'hdrs'
          Response.Headers⍪←hdrs
      :ElseIf ~∨/Response.Headers[;1](≡#.Strings.nocase)¨⊂'content-type'
          Response.Headers⍪←'content-type'(type,'; charset=utf-8') ⍝ set content-type if not already set
      :EndIf
    ∇

    ∇ {hdrs}ReturnFile filename
    ⍝ Queues a file to be returned to client
    ⍝ filename - physical name of the file
    ⍝ {hdrs}   - optional 2-column header name/value pairs
    ⍝ if hdrs is not supplied, we attempt to set the content type of file response based on the file extension
      :Access Public Instance
      :If #.Files.Exists filename
          Response.(HTML File)←filename 1
          :If 2=⎕NC'hdrs'
              Response.Headers⍪←hdrs
          :Else
              SetContentType filename
          :EndIf
      :Else
          Fail 404  ⍝ file not found
      :EndIf
    ∇

    ∇ SetContentType x
      :Access public instance
      'content-type'SetHeader'application/binary'(Server.Config.ContentTypes GetFromTableDefault)#.Strings.lc{(1-(⌽⍵)⍳'.')↑⍵}x
    ∇

    ∇ {hdr}SetHeaderIfNotSet value;val;mask
      :Access public instance
    ⍝ accepts value in forms
    ⍝ ('hdr1' 'val1')[('hdr2' 'val2')]
    ⍝ 'hdr1:val1' ['hdr2:val2']
    ⍝ 'hdr1' 'val1' ['hdr2' 'val2']
      :If 0=⎕NC'hdr'
          :If 3≡|≡value ⍝ pairs of strings
              val←↑value
          :ElseIf 2≡|≡value
              :If ∧/':'∊¨value
                  val←↑':'split¨value
              :Else
                  val←((0.5×⍴value),2)⍴value
              :EndIf
          :Else
              val←1 2⍴':'split value
          :EndIf
      :Else
          (hdr value)←eis¨hdr value
          val←hdr,⍪value
      :EndIf
      :If ∨/mask←~hdr(∊#.Strings.nocase)Response.Headers[;1]
          Response.Headers⍪←mask⌿val
      :EndIf
    ∇

    ∇ {hdr}SetHeader value;val;mask
      :Access public instance
    ⍝ accepts value in forms
    ⍝ ('hdr1' 'val1')[('hdr2' 'val2')]
    ⍝ 'hdr1:val1' ['hdr2:val2']
    ⍝ 'hdr1' 'val1' ['hdr2' 'val2']
      :If 0=⎕NC'hdr'
          :If 3≡|≡value ⍝ pairs of strings
              val←↑value
          :ElseIf 2≡|≡value
              :If ∧/':'∊¨value
                  val←↑':'split¨value
              :Else
                  val←((0.5×⍴value),2)⍴value
              :EndIf
          :Else
              val←1 2⍴':'split value
          :EndIf
      :Else
          (hdr value)←eis¨hdr value
          val←hdr,⍪value
      :EndIf
      Response.Headers⍪←val
    ∇

    ∇ {tags}Script x
      :Access Public Instance
      :If 0=⎕NC'tags' ⋄ tags←'type="text/javascript"'
      :ElseIf ~∨/'type='⍷tags ⋄ tags,←' type="text/javascript"'
      :EndIf
      Response.HTMLHead,←Response.HTMLHead inf'<script ',tags,'>',(x ine NL,x),'</script>',NL
    ∇

    ∇ {style}Style file ⍝ insert a style into head element
      :Access Public Instance
      :If 0=⎕NC'style'
          :If ~0∊⍴file
              Response.HTMLHead,←Response.HTMLHead inf'<link href="',file,'" rel="stylesheet" type="text/css" />',NL
          :EndIf
      :Else
          :If 1<|≡style ⋄ style←NL,∊(,∘NL)¨style ⋄ :EndIf
          Response.HTMLHead,←Response.HTMLHead inf'<style type="text/css">',style,'</style>',NL
      :EndIf
    ∇

    ∇ Title x
      :Access Public Instance
      Response.HTMLHead,←'<title>',x,'</title>',NL
    ∇

    ∇ Use x;n;ind
      :Access public instance
      :If 0≠⎕NC⊂'Server.Config.Resources'
      :AndIf ~0∊n←1↑⍴Server.Config.Resources
          :If n≥ind←Server.Config.Resources[;1]⍳⊂x
              (Script∘'')¨{'src="',⍵,'"'}¨{(⊃⍣(2<≡⍵))⍵}(⊂ind 2)⊃Server.Config.Resources
              Style¨{(⊃⍣(2<≡⍵))⍵}(⊂ind 3)⊃Server.Config.Resources
          :Else
              1 Server.Log Page,' references unknown resource: ',x
          :EndIf
      :EndIf
    ∇

    :endsection

      Split←{(⎕IO ⎕ML)←0 3         ⍝ Split Http syntax.
          ⍺←'?&=' ⋄ (,⍺){        ⍝ Default separator chars.
              0=⍴⍺:⍵             ⍝ No separator chars left: finished.
              sepr←↑⍺            ⍝ First char is separator char.
              (1↓⍺)∘∇∘{          ⍝ Recursively split at each sub level.
                  ⍵~sepr         ⍝ Separator removed.
              }¨(1+⍵=sepr)⊂⍵     ⍝ String partitioned at separator char.
          }⍵                     ⍝ Http string.
      }

      CookieSplit←{(⎕IO ⎕ML)←0 3   ⍝ Split cookies
          {{db←{⍵/⍨∨\⍵≠' '} ⋄ ⌽db⌽db ⍵}¨⍵⊂⍨~<\'='=⍵}¨⍵⊂⍨⍵≠';'}

      DeCode←{(⎕IO ⎕ML)←0 3          ⍝ Decode Special chars in HTML string.
          hex←'0123456789ABCDEF'     ⍝ Hex chars.
          {                          ⍝ Convert numbers.
              v f←⎕VFI ⍵             ⍝ Check for numbers.
              ~∧/v:⍵                 ⍝ Not all numbers: char vec.
              1=⍴f:↑f ⋄ f            ⍝ Numeric scalar or vector.
          }∊{                        ⍝ Enlist of segments.
              '%'≠↑⍵:⍵               ⍝ 1st seg may not contain special char.
              (⎕UCS 16⊥hex⍳1↓3↑⍵),3↓⍵  ⍝ Hex code replaced with corresp. ⎕AV char.
          }¨(1+⍵='%')⊂,⍵             ⍝ Segments split at '%' char.
      }

    ∇ Show
      :Access public
      ↑{⍵(⍎⍵)}¨'Input' 'Method' 'Page' 'Headers' 'Arguments' 'Data' 'Cookies'
    ∇

:EndClass
⍝)(!DecodeHeader!!0 0 0 0 0 0 0!0
⍝)(!HttpStatus!!0 0 0 0 0 0 0!0
⍝)(!PercentDecode!!0 0 0 0 0 0 0!0
⍝)(!URLDecodeArgs!!0 0 0 0 0 0 0!0
⍝)(!URLEncode!!0 0 0 0 0 0 0!0
⍝)(!eis!!0 0 0 0 0 0 0!0


jqSortable documentation fix

jqSortable documentation fix

:Class jqSortable : #._JQ._jqUIWidget

⍝ Description:: jQueryUI Sortable widget/interaction
⍝ Constructor:: [items|selector]
⍝ items     - vector of items to make sortable
⍝ selector - the jQuery/CSS selector to which to apply sortable interaction
⍝ Public Fields::
⍝ Items     - vector of items to make sortable
⍝ Selector - the jQuery/CSS selector to which to apply sortable interaction
⍝ Examples::
⍝ Add _.jqSortable '#myul'               ⍝ jqSortable will use the already existing element #myul
⍝ Add _.jqSortable ('One' 'Two' 'Three') ⍝ jqSortable will build a <ul> / <li> structure
⍝ Add _.jqSortable ''                    ⍝ jqSortable will insert an empty <ul> structure where items can be dropped

    :field public shared readonly DocBase←'https://jqueryui.com/sortable/'
    :field public shared readonly ApiLevel←3
    :field public shared readonly IntEvt←'activate' 'beforeStop' 'change' 'create' 'deactivate' 'out' 'over' 'receive' 'remove' 'sort' 'start' 'stop' 'update'
    :Field Public Items←UNDEF         ⍝ vector of items

    ∇ Make
      :Access public
      :Implements constructor
      JQueryFn←'sortable'
      InternalEvents←IntEvt
    ∇

    ∇ Make1 arg
      :Access public
      :Implements constructor
      JQueryFn←'sortable'
      InternalEvents←IntEvt
      :If 326≠⎕DR arg
      :AndIf ''≢arg
          Selector←arg
      :Else
          Items←arg
      :EndIf
    ∇

    ∇ r←Render
      :Access Public
      r←''
      :If UNDEF≢Items
          :If ''≢Selector
              id←Selector↓⍨'#'=⊃Selector
          :Else
              Selector←'#',SetId
          :EndIf
          r,←(id New _.Ul,⊂⊂Items).Render
      :EndIf
      r,←⎕BASE.Render
    ∇

:EndClass

:Class jqSortable : #._JQ._jqUIWidget

⍝ Description:: jQueryUI Sortable widget/interaction
⍝ Constructor:: [items|selector]
⍝ items     - vector of items to make sortable
⍝ selector - the jQuery/CSS selector to which to apply sortable interaction
⍝ Public Fields::
⍝ Items     - vector of items to make sortable
⍝ Selector - the jQuery/CSS selector to which to apply sortable interaction
⍝ Examples::
⍝ Add _.jqSortable '#myul'             ⍝ jqSortable will use the already existing element #myul
⍝ Add _.jqSortable 'One' 'Two' 'Three' ⍝ jqSortable will build a <ul> / <li> structure
⍝ Add _.jqSortable ''                  ⍝ jqSortable will insert an empty <ul> structure where items can be dropped

    :field public shared readonly DocBase←'https://jqueryui.com/sortable/'
    :field public shared readonly ApiLevel←3
    :field public shared readonly IntEvt←'activate' 'beforeStop' 'change' 'create' 'deactivate' 'out' 'over' 'receive' 'remove' 'sort' 'start' 'stop' 'update'
    :Field Public Items←UNDEF         ⍝ vector of items

    ∇ Make
      :Access public
      :Implements constructor
      JQueryFn←'sortable'
      InternalEvents←IntEvt
    ∇

    ∇ Make1 arg
      :Access public
      :Implements constructor
      JQueryFn←'sortable'
      InternalEvents←IntEvt
      :If 326≠⎕DR arg
      :AndIf ''≢arg
          Selector←arg
      :Else
          Items←arg
      :EndIf
    ∇

    ∇ r←Render
      :Access Public
      r←''
      :If UNDEF≢Items
          :If ''≢Selector
              id←Selector↓⍨'#'=⊃Selector
          :Else
              Selector←'#',SetId
          :EndIf
          r,←(id New _.Ul,⊂⊂Items).Render
      :EndIf
      r,←⎕BASE.Render
    ∇

:EndClass


Small tweaks to FieldsetSimple sample - now passes QA!

:Class FieldsetSimple : MiPageSample
⍝ Control:: _DC.Fieldset _html.label _.EditField
⍝ Description:: Enter first and last name in a FieldSet and react to onkeypress events

    ∇ Render;fieldset;field;prompt;edit;frm;label
      :Access Public

     ⍝ We need a form, so that data will be serialized on each callback
      frm←Add _.form

      fieldset←frm.Add _.Fieldset'Personalia:' ⍝ The fieldset will surround our input fields

      :For (field prompt) :In ('fname' 'First Name:')('lname' 'Last Name:')
          label←('for'field)fieldset.Add _.label prompt   ⍝ Create label
          edit←field fieldset.Add _.EditField             ⍝ Create EditField
          edit.On'keyup' 'CallbackFn'
          ⍝ NB: unlike keypress, the keyup event is called AFTER the field is updated
          fieldset.Add _.br                               ⍝ Next input on new line
      :EndFor

      'result'Add _.div ⍝ CallbackFn will update this
    ∇

    ∇ r←CallbackFn;text
      :Access Public

      text←'Hi ',(Get'fname'),' ',(Get'lname'),'!'
      r←'#result'Replace _.h2 text
    ∇

:EndClass

:Class FieldSetSimple : MiPageSample
⍝ Control:: _DC.FieldSet _html.label _.EditField
⍝ Description:: Enter first and last name in a FieldSet and react to onkeypress events

    ∇ Render;fieldset;field;prompt;edit;frm
      :Access Public

     ⍝ First we need a form for the button below to serialize the data to be sent
      frm←Add _.form

      fieldset←frm.Add _.Fieldset'Personalia:'       ⍝ The fieldset will surround our input fields

      :For (field prompt) :In ('fname' 'First Name:')('lname' 'Last Name:')
          ('for'field)fieldset.Add _.label prompt   ⍝ Create label
          edit←field fieldset.Add _.EditField         ⍝ Create EditField
          edit.On'keypress' 'CallbackFn'     ⍝ Catch keypress events
          fieldset.Add _.br                           ⍝ Line Break
      :EndFor

      'result'Add _.div
    ∇

    ∇ r←CallbackFn;text
      :Access Public

      text←'Hi ',(Get'fname'),' ',(Get'lname'),'.'
      r←'#result'Replace _.h2 text
    ∇

:EndClass


Make MS3 site page unsessioned

Make MS3 site page unsessioned

:Class MiPageSample : #.MiPage
   ⍝ This is a template that "wraps" the page content by
   ⍝ - adding a header and footer
   ⍝ - adding a handler that will toggle the display of the web page and its APL source code

    :Field Public _Sessioned←0

    ∇ {r}←Wrap;lang;server;src;c
      :Access Public

      :If 0∊⍴Get'nowrapper'
          server←_Request.Server

        ⍝ we need jQuery to toggle content/source, other resources will be loaded if needed
          Use'JQuery' ⍝ this is a resource defined in Config/Resources.xml

        ⍝ set the title display in the browser to the name of the page or application
          Add _.title('Advanced' 'Simple' 'Examples?' 'index' '_'⎕R'' '' ''server.Config.Name' '((-'.'⍳⍨⌽)↓⊢↑⍨1-'/'⍳⍨⌽)_Request.Page)

        ⍝ add a link to our CSS stylesheet
          _CssReset←'/Styles/cssreset.css'
          _CssOverride←'/Styles/sampleStyles.css'

        ⍝ set a meta tag to make it explicitly UTF-8
        ⍝  (Add _.meta).Set'http-equiv="content-type" content="text/html;charset=UTF-8"'

        ⍝ wrap the content of the  element in a div
          'contentblock'Body.Push _.div

        ⍝ add a hidden division to the body containing the APL source code
          (Add _.div(#.HtmlUtils.APLToHTMLColor src←⎕SRC⊃⊃⎕CLASS ⎕THIS)).Set'id="codeblock"' 'style="display: none;"'

        ⍝ extract the content and re-write it in parallel to the controls info div
          c←Body.Content
          Body.Content←''
          Body.Add c

        ⍝ create a division with info about the controls used
          Body.Add CtrlsDiv src

        ⍝ wrap the content of the  element in a div
          '.bodyblock'Body.Push _.div

        ⍝ add the header to the top of the page and wrap the body in a div with id="wrapper"
          Body.Push #.Files.ReadText server.Config.Root,'Styles/banner.txt'

          'wrapper'Body.Push _.div

        ⍝ add a JQuery event handler to toggle the web page/APL source code
          Add _.Script ScriptFollows
          ⍝ $(function(){
          ⍝   $("#tocode").click(function(){
          ⍝     $("#contentblock").hide();
          ⍝     $("#codeblock").show();
          ⍝     $("#widgethelp").addClass("sliddown");
          ⍝     $("#topage").removeClass("activetab");
          ⍝     $("#tocode").addClass("activetab");
          ⍝   });
          ⍝   $("#topage").click(function(){
          ⍝     $("#contentblock").show();
          ⍝     $("#codeblock").hide();
          ⍝     $("#widgethelp").removeClass("sliddown");
          ⍝     $("#topage").addClass("activetab");
          ⍝     $("#tocode").removeClass("activetab");
          ⍝   });
          ⍝ });

      :Else
          Head.Add _.StyleSheet'/Styles/sampleStyles.css'
          Body.class←'bodyblock'
      :EndIf
     ⍝ call the base class Wrap function

      r←⎕BASE.Wrap
    ∇

    ∇ r←CtrlsDiv source;control;controls;ns;pattern
     ⍝ Creates a div with info on the controls used in source

     ⍝ First we extract proper lists of controls
      controls←(⍴#.MS3SiteUtils.NSS)/⊂''
      source←(⍳∘'⍝'↑⊢)¨source      ⍝ remove comments
      source←{⍵/⍨~≠\''''=⍵}¨source ⍝ remove strings but leaves one ' as separator
      pattern←8⌽')\.(\w+)_(',∊#.MS3SiteUtils.NSS,¨'|'
      :For control :In 'Handler' 'Position'~⍨∪(pattern ⎕S'\2')source ⍝ find controls
          :Trap 6 ⍝ unknown
              ns←(#.MS3SiteUtils.NSS(∨/⍷)¨⊂⍕⍎'#._.',control)/⍳⍴#.MS3SiteUtils.NSS ⍝ add to right list
          :Else
              ns←⍬
          :EndTrap
          controls[ns],←⊂⊂control
      :EndFor
      controls←{⍵[⍋↑⍵]}¨controls   ⍝ sort the lists

     ⍝ Now we create and populate the info div
      r←'
Controls used on this page:'
      :For ns :In (×≢¨controls)/⍳⍴#.MS3SiteUtils.NSS ⍝ do not process if empty
          r,←' ',(ns⊃#.MS3SiteUtils.GROUPS),': '
          :For control :In ns⊃controls
              r,←'',control,', '
          :EndFor
          r↓⍨←¯7
      :EndFor
      r,←'
'
    ∇

    ∇ r←Walk content;e
      r←⍬
      :For e :In content
          :If (isClass e)∨isInstance e
              r←r,⊃⎕CLASS e
              :Trap 6
                  r,←Walk e.Content
              :EndTrap
          :EndIf
      :EndFor
    ∇

:EndClass

:Class MiPageSample : #.MiPage
   ⍝ This is a template that "wraps" the page content by
   ⍝ - adding a header and footer
   ⍝ - adding a handler that will toggle the display of the web page and its APL source code

    ∇ {r}←Wrap;lang;server;src;c
      :Access Public

      :If 0∊⍴Get'nowrapper'
          server←_Request.Server

        ⍝ we need jQuery to toggle content/source, other resources will be loaded if needed
          Use'JQuery' ⍝ this is a resource defined in Config/Resources.xml

        ⍝ set the title display in the browser to the name of the page or application
          Add _.title('Advanced' 'Simple' 'Examples?' 'index' '_'⎕R'' '' ''server.Config.Name' '((-'.'⍳⍨⌽)↓⊢↑⍨1-'/'⍳⍨⌽)_Request.Page)

        ⍝ add a link to our CSS stylesheet
          _CssReset←'/Styles/cssreset.css'
          _CssOverride←'/Styles/sampleStyles.css'

        ⍝ set a meta tag to make it explicitly UTF-8
        ⍝  (Add _.meta).Set'http-equiv="content-type" content="text/html;charset=UTF-8"'

        ⍝ wrap the content of the  element in a div
          'contentblock'Body.Push _.div

        ⍝ add a hidden division to the body containing the APL source code
          (Add _.div(#.HtmlUtils.APLToHTMLColor src←⎕SRC⊃⊃⎕CLASS ⎕THIS)).Set'id="codeblock"' 'style="display: none;"'

        ⍝ extract the content and re-write it in parallel to the controls info div
          c←Body.Content
          Body.Content←''
          Body.Add c

        ⍝ create a division with info about the controls used
          Body.Add CtrlsDiv src

        ⍝ wrap the content of the  element in a div
          '.bodyblock'Body.Push _.div

        ⍝ add the header to the top of the page and wrap the body in a div with id="wrapper"
          Body.Push #.Files.ReadText server.Config.Root,'Styles/banner.txt'

          'wrapper'Body.Push _.div

        ⍝ add a JQuery event handler to toggle the web page/APL source code
          Add _.Script ScriptFollows
          ⍝ $(function(){
          ⍝   $("#tocode").click(function(){
          ⍝     $("#contentblock").hide();
          ⍝     $("#codeblock").show();
          ⍝     $("#widgethelp").addClass("sliddown");
          ⍝     $("#topage").removeClass("activetab");
          ⍝     $("#tocode").addClass("activetab");
          ⍝   });
          ⍝   $("#topage").click(function(){
          ⍝     $("#contentblock").show();
          ⍝     $("#codeblock").hide();
          ⍝     $("#widgethelp").removeClass("sliddown");
          ⍝     $("#topage").addClass("activetab");
          ⍝     $("#tocode").removeClass("activetab");
          ⍝   });
          ⍝ });

      :Else
          Head.Add _.StyleSheet'/Styles/sampleStyles.css'
          Body.class←'bodyblock'
      :EndIf
     ⍝ call the base class Wrap function

      r←⎕BASE.Wrap
    ∇

    ∇ r←CtrlsDiv source;control;controls;ns;pattern
     ⍝ Creates a div with info on the controls used in source

     ⍝ First we extract proper lists of controls
      controls←(⍴#.MS3SiteUtils.NSS)/⊂''
      source←(⍳∘'⍝'↑⊢)¨source      ⍝ remove comments
      source←{⍵/⍨~≠\''''=⍵}¨source ⍝ remove strings but leaves one ' as separator
      pattern←8⌽')\.(\w+)_(',∊#.MS3SiteUtils.NSS,¨'|'
      :For control :In 'Handler' 'Position'~⍨∪(pattern⎕S'\2')source ⍝ find controls
          :Trap 6 ⍝ unknown
              ns←(#.MS3SiteUtils.NSS(∨/⍷)¨⊂⍕⍎'#._.',control)/⍳⍴#.MS3SiteUtils.NSS ⍝ add to right list
          :Else
              ns←⍬
          :EndTrap
          controls[ns],←⊂⊂control
      :EndFor
      controls←{⍵[⍋↑⍵]}¨controls   ⍝ sort the lists

     ⍝ Now we create and populate the info div
      r←'
Controls used on this page:'
      :For ns :In (×≢¨controls)/⍳⍴#.MS3SiteUtils.NSS ⍝ do not process if empty
          r,←' ',(ns⊃#.MS3SiteUtils.GROUPS),': '
          :For control :In ns⊃controls
              r,←'',control,', '
          :EndFor
          r↓⍨←¯7
      :EndFor
      r,←'
'
    ∇

    ∇ r←Walk content;e
      r←⍬
      :For e :In content
          :If (isClass e)∨isInstance e
              r←r,⊃⎕CLASS e
              :Trap 6
                  r,←Walk e.Content
              :EndTrap
          :EndIf
      :EndFor
    ∇

:EndClass


jqButtonset API

jqButtonset API

:class jqButtonset : #._JQ._jqUIWidget

⍝ Description:: jQueryUI Sortable widget/interaction
⍝ Constructor:: [items [type]|selector]
⍝ items    - vector of input elements to create
⍝ type     - the input types (default 'button') of the created elements
⍝ selector - the jQuery/CSS selector to which to convert and manage as buttons
⍝ Public Fields::
⍝ Items    - vector of input elements to create
⍝ Type     - the input types (default 'button') of the created elements
⍝ Selector - the jQuery/CSS selector to which to convert and manage as buttons
⍝ Legend   - the legend of the containing Fieldset-to-be
⍝ Examples::
⍝ Add _.jqButtonset '#myul'                            ⍝ jqButtonset will use the already existing element #myul
⍝ Add _.jqButtonset ('One' 'Two' 'Three')              ⍝ jqButtonset will build a
 / <input> structure
⍝ Add _.jqButtonset (('One' 'Two' 'Three') 'radio')    ⍝ only one button can be selected
⍝ Add _.jqButtonset (('One' 'Two' 'Three') 'checkbox') ⍝ the buttons stay "pushed in

    :field public shared readonly DocBase←'https://jqueryui.com/buttonset/'
    :field public shared readonly ApiLevel←3
    :field public shared readonly IntEvt←'create'
    :Field Public Items←UNDEF         ⍝ vector of items
    :Field Public Type←''
    :Field Public Legend←UNDEF

⍝'mybset1' Add _.jqButtonset ('Phone' 'Email') 'radio' >
⍝
⍝

⍝<input id="mybset1_1" type="button" name="mybset1" value="1">Phone
⍝<input id="mybset1_2" type="checkbox" name="mybset1" value="2">Email
⍝

    ∇ Make
      :Access public
      :Implements constructor
      JQueryFn←'buttonset'
      InternalEvents←IntEvt
    ∇

    ∇ Make1 arg
      :Access public
      :Implements constructor
      JQueryFn←'buttonset'
      InternalEvents←IntEvt
      :If ''≢arg
          :Select |≡arg
          :CaseList 0 1 ⍝ '#myul'
              Selector←arg
          :Case 2  ⍝ ('One' 'Two' 'Three')
              Items←arg
          :Case 3 ⍝ (('One' 'Two' 'Three') 'radio')
              (Items Type)←arg
          :EndSelect
      :EndIf
    ∇

    ∇ r←Render;container;i;item;fs;type;button
      :Access Public
      r←''
      :If ''≢Selector
          id←Selector↓⍨'#'=⊃Selector
      :Else
          Selector←'#',SetId
      :EndIf
      :If UNDEF≢Items
          container←id New _.div
          :For i item type :InEach (⍳≢Items)Items((≢Items)⍴⊂⍣(1=≡,Type)⊢Type)
              :Select type
              :CaseList '' 'button' 'submit' 'reset'
                  button←container.Add _.Button,⊂item id i
                  button.type←type,'button'/⍨0∊⍴type
              :CaseList 'checkbox' 'radio'
                  button←container.Add _.Input,⊂type i item'right'
                  button.name←id
              :Else
                  'Invalid Type set for jqButtonset'⎕SIGNAL 11
              :EndSelect
              button.id←id,'_',⍕i
          :EndFor
          :If UNDEF≢Legend
              fs←container.Push _.Fieldset
              fs.Legend←Legend
          :EndIf
          r,←container.Render
      :EndIf
      r,←⎕BASE.Render
    ∇
:endclass

:class jqButtonset : #._JQ._jqUIWidget
    ∇ Make0
      :Access public
      :Implements constructor
      JQueryFn←'buttonset'
    ∇

    ∇ Make1 pars
      :Access public
      pars←(⊂'buttonset'),eis pars
      :Implements constructor :base pars
    ∇
:endclass