Wednesday, 11 November 2009

On-Rev: mixing revTalk and HTML


On-Rev scripting allows us to have scriptable web pages. This means our web pages can be scripted as they are served, so they are dynamic instead of static. However this still requires HTML tags to be inserted into the page.

The revTalk "put" command is used to display data on the web page, when used in an irev file.
e.g.
   put the long date
or
   put "<h1>" & tHeader & "</h1>"

But what if you want a more complex mix of HTML & revTalk?

As an example, on my web site I have text files with lists of related links. These are in the format of one link per line, with the name of the linked page and the address of the linked page separated by tabs. When the page is opened by a browser, I want the revTalk to loop through the lines of the relevant links file, getting the name and address for each entry and constructing a valid HTML link.

These links have to be in the following format:
<a href="page_address">Page Name</a>
so it's a mix of HTML and Rev variables, with quotes around the page address.

This can be constructed purely using revTalk, but it gets a bit messy. Let's see what it looks like if we build a command that has two parameters: the page address and the page name, and uses them to build a link and display it.
<?rev
   command constructLink pAddress, pName
      put "<a href=" & quote & pAddress & quote & ">" & pName & "</a>" into tLink
      put tLink
   end constructLink
?>

So it's not too bad, but fiddling with quotes is always a pain, and there is too much chance of getting them unbalanced.

Now let's look at the same function in an iRev page where it mixes HTML & revTalk:
<?rev command constructLink pAddress, pName ?>
   <a href="
   <?rev put pAddress ?>
   ">
   <?rev put pName ?>
   </a><br />
<?rev end constructLink ?>

As you can see, the revTalk sections are only single lines, interspersed with standard HTML lines.

Now that is a fairly simple example, and it's really a matter of preference which way you do it.
But how about when you want to display different things based on an "if" statement.
<?rev if tHour >= 12 then ?>
   Good afternoon. <br />
   I hope your day has been going well.
<?rev else ?>
   Good morning. <br />
   What are you going to do today?
<?rev end if ?>

Again, a simple example, but it shows a very useful technique. This can be used to change the display, to do multi-page forms etc.

Until next time,
Sarah

Tuesday, 20 October 2009

Using comma for string concatenation


I had long been familiar with using & and && to concatenate strings. The & just joins strings directly while && joins them with a space in between which can be incredibly useful e.g.

-- using &
put "Revolution" into tLanguage
put "I use " & tLanguage
-- gives "I use Revolution"

-- using &&
put "Sarah" into tFirstName
put "Reichelt" into tLastName
put tFirstName && tLastName
-- gives "Sarah Reichelt" with a space between the names

However I have only just worked out that you can use comma in this way too.

-- using ,
put 2009 into tYear
put 10 into tMonth
put 20 into tDay
put tYear, tMonth, tDay
-- gives "2009,10,20"

Previously I would have used this much more long-winded version:

put tYear & comma & tMonth & comma & tDay
-- gives "2009,10,20"

Note that even if you leave spaces before or after the commas when concatenating, these spaces do not appear in the final string, so you can spread out the script for easier reading.

There is no equivalent of && to allow inserting a space at the same time, but you can add spaces using something like this:

put tYear, space & tMonth, space & tDay
-- gives "2009, 10, 20"

Just think of , as being shorthand for " & comma & ".

Until next time,
Sarah

Monday, 5 October 2009

On-Rev variables


On-Rev allows us access to three special variables to get data from the server that is normally accessible to a web page or a CGI script. These variables are arrays called $_SERVER, $_POST and $_GET.

$_POST is used to hold data sent to a web page via a POST.
$_GET is for data sent using GET.
$_SERVER shows information about the server, the page and site and the calling computer. This can be used for logging, to work out what sort of computer is calling, for version checking, reading cookies etc.

As these are arrays, they are used with syntax like this:
   put $_SERVER["REMOTE_ADDR"] into tYourIPaddress
If you go to http://www.troz.net/onrev/samples/test.irev you can also see examples of reading the $_GET & $_POST arrays.

Here is an iFrame coming from my On-Rev site showing the current contents of the $_SERVER variable:


Until next time,
Sarah

Friday, 2 October 2009

On-Rev


I am really excited about the new On-Rev technology being developed by the people at RunRev which allows us to use the Revolution language to script web pages. This is similar to PHP, and does not replace JavaScript as it is server-side scripting, not client-side. This means that the script changes what gets sent to a browser when a page is requested - it does not react to the user's actions like JavaScript can do. However it is possible to combine the technologies.

I have been experimenting with On-Rev since it came out and have re-designed my own TrozWare website to use this scripting ability. I also have provided lots of examples at the On-Rev page.

But first, why do you need to be able to script a web site? Why not just design it in iWeb or FrontPage and leave it at that? Because it makes the pages dynamic instead of static. It means that you can change your pages easily, or even automatically. Your site will become more interesting so people will keep coming back to it, knowing that it won't be exactly the same every time.

Here is a very simple example: suppose you want your web page to show the current date. This can be done in PHP or JavaScript but it isn't easy. I looked them up, and at least the PHP version is short - obscure, but short:
   <?php
    $date = date("l, F j, Y",time());
    print ("$date");
   ?>

The JavaScript version took more lines to produce the same information:
<script language="JavaScript">
  <!--
    var now = new Date();
    var days = new Array(
      'Sunday','Monday','Tuesday',
      'Wednesday','Thursday','Friday','Saturday');
    var months = new Array(
      'January','February','March','April','May',
      'June','July','August','September','October',
      'November','December');
    var date = now.getDate();
    function fourdigits(number) {
      return (number < 1000) ? number + 1900 : number;}
    today = days[now.getDay()] + ", " +
       months[now.getMonth()] + " " +
       date + ", " +
       (fourdigits(now.getYear()));
     document.write(today);
  //-->
</script>

Now for the On-Rev version:
   <?rev
      put the long date
   ?>
How easy is that? You can write it and you can read it!

I'll be writing more about On-Rev scripting in the future, but for now, have a look at the examples on my On-Rev page and read the scripts. Some get more advanced and use JavaScript or AJAX as well as On-Rev, but since they are really just a blend of HTML & On-Rev scripting, they should be reasonably easy to follow.

There are also links in the sidebar to other On-Rev pages and sites, so check them out too.

Until next time,
Sarah

Thursday, 1 October 2009

RegEx or not?


I'm a big fan of Revolution's chunking abilities i.e. the ability to extract sections of text and manipulate them using characters, words, items and lines. However Revolution also allows us to use Regular Expressions.

I have always tried to avoid RegEx as I have great trouble reading it. I don't really want my scripts to include something that I can't even read now, and I know that I won't have a clue what it does when I come back in 6 months to read it again.

But a recent discussion on the use-rev mailing list inspired me to have another look. There was a good quick start guide to RegEx at PerlDoc. You can try nearly all the examples they give. Where they have something like:
   "Hello World" =~ /World/;

in Revolution, use this:
   put matchText("Hello World", "World")

You have to leave off the "/" at the start and end of the RegEx.

Some of the examples use returned variables and back references. I haven't worked out a way to use back references, but returning variables is easy enough:
   put empty into tMatch
   get matchText("Hello World", "(World)", tMatch)
   put tMatch

The RegEx part has to be inside brackets for any variable to be filled, and the variable must be declared first in some way, either as I did or by declaring it as a local variable.

One last hint: where standard RegEx uses 'i' at the end to indicate a case-insensitive search, in Revolution, you start the RegEx with "(?i)" to do the same thing.

My interest in RegEx was really sparked when I realised that I could use a one liner to parse dates and times into their components. For example, if I had a short date (m/d/y) and I declared three local variables: m, d & y, I could use the following line to fill them:
   get matchText(tDate, "(\d+)/(\d+)/(\d+)", m, d, y)

Doing the same using chunks needed this code:
   set the itemdel to "/"
   put item 1 of tDate into m
   put item 2 of tDate into d
   put item 3 of tDate into y

OK, so the chunking method is much easier to read (in my opinion) but it's 4 lines of scripts instead of 1 - surely that's less efficient.

So then I did some benchmarking. I set up the following 2 scripts which parse a date and a time into their components, each repeating 1000 times so I could get a time long enough to measure.

Chunking:
on mouseUp
   put the short date into tDate
   put the long time into tTime

   put the milliseconds into tStart

   repeat 1000 times
      set the itemdel to "/"
      put item 1 of tDate into m
      put item 2 of tDate into d
      put item 3 of tDate into y

      set the itemdel to ":"
      put word 1 of tTime into tTime
      put item 1 of tTime into h
      put item 2 of tTime into mm
      put item 3 of tTime into s
   end repeat

   put the milliseconds - tStart into tElapsed
   put "Chunk elapsed time: " & tElapsed & " milliseconds"
end mouseUp

RegEx:
on mouseUp
   put the short date into tDate
   put the long time into tTime

   local d, m, y, h, mm, s

   put the milliseconds into tStart

   repeat 1000 times
      get matchtext(tDate, "(\d+)/(\d+)/(\d+)", m, d, y)
      get matchtext(tTime, "(\d+):(\d\d):(\d\d)", h, mm, s)
   end repeat

   put the milliseconds - tStart into tElapsed
   put "RegEx elapsed time: " & tElapsed & " milliseconds"
end mouseUp

The results astonished me!

One my computer (2.8 GHz iMac), the RegEx version takes 49 - 50 milliseconds, while the chunking version takes 1-2 milliseconds. I am sure some RegEx experts could write my RegEx more efficiently and speed it up, but can it be sped up that much?

So it would seem that the Revolution engine has some serious optimisation in place for chunking, and I can stop worrying about using more verbose scripts and rejoice in the fact that I won't gain any speed for the inconvenience of making my scripts unreadable.

Until next time,
Sarah

Wednesday, 3 December 2008

Whitespace - good & bad


I'm a big fan of using lots of whitespace to make my code more readable. Blank lines, indentation, spaces in comments, spaces between parameters etc. Also I go for more long-winded forms of multiline structures and I prefer not to have functions as parameters.

As an example, consider the following 2 code snippets:
    if x > 100 then put "Over limit" into fld "Result"
    else put "OK" into fld "Result"
    put char 1 to 3 of line lineoffset("Text",fld 3,10) of fld 3 into fld 2
and
    if x > 100 then
        put "Over limit" into fld "Result"
    else
        put "OK" into fld "Result"
    end if

    put lineoffset("Text", fld 3, 10) into tLineNum
    put char 1 to 3 of line tLineNum of fld 3 into tChars
    put tChars into fld 2


Both do exactly the same thing without error.
The second example has 9 lines while the first example only uses 3 lines, but which is easier to understand, debug and error-check? I bench-marked the two scripts and they gave virtually identical times.

However, my passion for whitespace caused a problem last week which took a while to work out. I had an "errorDialog" handler and I wanted to trap for certain error numbers, so I wrote the following script:

on errorDialog pErrorContents
    put "324, 624, 538" into tIgnoredErrors -- move abort, wait abort, script abort
    if item 1 of pErrorContents is among the items of tIgnoredErrors then exit errorDialog

    saveErrorLog pErrorContents

    pass errorDialog
end errorDialog

Then I found that my error log was recording errors with the first number being 624, which it should have been ignoring. I couldn't work this out for a while until I realised that "624 is among the items of tIgnoredErrors" returned false, because I had added spaces between each item. Once I changed the first line of the handler to read:
    put "324,624,538" into tIgnoredErrors   -- move abort, wait abort, script abort

Then it all worked fine. So the moral of the tale is, don't add extra white space to a string if you are doing item comparisons.

Until next time,
Sarah

Wednesday, 19 November 2008

Help for disabled buttons

I like to perform a lot of data evaluation on whatever information users of my applications are allowed to enter. Going along with one of my favourite programming principles:

Never ask for any information that the program can find out for itself.

I try to keep user input at a minimum and I like to provide help for any that is entered. The help involves tool tips, best-guess data entered automatically, and disabling controls that are either not needed or are not valid with the currently entered data.

I do a lot of process control and there is no use trying to ask the program to open a valve if you haven't told it which valve to open yet. In this case, the "Open valve" button would probably be disabled.

However this can be a bit frustrating. A user keeps trying to open the valve, it doesn't work and they don't know why. So I developed a practice of putting another button UNDERNEATH any button that might be disabled. This second button will only be clickable if the button on top of it is disabled and it can provide some explanation about what needs to be done before the button is re-enabled.

Here is an example:

Make a new stack.
Add an option menu called "Valve" and have it's options be None, Valve 1, Valve 2 & Valve 3.
Add a button called "Open valve".

Edit the script of the option menu as follows:
on menuPick pChoice
    if pChoice = "None" then
        disable btn "Open valve"
    else
        enable btn "Open valve"
    end if
end menuPick

Test the operation of this menu button and you should see the "Open valve" button disable & enable as required.

Edit the "Open valve" button script as follows:
on mouseUp
    put the selectedtext of btn "Valve" into tValve
    answer info tValve & " is now open."
end mouseUp

Choose "None" from the option menu and check that the button is disabled.
Click the button - nothing happens.
Chose a valve from the option menu and click the "Open valve" button which should now be enabled. You will get a message indicating the valve has been opened.

Duplicate the "Open valve" button (duplicate so that it is exactly the same style and size).
Rename it to something like "Open valve disabled".
Turn off it's "Show name" property and make sure it is enabled.
Move it so that it is positioned exactly on top of the original button.
Set it's script to:
on mouseUp
    answer warning "Please select a valve to turn on."
end mouseUp

Now send this button to the back so that it is hidden behind the "Open valve" button.

Test by making selections in the option menu and clicking the "Open valve" button.
You should either get a message saying the valve is opened, or a message telling you to select a valve. Either way, you get some feedback as to what is happening or why nothing has happened.

Obviously, in such a simple example this is not really necessary, but for a more complicated layout with multiple reasons why a button did not work, it can be really useful. You could also extend the script in the hidden button to work out how much data is incorrect or missing and present a specific list.

Until next time,
Sarah