update Files.List for Mac to remove path from returned file names

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←data Append name
     ⍝ Append data to existing file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND ⍵}Nopen name
    ∇

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←Nopen name
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm -f ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←{1≠⊃⍴⍵:0 ⋄ ⍵[1;4]}'.'List path
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Chars←GetText name;nid;signature;nums;sz;b
     ⍝ Read ANSI or Unicode character file
      sz←⎕NSIZE nid←(unixfix name)⎕NTIE 0
      signature←⎕NREAD nid 83 3 0
      :If signature≡¯17 ¯69 ¯65 ⍝ UTF-8?
          nums←⎕NREAD nid 83 sz
          Chars←'UTF-8'⎕UCS 256|nums ⍝ Signed ints
      :ElseIf ∨/b←(2↑signature)∧.=2 2⍴¯1 ¯2 ¯2 ⍝ Unicode (UTF-16)
          Chars←{,⌽(2,⍨2÷⍨⍴⍵)⍴⍵}⍣(⊣/b)⎕NREAD nid 83 sz 2
          Chars←'UTF-16'⎕UCS(2*16)|163 ⎕DR Chars
      :Else ⍝ ANSI or UTF-8
          Chars←{11::⎕UCS ⍵ ⋄ 'UTF-8'⎕UCS ⍵}256|⎕NREAD nid 83 sz 0
      :EndIf
      ⎕NUNTIE nid
    ∇

    ∇ vtv←GetVTV name
     ⍝ Read ANSI or Unicode character file as vector of text vectors
      vtv←{1↓¨(v=n)⊂v←(n←⎕UCS 10),⍵}(GetText name)~⎕UCS 13
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{pattern}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter;isFolder
    ⍝ path and pattern are related.
    ⍝ If there is no pattern (or pattern is empty)
    ⍝   If path ends with '/' or '\' then return information for the contents of the folder, otherwise, return information about the folder (or file) itself
    ⍝ If a non-empty pattern exists, it is used as a filter on the contents of path (path is treated as a folder name)
    ⍝ Examples:
    ⍝   List '/dir/foo'  ⍝ returns information about /dir/foo
    ⍝   List '/dir/foo/' ⍝ returns information about the contents of /dir/foo/
    ⍝   '*.dyalog' List '/dir/foo'  ⍝ returns information about the .dyalog files in /dir/foo/
    ⍝   List '/dir/foo/*.dyalog'    ⍝ also returns information about the .dyalog files in /dir/foo/

    ⍝ Information returned is:
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If 0=⎕NC'pattern' ⋄ pattern←'' ⋄ :EndIf
      isFolder←'/\'∊⍨¯1↑path
      filter←''
      :If isFile←~0∊⍴pattern
          filter←isFolder↓'/',pattern
      :EndIf

      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al',isFolder↓'d --time-style=full-iso ',unixfix path,filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          r←((1↑⍴rslt),4)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                         ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃¨⎕VFI¨rslt[;4]               ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                                    ⍝ 0 msec for MacOS to Timestamp
          r[;0]←path{⍺((⍴⍺){⍵↓⍨⍺⍺×⍺≡⍺⍺⍴⍵})⍵}¨rslt[;8]   ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          →(0∊⍴rslt←↓[0]↑rslt)⍴0
          r←((1↑⍴0⊃rslt),4)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt    ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt           ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ tn←Nopen name
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
      :EndTrap
    ∇

    ∇ r←data Put name
     ⍝ Write data to file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND(0 ⎕NRESIZE ⍵)(⎕DR data)}Nopen name
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      tn←Nopen name
      0 ⎕NRESIZE tn
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~isWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      :If 0=⎕NC'FindFirstFileX'
          WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
          'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
          'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
          ⎕NA'kernel32.C32|FindClose I4'
          ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
          ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
          ⎕NA'I4 kernel32.C32∣GetLastError'
      :EndIf
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←isWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ∘∘∘ ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      r←a{⍺{(⎕FUNTIE ⍵)⊢⎕FREAD ⍵ ⍺}w ⎕FSTIE 0}w
    ∇

    ∇ r←filename Fopen tieno
    ⍝ tieno is tie number and optionally 1 to tie exclusively
      tieno←2↑tieno ⍝ default to shares tie
      :Trap 22
          r←filename{⍵[2]:⍺ ⎕FTIE ⍵[1] ⋄ ⍺ ⎕FSTIE ⍵}tieno
      :Else
          r←filename{⍵[2]:⍵[3] ⋄ ⍺ ⎕FSTIE ⍵[2]⊣⎕FUNTIE ⍵[3]}tieno,filename ⎕FCREATE tieno[1]
      :EndTrap
    ∇
:EndNamespace

:Namespace Files

⍝ Provides cover functions for many file operations
⍝ Eventually all will be implemented for both Windows and *nix

    (⎕IO ⎕ML)←1

    ∇ r←data Append name
     ⍝ Append data to existing file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND ⍵}Nopen name
    ∇

    ∇ r←text AppendText name;tn
     ⍝ Append text to existing file (must be single byte text)
      tn←Nopen name
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ (tn name)←{ext}CreateTemp folder;cnt
     ⍝ Create a temp file
     ⍝ folder - folder to create temp file in
     ⍝ ext - file extension
      :If 0=⎕NC'ext' ⋄ ext←'tmp' ⋄ :EndIf
      :If 0∊⍴ext ⋄ ext←'tmp' ⋄ :EndIf
      cnt←tn←0
      :Repeat
          name←folder,('/\'∊⍨¯1↑folder)↓'/',⎕A[?8⍴⍴⎕A],'.',ext
          :Trap 22 ⋄ tn←name ⎕NCREATE 0 ⋄ :EndTrap
          cnt+←1
      :Until (cnt>20)∨tn≠0
      'Unable to create temporary file'⎕SIGNAL tn↓22
    ∇

    ∇ {protect}Copy FmTo;CopyFileX;GetLastError ⍝ Copy file Fm -> To
      :Select APLVersion
      :Case '*nix'
          ∘
      :Case 'Win'
          :If 0=⎕NC'protect'     ⍝ Copy fails if <protect> and 'To' exists.
              protect←0          ⍝ Default unprotected copy.
          :EndIf
          'CopyFileX'⎕NA'I kernel32.C32∣CopyFile* <0T <0T I'
          :If 0=CopyFileX FmTo,protect
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'CopyFile error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Delete name;DeleteFileX;GetLastError;FindFirstFile;FindNextFile;FindClose;handle;rslt;ok;next;⎕IO;path
      ⎕IO←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          _SH'rm -f ',unixfix name
      :Case 'Win'
          'DeleteFileX'⎕NA'I kernel32.C32∣DeleteFile* <0T'
          ⎕NA'I4 kernel32.C32|GetLastError'
          :If ∨/'*?'∊name ⍝ wildcards?
              path←{(⌽∨\⌽⍵∊'\/')/⍵}name
              _FindDefine
              handle rslt←_FindFirstFile name
              :If 0=handle
                  :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
              :EndIf
              :If '.'≠⊃6⊃rslt
                  {}DeleteFileX⊂path,6⊃rslt
              :EndIf
              :While 1=0⊃ok next←_FindNextFile handle
                  :If '.'≠⊃6⊃next
                      {}DeleteFileX⊂path,6⊃next
                  :EndIf
              :EndWhile
              :If 0 18∨.≠ok next
                  ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
              :EndIf
              {}FindClose handle
          :Else
              :If 0=DeleteFileX⊂name
                  11 ⎕SIGNAL⍨'DeleteFile error:',⍕GetLastError
              :EndIf
          :EndIf
      :EndSelect
    ∇

    ∇ r←DirExists path
      r←0
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :Trap 11
              r←0<⍴_SH'ls -adl ',unixfix path
          :EndTrap
      :Case 'Win'
          r←{1≠⊃⍴⍵:0 ⋄ ⍵[1;4]}'.'List path
      :EndSelect
    ∇

    ∇ r←Exists name
     ⍝ Does file exist?
      r←1
      :Trap 22
          :Trap 19 ⍝ file access error means file exists
              ⎕NUNTIE(unixfix name)⎕NTIE 0
          :EndTrap
      :Else
          r←0
      :EndTrap
    ∇

    ∇ r←GetCurrentDirectory;GCD;GetLastError
     ⍝ Get Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          r←⊃_SH'pwd'
      :Case 'Win'
          'GCD'⎕NA'I kernel32.C32∣GetCurrentDirectory* I4 >0T'
          :If 0≠1⊃r←GCD 256 256
              r←2⊃r
          :Else
              ⎕NA'I4 kernel32.C32|GetLastError'
              11 ⎕SIGNAL⍨'GetCurrentDirectory error:',⍕GetLastError
          :EndIf
      :EndSelect
    ∇

    ∇ Chars←GetText name;nid;signature;nums;sz;b
     ⍝ Read ANSI or Unicode character file
      sz←⎕NSIZE nid←(unixfix name)⎕NTIE 0
      signature←⎕NREAD nid 83 3 0
      :If signature≡¯17 ¯69 ¯65 ⍝ UTF-8?
          nums←⎕NREAD nid 83 sz
          Chars←'UTF-8'⎕UCS 256|nums ⍝ Signed ints
      :ElseIf ∨/b←(2↑signature)∧.=2 2⍴¯1 ¯2 ¯2 ⍝ Unicode (UTF-16)
          Chars←{,⌽(2,⍨2÷⍨⍴⍵)⍴⍵}⍣(⊣/b)⎕NREAD nid 83 sz 2
          Chars←'UTF-16'⎕UCS(2*16)|163 ⎕DR Chars
      :Else ⍝ ANSI or UTF-8
          Chars←{11::⎕UCS ⍵ ⋄ 'UTF-8'⎕UCS ⍵}256|⎕NREAD nid 83 sz 0
      :EndIf
      ⎕NUNTIE nid
    ∇

    ∇ vtv←GetVTV name
     ⍝ Read ANSI or Unicode character file as vector of text vectors
      vtv←{1↓¨(v=n)⊂v←(n←⎕UCS 10),⍵}(GetText name)~⎕UCS 13
    ∇

    ∇ r←LikelyURL w
      →0↓⍨r←(⎕DR w)∊80 82
      r←{(0∊⍴⍵)<∧/⍵∊'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%-._~:/?#[]@!$&''()*+,;='}w ⍝ identify likely URIs
    ∇

    ∇ r←{pattern}List path;z;rslt;handle;next;ok;attrs;⎕IO;FindFirstFileX;FindNextFileX;FindClose;FileTimeToLocalFileTime;FileTimeToSystemTime;GetLastError;isFile;filter;isFolder
    ⍝ path and pattern are related.
    ⍝ If there is no pattern (or pattern is empty)
    ⍝   If path ends with '/' or '\' then return information for the contents of the folder, otherwise, return information about the folder (or file) itself
    ⍝ If a non-empty pattern exists, it is used as a filter on the contents of path (path is treated as a folder name)
    ⍝ Examples:
    ⍝   List '/dir/foo'  ⍝ returns information about /dir/foo
    ⍝   List '/dir/foo/' ⍝ returns information about the contents of /dir/foo/
    ⍝   '*.dyalog' List '/dir/foo'  ⍝ returns information about the .dyalog files in /dir/foo/
    ⍝   List '/dir/foo/*.dyalog'    ⍝ also returns information about the .dyalog files in /dir/foo/

    ⍝ Information returned is:
    ⍝ [;0] Name [;1] Length [;2] LastAccessTime [;3] IsDirectory
      ⎕IO←0
      :If 0=⎕NC'pattern' ⋄ pattern←'' ⋄ :EndIf
      isFolder←'/\'∊⍨¯1↑path
      filter←''
      :If isFile←~0∊⍴pattern
          filter←isFolder↓'/',pattern
      :EndIf

      r←0 4⍴'' 0 0 0

      :Select APLVersion
      :Case '*nix'
          →(0∊⍴rslt←1 _SH'ls -al',isFolder↓'d --time-style=full-iso ',unixfix path,filter)⍴0
          rslt←↑rslt
          rslt←' ',('total '≡6⍴rslt)↓[0]rslt
          r←((1↑⍴rslt),4)⍴0
          z←∧⌿' '=rslt ⍝ entirely blank columns
          z←z∧10>+\z    ⍝ Do not split file names
          rslt←z⊂rslt
          r[;3]←'d'=(0⊃rslt)[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃⎕VFI,4⊃rslt ⍝ Size
          z←,(5⊃rslt),6⊃rslt ⋄ ((z∊'-:')/z)←' ' ⋄ z←((1↑⍴r),6)⍴1⊃⎕VFI z
          r[;2]←↓⌊z,1000×1|z[;5]                ⍝ Add msec to Timestamp
          r[;0]←{(⌽~∨\⌽⍵='/')/⍵}¨{(-+/∧\' '=⌽⍵)↓¨↓⍵}0 1↓8⊃rslt    ⍝ Name

      :Case 'Mac'
          →(0∊⍴rslt←1 _SH'stat -lt "%F %T %z" ',unixfix path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter)⍴0
          r←((1↑⍴rslt),4)⍴0
          rslt←↑{⎕ML←3 ⋄ ⍵⊂⍨~{⍵∧9>+\⍵}' '=⍵}¨rslt
          r[;3]←'d'=0⊃¨rslt[;1]                 ⍝ IsDirectory
          r[;1]←(~r[;3])×1⊃¨⎕VFI¨rslt[;4]       ⍝ Size
          z←↑∊¨↓{w←⍵ ⋄ ((w∊'-:')/w)←' ' ⋄ 1⊃⎕VFI w}¨rslt[;5 6] ⍝
          r[;2]←↓z,0                            ⍝ 0 msec for MacOS to Timestamp
          r[;0]←rslt[;8]                        ⍝ Name

      :Case 'Win'
      ⍝ See DirX for explanations of results of _FindNextFile etc
          _FindDefine
          handle rslt←_FindFirstFile path,isFolder{0∊⍴⍵:⍺/'*' ⋄ ⍵}filter
          :If 0=handle
              :Return ⍝ ('ntdir error:',⍕rslt)⎕SIGNAL 102      ⍝ file not found
          :EndIf
          rslt←,⊂rslt
          :While 1=0⊃ok next←_FindNextFile handle
              rslt,←⊂next
          :EndWhile
          :If 0 18∨.≠ok next
              ('ntdir error:',⍕next)⎕SIGNAL 11   ⍝ DOMAIN
          :EndIf
          ok←FindClose handle
          →(0∊⍴rslt←↓[0]↑rslt)⍴0
          r←((1↑⍴0⊃rslt),4)⍴0
          (0⊃rslt)←⍉attrs←(32⍴2)⊤0⊃rslt    ⍝ Get attributes into bits
          r[;3]←(0⊃rslt)[;27]              ⍝ IsDirectory?
          r[;1]←0(2*32)⊥⍉↑4⊃rslt           ⍝ combine size elements
          r[;2]←_Filetime_to_TS¨3⊃rslt     ⍝ As ⎕TS vector
          r[;0]←6⊃rslt                     ⍝ Name
      :EndSelect
      r←r[⍋↑r[;0];]
    ∇

    ∇ MkDir path;CreateDirectory;GetLastError;err
      ⍝ Create a folder
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          :If ~DirExists path
              1 _SH'mkdir ',unixfix path
              ('mkdir error on ',path)⎕SIGNAL 11/⍨~DirExists path
          :EndIf
      :Case 'Win'
          ⎕NA'I kernel32.C32∣CreateDirectory* <0T I4' ⍝ Try for best function
          →(0≠CreateDirectory path 0)⍴0 ⍝ 0 means "default security attributes"
          ⎕NA'I4 kernel32.C32|GetLastError'
          err ⎕SIGNAL⍨'CreateDirectory error:',⍕err←GetLastError
      :EndSelect
    ∇

    ∇ Move filenames;MoveFileX;MoveFileExA;GetLastError;err
    ⍝ Move (from to) - move/rename file
      :Select APLVersion
      :Case '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣MoveFileEx* <0T <0T I4' ⍝ Try for best function
          :If 0≠MoveFileExA filenames,3 ⍝ REPLACE_EXISTING (1) + COPY_ALLOWED (2)
              :Return
          :EndIf
          ⎕NA'I4 kernel32.C32|GetLastError'
          :Select err←GetLastError
          :Case 120                     ⍝ ERROR_CALL_NOT_IMPLIMENTED
              'MoveFileX'⎕NA'I Kernel32.C32∣MoveFile* <0T <0T' ⍝ accept 2nd best - win 95
              :If 0≠MoveFileX filenames
                  :Return
              :EndIf
              err←GetLastError
          :EndSelect
          11 ⎕SIGNAL⍨'MoveFile error:',⍕err
      :EndSelect
    ∇

    ∇ tn←Nopen name
      :Trap 0
          tn←name ⎕NCREATE 0
      :Else
          tn←name ⎕NTIE 0
      :EndTrap
    ∇

    ∇ r←data Put name
     ⍝ Write data to file
      r←{(⎕NUNTIE ⍵)⊢data ⎕NAPPEND(0 ⎕NRESIZE ⍵)(⎕DR data)}Nopen name
    ∇

    ∇ r←text PutText name;tn
     ⍝ Write text to file (must be single byte text)
      tn←Nopen name
      0 ⎕NRESIZE tn
      r←text ⎕NAPPEND tn(⎕DR' ')
      ⎕NUNTIE tn
    ∇

    ∇ RmDir path;RemoveDirectoryA;GetLastError
     ⍝ Remove folder/directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          ⎕NA'I kernel32.C32∣RemoveDirectory* <0T'
          →(0≠RemoveDirectory,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'RemoveDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ SetCurrentDirectory path;SCD;GetLastError
     ⍝ Set Current Directory
      :Select APLVersion
      :CaseList '*nix' 'Mac'
          ∘
      :Case 'Win'
          'SCD'⎕NA'I kernel32.C32∣SetCurrentDirectory* <0T'
          →(0≠SCD,⊂path)⍴0
          ⎕NA'I4 kernel32.C32|GetLastError'
          11 ⎕SIGNAL⍨'SetCurrentDirectory error:',⍕GetLastError
      :EndSelect
    ∇

    ∇ r←{noext}SplitFilename filename;filesep;mask;path;file;ext
    ⍝ splits a filename into: path name ext
      noext←{6::0 ⋄ noext}''
      filesep←(~isWin)↓'\/'
      mask←⌽∨\⌽filename∊filesep
      path←mask/filename
      file←(~mask)/filename
      :If noext
          r←path file
      :Else
          mask←∨\⌽<\⌽'.'=file
          ext←mask/file
          file←(~mask)/file
          r←path file ext
      :EndIf
    ∇

    :Section Windows
    ∇ rslt←_Filetime_to_TS filetime;⎕IO
      :If 1≠0⊃rslt←FileTimeToLocalFileTime filetime(⎕IO←0)
      :OrIf 1≠0⊃rslt←FileTimeToSystemTime(1⊃rslt)0
          rslt←0 0                   ⍝ if either call failed then zero the time elements
      :EndIf
      rslt←1 1 0 1 1 1 1 1/1⊃rslt    ⍝ remove day of week
    ∇

    ∇ _FindDefine;WIN32_FIND_DATA
      :If 0=⎕NC'FindFirstFileX'
          WIN32_FIND_DATA←'{I4 {I4 I4} {I4 I4} {I4 I4} {U4 U4} {I4 I4} T[260] T[14]}'
          'FindFirstFileX'⎕NA'I4 kernel32.C32|FindFirstFile* <0T >',WIN32_FIND_DATA
          'FindNextFileX'⎕NA'U4 kernel32.C32|FindNextFile* I4 >',WIN32_FIND_DATA
          ⎕NA'kernel32.C32|FindClose I4'
          ⎕NA'I4 kernel32.C32|FileTimeToLocalFileTime <{I4 I4} >{I4 I4}'
          ⎕NA'I4 kernel32.C32|FileTimeToSystemTime <{I4 I4} >{I2 I2 I2 I2 I2 I2 I2 I2}'
          ⎕NA'I4 kernel32.C32∣GetLastError'
      :EndIf
    ∇

    ∇ rslt←_FindFirstFile name;⎕IO
      rslt←FindFirstFileX name(⎕IO←0)
      :If ¯1=0⊃rslt                   ⍝ INVALID_HANDLE_VALUE
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0        ⍝ shorten the file name at the null delimiter
          (1 7⊃rslt)_FindTrim←0        ⍝ and for the alternate name
      :EndIf
    ∇

    ∇ rslt←_FindNextFile handle;⎕IO
      rslt←FindNextFileX handle(⎕IO←0)
      :If 1≠0⊃rslt
          rslt←0 GetLastError
      :Else
          (1 6⊃rslt)_FindTrim←0             ⍝ shorten the filename
          (1 7⊃rslt)_FindTrim←0             ⍝ shorten the alternate name
      :EndIf
    ∇

    ∇ name←name _FindTrim ignored;⎕IO
     ⍝ Truncates a character vector at the null delimiting byte.
     ⍝ The null is not included in the result.
      ⎕IO←0
      name↑⍨←name⍳⎕UCS 0
    ∇

    ∇ {r}←{suppress}_CMD cmd
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕CMD cmd
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ r←isWin
      r←'Win'≡APLVersion
    ∇

    :endsection

    :section *nix
    ∇ {r}←{suppress}_SH cmd
    ⍝ SH cover to suppress any error messages
    ⍝ suppress will suppress error from being signaled
      :If 0=⎕NC'suppress' ⋄ suppress←0 ⋄ :EndIf
      r←''
      :Trap 0
          r←⎕SH cmd,' 2>/dev/null'
      :Else
          ('shell command failed: ',cmd)⎕SIGNAL 11/⍨~suppress
      :EndTrap
    ∇

    ∇ f←unixfix f
    ⍝ replaces Windows file separator \ with Unix file separator /
    ⍝ this approach is mindnumbingly simple and probably dangerous
    ⍝ which is why we call unixfix very cautiously
      :If (⊂APLVersion)∊'*nix' 'Mac' ⋄ ((f='\')/f)←'/' ⋄ :EndIf
    ∇

    ∇ r←APLVersion
      :Select 3↑⊃'.'⎕WG'APLVersion'
      :CaseList 'Lin' 'AIX' 'Sol'
          r←'*nix'
      :Case 'Win'
          r←'Win'
      :Case 'Mac'
          r←'Mac'
      :Else
          ∘∘∘ ⍝ unknown version
      :EndSelect
    ∇
    :endsection

    ∇ r←a FREAD w;t
      r←a{⍺{(⎕FUNTIE ⍵)⊢⎕FREAD ⍵ ⍺}w ⎕FSTIE 0}w
    ∇

    ∇ r←filename Fopen tieno
    ⍝ tieno is tie number and optionally 1 to tie exclusively
      tieno←2↑tieno ⍝ default to shares tie
      :Trap 22
          r←filename{⍵[2]:⍺ ⎕FTIE ⍵[1] ⋄ ⍺ ⎕FSTIE ⍵}tieno
      :Else
          r←filename{⍵[2]:⍵[3] ⋄ ⍺ ⎕FSTIE ⍵[2]⊣⎕FUNTIE ⍵[3]}tieno,filename ⎕FCREATE tieno[1]
      :EndTrap
    ∇
:EndNamespace


Enhanced ejGridSimple example to use stacked headers.

:Class ejGridSimple : MiPageSample
⍝ Control:: _SF.ejGrid
⍝ Description:: A read-only grid with formatting and alignment options

    ∇ InitData
      VALUES←1 #.Files.FREAD #.Boot.ms.Config.AppRoot,'Examples\data\stats.DCF'
      VALUES←VALUES[;1 3 4 2 5]
      VALUES[;3]←VALUES[;3]÷1000000 ⍝ Millions
      COLTITLES←'Name' 'Population' 'GDP ($M)' '% of GDP' '$ Per Capita'
      COLNAMES←'Country' 'Population' 'GDPAmt' 'GDPPct' 'PerCap'
      CELLWIDTHS←100 40 25 40 25
      CELLALIGN←1 4/'left' 'right'
      FORMATS←'' '{0:n0}' '{0:n0}' '{0:n1}' '{0:c1}'
    ∇

    ∇ Compose
      :Access Public

      InitData
      Add _.h2'Health Care Spending'
      mygrid←Add _.ejGrid(VALUES COLTITLES CELLWIDTHS)
      mygrid.CellFormats←FORMATS
      mygrid.ColNames←COLNAMES
      mygrid.CellAlign←CELLALIGN

      'allowPaging' 'allowSorting' 'allowResizing' 'showStackedHeader'mygrid.Set¨_true
      'stackedHeaderRows'mygrid.Set'⍎[{stackedHeaderColumns:[{headerText:"Country Facts",column:"Country,Population,GDPAmt"},{headerText:"Health Care Spending",column:"GDPPct,PerCap"}]}]'
    ∇

:EndClass

:Class ejGridSimple : MiPageSample
⍝ Control:: _SF.ejGrid
⍝ Description:: A read-only grid with formatting and alignment options

    ∇ Make
      :Access Public
      :Implements Constructor

      VALUES←1 #.Files.FREAD #.Boot.ms.Config.AppRoot,'Examples\data\stats.DCF'
      COLTITLES ← 'Country' '% GDP' 'Population' 'GDP' '$ Per Capita'
      COLNAMES  ← 'Country' 'PctGDP' 'Population' 'GDP' 'PerCap'
      CELLWIDTHS← 100 25 40 40 25
      CELLALIGN ← 1 4/'left' 'right'
      FORMATS   ← '' '{0:n1}' '{0:n0}' '{0:c0}' '{0:c1}'
    ∇

    ∇ Compose
      :Access Public
      Add _.h2 'Health Care Spending'
      mygrid←Add _.ejGrid (VALUES COLTITLES CELLWIDTHS)
      mygrid.CellFormats←FORMATS
      mygrid.ColNames   ←COLNAMES
      mygrid.CellAlign  ←CELLALIGN
      'allowPaging' 'allowSorting' 'allowResizing' mygrid.Set¨ _true
    ∇

:EndClass


Delete MiPageSample.dyalog.BASE.15104.dyalog

:Class MiPageSample : #.EAWC
⍝ 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

    ∇ Wrap;lang;server
      :Access Public

      server←_Request.Server

    ⍝ we use JQuery to set up the handler, so we tell the page to include JQuery resources
      Use'JQuery' ⍝ "JQuery" is a resource defined in Config/Resources.xml

    ⍝ set the title display in the browser to the name of the application defined in Config/Server.xml
      Add title server.Config.Name

    ⍝ add a link to our CSS stylesheet
      (Add link).SetAttr(('href' '/Styles/style.css')('rel' 'stylesheet')('type' 'text/css'))

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

    ⍝ wrap the content of the <body> element in a div
      Body.Push div'id="contentblock"'

    ⍝ add a hidden division to the body containing the APL source code
      (Add div(#.HTMLInput.APLToHTML ⎕SRC⊃⊃⎕CLASS ⎕THIS)).SetAttr'id="codeblock"' 'style="display: none;"'

    ⍝ add a JQuery event handler to toggle the web page/APL source code
      Add _HTML.Script'$(function(){$("#bannerimage").on("click", function(evt){$("#contentblock,#codeblock").toggle();});});'

    ⍝ add the footer to the bottom of the page
      Add #.Files.GetText server.Config.Root,'Styles\footer.txt'

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

    ⍝ set the language for the page
      lang←server.Config.Lang ⍝ use the language specified in Server.xml
      Head.SetAttr'lang="',lang,'" xml:lang="',lang,'" xmlns="http://www.w3.org/1999/xhtml"'
      ∘∘∘
    ⍝ call the base class Wrap function
      ⎕BASE.Wrap
    ∇

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

:EndClass


added comments

added comments

:Class InputGridSimple : MiPageSample
⍝ Control:: _DC.InputGrid
⍝ Description:: Enter first, last name and birthdate

    ∇ Render;ig;btn;handler;frm
      :Access Public

      Add _.h2'Please tell us about yourself'

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

     ⍝ The grid
      ig←frm.Add _.InputGrid

     ⍝ There are a few fields we can use. Here we set the Border to none
      ig.Border←0
     ⍝ The labels, by default they are aligned to the left of the input areas
     ⍝ ig.Flip←1 would put them to the right
      ig.Labels←'First name' 'Last name' 'Birthdate'
     ⍝ We give an ID to each so we can retrieve their values in the callback
      ig.Inputs←'fname' 'lname' 'bdate'New¨_.EditField _.EditField _.jqDatePicker

     ⍝ Finally we add a button to generate the display in the 'result' div below
      btn←'style="margin-top,margin-bottom: 50px"'frm.Add _.Button'Click Me!'
      btn.type←'button'
      btn.On'click' 'CallbackFn'

      'result'frm.Add _.div  ⍝ this is where the result will appear
    ∇

    ∇ r←CallbackFn
      :Access Public
     ⍝ First we instruct to display 'Hi' followed by the full name
      r←'#result'Replace _.h2('Hi',⍕Get'fname lname')
     ⍝ and if the birthdate given happens to be today's we pop up a birthday message:
      :If ⎕TS[2 3]≡2⍴(4⍴100)⊤2⊃⎕VFI(Get'bdate')∩⎕D
          r,←Execute'alert("Happy Birthday!")'
      :EndIf
    ∇

:EndClass

:Class InputGridSimple : MiPageSample
⍝ Control:: _DC.InputGrid
⍝ Description:: Enter first, last name and birthdate

    ∇ Render;frm;ig;btn;handler
      :Access Public

      Add _.h2'Please tell us about yourself'
      frm'myform'Add _.Form
      ig←frm.Add _.InputGrid

      ig.Border←0
      ig.Labels←'First name' 'Last name' 'Birthdate'
      ig.Inputs←'fname' 'lname' 'bdate'New¨_.EditField _.EditField _.jqDatePicker

      btn←'style="margin-top,margin-bottom: 50px"'frm.Add _.Button'Click Me!'
      btn.On'click' 'CallbackFn'

      'result'frm.Add _.div
    ∇

    ∇ r←CallbackFn
      :Access Public

      r←'#result'Replace _.h2('Hi ',⍕Get'fname lname')
      :If ⎕TS[2 3]≡2⍴(4⍴100)⊤2⊃⎕VFI(Get'bdate')∩⎕D
          r,←Execute'alert("Happy Birthday!")'
      :EndIf
    ∇

:EndClass


Don't assume 5 groups of controls

Don't assume 5 groups of controls

: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

: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←5/⊂''
      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


MiPageSample: Rewritten and enabled Controls Info Div. NB: Old fns Walk and FormatControls still present, but unused.

: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;c;src;ctrlsdiv
      :Access Public

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

        ⍝ we use Syncfusion (which uses jQuery) to set up the controls to do cool stuff
          Use'Syncfusion' ⍝ this is a resource defined in Config/Resources.xml

        ⍝ set the title display in the browser to the name of the application defined in Config/Server.xml
          Add _.title server.Config.Name

        ⍝ 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
          Body.Push _.div'id="contentblock"'

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

        ⍝ create a division with info about the controls used
          ctrlsdiv←CtrlsDiv src

        ⍝ extract the content and re-write it in parallel to the controls info div
          c←Body.Content
          Body.Content←''
          (Body.Add _.StackPanel ctrlsdiv c).Horizontal←1

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

        ⍝ add the footer to the bottom of the page
          Add #.Files.GetText server.Config.Root,'Styles\footer.txt'

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

          Body.Push _.div'id="wrapper"'

        ⍝ add a JQuery event handler to toggle the web page/APL source code
          Add _.Script'$(function(){$("#bannerimage").on("click", function(evt){$("#contentblock,#codeblock,.widgethelp").toggle(400,"swing");});});'

        ⍝ set the language for the page
          lang←server.Config.Lang ⍝ use the language specified in Server.xml
          Set'lang="',lang,'" xml:lang="',lang,'" xmlns="http://www.w3.org/1999/xhtml"'
      :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;lia;ns;ul;cali;h
     ⍝ Creates a div with info on the controls used in source

     ⍝ First we extract proper lists of controls
      controls←4/⊂''
      source←(⍳∘'⍝'↑⊢)source       ⍝ remove comments
      source←{⍵/⍨~≠\''''=⍵}¨source ⍝ remove strings but leaves one ' as separator
      :For control :In 'Handler' 'Position'~⍨∪('_(DC|SF|JQ|html|)\.(\w+)'⎕S'\2')source ⍝ find controls
         ⍝ add to right list: /¯¯¯¯¯SF¯¯¯¯¯\  /¯¯¯¯¯JQ¯¯¯¯¯\  /¯¯¯¯DC¯¯¯¯\ /¯html
          controls[⊃2 3 1 4/⍨('ej'≡2↑control)('jq'≡2↑control)(⎕A∊⍨⊃control)1],←⊂⊂control
      :EndFor
      controls←{⍵[⍋↑⍵]}¨controls   ⍝ sort the lists

     ⍝ Now we create and populate the info div
      r←'.widgethelp'New _.div'This Page Contains
'
      lia←'
',control,'
'
              :Select ns
              :Case 1 ⋄ ul.Add lia,'/Documentation/DyalogAPIs/WidgetDoc?namespace=_DC&widget=',control,cali           ⍝ DC
              :Case 2 ⋄ ul.Add lia,h,'js.syncfusion.com/demos/web/default.htm#!/azure/',(#.Strings.lc 2↓control),cali ⍝ SF
              :Case 3 ⋄ ul.Add lia,h,'jqueryui.com/',(2↓control),cali                                                 ⍝ JQ
              :Case 4 ⋄ ul.Add lia,h,'www.w3schools.com/tags/tag_',(('\d'⎕R'n')control),'.asp',cali                   ⍝ html
              :EndSelect
          :EndFor
      :EndFor
    ∇

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

    ∇ r←FormatControls controls;ctrls;ns;desc;field;n;ctrl;i;c;ref;l;u;nss;item;cn
      ctrls←⊃{⍺ ⍵}#.Utils.∆key/↓[1]0 1↓↑{⎕ML←3 ⋄ {⍵⊂⍨⍵≠'.'}⍕⍵}¨controls
      field←{0::'' ⋄ ⍺⍎⍵}
      nss←'_DC' '_SF' '_JQ' '_html'
      desc←'Dyalog Controls' 'Syncfusion Widgets' 'jQuery Widgets' 'Native HTML5 Elements'
      (r←⎕NEW _.div).class←'widgethelp'
      r.Add'This Page Contains
'
      :Trap 0
          :For (ns ctrl i) :In ctrls{↓(⍺,⍵)[⍋⍵;]}nss⍳{(⊃⍣(¯1+≡⍵))⍵}¨ctrls[;1]
              (r.Add _.span(i⊃desc)).class←'widgetNs'
              u←r.Add _.ul
              n←#.⍎ns←⊃ns
              :For c :In ctrl
                  ref←n.⍎c
                  item←⍬
                  :Select ns
                  :Case '_DC'
                      item←u.Add _.li(New _.a(c(('href=/Documentation/DyalogAPIs/WidgetDoc?namespace=',ns,'&widget=',c)'target=_blank')))
                  :Case '_SF'
                      item←u.Add _.li(New _.a c(('href'('http://js.syncfusion.com/demos/web/default.htm#!/azure/',#.Strings.lc(2×'ej'≡2↑c)↓c))('target' '_blank')))
                  :Case '_JQ'
                      item←u.Add _.li(New _.a c(('href'('http://jqueryui.com/',(2×'jq'≡2↑c)↓c))('target' '_blank')))
                  :Case '_html'
                      cn←c
                      :If ~0∊⍴('^h[1-6]$'⎕S{⍵.Match})c
                          cn←'hn'
                      :EndIf
                      item←u.Add _.li(New _.a c(('href'('http://www.w3schools.com/tags/tag_',cn,'.asp'))('target' '_blank')))
                  :EndSelect
                  :If 0∊⍴item
                      u.Add _.li c
                  :EndIf
              :EndFor
          :EndFor
      :EndTrap
    ∇

: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;controls;sp;c
      :Access Public

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

          controls←⍬ ⍝∪Walk Body.Content

        ⍝ we use Syncfusion (which uses jQuery) to set up the controls to do cool stuff
          Use'Syncfusion' ⍝ this is a resource defined in Config/Resources.xml

        ⍝ set the title display in the browser to the name of the application defined in Config/Server.xml
          Add _.title server.Config.Name

        ⍝ 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 <body> element in a div
          Body.Push _.div'id="contentblock"'

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

          :If ~0∊⍴controls
              c←Body.Content
              Body.Content←''
              (sp←Body.Add _.StackPanel(⊂FormatControls controls)c).Horizontal←1

          :EndIf

        ⍝ wrap the content of the <body> element in a div
          Body.Push _.div'class="bodyblock"'

        ⍝ add the footer to the bottom of the page
          Add #.Files.GetText server.Config.Root,'Styles\footer.txt'

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

          Body.Push _.div'id="wrapper"'

        ⍝ add a JQuery event handler to toggle the web page/APL source code
          Add _.Script'$(function(){$("#bannerimage").on("click", function(evt){$("#contentblock,#codeblock,.widgethelp").toggle(400,"swing");});});'

        ⍝ set the language for the page
          lang←server.Config.Lang ⍝ use the language specified in Server.xml
          Set'lang="',lang,'" xml:lang="',lang,'" xmlns="http://www.w3.org/1999/xhtml"'
      :Else
          Head.Add _.StyleSheet'/Styles/sampleStyles.css'
          Body.class←'bodyblock'
      :EndIf
     ⍝ call the base class Wrap function

      r←⎕BASE.Wrap
    ∇

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

    ∇ r←FormatControls controls;ctrls;ns;desc;field;n;ctrl;i;c;ref;l;u;nss;item;cn
      ctrls←⊃{⍺ ⍵}#.Utils.∆key/↓[1]0 1↓↑{⎕ML←3 ⋄ {⍵⊂⍨⍵≠'.'}⍕⍵}¨controls
      field←{0::'' ⋄ ⍺⍎⍵}
      nss←'_DC' '_SF' '_JQ' '_html'
      desc←'Dyalog Controls' 'Syncfusion Widgets' 'jQuery Widgets' 'Native HTML5 Elements'
      (r←⎕NEW _.div).class←'widgethelp'
      r.Add'This Page Contains<hr/>'
      :Trap 0
          :For (ns ctrl i) :In ctrls{↓(⍺,⍵)[⍋⍵;]}nss⍳{(⊃⍣(¯1+≡⍵))⍵}¨ctrls[;1]
              (r.Add _.span(i⊃desc)).class←'widgetNs'
              u←r.Add _.ul
              n←#.⍎ns←⊃ns
              :For c :In ctrl
                  ref←n.⍎c
                  item←⍬
                  :Select ns
                  :Case '_DC'
                      item←u.Add _.li(New _.a(c(('href=/Documentation/DyalogAPIs/WidgetDoc?namespace=',ns,'&widget=',c)'target=_blank')))
                  :Case '_SF'
                      item←u.Add _.li(New _.a c(('href'('http://js.syncfusion.com/demos/web/default.htm#!/azure/',#.Strings.lc(2×'ej'≡2↑c)↓c))('target' '_blank')))
                  :Case '_JQ'
                      item←u.Add _.li(New _.a c(('href'('http://jqueryui.com/',(2×'jq'≡2↑c)↓c))('target' '_blank')))
                  :Case '_html'
                      cn←c
                      :If ~0∊⍴('^h[1-6]$'⎕S{⍵.Match})c
                          cn←'hn'
                      :EndIf
                      item←u.Add _.li(New _.a c(('href'('http://www.w3schools.com/tags/tag_',cn,'.asp'))('target' '_blank')))
                  :EndSelect
                  :If 0∊⍴item
                      u.Add _.li c
                  :EndIf
              :EndFor
          :EndFor
      :EndTrap
    ∇

:EndClass


Delete FontAwesome.dyalog

Delete FontAwesome.dyalog

No longer required (can be derived via FontIconsBase now!)

:Class FontAwesome :  #._DC.FontIconsBase    ⍝∇:require =\FontIconsBase.dyalog
    :field public shared readonly DocBase←'http://fortawesome.github.io/Font-Awesome/icons/'
    :field public ReadOnly Family←'fa'  ⍝ bt=Blacktie or other prefixes for FontIcons
    :field public Container←'i'
    :field private icon←''

⍝ niladic constructor makes no sense - provide empty make0 anyway?

    ∇ Make0
      :Access Public
      :Implements Constructor :base
    ∇

    ∇ Make1 args
      :Access Public
      :Implements Constructor :base    args
    ∇

    ∇ Make2(type list)
      :Access Public
      :Implements Constructor :base type   list
    ∇

    ∇ Make3(size lowerIcon topIcon)
      :Access Public
      ⍝ Size Stack1 Stack2
      :Implements Constructor :base (size lowerIcon topIcon)
    ∇

    ∇ r←Render;icon3;i1;i2
      :Access Public
      r←⎕BASE.Render
    ∇

:EndClass


Fix to SimpleSessions.KillSessions

Fix to SimpleSessions.KillSessions

:Class SimpleSessions
⍝ Default Session Handler, used by the WebServer sample
⍝ Manages HTTP sessions using cookies
⍝ When a HTTP request arrives, checks to see if the session cookie is included and the session
⍝ is in the list of valid sessions. If it is, assign the Session property of the request.
⍝ If it isn't, create a new session and set the cookie

    :Field Public Sessions ⍝ Should be private, really
    :Field Public Server

    :Class Page
        :Field Public URL←''
        :Field Public State←⎕NS ''
    :EndClass

    :Class Session
        :Field Public ID←0
        :Field Public User←''
        :Field Public LastActive←0
        :Field Public Cookie←''
        :Field Public AuthCookieName←''
        :Field Public Timeout←0
        :Field Public State
        :Field Public New←1
        :Field Public Pages←0⍴⎕NEW Page
        :Field Public Server←⍬
    :EndClass

    ∇ Start server;tn;root
      :Access Public
      :Implements Constructor
    ⍝ Initialize Session handler

      Sessions←0⍴⎕NEW Session
      root←server.Config.Root
      Timeout←server.Config.SessionTimeout
      timeout←Timeout÷24×60 ⍝ Convert minutes to fractions of a day

      :Trap 22
          tn←(root,'sessions.dcf')⎕FCREATE 0
          (0 ¯1 0)⎕FSTAC tn
          0 ⎕FAPPEND tn ⍝ Session Number
          ⎕FUNTIE tn
      :EndTrap
      tn←(root,'sessions.dcf')⎕FSTIE 0
      NextSession←⎕FREAD tn,1
      ⎕FUNTIE tn
      Server←server
    ∇

    ∇ GetSession req;c;tn;now;session;ns;new;t_out;i;r
      :Access Public
    ⍝ Return session. Right argument is a HTTPRequest.

      :Hold 'Sessions'
          session←req.GetCookie'Session'
          now←#.Dates.DateToIDN ⎕TS

          :If new←(1⊃⍴Sessions)<i←Sessions.Cookie⍳⊂session ⍝ Cookie is not in the table
              c←SessionCookie NextSession
              Sessions←Sessions,r←⎕NEW Session
              r.(ID User LastActive Cookie State Server)←NextSession''now c(⎕NS'')Server
              NextSession←(2*30)|NextSession+1
              tn←(req.Server.Config.Root,'sessions.dcf')⎕FSTIE 0
              NextSession ⎕FREPLACE tn,1
              ⎕FUNTIE tn
              req.SetCookie'Session'c
              req.Session←r
              req.Server.onSessionStart req

          :Else ⍝ Old session
              (r←i⊃Sessions).LastActive←now ⍝ Just register activity
              r.New←0
              req.Session←r
          :EndIf

      :EndHold
    ∇

    ∇ KillSessions ids;mask;i
      :Access Public
      :Hold 'Sessions'
          :If ∨/mask←Sessions.ID∊ids
              :For i :In mask/⍳⍴mask
                  Server.onSessionEnd i⊃Sessions
              :EndFor
              Sessions/⍨←~mask
          :EndIf
      :EndHold
    ∇

    ∇ Logout req;session;i;z
      :Access Public Instance
     ⍝ End session due to user request

      session←req.GetCookie'Session'
      :If (1⊃⍴Sessions)≥i←Sessions.Cookie⍳⊂session ⍝ Cookie is in the table
          req.Server.onSessionEnd(i⊃Sessions)
          req.DelCookie'Session'
          :If 0≠⍴z←req.Session.AuthCookieName ⋄ req.DelCookie z ⋄ :EndIf
          req.Return'Logged out ...'
      :Else
          req.Return'No session established ...'
      :EndIf
    ∇

    ∇ r←SessionCookie n;⎕RL;z;seed;p
      ⍝ Create Cookie from session ID or vice versa

      :If (10|⎕DR n)∊0 2 ⍝ Session ID from Cookie
          z←⎕UCS #.Base64.Decode n
          ⎕RL←256⊥2↑z ⋄ p←30?30
          r←30⍴0 ⋄ r[p]←2↓z
          r←256⊥4↑r

      :Else ⍝ Session ID to Cookie
          ⎕RL←10000000|3⊃⎕AI
          ⎕RL←seed←?¯1+256*2 ⋄ p←30?30
          r←#.Base64.Encode #.Base64.Char(256 256⊤seed),((256 256 256 256⊤n),¯1+?26⍴256)[p]
      :EndIf
    ∇

    ∇ HouseKeeping Server;now;m;i;p
    ⍝ Check to see if any sessions have expired due to inactivity
    ⍝ Call any page application callbacks (_Close) if necessary

      :Access Public
      now←#.Dates.DateToIDN ⎕TS
      :If ∨/m←Sessions.LastActive<now-timeout
          :Hold 'Sessions'
              :For i :In m/⍳⍴m
                  Server.onSessionEnd i⊃Sessions
                  :If 0≠⍴p←(i⊃Sessions).Pages
                  :AndIf 0≠⊃p.⎕NC⊂'_Close'
                      p._Close i⊃Sessions
                  :EndIf
              :EndFor
              Sessions←(~m)/Sessions
          :EndHold
      :EndIf
    ∇

:EndClass

:Class SimpleSessions
⍝ Default Session Handler, used by the WebServer sample
⍝ Manages HTTP sessions using cookies
⍝ When a HTTP request arrives, checks to see if the session cookie is included and the session
⍝ is in the list of valid sessions. If it is, assign the Session property of the request.
⍝ If it isn't, create a new session and set the cookie

    :Field Public Sessions ⍝ Should be private, really

    :Class Page
        :Field Public URL←''
        :Field Public State←⎕NS ''
    :EndClass

    :Class Session
        :Field Public ID←0
        :Field Public User←''
        :Field Public LastActive←0
        :Field Public Cookie←''
        :Field Public AuthCookieName←''
        :Field Public Timeout←0
        :Field Public State
        :Field Public New←1
        :Field Public Pages←0⍴⎕NEW Page
    :EndClass

    ∇ Start Server;tn;root
      :Access Public
      :Implements Constructor
    ⍝ Initialize Session handler

      Sessions←0⍴⎕NEW Session
      root←Server.Config.Root
      Timeout←Server.Config.SessionTimeout
      timeout←Timeout÷24×60 ⍝ Convert minutes to fractions of a day

      :Trap 22
          tn←(root,'sessions.dcf')⎕FCREATE 0
          (0 ¯1 0)⎕FSTAC tn
          0 ⎕FAPPEND tn ⍝ Session Number
          ⎕FUNTIE tn
      :EndTrap
      tn←(root,'sessions.dcf')⎕FSTIE 0
      NextSession←⎕FREAD tn,1
      ⎕FUNTIE tn
    ∇

    ∇ GetSession req;c;tn;now;session;ns;new;t_out;i;r
      :Access Public
    ⍝ Return session. Right argument is a HTTPRequest.

      :Hold 'Sessions'
          session←req.GetCookie'Session'
          now←#.Dates.DateToIDN ⎕TS

          :If new←(1⊃⍴Sessions)<i←Sessions.Cookie⍳⊂session ⍝ Cookie is not in the table
              c←SessionCookie NextSession
              Sessions←Sessions,r←⎕NEW Session
              r.(ID User LastActive Cookie State)←NextSession''now c(⎕NS'')
              NextSession←(2*30)|NextSession+1
              tn←(req.Server.Config.Root,'sessions.dcf')⎕FSTIE 0
              NextSession ⎕FREPLACE tn,1
              ⎕FUNTIE tn
              req.SetCookie'Session'c
              req.Session←r
              req.Server.onSessionStart req

          :Else ⍝ Old session
              (r←i⊃Sessions).LastActive←now ⍝ Just register activity
              r.New←0
              req.Session←r
          :EndIf

      :EndHold
    ∇

    ∇ KillSessions ids;mask;i
      :Access Public
      :Hold 'Sessions'
          :If ∨/mask←Sessions.ID∊id
              :For i :In mask/⍳⍴mask
                  Server.onSessionEnd i⊃Sessions
              :EndFor
              Sessions/⍨←~mask
          :EndIf
      :EndHold
    ∇

    ∇ Logout req;session;i;z
      :Access Public Instance
     ⍝ End session due to user request

      session←req.GetCookie'Session'
      :If (1⊃⍴Sessions)≥i←Sessions.Cookie⍳⊂session ⍝ Cookie is in the table
          req.Server.onSessionEnd(i⊃Sessions)
          req.DelCookie'Session'
          :If 0≠⍴z←req.Session.AuthCookieName ⋄ req.DelCookie z ⋄ :EndIf
          req.Return'Logged out ...'
      :Else
          req.Return'No session established ...'
      :EndIf
    ∇

    ∇ r←SessionCookie n;⎕RL;z;seed;p
      ⍝ Create Cookie from session ID or vice versa

      :If (10|⎕DR n)∊0 2 ⍝ Session ID from Cookie
          z←⎕UCS #.Base64.Decode n
          ⎕RL←256⊥2↑z ⋄ p←30?30
          r←30⍴0 ⋄ r[p]←2↓z
          r←256⊥4↑r

      :Else ⍝ Session ID to Cookie
          ⎕RL←10000000|3⊃⎕AI
          ⎕RL←seed←?¯1+256*2 ⋄ p←30?30
          r←#.Base64.Encode #.Base64.Char(256 256⊤seed),((256 256 256 256⊤n),¯1+?26⍴256)[p]
      :EndIf
    ∇

    ∇ HouseKeeping Server;now;m;i;p
    ⍝ Check to see if any sessions have expired due to inactivity
    ⍝ Call any page application callbacks (_Close) if necessary

      :Access Public
      now←#.Dates.DateToIDN ⎕TS
      :If ∨/m←Sessions.LastActive<now-timeout
          :Hold 'Sessions'
              :For i :In m/⍳⍴m
                  Server.onSessionEnd i⊃Sessions
                  :If 0≠⍴p←(i⊃Sessions).Pages
                  :AndIf 0≠⊃p.⎕NC⊂'_Close'
                      p._Close i⊃Sessions
                  :EndIf
              :EndFor
              Sessions←(~m)/Sessions
          :EndHold
      :EndIf
    ∇

:EndClass


_DC.Button implemented

_DC.Button implemented

:class Button : #._html.button
⍝ Description:: Dyalog Button widget
⍝ Constructor:: [content [name [value [attrs]]]]
⍝ content - the content of the button (what shows up on the button's face)
⍝ name  - the id/name for the button
⍝ value - the value for the button, this is what's returned to the server
⍝ attr  - attribute(s) for the button
⍝ Public Fields:: None
⍝ Notes::
⍝ If either the id or name are undefined, the undefined one is set to the same value as the defined one
⍝ If both id and name are undefined, a unique id is generated and the name is set to it as well
⍝ The default type for this button is 'button', not 'submit' which is the default for the native HTML5 button

    :field _content←''

    ∇ Make0
      :Access public
      :Implements constructor
      type←'button'
    ∇

    ∇ Make args;n;v
      :Access public
      :Implements constructor
      args←eis⍣(~0∊⍴args)⊢args
      (Content name value attrs)←args defaultArgs''UNDEF UNDEF''
      type←'button'
    ∇

    ∇ r←Render
      :Access public
      :If (⊂name)∊UNDEF''
          :If (⊂id)∊UNDEF''
              SetId
          :EndIf
          name←id
      :ElseIf UNDEF≡id
          id←name
      :EndIf
      r←⎕BASE.Render
    ∇

:endclass

:class Button : #._html.button
⍝ <button name=⍵[2] value=⍵[3] > ⍵[1] </ button>
⍝ the difference is that this button sets the default type to "button" instead of acting like a "submit"
    :field _content←''

    ∇ Make args;n;v
      :Access public
      :Implements constructor :base
     ⍝ arguments are content [name [value]]
      args←eis⍣(~0∊⍴args)⊢args
      (_content name value)←3↑args,(⍴args)↓'OK' 'button' ''
      Set('type' 'button')
    ∇

    ∇ html←Render
      :Access public
      :If id≡UNDEF ⋄ id←name ⋄ :EndIf
      SetId
      :If 0∊⍴Content
          Content←_content
      :EndIf
      html←⎕BASE.Render
    ∇

:endclass