• Login
  • login to access premium content (it's free).

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