Last modified on 20 April 2010, at 11:34

XForms/Calculator

One of the classic examples of a web application program is a calculator. This one is done with XForms and just a few lines of CSS to draw borders around the table and cells.

ProgramEdit

<html
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xf="http://www.w3.org/2002/xforms"
    xmlns:ev="http://www.w3.org/2001/xml-events">
   <head>
      <!-- Licensed by the w3c.org under the GPL2 -->
      <title>Calculator Sample</title>
      <xf:model>
         <xf:instance>
            <equation xmlns="">
               <display>0</display>
               <displaybuffer>0</displaybuffer>
               <first>0</first>
               <second>0</second>
               <memory>0</memory>
               <result />
            </equation>
         </xf:instance>
      </xf:model>
      <style type="text/css">
      table {
         border: thin outset;
      }
      td {
         border: thin inset;
      }
      .display {
          text-align: right;
      }
  </style>
   </head>
   <body>
      <p>A simple calculator</p>
      <table>
         <tr>
            <td colspan="6" class="display">
               <xf:output ref="/equation/display" />
            </td>
         </tr>
         <tr>
            <td>
               <xf:output ref="/equation/memory">
                  <xf:label>M:</xf:label>
               </xf:output>
            </td>
            <td />
            <td />
            <td />
            <td colspan="2">
               <xf:trigger>
                  <xf:label>Clear</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/first" value="0" />
                     <xf:setvalue ref="/equation/second" value="0" />
                     <xf:setvalue ref="/equation/result" value="0" />
                     <xf:setvalue ref="/equation/display" value="0" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                     <xf:toggle case="add" />
                  </xf:action>
               </xf:trigger>
            </td>
         </tr>
         <tr>
            <td>
               <xf:trigger>
                  <xf:label>MC</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/memory" value="0" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>7</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 7" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>8</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 8" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>9</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 9" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>/</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/first" value="/equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                     <xf:toggle case="divide" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td />
         </tr>
         <tr>
            <td>
               <xf:trigger>
                  <xf:label>MR</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/display" value="/equation/memory" />
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/memory" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>4</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 4" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>5</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 5" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>6</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 6" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>*</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/first" value="/equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                     <xf:toggle case="multiply" />
                  </xf:action>
               </xf:trigger>
            </td>
         </tr>
         <tr>
            <td>
               <xf:trigger>
                  <xf:label>MS</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/memory" value="/equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>1</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 1" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>2</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 2" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>3</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 3" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>-</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/first" value="/equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                     <xf:toggle case="subtract" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>1/x</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/display" value="1 div /equation/display" />
                  </xf:action>
               </xf:trigger>
            </td>
         </tr>
         <tr>
            <td>
               <xf:trigger>
                  <xf:label>M+</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/memory" value="/equation/memory + /equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>0</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10" />
                     <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:trigger>
                  <xf:label>+/-</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/display" value="/equation/display * -1" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td />
            <td>
               <xf:trigger>
                  <xf:label>+</xf:label>
                  <xf:action ev:event="DOMActivate">
                     <xf:setvalue ref="/equation/first" value="/equation/display" />
                     <xf:setvalue ref="/equation/displaybuffer" value="0" />
                     <xf:toggle case="add" />
                  </xf:action>
               </xf:trigger>
            </td>
            <td>
               <xf:switch>
                  <xf:case id="add" selected="true">
                     <xf:trigger>
                        <xf:label>=</xf:label>
                        <xf:action ev:event="DOMActivate">
                           <xf:setvalue ref="/equation/second" value="/equation/displaybuffer" />
                           <xf:setvalue ref="/equation/result" value="/equation/first + /equation/second" />
                           <xf:setvalue ref="/equation/display" value="/equation/result" />
                           <xf:setvalue ref="/equation/displaybuffer" value="0" />
                        </xf:action>
                     </xf:trigger>
                  </xf:case>
                  <xf:case id="subtract">
                     <xf:trigger>
                        <xf:label>=</xf:label>
                        <xf:action ev:event="DOMActivate">
                           <xf:setvalue ref="/equation/second" value="/equation/displaybuffer" />
                           <xf:setvalue ref="/equation/result" value="/equation/first - /equation/second" />
                           <xf:setvalue ref="/equation/display" value="/equation/result" />
                           <xf:setvalue ref="/equation/displaybuffer" value="0" />
                        </xf:action>
                     </xf:trigger>
                  </xf:case>
                  <xf:case id="multiply">
                     <xf:trigger>
                        <xf:label>=</xf:label>
                        <xf:action ev:event="DOMActivate">
                           <xf:setvalue ref="/equation/second" value="/equation/displaybuffer" />
                           <xf:setvalue ref="/equation/result" value="/equation/first * /equation/second" />
                           <xf:setvalue ref="/equation/display" value="/equation/result" />
                           <xf:setvalue ref="/equation/displaybuffer" value="0" />
                        </xf:action>
                     </xf:trigger>
                  </xf:case>
                  <xf:case id="divide">
                     <xf:trigger>
                        <xf:label>=</xf:label>
                        <xf:action ev:event="DOMActivate">
                           <xf:setvalue ref="/equation/second" value="/equation/displaybuffer" />
                           <xf:setvalue ref="/equation/result" value="/equation/first div /equation/second" />
                           <xf:setvalue ref="/equation/display" value="/equation/result" />
                           <xf:setvalue ref="/equation/displaybuffer" value="0" />
                        </xf:action>
                     </xf:trigger>
                  </xf:case>
               </xf:switch>
            </td>
         </tr>
      </table>
   </body>
</html>

DiscussionEdit

The calculator program is about 300 lines long. But much of the code is just telling what actions should happen when a button gets pressed.

The model of the calculator is very simple. Just a five variables:

  • display - the visible display
  • displaybuffer - a secondary display that is not visible
  • first - the first operator
  • second - the second operator
  • memory - used by the memory function

Here is the trigger for the "5" button:

<td>
   <xf:trigger>
      <xf:label>5</xf:label>
          <xf:action ev:event="DOMActivate">
              <xf:setvalue ref="/equation/displaybuffer" value="/equation/displaybuffer * 10 + 5" />
              <xf:setvalue ref="/equation/display" value="/equation/displaybuffer" />
          </xf:action>
   </xf:trigger>
</td>

The actions for the trigger is to do just two things:

  1. are to multiply the value of the display buffer by 10 (shifting the digits over by one) and then to add a five.
  2. copy the value of the display buffer to the display.

There are also scientific function calculators but they have a smiliar structure.

It is interesting to compare a JavaScript version to the XForms version. The JavaScript version is shorter but the JavaScript can take advantage of some of the tools that are available to JavaScript.

ReferencesEdit

Hixie's Natural Log

Next Page: Crime Profile | Previous Page: Invoice Manager

Home: XForms