• Login
  • login to access premium content (it's free).
 # 
 # File Formats
 # JSON
 # XML
 #
 # We rely on a global tag_filetype to determine output text format
 # if tag_filetype is ambiguous then default to json
 #  - if tag_filetype='XML' then filetype='XML' else filetype='JSON' /if
/#

The api library is used for generating responses to api requests.

It focuses on building a json or an xml string to be delivered via webpush()

Build nodes by adding name/value pairs with outvar=appendNode(outvar,name,value) then render final out using push_nodes(outvar)

For an example suppose we wanted to create a xml or json object to describe the properties of a file

a=fileinfo('/logo.jpg')

       name       size   last modify         kind   area
a ==> /logo.jpg   1950   09/10/05 12:43:00   FILE   PUBLIC

This can be accomplished by creating 5 nodes using appendnode()

info='ERROR'
info=appendNode(info,'name',a[1,1])
info=appendNode(info,'size',a[2,1])
info=appendNode(info,'modify',a[3,1])
info=appendNode(info,'kind',a[4,1])
info=appendNode(info,'area',a[5,1])

out=push_nodes(info)

Notice push_nodes() actually delivers the rendered json or XML pen here

The above code will render the variable out as either JSON or XML, uses global tag_filetype to determine output format.

JSON
{
  name :  "/logo.png",
  size :  1048,
  modify :  "2017-10-05T12:43",
  kind :  "FILE",
  area :  "PUBLIC"
}

XML
<document>
 <name>
  /logo.png
 </name>
 <size>
  1048
 </size>
 <modify>
  10/05/17 12:43:00
 </modify>
 <kind>
  FILE
 </kind>
 <area>
  PUBLIC
 </area>
</document>

You can also send out a table like this

table='ERROR'
table=appendTableNode(table,'fileinfo',a)

out=push_nodes(table)

JSON
[
 "/logo.png",
 1048,
 "10/05/17 12:43:00",
 "FILE",
 "PUBLIC"
]

XML
<fileinfo cols="5" rows="1">
 <row>
  <col>
   /logo.png
  </col>
  <col>
   1048
  </col>
  <col>
   10/05/17 12:43:00
  </col>
  <col>
   FILE
  </col>
  <col>
   PUBLIC
  </col>
 </row>
</fileinfo>

and finally you can nest and create hierarchical trees.

Suppose you want to deliver the json as name values and also as a table you can do it this way...

info='ERROR'
info=appendNode(info,'name',a[1,1])
info=appendNode(info,'size',a[2,1])
info=appendNode(info,'modify',a[3,1])
info=appendNode(info,'kind',a[4,1])
info=appendNode(info,'area',a[5,1])

out='ERROR'
out=appendNode(out,'properties',info)
out=appendTableNode(out,'fileinfo',a)

out=push_nodes(out)


JSON
{
  properties : {
    name :  "/logo.png",
    size :  1048,
    modify :  "2017-10-05T12:43",
    kind :  "FILE",
    area :  "PUBLIC"
  },
  fileinfo : [
    "/logo.png",
    1048,
    "10/05/17 12:43:00",
    "FILE",
    "PUBLIC"
  ]
}

XML
<document>
 <properties>
  <name>
   /logo.png
  </name>
  <size>
   1048
  </size>
  <modify>
   10/05/17 12:43:00
  </modify>
  <kind>
   FILE
  </kind>
  <area>
   PUBLIC
  </area>
 </properties>
 <fileinfo cols="5" rows="1">
  <row>
   <col>
    /logo.png
   </col>
   <col>
    1048
   </col>
   <col>
    10/05/17 12:43:00
   </col>
   <col>
    FILE
   </col>
   <col>
    PUBLIC
   </col>
  </row>
 </fileinfo>
</document>

And one final note, you could use XML attributes to represent the data as well

attribs='ERROR'
attribs=setvalue(attribs,'size',a[2,1])
attribs=setvalue(attribs,'modify',a[3,1])
attribs=setvalue(attribs,'kind',a[4,1])
attribs=setvalue(attribs,'area',a[5,1])

out='ERROR'
out=appendNodeAttribs(out,'file',a[1,1],attribs)

out=push_nodes(out)

XML
<file size="1048" modify="10/05/17 12:43:00" kind="FILE" area="PUBLIC">
 /logo.png
</file>

JSON
{
 @size :  1048,
 @modify :  2017-10-05T12:43,
 @kind :  FILE,
 @area :  PUBLIC,
 file :  /logo.png
}

end.

Here is some code to test/demonstrate the bug

Updated with a better solution

I have included a page that will demonstrate the bug, and also offer a workaround. The workaround is system specific, it assumes Linux as the underlying platform and also assumes the system folder is set and set to /.

The idea of the workaround it to postprep() the command, send it, unpostprep it, and finally execute it. See: this page for the unpostprep part of the solution.

The Workaround

function system_with_commas(cmd) do
  return system(^bin/echo ^+postprep(cmd)+^ | sed "s@+@ @g;s@%@\\\\x@g" | xargs -0 printf "%b" | /bin/bash^) /return
/function

Demonstrate the Problem

<<
 function system_with_commas(cmd) do
  return system(^bin/echo ^+postprep(cmd)+^ | sed "s@+@ @g;s@%@\\\\x@g" | xargs -0 printf "%b" | /bin/bash^) /return
 /function
>>
<html>
 <head>
  <title>Comma Bug</title>
 </head>
 <body>
  <h1>System tag Comma Bug</h1>
  <p>When submitting a command using the system tag it seems to be eating commas from the input. On this installation the system folder is /.
  <hr>
  <pre><<system('bin/echo -n "Hello,1,2,3,4, World"')>></pre>
  <hr>
  <pre><<system('bin/echo -n "Hello,1,2,3,4, World"')>></pre>
  <hr>
  <p>Typing the same command from the command line yields the following...
  <hr>
  <pre>Hello,1,2,3,4, World</pre>
  <hr>
  <h2>Is the bug fixed?</h2>
  NATIVE:<br><<system('bin/echo -n "Hello,1,2,3,4, World"')>>
  <hr>
  SYSCALL.SH:<br><<system(^usr/bin/syscall.sh ^+postprep('/bin/echo -n "Hello,1,2,3,4, World"'))>>
  <hr>
  WITH_COMMAS:<br><<system_with_commas('/bin/echo -n "Hello,1,2,3,4, World"')>>
 </body>
</html>

To fix change any system calls to this...

Be sure to include the function at the top of the example in your code somewhere.

   BEFORE: temp=system(command)
    AFTER: temp=system_with_commas(command))

This alternate solution is not recommended. It is here for historical reasons.

Alternate Solution

Older Solution Requiring SSH access to the underlying server

Here is a shell script that can be used to get around the bug

#!/bin/bash

urldecode() {
    # urldecode <string>
    local url_encoded="${1//+/ }"
    printf '%b' "${url_encoded//%/\x}"
}

cmd=$(urldecode $1)
eval $cmd

Be sure to save it to /usr/bin/syscall.sh and chmod 755 /usr/bin/syscall.sh

To fix change any system calls to this...

   BEFORE: temp=system(command)
    AFTER: temp=system(^usr/bin/syscall.sh ^+postprep('/'+command))

Also I have reported the bug over here...

Comma bug Report

Responsive Images for Every Browser on the Planet

my contribution to the conversation

its basically the <picture> tag for older browsers

See Demo

Resize your browser, rotate your phone to see the various images load as the environment changes.

The new HTML5 picture tag requires us to abandon older browsers to get responsive images. But all of the pieces are in place already for those browsers. Here is how to do it.

This approach uses the existing tag with the src attribute specially designed for the browser to ignore.

<img src="/0#/path/to/image.jpg">

This approach requires a 1 pixel gif on the server at the root level named 0.

Here is what we get with our responsive images

Similar syntax to the existing <img> tag Serves the correct image to Mobile, Desktop, and Retina for landscape or portrait. Uses a wrapper that allows for Mobile/Desktop styling similar to the way bootstrap does it. For the most part this does not rely on any type of browser sniffing*. * This demo sniffs for firefox broswers and uses the /0# syntax and also sniffs for IE9 and comments out the proprietary filters that are only for IE678.

All of the images on this page are generated (server side) from the same image. The correct image is served up based on the Screen size, Retina capabilities, and device orientation.

The markup for each images is as follows M100 would indicate 100% on mobile, D70 indicates 70% on Desktop.

Notice the strange src in the <img> tag. The strange syntax is designed so that the client never makes a request for the source. Then the src is chosen with a css selector to determine the actual src depending on the device.

 ...
<div class="image center M100 D70">
 <img src="/0#/apps/test/test.jpg">
</div>
...
<div class="image right M50 D30">
 <img src="/0#/apps/test/test.jpg">
</div>
...
<div class="image left M70 D30">
 <img src="/0#/apps/test/test.jpg">
</div>
...

The path to the image can be a real path or a meta path to the image on the server. The src path is used in the CSS declaration to actually decide what images will be delivered based on screen size, orientation and retina capabilities.

The syntax allows for delivery of the correct content and hinted display for mobile or desktop. It could easily be extended for additional breakpoints

Art Direction means it will send the correct image server for portrait and landscape devices. Consequently all the problems the picture element was created for are solved with this approach. If you look at the syntax for the picture element its syntax relies heavily on CSS. The system I am proposing actually just uses CSS with inline <style> tags for defining the context of the images.

The images are actually loaded as background images and using an IE filter for IE678.

Supported Browsers. (haven't found one where it doesn't work)

  • Internet Explorer - All versions from 6 on up (versions 6,7,8 require respond.js and also for 6 it requires Dean Edwards IE7.js)
  • Chrome - All versions
  • Safari - All Versions
  • Opera - All versions
  • Firefox - All versions from 4 and up
  • Mobile Safari - All versions
  • Android - All Versions

The <style> tag looks like this for each image.

  <style>

     /* IE 6,7,8 Image                 */
     img[src*="/apps/test/test.jpg"] {
      filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/apps/test/test.jpg?for=ie678',sizingMethod='scale');
      -ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/apps/test/test.jpg?for=ie678',sizingMethod='scale')";
      padding-bottom:56.25%;
     }

     /* Mobile Image Portrait          */ @media screen and (orientation:portrait) and (max-width: 980px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=mobile-portrait');
      padding-bottom:100%;
     }}

     /* Mobile Image Landscape         */ @media screen and (orientation:landscape) and (max-width: 980px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=mobile-landscape');
      padding-bottom:56.25%;
     }}




     /* Retina Mobile Image Portrait   */ @media screen and (orientation:portrait) and (-webkit-min-device-pixel-ratio: 2), only screen and (orientation:portrait) and (min-resolution: 192dpi) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/XPATH/apps/test/test.jpg?for=mobile-retina-portrait');
      padding-bottom:100%;
     }}

     /* Retina Mobile Image Landscape  */ @media screen and (orientation:landscape) and (-webkit-min-device-pixel-ratio: 2), only screen and (orientation:landscape) and (min-resolution: 192dpi) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=mobile-retina-landscape');
      padding-bottom:56.25%;
     }}




     /* Desktop Image Portrait         */ @media screen and (orientation:portrait) and (min-width: 981px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=desktop-portrait');
      padding-bottom:100%;
     }}

     /* Desktop Image Landscape        */ @media screen and (orientation:landscape) and (min-width: 981px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=desktop-landscape');
      padding-bottom:56.25%;
     }}




     /* Retina Desktop Image Portrait  */
     @media screen and (orientation:portrait) and (-webkit-min-device-pixel-ratio: 2) and (min-width: 981px),screen and (orientation:portrait) and (min-resolution: 192dpi) and (min-width: 981px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=desktop-retina-portrait');
      padding-bottom:100%;
     }}

     /* Retina Desktop Image Landscape */ @media screen and (orientation:landscape) and (-webkit-min-device-pixel-ratio: 2) and (min-width: 981px),screen and (orientation:landscape) and (min-resolution: 192dpi) and (min-width: 981px) {
     img[src*="/apps/test/test.jpg"] {
      background-image:url('/apps/test/test.jpg?for=desktop-retina-landscape');
      padding-bottom:56.25%;
     }}

  </style>

Reference this Stackoverflow Question regarding the img src syntax.

dynamically loaded javascript

I was starting to get a lot of jQuery plugins on WebBlocks.co the CMS that runs learnhtmlos.com. They were all needed but only in certain contexts. I found these 3 articles useful. I rolled them into my own safe code execution script.

Start by specifying a global Javascript variable like jQuery.colorbox and its url and the code you want to run. If the url is already loaded it will execute the code, if not it will load url then execute the code.

The ridiculous case of adding a script element

Check if a jQuery plugin is loaded

Dynamically load js inside js

The script

runSafe=function(name,url,callback){
 if(typeof window[name] != 'undefined'){
  callback;
 } else {
  var js = document.createElement('script');
  js.src = url;
  js.onload = js.onreadystatechange = callback;
  var first = document.getElementsByTagName('script')[0];
  first.parentNode.insertBefore(js, first);
 }
}

Usage

runSafe('jQuery.colorbox', '/apps/scripts/colorbox.min.js',function(){

 // do something with the colorbox plugin

});

UTF and HTML/OS

I decided to roll my own UTF-8 string compatability.

So if you have a string of text that you know is UTF-8 encoded you can get its length and also parse it just like regular text.

Step 1.Step 2.Step 3.
Prepare:
temp=asUTF(text)
Perform string manipulations using the alternative tags length_(temp), middle_(temp) etc (notice the underscore)...Convert back to string
text=asText(temp)

For Example this page...

<!doctype html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title>UTF8 Demo</title>
 </head>
 <body>
  <<
   msg="Everybody is using emoticons (πŸ˜ŠπŸ˜‹πŸ˜¨πŸ˜†) These days"
   display "msg="+msg+'<br>' /display
   display "Length(msg)="+length(msg)+'<br>' /display
   msg2=asUTF(msg)
   display "Length_(msg2)="+length_(msg2)+'<br>' /display
   msg_middle=middle_(msg2,31,31)
   display '31st character is "'+asText(msg_middle)+'"' /display
  >>
 </body>
</html>

Would display this output...

msg=Everybody is using emoticons (πŸ˜ŠπŸ˜‹πŸ˜¨πŸ˜†) These days
Length(msg)=58
Length_(msg2)=46
31st character is "😊"

Here are the Functions

function asUTF(text) locals x,s,st,i,a do
 x=1
 st='ERROR'
 while x<length(text)+1 do
  i=getascii(middle(text,x,x))
    if i<192 then s=1
  elif i<224 then s=2
  elif i<240 then s=3
  elif i<248 then s=4
  elif i<252 then s=5
  elif i<254 then s=6
  /if
  a='ERROR'
  a=middle(text,x,x+(s-1))
  if st='ERROR' then st=a else st=merge(st,a) /if
  x=x+s
 /while
 return st /return
/function


function middle_(text_UTF,s,e) do
 return gettable(text_UTF,s,e,1,1) /return
/function


function length_(text_UTF) do
 return cols(text_UTF) /return
/function


function left_(text_UTF,i) do
 return gettable(text_UTF,1,i,1,1) /return
/function


function right_(text_UTF,i) locals l do
 l=cols(text_utf)
 return gettable(text_UTF,l-i+1,l,1,1) /return
/function


function reverse_(text_UTF,i) do
 return reversecols(text_UTF) /return
/function


function concat_(text1_UTF,text2_UTF) do
 return merge(text1_UTF,text2_UTF) /return
/function



function asText(text_UTF) locals text,x do
 text=''
 for name=x value=1 to cols(text_UTF) do
  text=text+text_UTF[x,1]
 /for
 return text /return
/function

Semicolon URL notation with HTML/OS

Sometimes it is desirable to use the semicolon notation to reuse overlay's. Instead of duplicate overlays we can instead opt to use semicolon notation.

This allows you to reference a single overlay from multiple pages.

The problem

The main issue with doing it this way is the "page" value gets lost.

index.html

<!doctype html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title></title>
 </head>
 <body>
  <a href="master.overlay;doit">Run doit Overlay</a><hr>
 </body>
</html>

master.overlay

<<goto error(400)>>
<<overlay doit
 goto page
>>

The Solution

To overcome that you can use the following coding pattern.

index.html

<<
expand file="/system/clearimage/DLL.lib" /expand
>>
<!doctype html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <title></title>
 </head>
 <body>
  <a href="<<page>>">RELOAD</a><br>
  <a href="<<pagelink('master.overlay;doit','qwerty','Terry')>>">doit</a><hr>
  <<page>><br>
  <<page_>><br>
  <<page_()>><br>
  <<pagefromdoit>><br>
  <<qwerty>><<qwerty='ERROR'>>
 </body>
</html>

master.overlay

<<goto error(400)
 # 
 # This is a pattern for master overlay's 
 # 
 # To call a master overlay use the following syntax
 #
 # <a href="<<pagelink1('master.overlay;doit')>>">Run Do It</a>
 # <a href="<<pagelink('master.overlay;doit',varname,varvalue)>>">Run Do It</a>
 #
 # execute get_() to initialize each overlay
 # 
 # Use page_ in place of page for goto's
 # tag can also be used for goto's
 # 
 # notice the goto error(400) that will be invoked if you forget and do a goto page
/#
>>

<<overlay doit get_()

 pagefromdoit='Welcome: '+qwerty+' '+random(1000,9999)+' '+page_+' '+page+' '+page_()+' '+tag
 goto page_

>>

Thats it for now.

LAYOUT()

A new way to use layout

The layout tag is usually used to take an Aestiva table and create some html markup with it. For example

 mystuff
+-----------------------+
| Dog   | Cat   | Mouse |
|-------+-------+-------|
| Blue  | Green | Red   |
+-----------------------+

Could be displayed as a table like this using layout()

<table>
 <<layout(mystuff,'<tr><td>',[1],'</td><td>',[2],'</td><td>',[3],'</td></tr>')>>
</table>
DogCatMouse
BlueGreenRed

But...

There is another very useful way to use layout

It can be used to extract columns from a variable and place them into a new variable. For example using the variable mystuff we could extract the third and the first column in that order using layout.

newvar=layout(mystuff,[3],[1])

Which would yield newvar...

 newvar
+---------------+
| Mouse | Dog   |
|-------+-------|
| Red   | Blue  |
+---------------+

Separation of concerns

Programming usually follows this pattern.

  1. Get it working - At this stage we are just trying to get things working, we are trying to prove our concept and show that it can work. It is easy to use sloppy programming techniques in the name of expediency and in an effort to just get the thing working.
  2. Extend its functionality - At this stage we have a basic working system and we decide to add additional features to extend its usefulness. This stage can often times reveal problems with the style of coding we used in step 1.

I promise to never...

After doing a project or two we usually come to the realization that we need to enforce more rigor into our programming style. This can be a huge win for step 2 and a huge loss for step 1. Sometimes the inertia necessary to start a project is thwarted by past projects that became unmanageable.

This post aims to offer some help in programming styles that will not be so overly burdensome to stifle the creative process we all love in step 1 and yet also foster a more manageable more maintainable code base for future extensibility.

Separation of concerns

So What do we mean when we talk about Separation of concerns. This is a huge topic. We could be talking about keeping your javascript/html/css separate. The Separation I am going to address is the Separation of your data from its presentation.

Lets start with a simple example. Suppose we have a database that stores

name,address,city,state,friendstatus

We might built a piece of code to display all my friends

Get It Working

<<
 friends=dbsearch('frienddb','FRIENDSTATUS="y"',1,1000)
>>
<html>
 <h1>My Friends</h1>
 <table>
  <tr><td>Name</td><td>City</td></tr>
  <<
   for name=friends rowname=x do
    display '<tr><td>'+x[2]+'</td><td>'+x[3]+'</td></tr>'
   /for
  >>
 </table>
</html>

This code is basically step 1. It does exactly what we want it to do but it doesn't extend very well. So we might want to encapsulate it all into a single monlithic function and call it our friend function or our friend widget

Extend its functionality

<<
 function friends(db,title,allorjustfriends) locals friends,x,text,thesearch do
  if allorjustfriends='all' then thesearch='' else thesearch='Y' /if
  friends=dbsearch(db,'FRIENDSTATUS="'+thesearch+'"',1,1000)
  text='<h1>'+title+'</h1>'+lf+
       '<table>'+lf+
       ' <tr><td>Name</td><td>City</td></tr>'+lf+
  for name=friends rowname=x do
   text=text+'<tr><td>'+x[2]+'</td><td>'+x[3]+'</td></tr>'
  /for
  text=text+'</table>'
  return text /return
 /function
>>
<html>
 <<friends('frienddb','My Friends','JUSTFRIENDS')>>
</html>

I have followed this exact pattern more times that I can count. The only problem is that it is completely wrong and doesn't actually make my code any more extensible. All it really did is create a black box that I can use on other pages, but that black box is to rigid.

So the temptation is to try to tack on extensibility to a wrong pattern. I might add additional parameters to send to my function etc.

The problem is this pattern will not extend and is destined to be relegated to code that once was cool but now is a pain to maintain.

So the fundamental flaw here is the failure to separate concerns. There are at least 2 concerns going on.

  1. We Get Data
  2. We Present Data

So we should really be separating our code into...

  1. Code that gets data
  2. and Code that presents data

And this pattern can be expressed in several ways. We can decide to go with functions or we could decide to go with pages. Both are perfectly fine.

Here is a page approach...

index.html

<<
 #
 # Begin GET DATA
 #
 # First we have all the logic to get data
 # notice there IS NO HTML here
 # If you feel the need to add HTML here then STOP and
 # think it through, make sure you are keeping your 
 # concerns separate
/#
 if mypref='showall' then
  thedata=getfriends('all')
 else
  thedata=getfriends('justfriends')
 /if
 #
 # End GET DATA
/#



 # 
 # Begin Presentation
/#
 if mystyle='modern' then
  goto 'modern.html'
 else
  goto 'traditional.html'
 /if
 # 
 # End Presentation
/#
>>

modern.html

<<
 # This page expects the following data
 # THEDATA - A Six column table record,name,address,city,state,friendstatus
/#
>>
<html>
 <<
   for name=thedata rowname=x do
    display x[1]+'~'+x[2]+'<br>' /display
   /for
 >>
</html>

traditional.html

<<
 # This page expects the following data
 # THEDATA - A Six column table record,name,address,city,state,friendstatus
/#
>>
<html>
 <<
   for name=thedata rowname=x do
    display x[1]+'|'+x[2]+'<br>' /display
   /for
 >>
</html>

Parsing URL's and file paths

ramblings about maintainable code

So today I wrote this ugly little piece of code

<<
 reverse(chopleft(replace(reverse(replaceall(
  getenv('DOCUMENT_ROOT')+'/'
 ,'//','/')),'/',''),'/'))
>>

The task I am trying to accomplish is I want to take DOCUMENT_ROOT and remove the last part of the path, I wanted a one liner and I discovered that DOCUMENT_ROOT may not always have a trailing slash. So that added to the complexity of the one liner.

  1. Add Trailing /
  2. Replace all // with / (in case adding it wasn't needed)
  3. Reverse the result
  4. Remove the first /
  5. Chop everything to the left of the first /
  6. reverse again

These...

/home/docs/public_html
/home/docs/public_html/

Would produce...

/home/docs/

Done!

So It would be nice to have a URL/File Path parsing system. Perhaps something like...

pathdice('/1/2/3','/.../-1')  ==> /1/2
pathdice('/1/2/3/','/.../-1') ==> /1/2/

Are there libraries like this for other Languages? If so how do they tackle the problem?

The issue I am trying to solve is I want to have maintainable code, and a one liner like that is almost impossible to recognize the original intent of the one writing the code, so it is not maintainable.

If instead I had some generalized file path library and I used it then the intent of my code would be clear to the person looking it at some point in the future.

The code as written does give a few clues into my intent. First since I am asking for document_root I can see I am expecting to be dealing with a file path. And since I am using a forward slash then I understand the file paths not to have back slashes in them.

But beyond that the code isn't clear at all as to intent.

In the absence of any type of library heavy comments are probably the next best thing. Of course comments can be get out of sync with the actual code.

So would my original code be better written...

<<
 reverse(chopleft(replace(reverse(replaceall(
  getenv('DOCUMENT_ROOT')+'/'
 ,'//','/')),'/',''),'/'))

 #
 # 1. Add Trailing /
 # 2. Replace all // with / (in case adding it wasn't needed)
 # 3. Reverse the result
 # 4. Remove the first /
 # 5. Chop everything to the left of the first /
 # 6. reverse again
/#

>>

Current or Historical Weather

Have you ever wanted to show current or historical weather reports on your HTML/OS based web pages? If the answer is "Yes" you should continue reading because this post will show you how to access the Weather Underground API and display the XML formatted weather results in your page.

Before you begin coding you'll need to sign up for a free account and obtain your own API identity string:

http://www.wunderground.com/weather/api/

In the sample code below I am going to replace my identity string with a random value, but this will need to be changed to your own identity in order for it to work.

I have found it convenient to encode this feature as a FUNCTION that takes two parameters, date and zip code, as follows:

FUNCTION WeatherBox( d, z ) LOCALS h, wid, df, os, wux, flag DO
   #  Parameters:
      d = The current date as MM/DD/YYYY
      z = Zip Code as #####
   /#

   # Header value used for NETWEB command /#
   h = 'Accept: text/html, application/xhtml+xml, */*
        Accept-Language: en-US
        User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; EIE10;ENUSWOL
        Connection: close
        Accept-Encoding: identity'
   flag = 'OK'
   wid = '12345ABCDE67890F1'   # Change this to your IDENTITY STRING /#

   IF LENGTH( z ) !=5 OR ISINTEGER( z ) = 'FALSE' THEN
      flag = 'Weather N/A - Invalid Zip Code: "' + z + '"'
   ELIF LENGTH( d ) !=10 OR ISDATE( d ) = 'FALSE' THEN
      flag = 'Weather N/A - Invalid Date: "' + d + '"'
   ELSE
      # Date Formatted for how Weather Underground wants it /#
      df = CONCAT( CONCAT( RIGHT( d, 4 ), LEFT( d, 2 )), MIDDLE( d, 4, 5 ))

      # If you use the Clear Image Library, replace line above with:
        df = MOMENT( d, 'YYYYMMDD' )
      /#

      wux = NETWEB(
             'http://api.wunderground.com/api/' + wid + '/history_' +
             df + '/pws:0/q/' + z + '.xml', '50000', '10', 'GET', '', h )
      IF LOCATE( wux, '<error>' ) OR wux = '' THEN
         flag = XMLGETVALUE( wux, '<response><error><description>' )
      ELSE
         # Truncate returned XML to only the <summary> area, speeds string
           processing and simplifies further references below
         /#
         wux = XMLGET( wux, '<response><history><dailysummary><summary>' )
      /IF
   /IF

   IF flag !='OK' THEN # Something went wrong, so send back the error /#
      os = '<p>Error Message: ' + flag + '</p>'
   ELSE
      w_mint  = XMLGETVALUE( wux, '<summary><mintempi>' )
      w_meant = XMLGETVALUE( wux, '<summary><meantempi>' )
      w_maxt  = XMLGETVALUE( wux, '<summary><maxtempi>' )

      w_minw  = XMLGETVALUE( wux, '<summary><minwspdi>' )
      IF w_minw='' THEN w_minw  = 'n/a' /IF
      w_meanw = XMLGETVALUE( wux, '<summary><meanwindspdi>' )
      IF w_meanw='' THEN w_meanw = 'n/a' /IF
      w_maxw  = XMLGETVALUE( wux, '<summary><maxwspdi>' )
      IF w_maxw='' THEN w_maxw  = 'n/a' /IF

      w_minv  = XMLGETVALUE( wux, '<summary><minvisi>' )
      w_meanv = XMLGETVALUE( wux, '<summary><meanvisi>' )
      w_maxv  = XMLGETVALUE( wux, '<summary><maxvisi>' )

      w_fog = XMLGETVALUE( wux, '<summary><fog>' )
      IF w_fog = 0 OR w_fog  = '' THEN w_fog  = 'No' ELSE w_fog  = 'Yes' /IF
      w_rain = XMLGETVALUE( wux, '<summary><rain>' )
      IF w_rain = 0 OR w_rain = '' THEN w_rain = 'No' ELSE w_rain = 'Yes' /IF
      w_snow = XMLGETVALUE( wux, '<summary><snow>' )
      IF w_snow = 0 OR w_snow = '' THEN w_snow = 'No' ELSE w_snow = 'Yes' /IF

      os = '<dl id="col1">
               <dt>Min Temp:</dt><dd>' + w_mint + ' &deg;F</dd>
               <dt>Avg Temp:</dt><dd>' + w_meant + ' &deg;F</dd>
               <dt>Max Temp:</dt><dd>' + w_maxt + ' &deg;F</dd>
            </dl>
            <dl id="col2">
               <dt>Min Wind:</dt><dd>' + w_minw + ' MPH</dd>
               <dt>Avg Wind:</dt><dd>' + w_meanw + ' MPH</dd>
               <dt>Max Wind:</dt><dd>' + w_maxw + ' MPH</dd>
            </dl>
            <dl id="col1">
               <dt>Min Vis:</dt><dd>' + w_minv + ' Mi</dd>
               <dt>Avg Vis:</dt><dd>' + w_meanv + ' Mi</dd>
               <dt>Max Vis:</dt><dd>' + w_maxv + ' Mi</dd>
            </dl>
            <dl id="col2">
               <dt>Rain:</dt><dd>' + w_rain + '</dd>
               <dt>Snow:</dt><dd>' + w_snow + '</dd>
               <dt>Fog:</dt><dd>' + w_fog + '</dd>
            </dl>'
   /IF
   RETURN os /RETURN
/FUNCTION

The last piece of code where the value "os" is being set up as a formatted string with <dl> tags should be adjusted to give you back the values however you'd like to display them, such as a definition list (shown), table or a formatted sentence.

Note also that you really should declare the various w_* variables as LOCALS in the FUNCTION declaration, as I have omitted this for the sake of readability.

So in summary, let's say I wanted to get the weather on the current date in the middle of Las Vegas, I would make a call like this:

<<
   DISPLAY WeatherBox( GETDATE(NOW,"NORMAL"), '89101' ) /DISPLAY
>>

More Fun with Dates and Time

It seems my work has had a lot to do with dates and times lately. I found myself in need of a function that would convert a Timezone name to its equivalant UTC+n UTC-n format. So here it is...

function TimeZoneToUTC(timezonename) locals text,t,x do
 text="Pacific/Midway,-11:00,Midway"+lf+"Pacific/Niue,-11:00,Niue"+lf+"Pacific/Pago_Pago,-11:00,Pago Pago"+lf+"Pacific/Honolulu,-10:00,Hawaii Time"+lf+"Pacific/Rarotonga,-10:00,Rarotonga"+lf+"Pacific/Tahiti,-10:00,Tahiti"+lf+"Pacific/Marquesas,-09:30,Marquesas"+lf+"America/Anchorage,-09:00,Alaska Time"+lf+"Pacific/Gambier,-09:00,Gambier"+lf+"America/Los_Angeles,-08:00,Pacific Time"+lf+"America/Tijuana,-08:00,Pacific Time - Tijuana"+lf+"America/Vancouver,-08:00,Pacific Time - Vancouver"+lf+"America/Whitehorse,-08:00,Pacific Time - Whitehorse"+lf+"Pacific/Pitcairn,-08:00,Pitcairn"+lf+"America/Dawson_Creek,-07:00,Mountain Time - Dawson Creek"+lf+"America/Denver,-07:00,Mountain Time"+lf+"America/Edmonton,-07:00,Mountain Time - Edmonton"+lf+"America/Hermosillo,-07:00,Mountain Time - Hermosillo"+lf+"America/Mazatlan,-07:00,Mountain Time - Chihuahua^, Mazatlan"+lf+"America/Phoenix,-07:00,Mountain Time - Arizona"+lf+"America/Yellowknife,-07:00,Mountain Time - Yellowknife"+lf+"America/Belize,-06:00,Belize"+lf+"America/Chicago,-06:00,Central Time"+lf+"America/Costa_Rica,-06:00,Costa Rica"+lf+"America/El_Salvador,-06:00,El Salvador"+lf+"America/Guatemala,-06:00,Guatemala"+lf+"America/Managua,-06:00,Managua"+lf+"America/Mexico_City,-06:00,Central Time - Mexico City"+lf+"America/Regina,-06:00,Central Time - Regina"+lf+"America/Tegucigalpa,-06:00,Central Time - Tegucigalpa"+lf+"America/Winnipeg,-06:00,Central Time - Winnipeg"+lf+"Pacific/Easter,-06:00,Easter Island"+lf+"Pacific/Galapagos,-06:00,Galapagos"+lf+"America/Bogota,-05:00,Bogota"+lf+"America/Cayman,-05:00,Cayman"+lf+"America/Grand_Turk,-05:00,Grand Turk"+lf+"America/Guayaquil,-05:00,Guayaquil"+lf+"America/Havana,-05:00,Havana"+lf+"America/Iqaluit,-05:00,Eastern Time - Iqaluit"+lf+"America/Jamaica,-05:00,Jamaica"+lf+"America/Lima,-05:00,Lima"+lf+"America/Montreal,-05:00,Eastern Time - Montreal"+lf+"America/Nassau,-05:00,Nassau"+lf+"America/New_York,-05:00,Eastern Time"+lf+"America/Panama,-05:00,Panama"+lf+"America/Port-au-Prince,-05:00,Port-au-Prince"+lf+"America/Rio_Branco,-05:00,Rio Branco"+lf+"America/Toronto,-05:00,Eastern Time - Toronto"+lf+"America/Caracas,-04:30,Caracas"+lf+"America/Antigua,-04:00,Antigua"+lf+"America/Asuncion,-04:00,Asuncion"+lf+"America/Barbados,-04:00,Barbados"+lf+"America/Boa_Vista,-04:00,Boa Vista"+lf+"America/Campo_Grande,-04:00,Campo Grande"+lf+"America/Cuiaba,-04:00,Cuiaba"+lf+"America/Curacao,-04:00,Curacao"+lf+"America/Guyana,-04:00,Guyana"+lf+"America/Halifax,-04:00,Atlantic Time - Halifax"+lf+"America/La_Paz,-04:00,La Paz"+lf+"America/Manaus,-04:00,Manaus"+lf+"America/Martinique,-04:00,Martinique"+lf+"America/Port_of_Spain,-04:00,Port of Spain"+lf+"America/Porto_Velho,-04:00,Porto Velho"+lf+"America/Puerto_Rico,-04:00,Puerto Rico"+lf+"America/Santiago,-04:00,Santiago"+lf+"America/Santo_Domingo,-04:00,Santo Domingo"+lf+"America/Thule,-04:00,Thule"+lf+"Antarctica/Palmer,-04:00,Palmer"+lf+"Atlantic/Bermuda,-04:00,Bermuda"+lf+"America/St_Johns,-03:30,Newfoundland Time - St. Johns"+lf+"America/Araguaina,-03:00,Araguaina"+lf+"America/Argentina/Buenos_Aires,-03:00,Buenos Aires"+lf+"America/Bahia,-03:00,Salvador"+lf+"America/Belem,-03:00,Belem"+lf+"America/Cayenne,-03:00,Cayenne"+lf+"America/Fortaleza,-03:00,Fortaleza"+lf+"America/Godthab,-03:00,Godthab"+lf+"America/Maceio,-03:00,Maceio"+lf+"America/Miquelon,-03:00,Miquelon"+lf+"America/Montevideo,-03:00,Montevideo"+lf+"America/Paramaribo,-03:00,Paramaribo"+lf+"America/Recife,-03:00,Recife"+lf+"America/Sao_Paulo,-03:00,Sao Paulo"+lf+"Antarctica/Rothera,-03:00,Rothera"+lf+"Atlantic/Stanley,-03:00,Stanley"+lf+"America/Noronha,-02:00,Noronha"+lf+"Atlantic/South_Georgia,-02:00,South Georgia"+lf+"America/Scoresbysund,-01:00,Scoresbysund"+lf+"Atlantic/Azores,-01:00,Azores"+lf+"Atlantic/Cape_Verde,-01:00,Cape Verde"+lf+"Africa/Abidjan,+00:00,Abidjan"+lf+"Africa/Accra,+00:00,Accra"+lf+"Africa/Bamako,+00:00,Bamako"+lf+"Africa/Banjul,+00:00,Banjul"+lf+"Africa/Bissau,+00:00,Bissau"+lf+"Africa/Casablanca,+00:00,Casablanca"+lf+"Africa/Conakry,+00:00,Conakry"+lf+"Africa/Dakar,+00:00,Dakar"+lf+"Africa/El_Aaiun,+00:00,El Aaiun"+lf+"Africa/Freetown,+00:00,Freetown"+lf+"Africa/Lome,+00:00,Lome"+lf+"Africa/Monrovia,+00:00,Monrovia"+lf+"Africa/Nouakchott,+00:00,Nouakchott"+lf+"Africa/Ouagadougou,+00:00,Ouagadougou"+lf+"Africa/Sao_Tome,+00:00,Sao Tome"+lf+"America/Danmarkshavn,+00:00,Danmarkshavn"+lf+"Atlantic/Canary,+00:00,Canary Islands"+lf+"Atlantic/Faroe,+00:00,Faeroe"+lf+"Atlantic/Reykjavik,+00:00,Reykjavik"+lf+"Atlantic/St_Helena,+00:00,St Helena"+lf+"Etc/GMT,+00:00,GMT (no daylight saving)"+lf+"Europe/Dublin,+00:00,Dublin"+lf+"Europe/Lisbon,+00:00,Lisbon"+lf+"Europe/London,+00:00,London"+lf+"Africa/Algiers,+01:00,Algiers"+lf+"Africa/Bangui,+01:00,Bangui"+lf+"Africa/Brazzaville,+01:00,Brazzaville"+lf+"Africa/Ceuta,+01:00,Ceuta"+lf+"Africa/Douala,+01:00,Douala"+lf+"Africa/Kinshasa,+01:00,Kinshasa"+lf+"Africa/Lagos,+01:00,Lagos"+lf+"Africa/Libreville,+01:00,Libreville"+lf+"Africa/Luanda,+01:00,Luanda"+lf+"Africa/Malabo,+01:00,Malabo"+lf+"Africa/Ndjamena,+01:00,Ndjamena"+lf+"Africa/Niamey,+01:00,Niamey"+lf+"Africa/Porto-Novo,+01:00,Porto-Novo"+lf+"Africa/Tunis,+01:00,Tunis"+lf+"Africa/Windhoek,+01:00,Windhoek"+lf+"Europe/Amsterdam,+01:00,Amsterdam"+lf+"Europe/Andorra,+01:00,Andorra"+lf+"Europe/Belgrade,+01:00,Central European Time - Belgrade"+lf+"Europe/Berlin,+01:00,Berlin"+lf+"Europe/Brussels,+01:00,Brussels"+lf+"Europe/Budapest,+01:00,Budapest"+lf+"Europe/Copenhagen,+01:00,Copenhagen"+lf+"Europe/Gibraltar,+01:00,Gibraltar"+lf+"Europe/Luxembourg,+01:00,Luxembourg"+lf+"Europe/Madrid,+01:00,Madrid"+lf+"Europe/Malta,+01:00,Malta"+lf+"Europe/Monaco,+01:00,Monaco"+lf+"Europe/Oslo,+01:00,Oslo"+lf+"Europe/Paris,+01:00,Paris"+lf+"Europe/Prague,+01:00,Central European Time - Prague"+lf+"Europe/Rome,+01:00,Rome"+lf+"Europe/Stockholm,+01:00,Stockholm"+lf+"Europe/Tirane,+01:00,Tirane"+lf+"Europe/Vienna,+01:00,Vienna"+lf+"Europe/Warsaw,+01:00,Warsaw"+lf+"Europe/Zurich,+01:00,Zurich"+lf+"Africa/Blantyre,+02:00,Blantyre"+lf+"Africa/Bujumbura,+02:00,Bujumbura"+lf+"Africa/Cairo,+02:00,Cairo"+lf+"Africa/Gaborone,+02:00,Gaborone"+lf+"Africa/Harare,+02:00,Harare"+lf+"Africa/Johannesburg,+02:00,Johannesburg"+lf+"Africa/Kigali,+02:00,Kigali"+lf+"Africa/Lubumbashi,+02:00,Lubumbashi"+lf+"Africa/Lusaka,+02:00,Lusaka"+lf+"Africa/Maputo,+02:00,Maputo"+lf+"Africa/Maseru,+02:00,Maseru"+lf+"Africa/Mbabane,+02:00,Mbabane"+lf+"Africa/Tripoli,+02:00,Tripoli"+lf+"Asia/Amman,+02:00,Amman"+lf+"Asia/Beirut,+02:00,Beirut"+lf+"Asia/Damascus,+02:00,Damascus"+lf+"Asia/Gaza,+02:00,Gaza"+lf+"Asia/Jerusalem,+02:00,Jerusalem"+lf+"Asia/Nicosia,+02:00,Nicosia"+lf+"Europe/Athens,+02:00,Athens"+lf+"Europe/Bucharest,+02:00,Bucharest"+lf+"Europe/Chisinau,+02:00,Chisinau"+lf+"Europe/Helsinki,+02:00,Helsinki"+lf+"Europe/Istanbul,+02:00,Istanbul"+lf+"Europe/Kiev,+02:00,Kiev"+lf+"Europe/Riga,+02:00,Riga"+lf+"Europe/Sofia,+02:00,Sofia"+lf+"Europe/Tallinn,+02:00,Tallinn"+lf+"Europe/Vilnius,+02:00,Vilnius"+lf+"Africa/Addis_Ababa,+03:00,Addis Ababa"+lf+"Africa/Asmara,+03:00,Asmera"+lf+"Africa/Dar_es_Salaam,+03:00,Dar es Salaam"+lf+"Africa/Djibouti,+03:00,Djibouti"+lf+"Africa/Kampala,+03:00,Kampala"+lf+"Africa/Khartoum,+03:00,Khartoum"+lf+"Africa/Mogadishu,+03:00,Mogadishu"+lf+"Africa/Nairobi,+03:00,Nairobi"+lf+"Antarctica/Syowa,+03:00,Syowa"+lf+"Asia/Aden,+03:00,Aden"+lf+"Asia/Baghdad,+03:00,Baghdad"+lf+"Asia/Bahrain,+03:00,Bahrain"+lf+"Asia/Kuwait,+03:00,Kuwait"+lf+"Asia/Qatar,+03:00,Qatar"+lf+"Asia/Riyadh,+03:00,Riyadh"+lf+"Europe/Kaliningrad,+03:00,Moscow-01 - Kaliningrad"+lf+"Europe/Minsk,+03:00,Minsk"+lf+"Indian/Antananarivo,+03:00,Antananarivo"+lf+"Indian/Comoro,+03:00,Comoro"+lf+"Indian/Mayotte,+03:00,Mayotte"+lf+"Asia/Tehran,+03:30,Tehran"+lf+"Asia/Baku,+04:00,Baku"+lf+"Asia/Dubai,+04:00,Dubai"+lf+"Asia/Muscat,+04:00,Muscat"+lf+"Asia/Tbilisi,+04:00,Tbilisi"+lf+"Asia/Yerevan,+04:00,Yerevan"+lf+"Europe/Moscow,+04:00,Moscow+00"+lf+"Europe/Samara,+04:00,Moscow+00 - Samara"+lf+"Indian/Mahe,+04:00,Mahe"+lf+"Indian/Mauritius,+04:00,Mauritius"+lf+"Indian/Reunion,+04:00,Reunion"+lf+"Asia/Kabul,+04:30,Kabul"+lf+"Antarctica/Mawson,+05:00,Mawson"+lf+"Asia/Aqtau,+05:00,Aqtau"+lf+"Asia/Aqtobe,+05:00,Aqtobe"+lf+"Asia/Ashgabat,+05:00,Ashgabat"+lf+"Asia/Dushanbe,+05:00,Dushanbe"+lf+"Asia/Karachi,+05:00,Karachi"+lf+"Asia/Tashkent,+05:00,Tashkent"+lf+"Indian/Kerguelen,+05:00,Kerguelen"+lf+"Indian/Maldives,+05:00,Maldives"+lf+"Asia/Calcutta,+05:30,India Standard Time"+lf+"Asia/Colombo,+05:30,Colombo"+lf+"Asia/Katmandu,+05:45,Katmandu"+lf+"Antarctica/Vostok,+06:00,Vostok"+lf+"Asia/Almaty,+06:00,Almaty"+lf+"Asia/Bishkek,+06:00,Bishkek"+lf+"Asia/Dhaka,+06:00,Dhaka"+lf+"Asia/Thimphu,+06:00,Thimphu"+lf+"Asia/Yekaterinburg,+06:00,Moscow+02 - Yekaterinburg"+lf+"Indian/Chagos,+06:00,Chagos"+lf+"Asia/Rangoon,+06:30,Rangoon"+lf+"Indian/Cocos,+06:30,Cocos"+lf+"Antarctica/Davis,+07:00,Davis"+lf+"Asia/Bangkok,+07:00,Bangkok"+lf+"Asia/Hovd,+07:00,Hovd"+lf+"Asia/Jakarta,+07:00,Jakarta"+lf+"Asia/Omsk,+07:00,Moscow+03 - Omsk^, Novosibirsk"+lf+"Asia/Phnom_Penh,+07:00,Phnom Penh"+lf+"Asia/Saigon,+07:00,Hanoi"+lf+"Asia/Vientiane,+07:00,Vientiane"+lf+"Indian/Christmas,+07:00,Christmas"+lf+"Antarctica/Casey,+08:00,Casey"+lf+"Asia/Brunei,+08:00,Brunei"+lf+"Asia/Choibalsan,+08:00,Choibalsan"+lf+"Asia/Hong_Kong,+08:00,Hong Kong"+lf+"Asia/Krasnoyarsk,+08:00,Moscow+04 - Krasnoyarsk"+lf+"Asia/Kuala_Lumpur,+08:00,Kuala Lumpur"+lf+"Asia/Macau,+08:00,Macau"+lf+"Asia/Makassar,+08:00,Makassar"+lf+"Asia/Manila,+08:00,Manila"+lf+"Asia/Shanghai,+08:00,China Time - Beijing"+lf+"Asia/Singapore,+08:00,Singapore"+lf+"Asia/Taipei,+08:00,Taipei"+lf+"Asia/Ulaanbaatar,+08:00,Ulaanbaatar"+lf+"Australia/Perth,+08:00,Western Time - Perth"+lf+"Asia/Dili,+09:00,Dili"+lf+"Asia/Irkutsk,+09:00,Moscow+05 - Irkutsk"+lf+"Asia/Jayapura,+09:00,Jayapura"+lf+"Asia/Pyongyang,+09:00,Pyongyang"+lf+"Asia/Seoul,+09:00,Seoul"+lf+"Asia/Tokyo,+09:00,Tokyo"+lf+"Pacific/Palau,+09:00,Palau"+lf+"Australia/Adelaide,+09:30,Central Time - Adelaide"+lf+"Australia/Darwin,+09:30,Central Time - Darwin"+lf+"Antarctica/DumontDUrville,+10:00,Dumont D'Urville"+lf+"Asia/Yakutsk,+10:00,Moscow+06 - Yakutsk"+lf+"Australia/Brisbane,+10:00,Eastern Time - Brisbane"+lf+"Australia/Hobart,+10:00,Eastern Time - Hobart"+lf+"Australia/Sydney,+10:00,Eastern Time - Melbourne^, Sydney"+lf+"Pacific/Chuuk,+10:00,Truk"+lf+"Pacific/Guam,+10:00,Guam"+lf+"Pacific/Port_Moresby,+10:00,Port Moresby"+lf+"Pacific/Saipan,+10:00,Saipan"+lf+"Asia/Vladivostok,+11:00,Moscow+07 - Yuzhno-Sakhalinsk"+lf+"Pacific/Efate,+11:00,Efate"+lf+"Pacific/Guadalcanal,+11:00,Guadalcanal"+lf+"Pacific/Kosrae,+11:00,Kosrae"+lf+"Pacific/Noumea,+11:00,Noumea"+lf+"Pacific/Pohnpei,+11:00,Ponape"+lf+"Pacific/Norfolk,+11:30,Norfolk"+lf+"Asia/Kamchatka,+12:00,Moscow+08 - Petropavlovsk-Kamchatskiy"+lf+"Asia/Magadan,+12:00,Moscow+08 - Magadan"+lf+"Pacific/Auckland,+12:00,Auckland"+lf+"Pacific/Fiji,+12:00,Fiji"+lf+"Pacific/Funafuti,+12:00,Funafuti"+lf+"Pacific/Kwajalein,+12:00,Kwajalein"+lf+"Pacific/Majuro,+12:00,Majuro"+lf+"Pacific/Nauru,+12:00,Nauru"+lf+"Pacific/Tarawa,+12:00,Tarawa"+lf+"Pacific/Wake,+12:00,Wake"+lf+"Pacific/Wallis,+12:00,Wallis"+lf+"Pacific/Apia,+13:00,Apia"+lf+"Pacific/Enderbury,+13:00,Enderbury"+lf+"Pacific/Fakaofo,+13:00,Fakaofo"+lf+"Pacific/Tongatapu,+13:00,Tongatapu"+lf+"Pacific/Kiritimati,+14:00,Kiritimati"
 t=csvtotable467(text)
 for name=t rowname=x do
  if timezonename=x[1] then return x[2] /return /if
 /for
 return 'ERROR' /return
/function

Example Code:

TimeZoneToUTC('Pacific/Wallis')

Results

+12:00

Disable Form Buttons on Submit

Sometimes a web server may be slow to responsd from a form submit, thus giving a user the opportunity and motive to press the button again. Rarely does this produce good results, so I use this technique to prevent the situation.

When any submit button is pressed the entire page is covered with a translucent <DIV> that slightly dims the content and prevents any of the on-screen controls from being touched during the wait for a server response. Simple, clean and effective.

Note that it does rely on JQUERY, so do take care to put a reference to this in your page header <SCRIPT> area.

Place this piece of <STYLE> in your page, I typically have it in my page header function:

<style>
  .FreezePaneOff {
    visibility: hidden;
    display: none;
    position: absolute;
    top: -100px;
    left: -100px;
  }
  .FreezePaneOn {
    position: absolute;
    top: 0px;
    left: 0px;
    visibility: visible;
    display: block;
    width: 100%;
    height: 100%;
    z-index: 999;
    filter:alpha(opacity=15);
    -moz-opacity: 0.25;
    padding-top: 20%;
    background: rgb(110, 110, 110);
    background: rgba(110, 110, 110, .2);
  }
</style>

Place this <DIV> and <SCRIPT> after all of your page output, I typically make it the last thing in my page footer function:

<div align="center" id="FreezePane" class="FreezePaneOff"></div>
<script type="text/javascript">
  $('input:submit').click(function FreezeScreen(){
     scroll(0,0);
     var outerPane = document.getElementById("FreezePane");
     if (outerPane) outerPane.className = "FreezePaneOn";
  })
</script>

This technique performs well in virtually every browser and in situations ranging from an instant response to a long delay.

Requiring HTTPS on all pages

How To require that all pages be served with HTTPS instead of HTTP. The best way is to prevent any and all HTTP requests. This may not be possible and would require configuring your HTTP server to reject any requests on port 80.

You can however make sure that all of your HTML/OS pages stop delivery of anything not requested over HTTPS.

First edit your htmlos.conf file and look for a line with ServiceType in it

ServiceType https

Next, you will need to protect any potential point of entry, like /system/login.html and /system/rs.html also on any startlinks you might have. Make sure all of these pages have this snip of code at the top of them. Your code might look something like this…

<<
 if getenv('HTTPS')='ERROR' then
  # Force HTTPS /#
  goto 'https://'+domainname+getenv('REQUEST_URI')
 /if
>>

Placing that little snip of code at the start of all your HTML/OS pages will prevent them from being delivered over HTTP.

To verify your code is working take any page and change the https://... to http://... and load it. If it loads over HTTP then you have a problem and need to make sure you have the conditional on the page.

To protect /system/rs.html and /system/login.html do the following

First copy /system/rs.html to /system/rsAESTIVA.html and /system/login.html to /system/loginAESTIVA.html

Next create a new /system/rs.html and add the following code to it...

<<
 if getenv('HTTPS')='ERROR' then
  # Force HTTPS /#
  goto 'https://'+domainname+getenv('REQUEST_URI')
 /if
 goto '/system/rs.html'
>>

And lastly Next create a new /system/login.html and add the following code to it...

<<
 if getenv('HTTPS')='ERROR' then
  # Force HTTPS /#
  goto 'https://'+domainname+getenv('REQUEST_URI')
 /if
 goto '/system/login.html'
>>

How to get UTC+0 / GMT

This seems like a very simple question with a simple answer.

NOW

A simple use of NOW will only return the time and date portion of what the underlying server is reporting. For example if you were to type date from the command line on your server you would get something like this...

$ date
Wed Nov 19 11:18:56 PST 2014

And then if you run NOW in your html/os code you get something like this...

11/19/2014 11:18

Notice the timezone is no where to be seen. And to complicate things if you use the Aestiva Control Panel to modify your time to say add 120 minutes to the server time then the Aestiva Time will return...

11/19/2014 13:18

And once again the concept of which timezone this represents is lost.

Thankfully newer versions of HTML/OS include a nice function called UTCOFFSET(now) in the example listed above UTCOFFSET(now) would return -0800, but it would return that regardless of any time modifications you might have made with the time control panel.

So in order to get the UTC+0 time on your server you can use the function ADDMINUTES() to subtract the UTCOFFSET times 60 but then you will also have to subtract the minutes offset you may have entered into your Time Control Panel.

Here is a function that encapsulates these two arithmetic operations to give you the true UTC+0 or GMT date and time.

function nowUTC() locals u,s,timeconf do
 copy file="/system/conf/time.conf" ts=',' to timeconf /copy
 if timeconf[1,1]='ERROR' then timeconf[1,1]=0 /if
 if timeconf[1,1]=''      then timeconf[1,1]=0 /if
 u=utcoffset(now)
 s=left(u,1)
 u=middle(u,2,3)
 u=u+0
 if s='-' then u=u*-1 /if
 return addminutes(now,(u*-60)-timeconf[1,1]) /return
/function 

Let me know in the comments if this works for you and if not why.

LearnHTMLOS.com

We hope to make this a resource for new and experienced HTML/OS programmers to gather to learn new coding techniques and also to share and help each other with issues that may arise while building their web application with HTML/OS