Fabula Example: Calculator

Our first example of a Fabula application is Calculator. This application simulates a simple calculator.

Like with any web application, we need an HTML file (index.html) to start the application in a web browser. Fabula Project Builder creates this file automatically when the project is saved, so we omit the details here.

Next, we need CSS declarations, so-called style, that define the appearance of the specific elements of the calculator. The entire style is below:

@import url(https://fonts.googleapis.com/css?family=VT323|Roboto+Mono);

.calculator-body {
 font-size: 1.6em;
 border-color: black;
 background-color: #B0C4DE;
 padding-bottom: 20px;
 padding-left: 20px;
 padding-right: 20px;
 padding-top: 20px;
 -moz-border-radius: 2px;
 -webkit-border-radius: 2px;
 border-radius: 4px;
 margin: 0 auto 25px;
 margin-top: 50px;
 font-family: 'Roboto Mono';
 font-size: 1.4em;
 font-weight: bold;
}
.calculator-display {
 margin: 5px;
 text-align: right;
 border: 2px solid;
 border-style: inset;
 border-radius: 4px;
 border-color: black;
 color: DimGray;
 background-color: SpringGreen;
 padding: 5px;
 font-family: 'VT323';
 font-size: 1.4em;
}
td {
 padding: 2px;
}

The last remaining thing is to define the Fabula library. The source is below. The rest of the article will explain the code in detail.

<?xml version="1.0" encoding="UTF-8" ?>
<library>
	<!--             Calculator Demo                 -->
	<!-- Author: Roman Movchan, Melbourne, Australia -->
	<typedef name="operator">
		<var>
			<prop name="plus"/>
			<prop name="minus"/>
			<prop name="multiply"/>
			<prop name="divide"/>
		</var>
	</typedef>
	<channel name="input">
<!-- 	Channel for input from the 'white' buttons -->
		<string/>
	</channel>
	<channel name="command">
<!-- 		Channel for commands from the other buttons -->
		<var>
			<prop name="operator">
				<type name="operator"/>
			</prop>
			<prop name="clear"/>
			<prop name="display"/>
		</var>
	</channel>
	<common>
<!-- 		Let use the core routines without qualifying -->
		<unwrap>core</unwrap>
		<unwrap>string</unwrap>
		<unwrap>format</unwrap>
		<unwrap>math</unwrap>
<!-- 		Maximum length of output value -->
		<def var="maxlen">15</def>
	</common> 
	<applet name="main">
<!-- This applet integrates the other applets into one view	 -->
		<view applets="display: calculator-display, clear: clear-button, divide: divide-button, multiply: multiply-button, minus: minus-button, plus: plus-button, input: input-button, equal: display-button">
			<text>
				<div class="container">
					<table class="calculator-body">
						<tr>
							<td colspan="4">
								<div class="[%display%]">
								</div>
							</td>
						</tr>
						<tr>
							<td>
								<div class="[%clear%]" ></div>
							</td>
							<td>
								<div class="[%divide%]" ></div>
							</td>
							<td>
								<div class="[%multiply%]" ></div>
							</td>
							<td>
								<div class="[%minus%]" ></div>
							</td>
						</tr>
						<tr>
							<td>
								<div class="[%input%]" data-arg="7"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="8"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="9"></div>
							</td>
							<td rowspan="2">
								<div class="[%plus%]" ></div>
							</td>
						</tr>
						<tr>
							<td>
								<div class="[%input%]" data-arg="4"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="5"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="6"></div>
							</td>
						</tr>
						<tr>
							<td>
								<div class="[%input%]" data-arg="1"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="2"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="3"></div>
							</td>
							<td rowspan="2">
								<div class="[%equal%]"></div>
							</td>
						</tr>
						<tr>
							<td colspan="2">
								<div class="[%input%]" data-arg="0"></div>
							</td>
							<td>
								<div class="[%input%]" data-arg="."></div>
							</td>
						</tr>
					</table>
				</div>
			</text>
		</view>
	</applet>
	<applet name="input-button">
<!-- 	A button for entering digits or period -->
<!-- 	The argument (passed via the data-arg attribute) is the string to be sent to the input channel when the button is pressed -->
		<model state="state">
<!-- 		The state simply stores the value of the argument -->
			<string/>
		</model>
		<view>
			<calc>
				<text>
					<button type="button" class="btn btn-default" data-arg="[%state%]" style="[%style%]">[%nbsp%][%state%][%nbsp%]</button>
				</text>
				<where>
					<any>
						<all>
<!-- 							Zero is displayed full width -->
							<is>state = '0'</is>
							<def var="style">'width: 99%;'</def>
						</all>
						<def var="style">''</def>
					</any>
				</where>
			</calc>
		</view>
		<init arg="arg">arg</init>
		<events>
<!-- 			When the button is clicked, send the associated string to the input channel -->
			<click>
				<send channel="input">state</send>
			</click>
		</events>
	</applet>
	<applet name="clear-button">
<!-- 		Button that clears display -->
		<view>
			<text>
				<button type="button" class="btn btn-danger">[%nbsp%]C[%nbsp%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{clear:}</send>
			</click>
		</events>
	</applet>
	<applet name="display-button">
<!-- 		Button that displays the result -->
		<view>
			<text>
				<button type="button" class="btn btn-success">[%br%][%nbsp%]=[%nbsp%][%br%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{display:}</send>
			</click>
		</events>
	</applet>
	<applet name="plus-button">
		<view>
			<text>
				<button type="button" class="btn btn-primary">[%br%][%nbsp%]+[%nbsp%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{operator:{plus:}}</send>
			</click>
		</events>
	</applet>
	<applet name="minus-button">
		<view>
			<text>
				<button type="button" class="btn btn-primary">[%nbsp%]-[%nbsp%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{operator:{minus:}}</send>
			</click>
		</events>
	</applet>
	<applet name="multiply-button">
		<view>
			<text>
				<button type="button" class="btn btn-primary">[%nbsp%]x[%nbsp%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{operator:{multiply:}}</send>
			</click>
		</events>
	</applet>
	<applet name="divide-button">
		<view>
			<text>
				<button type="button" class="btn btn-primary">[%nbsp%]/[%nbsp%]</button>
			</text>
		</view>
		<events>
			<click>
				<send channel="command">{operator:{divide:}}</send>
			</click>
		</events>
	</applet>
	<applet name="calculator-display">
<!-- 	This applet receives and executes the commands and displays the result -->
		<model state="state">
			<com>
				<prop name="input">
<!-- 					Input string -->
					<string/>
				</prop>
				<prop name="result">
<!-- 					Last operation result -->
					<number/>
				</prop>
				<prop name="op">
<!-- 					Current operator, if any -->
					<var>
						<prop name="operator">
							<type name="operator"/>
						</prop>
						<prop name="none"/>
					</var>
				</prop>
			</com>
		</model>
		<view>
			<calc>
				<text>
					<div class="calculator-display">[%value%]</div>
				</text>
				<where>
<!-- 					Calculate displayed value -->
					<any>
						<all>
<!-- 							Display the input, if any -->
							<is>state.input 'ne' ''</is>
							<any>
								<all>
<!-- 									Is input value too long? -->
									<is>length(state.input) 'gt' maxlen</is>
									<def var="value">'***************'</def>
								</all>
								<def var="value">state.input</def>
							</any>
						</all>
						<all>
<!-- 							Is result an infinite or invalid number? -->
							<any>
								<is>isNaN(state.result)</is>
								<not>
									<is>isFinite(state.result)</is>
								</not>
							</any>
							<def var="value">'ERROR'</def>
						</all>
<!-- 						Otherwise display last result -->
						<all>
							<def var="str">numToStr(state.result)</def>
							<any>
								<all>
<!-- 									If value is too long, display in exponential format -->
									<is>length(str) 'gt' maxlen</is>
									<def var="value">formatNum({num: state.result, format: {exp: 5}})</def>
								</all>
<!-- 								Otherwise, in general format -->
								<def var="value">numToStr(state.result)</def>
							</any>
						</all>
					</any>
				</where>
			</calc>
		</view>
		<init>{ input: '', result: 0.0, op: {none:} }</init>
		<receive data="input">
			<from channel="input">
<!-- 			Calculate new state -->
				<calc>
					{ input: value, result: state.result, op: state.op }
					<where>
						<any>
							<all>
								<is>input = '.'</is>
<!-- 												Already has period? -->
								<is>indexOf({str: state.input, substr: '.'})</is>
<!-- 												Keep input value -->
								<def var="value">state.input</def>
							</all>
							<def var="value">
<!-- 											Otherwise, append new character (digit or period) to input string -->
								<text>[%state.input%][%input%]</text>
							</def>
						</any>
					</where>
				</calc>
			</from>
			<from channel="command">
<!-- 			Calculate new state -->
				<calc>
					{ input: value, result: result, op: op }
					<where>
						<any>
							<all>
<!-- 								Process 'clear' -->
								<is>input.clear</is>
								<def var="value">''</def>
								<def var="result">0.0</def>
								<def var="op">{none:}</def>
							</all>
							<all>
								<any>
<!-- 									First, calculate op and value -->
									<all>
										<is>input.operator.plus</is>
										<def var="op">{operator:{plus:}}</def>
										<def var="value">''</def>
									</all>
									<all>
										<is>input.operator.minus</is>
										<def var="op">{operator:{minus:}}</def>
										<def var="value">''</def>
									</all>
									<all>
										<is>input.operator.multiply</is>
										<def var="op">{operator:{multiply:}}</def>
										<def var="value">''</def>
									</all>
									<all>
										<is>input.operator.divide</is>
										<def var="op">{operator:{divide:}}</def>
										<def var="value">''</def>
									</all>
									<all>
										<is>input.display</is>
										<def var="op">{none:}</def>
										<def var="value">''</def>
									</all>
<!-- 									<all>
										<def var="op">state.op</def>
									</all> -->
								</any>
								<any>
<!-- 									Calculate result, if requested -->
									<all>
										<any>
											<is>input.display</is>
											<is>input.operator</is>
										</any>
										<any>
											<all>
												<is>state.op.operator.plus</is>
												<def var="result">state.result + strToNum(state.input)</def>
											</all>
											<all>
												<is>state.op.operator.minus</is>
												<def var="result">state.result - strToNum(state.input)</def>
											</all>
											<all>
												<is>state.op.operator.multiply</is>
												<def var="result">state.result * strToNum(state.input)</def>
											</all>
											<all>
												<is>state.op.operator.divide</is>
												<def var="result">state.result / strToNum(state.input)</def>
											</all>
											<def var="result">strToNum(state.input)</def>
										</any>
									</all>
									<def var="result">state.result</def>
								</any>
							</all>
						</any>
					</where>
				</calc>
			</from>
		</receive>
	</applet>
</library>

Our library contains one type definition (‘operator’), two channel definitions (‘command’ and ‘input’), several applets (‘main’, ‘input-button’, ‘clear-button’, ‘display-button’, ‘plus-button’, ‘minus-button’, ‘multiply-button’, ‘divide-button’ and ‘calculator-display’) and some common data definitions (shortcuts for the core functions and a global integer constant ‘maxlen’).

There will be one instance of ‘main’ (created initially by the page body), containing one instance of ‘calculator-display’, one instance of each command button (‘clear-‘, ‘display-‘, ‘plus-‘, ‘minus-‘, ‘multiply-‘ and ‘divide-button’) and multiple instances of ‘input-button’.

User-defined type ‘operator’ is a variant object type that represents an arithmetic operator (plus, minus, multiply, divide).

Channel ‘command’ will be used to send commands (‘clear’, an operator etc.).

Channel ‘input’ will transfer single-character strings (digits or period) when an input button is pressed.

Now let’s consider each applet.

Applet ‘main’ has no state of its own and simply integrates everything together and defines the layout of the calculator. For this reason, it consists of the only element – <view>. The view determines what is displayed within the corresponding web page element (initially and whenever the state changes); it contains an expression that returns the HTML content of an instance of the applet. In our case, the HTML code contains subelements of special classes (with the names returned by the built-in function ‘appletclass’); such subelements will also be substituted by the corresponding applets. In this particular case, there will be one instance of applet ‘calculator-display’ and multiple instances of various buttons.

Applet ‘input-button’ is also quite simple. Its model consists of a string that will be sent to channel ‘input’ when a button is pressed; since there is no <receive> section, the state never changes. The string identifies the button, i.e. each instance of this applet has an individual value of that string (digit or period), assigned when the instance is created. To do that, we use the ‘data-arg’ attribute of the HTML element that creates an instance of the applet; the <init> section of the applet receives this value via the variable whose name was specified as the “arg” attribute and stores it as the state (that will never change).

The other button applets are even simpler. Each of them just sends the corresponding command (e.g. ‘clear’) to the ‘command’ channel.

Finally, applet ‘calculator-display’ implements the most of the functionality of the calculator. It receives the commands from the buttons, calculates and displays the result. Accordingly, its model is a complex value (an object) that consists of three parts (properties):

  1. the input string (i.e. string typed by the end user with the “white” keys)
  2. the result of the last operation, or zero
  3. the current operation, or ‘none’

The view expression is a <calc> block that computes a value based on an algorithm. To define the logic of an algorithm, Fabula uses its unique ‘statement logic’, see our post Fabula‚Äôs Computation Logic.

Initially, the input string is empty, the result is zero and current operation is none.

Finally, the <receive> section determines how the applet reacts on external input, in our case either commands (‘clear’, ‘display’, an operator) or text input. All we need is to compute the new state of the applet instance that receives input; after the state has changed, the view updates automatically. Again, we use a <calc> block to compute the new state.

Let’s summarize what we achieved. We have a fully declarative program, with no JavaScript, that is guaranteed to behave the way we want. All the ‘concerns’ are separated. When we want to change anything, we change only that, without worrying that we might break something else.

Before you leave, please provide your feedback by ticking an option and clicking ‘Vote’ in the box below:

Feel free to post any questions in the Leave a Reply box below.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s