vegUI.org home of the javascript based window manager and AJAX framework

vegUI Tutorial 18 - Writing a vegUI module

written by vegu on 28 Dec, 2006 04:41:16
A live example of this tutorial can be found here.

All image files i will use in this tutorial can be downloaded here.

Alright, we have gone over the basic vegUI elements and this tutorial definitely assumes that you have read the preceding vegUI tutorials and therefor have a basic understanding of how vegUI works. Also some basic knowledge about javascript objects and how to create and use them would be beneficial:

http://www.howtocreate.co.uk/tutorials/javascript/objects

This tutorial will show you how to create a basic web-application as well as it will show you how to create a vegUI module that can later be included in any vegUI based web-application you will write.

The application we will be creating is a simple calculator in a window.

The file structure of this tutorial will be a tad different from the previous tutorials as it will be split up into multiple files. The javascript code will not be written directly into the html page file but will included as an external file using the script tag.

There will be a file called calculator.class.js which will be the lib file that holds the calculator class. This is the module file and you will be able to include it in any vegUI application of your choice. Then there will be the tutorial_18.js file which holds the javascript code that creates, styles and builds the manager element and the calculator element. The css code will also be stored in a separate file called tutorial_18.css.

Here are the links to the files of the live example if you want to cross check your code to them later on:



The style of this tutorial will differ a bit as well, a lot will be explained via comments directly placed with the code, as this should be less confusing and easier to understand.

Part 1 - Writing the calculator class


Create a file called calculator.class.js, this is the file that will hold our calculator class. I like to split my code into sections using comments, i m not going to try to force my style of coding onto you and if you have your own style im sure you will be able to adapt my code examples to your liking ;).

First i create two sections in my file, one for global stuff and one for the calculator class. I separate my section using this comment style:

Code: Javascript
/****************************************
* S E C T I O N N A M E ********************
****************************************/



Lets start by creating the constructor function for the calculator object.

Code: Javascript
function VegUICalculator(refName, Parent, Manager) {

/* our constructor is the VegUIWindow element, doing it this way our
* calculator will inherit all the properties, methods and child
* element of the VegUIWindow
*/


/*
* constructor
*/


this.constructor = VegUIWindow;
this.constructor(refName, Parent, Manager);

/*
* properties
*/


/*
* child elements
*/


/*
* methods
*/

}
VegUICalculator.prototype = VegUIWindow;


The three arguments that the function can take are the reference name, which is the string name over which the object could be accessed from anywhere in the script. The second argument is the Parent VegUI element if there is any and last parameter is the vegUI manager element that spawned this element. In order to have our calculator inherit all the properties, methods and child elements of the VegUIWidnow we set its prototype to VegUIWindow and call the VegUIWindow function in the constructor, passing the three arguments to it.

I divided the function into three additional sub sections. The section called properties will hold any object properties, the child elements section will be the place were the calculator's child elements will be created and the methods section will hold the calculator's methods.

In order for the vegUI manager to be able to recognize and create our calculator we first need to assign it an object type id. Type ids 1-200 are reserved for standard vegUI elements so we will chose one larger than that, 345 for example. Put this code snippet outside of the VegUICalculator function, obviously - as this needs to be global ;)

Code: Javascript
var VUI_CALCULATOR = 345;


Update: 2.0.6

Now we need to add the calculator to the VEGUIOBJ array , before version 2.0.6 you could do so by assigning it directly to the array, but in order to provide module compatibility it is strongly recommended to use the vui_module_add() function. It takes three arguments, the module type, the module constructor function and the file name the module was defined in (ie the file that you are currently editing). The file name is so if there are other modules that use the same type id it is easier for the developer / user to find out which files are colliding with each other.

Code: Javascript
/* in order for the manager to be able to create our calculator later on
* it needs to be added to the VEGUIOBJ, we do this by calling the
* vui_module_add function
*/


vui_module_add(VUI_CALCULATOR, VegUICalculator, 'calculator.class.js');


This would already by a valid vegui module by the way, however it would be exactly the same as the VegUIWindow as we have not yet defined its own properties, methods and child elements.
Before we go and create the child elements we will add a property by the name of 'type'. It will hold the object type id of the calculator. Simply add this code snippet into the properties section of the VegUICalculator function.

Code: Javascript
this.type = VUI_CALCULATOR;


So what child elements will we need for our calculator? Buttons mainly.. We will need a button for each digit (0-9), buttons for math operations such as multiply, divide, add, subtract and equals. We will also need a button to invert the number from positive to negative and vice versa, plus buttons to clear the current number and erase the most recently entered digit. In addition to all those buttons there will also be a label that will display the number the user is working with.

We simply add all those elements directly in the constructor function of the calculator (the VegUICalculator function) by adding them as childs to the Ui child of the calculator. Remember that we made the calculator extend the VegUIWindow, that means it will have all the child elements and properties that VegUIWindow has, including it's Ui child where all additional children should be added to.

Code: Javascript

/*
* child elements
*/


/* buttons for the digits */

this.Btn0 = this.Ui.add_child('Btn0', VUI_BUTTON);
this.Btn1 = this.Ui.add_child('Btn1', VUI_BUTTON);
this.Btn2 = this.Ui.add_child('Btn2', VUI_BUTTON);
this.Btn3 = this.Ui.add_child('Btn3', VUI_BUTTON);
this.Btn4 = this.Ui.add_child('Btn4', VUI_BUTTON);
this.Btn5 = this.Ui.add_child('Btn5', VUI_BUTTON);
this.Btn6 = this.Ui.add_child('Btn6', VUI_BUTTON);
this.Btn7 = this.Ui.add_child('Btn7', VUI_BUTTON);
this.Btn8 = this.Ui.add_child('Btn8', VUI_BUTTON);
this.Btn9 = this.Ui.add_child('Btn9', VUI_BUTTON);

/* buttons for math operations */

this.BtnAdd = this.Ui.add_child('BtnAdd', VUI_BUTTON);
this.BtnSubtract = this.Ui.add_child('BtnSubtract', VUI_BUTTON);
this.BtnDivide = this.Ui.add_child('BtnDivide', VUI_BUTTON);
this.BtnMultiply = this.Ui.add_child('BtnMultiply', VUI_BUTTON);
this.BtnEquals = this.Ui.add_child('BtnEquals', VUI_BUTTON);
this.BtnInvert = this.Ui.add_child('BtnInvert', VUI_BUTTON);
this.BtnComma = this.Ui.add_child('BtnComma', VUI_BUTTON);
this.BtnPI = this.Ui.add_child('BtnPI', VUI_BUTTON);

/* buttons for calculator operations */

this.BtnClear = this.Ui.add_child('BtnClear', VUI_BUTTON);
this.BtnErase = this.Ui.add_child('BtnErase', VUI_BUTTON);

/* number label */

this.LblNumber = this.Ui.add_child('LblNumber', VUI_NODE);


I add the child elements and assign them as a direct property of the calculator in the same line. This is so the elements will be easily accessible later on, as i don't want to go over the calculator's C property every time i want to access any of it's children.

In addition to all those child elements we will also need to define some object properties. We need a property that stores the current number the user is working with, a property for the last result, a property for the current math operation mode (multiply, divide, subtract or add) and a property that defines if the user is currently working with a float number or an integer. Like the child elements these are defined in the constructor function of the calculator.

Code: Javascript

this.number = 0;
this.result = 0;
this.mod = '';
this.isFloat = false;

A method for everything


So we have got the child elements and the object properties defined, now we need to define the methods of the calculator, the functions that will run every thing. Before we dive into this let me explain how i add methods to my objects i usually define them as direct properties of the object. Like this:
Code: Javascript

this.functionname = function(..) {
do stuff!
};

If you do that style make sure you add a semicolon to the end of the definition just like you would do for any other property definition. The code will work fine without it but it will severely mess things up as soon as you compress your javascript code and remove the line breaks in it. So semicolons are our friends ;)

The first method we will talk about is the build method. Every vegUI element has it and its function is to build the HTML node of VegUI element from the element's template properties. The build method is the method where you should set the mouse states of your buttons , convert template properties to real properties and do any other preparation stuff. This is going to be quite a chunk of code, a lot of it will be explained via comments, so make sure you read them.

Code: Javascript
  
/****************************************************************/
/** Method: build_calculator */

this.build_calculator = this.build = function(toNode) {

/* we want to make sure the calculator is not resizable, so we set
* those properties right before the element is built so it will
* overwrite the property if it was set outside. This should be done
* only if you are sure that you want to prohibit an object from
* doing something and it goes against the flexibility concept of
* vegUI
*/


this.flags |= VUI_NORESIZE | VUI_NOMAXIMIZE;

/* convert template properties to real properties, this needs
* to happen before the dock() method is called, preferably
* even before the element is built
*/


this.maxLength = this.T.maxLength || 15;

/* build the calculator, we extended the calculator from the
* VegUIWindow element so we will make sure to call it's build
* function as well (build_win). Would we have extended the
* calculator from VegUINode instead we would be calling
* the build_node function
*/


if(!this.build_win())
return null;

/* were defining C to point to this, this is so we can use it as
* a reference to the calculator object within the functions
* that we set for the buttons
*/


var C = this;

/* setting the button mouse states */

/*
* digit buttons
*/


this.Btn0.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('0'); }
);

this.Btn1.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('1'); }
);

this.Btn2.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('2'); }
);

this.Btn3.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('3'); }
);

this.Btn4.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('4'); }
);

this.Btn5.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('5'); }
);

this.Btn6.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('6'); }
);

this.Btn7.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('7'); }
);

this.Btn8.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('8'); }
);

this.Btn9.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_digit('9'); }
);

/*
* buttons for calculator operations
*/


this.BtnClear.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.clear_number(); }
);

this.BtnErase.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.erase_digit(); }
);

this.BtnAdd.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.set_mod('+'); }
);

this.BtnSubtract.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.set_mod('-'); }
);

this.BtnDivide.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.set_mod('/'); }
);

this.BtnMultiply.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.set_mod('*'); }
);

this.BtnEquals.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.set_mod(); }
);

this.BtnInvert.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.invert_number(); }
);

this.BtnComma.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_comma(); }
);

this.BtnPI.States[VUI_MOUSE_DOWN].Scripts.add(
function() { C.put_number(new String(Math.PI).substr(0,10)); }
);

/* add a function to the key down state of the calculator
* that translates certain key presses to calculator operations
*/


this.States[VUI_KEY_DOWN].Scripts.add(
function() { C.keypress(); }
);

/* the dock function appends the built HTML node to the specified
* node, if no node is specified then it wont be appended
*/


this.dock(toNode);
return 1;
};


Alright, i told you this was quite a chunk of code ;) so lets go over it quickly. First, the definition, we are actually pointing two properties at the function. The build property and the build_calculator property. Why is that? The build() method of an vegUI element always needs to point at the direct build function of the element, if we would not point it to our new function then it would point to the build_win function of the VegUIWindow element that we extended the calculator from.

This also allows as future inheritance, we could have another element that extends our calculator into something new, but the calculator related stuff still would need to be built so we would need access to it's build function (build_calculator). Don't worry if this is confusing it will be explained a few lines down in the code!

Take a look at this code chunk:

Code: Javascript
if(!this.build_win())
return null;


Our build_calculator function is calling the build_win function. Why? So the window related stuff is built accordingly for our calculator. The build_win function then in turn calls the build_node function, so you can see it as a chain of functions. Everytime you create a new VegUI element and have it extend an existing element make sure that you call the build method of it's direct father element. Had we used VegUINode as the prototype for the calculator we would be calling the build_node() method instead of the build_win() method.

If the building fails some where down the line build_win() will return null, something went wrong and the whole building process will fail. Make sure to always return null in such a case, not 0.

Moving on, we then set the functions that should be executed by the buttons when they are clicked, the calculator's methods (like put_digit and set_mod) do not exist yet, but add those button functions now anyways.

The dock method appends the HTML nodes that are controlled by the vegUI element to the specified HTML node in toNode. If toNode is undefined or null then no nodes will be appended, this is so the nodes are only appended when the element is fully built in order to avoid unnecessary screen updates of the browser which can slow things down and we dont want that :)

OK, the next method we want to take a look at is the set method. Like the build method it will be assigned two properties, set and set_calculator.

Code: Javascript

/****************************************************************/
/** Method: set_calculator
* Calculator specific set method
*
* The maxLength argument defines how long the number held by the number
* label can be before "ERROR" is displayed.
*/


this.set_calculator = this.set = function(w,h,x,y,maxLength) {
this.set_win('Calculator',w,h,x,y);
if(!isNaN(maxLength))
this.T.maxLength = maxLength;
};


So what is happening here? Our set_calculator method can take 5 arguments, the width, height, x position, y position and max length of the number. w,h,x and y are simply passed to the set_win method of the calculator (inherited from VegUIWindow), we also hardcode the window title of the calculator to 'Calculator', this obviously takes away flexibility somewhat but i wanted to show this to you. Then we check if maxLength is a number, if it is the template property for max length is updated to the new value.

Let's do some math


We will need a method that updates our number label (LblNumber) with the number that is currently held in the calculator's number property. Although i want to be flexible, so lets add an optional argument to the function as well and if it is set the label be updated to hold whatever is in that argument (string or number).

Code: Javascript
  /****************************************************************/
/** Method: update_number
* updates the number label to hold the number stored in this.number unless
* n is submitted then the label is updated to hold the number in n
*/


this.update_number = function(n) {

if(!parseFloat(n))
var n = this.number;

/* if the amount of digits in the number is greater than max length
* output error instead of the actual number
*
* if the number is a float number then try to lower the precision
* first if the number is still too long even after the precision cut
* display "ERROR"
*/


var strN = new String(n);

if(strN.length > this.maxLength) {

var c = Math.pow(
10, (
strN.length -
(new String(Math.floor(n)).length+1)
) - (strN.length - this.maxLength)
);

var n = Math.round(n*c) / c;

if(new String(n).length > this.maxLength)
return this.LblNumber.clear(document.createTextNode('ERROR'));
}

/* output the number if everything is fine */

this.LblNumber.clear(document.createTextNode(n));
};


We're making sure that the number or string to be displayed by the label is not longer then the value stored in this.maxLength. If it is the label will display 'ERROR' instead. If the number is too long and a float number the function will try to reduce its precision before erroring (ie 55005.55515214214515 might become 55005.555).

We use the clear method of the LblNumber element to clear any existing HTML/Text nodes out of it and append a new text node to it in the same call.

The put_number() method will set this.number to a new value and call update_number, this function is pretty forward and no further explanation should be needed.

Code: Javascript
  /****************************************************************/
/** Method: put_number
* sets the number label to the specified number
*/


this.put_number = function(n) {
this.number = parseFloat(n);
this.update_number();
};


Calling parseFloat on n assures that this.number will be set to a number even if the argument passed to the function is a string.

We set our digit buttons to call the put_digit() method. This method will append a digit to the end of the current number,

Code: Javascript
  /****************************************************************/
/** Method: put_digit
* appends a digit to the number being held by the number label
*/


this.put_digit = function(c) {

/* we append the new digit to the number by making both the digit and
* the current number a string then appending the digit string
* to the number string and finally converting everything back to
* a number
*/


this.number = parseFloat(new String(this.number) + (this.isFloat?'.':'') + new String(c));
this.update_number();
this.isFloat = false;
};


If this.isFloat is true when this function is called then a comma will be appended before the digit is appended. The digit is appended by converting both the current number and the submitted argument to a string and then joining them together before the joined string is converted back to a number.

Next we need a function that clears the number label and also resets the calculator for a new calculation.

Code: Javascript
  /****************************************************************/
/** Method: clear_number
* resets the number and clears the number label
*/


this.clear_number = function() {
this.number = 0;
this.result = 0;
this.update_number();
};


Both this.number and this.result are set to 0, this resets the calculation process, update_number() is called to update the number label as well.

When one of the math operation buttons is clicked (multiply,divide, add or subtract) then the set_mod() method is called. This method sets the current math mode in this.mod. Every time set_mod is called the number stored in this.number is calculated with the number stored in this.result accordingly to the previously selected mod, then the new mod is set.

Code: Javascript
  /****************************************************************/
/** Method: set_mod
* set math mode to add, subtract, multiply or divide
*/


this.set_mod = function(m) {

/* if the number stored in result is 0 or null, then this is a new
* calculation, simply copy the current number over
*/


if(!this.result)
this.result = this.number;
else {

/* this is an ongoing calculation, do the math and then update the
* label to hold the newly calculated number (stored in result)
*/


switch(this.mod) {
case '+':
this.result += this.number;
break;
case '-':
this.result -= this.number;
break;
case '*':
this.result *= this.number;
break;
case '/':
this.result /= this.number;
break;
}
this.update_number(this.result);
}

/* set this.number to 0 to restart input but do not update the label
* as we want to display the last entered number until input begins
* also set this.mod to the specified mod
*/


this.number = 0;
this.mod = m;

};


The invert_number method simply inverts the number stored in this number from positive to negative and vice versa.

Code: Javascript
  /****************************************************************/
/** Method: invert_number
* Invert the number that is currently stored this.number
*/


this.invert_number = function() {
this.number = this.number ^ -2;
this.update_number();
};


The put_comma method sets the isFloat property to true so the put_digit method knows it is now dealing with a float number instead of an integer.

Code: Javascript
  /****************************************************************/
/** Method: put_comma
* makes this.number a float number
*/


this.put_comma = function() {
this.isFloat = true;
};


The erase_digit method erases the last digit of the number, like backspace would erase the last typed key.

Code: Javascript
  /****************************************************************/
/** Method: erase_digit
* removes the last digit
*/


this.erase_digit = function() {

/* in order to remove the last digit we need to convert the current number
* to a string and then remove the last character of that string before
* we convert it back to a number
*/


var nStr = new String(this.number);

if(nStr.length > 1)
this.number = parseFloat(nStr.substr(0,nStr.length-1));
else
this.number = 0;

this.update_number();
};


In the build_calculator function we set the VUI_KEY_DOWN state of the calculator to call the keypress method when a key is pressed, this is because i thought it would be nifty to support keyboard input of digits and math operations on our calculator. So whenever a key is pressed this.keypress is called and we can tell it to do stuff according to what key was pressed.

Code: Javascript

/****************************************************************/
/** Method: keypress()
* translate certain numpad keys to actions on the calculator
*/


this.keypress = function() {
switch(this.aKey) {
case 48:
case 96: this.put_digit('0'); break;
case 49:
case 97: this.put_digit(1); break;
case 50:
case 98: this.put_digit(2); break;
case 51:
case 99: this.put_digit(3); break;
case 52:
case 100: this.put_digit(4); break;
case 53:
case 101: this.put_digit(5); break;
case 54:
case 102: this.put_digit(6); break;
case 55:
case 103: this.put_digit(7); break;
case 56:
case 104: this.put_digit(8); break;
case 57:
case 105: this.put_digit(9); break;
case 111: this.set_mod('/'); break;
case 106: this.set_mod('*'); break;
case 109: this.set_mod('-'); break;
case 107: this.set_mod('+'); break;
}
};


The numpad digits have the key code 96-105, we want to support both the digits on the numpad as well as the digits on top of the keyboard. In addition we support math operations pushed on the numpad :)

Sweet, were done with our calculator class, so save the file and lets move on to the next one.

Creating, styling and building the calculator


Create a file called tutorial_18.js, in this file we will create our manager element as well as the calculator element. Let's start with the manager to get things going.

Code: Javascript
/*
* creating the manager
*/


Manager = new VegUIManager('Manager');
Manager.set(800,600);
Manager.T.Css.backgroundColor = '#b2b2b2';
Manager.build(document.body);


Now, i want you to open the source of the window tutorial and copy over the myWin element and everything we set up for it, except the css parts as we will do this later. Well you dont actually need to get it yourself as i will paste the code for you, i just said that so you know where the code below is coming from.

Code: Javascript

/*
* creating the template window, normally i would skip this and assign
* the properties to the calculator directly, however this is to demonstrate
* the template engine of vegUI again as we will clone the calculator
* from this window
*/


/*
* set and build the window
*/


myWin = Manager.get_new(VUI_WIN);
myWin.set('My Window', 300, 150, 50, 50);

myWin.Header.T.className = 'win_header';
myWin.Caption.T.className = 'win_caption';
myWin.T.className = 'win';


/* set up the buttons of the window */

myWin.BtnClose.set(null,2,18,18,'btn_close_1','btn_close_2');
myWin.BtnClose.set_marg(null,null,5);

myWin.BtnMaximize.set(null,2,18,18,'btn_maximize_1','btn_maximize_2');
myWin.BtnMaximize.set_marg(null,null,25);

myWin.BtnMinimize.set(null,2,18,18,'btn_minimize_1','btn_minimize_2');
myWin.BtnMinimize.set_marg(null,null,45);

/* set maximum proportions of the window */

myWin.T.maxY = 0;
myWin.T.maxH = 600;
myWin.T.maxW = 800;

/* set minimum proportions of the window */

myWin.T.minW = 150;
myWin.T.minH = 100;

/* skin window */

myWin.Skin.T.className = 'win_skin';
myWin.Skin.set_marg(2,2);
myWin.Header.set(2,2);
myWin.Header.set_marg(2);

/* creating the fade effect from the demonstration */

Manager.init_fx(30);

myWin.event_add('ontoback', function(a) { a[0].set_transparency(50); });
myWin.event_add(
'ontofront',
function(a) {
a[0].Manager.FX.effect_add(a[0], new VegUIFXFadeIn(1000));
}
);


We do this so we can now clone our calculator from this window, we can do this because we extended the calculator from the VegUIWindow element. Although you could also assign all those properties that we just assigned to myWin to the calculator directly i wanted to do it this way to show off vegUI cloning again, to make sure you understood how it works.

Code: Javascript

/*
* creating the calculator
*/


Calculator = Manager.get_new(VUI_CALCULATOR);
Calculator.clone(myWin);
Calculator.set(159,200,50,50);


Remember, our calculator's set method points to set_calculator();

There are quire a few buttons on our calculator so before we proceed by setting them up lets define a template button that we can use to clone the other buttons from so we dont need to set the css classes and proportions manually for every button.

Code: Javascript

/* style the calculator's buttons, first we define a common
* button template we will use for our buttons
*/


BtnCalcTpl = Manager.get_new(VUI_BUTTON);
BtnCalcTpl.set(0,0,24,19,'btn_calc_1','btn_calc_2');


Time to clone the buttons.

Code: Javascript

/* then we proceed by cloning the buttons of our calculator
* from that template.
*/


Calculator.Btn0.clone(BtnCalcTpl);
Calculator.Btn1.clone(BtnCalcTpl);
Calculator.Btn2.clone(BtnCalcTpl);
Calculator.Btn3.clone(BtnCalcTpl);
Calculator.Btn4.clone(BtnCalcTpl);
Calculator.Btn5.clone(BtnCalcTpl);
Calculator.Btn6.clone(BtnCalcTpl);
Calculator.Btn7.clone(BtnCalcTpl);
Calculator.Btn8.clone(BtnCalcTpl);
Calculator.Btn9.clone(BtnCalcTpl);
Calculator.BtnAdd.clone(BtnCalcTpl);
Calculator.BtnSubtract.clone(BtnCalcTpl);
Calculator.BtnMultiply.clone(BtnCalcTpl);
Calculator.BtnDivide.clone(BtnCalcTpl);
Calculator.BtnEquals.clone(BtnCalcTpl);
Calculator.BtnComma.clone(BtnCalcTpl);
Calculator.BtnInvert.clone(BtnCalcTpl);
Calculator.BtnClear.clone(BtnCalcTpl);
Calculator.BtnErase.clone(BtnCalcTpl);
Calculator.BtnPI.clone(BtnCalcTpl);


Then we reposition them and set their captions, ie digits for our digit buttons and math symbols for our math operation buttons.

Code: Javascript

/* now we need to position the buttons */

Calculator.Btn0.set(5,170,0,0,0,0,'0');
Calculator.Btn1.set(5,140,0,0,0,0,'1');
Calculator.Btn2.set(34,140,0,0,0,0,'2');
Calculator.Btn3.set(63,140,0,0,0,0,'3');
Calculator.Btn4.set(5,110,0,0,0,0,'4');
Calculator.Btn5.set(34,110,0,0,0,0,'5');
Calculator.Btn6.set(63,110,0,0,0,0,'6');
Calculator.Btn7.set(5,80,0,0,0,0,'7');
Calculator.Btn8.set(34,80,0,0,0,0,'8');
Calculator.Btn9.set(63,80,0,0,0,0,'9');

Calculator.BtnInvert.set(34,170,0,0,0,0,'+/-');
Calculator.BtnComma.set(63,170,0,0,0,0,',');

Calculator.BtnMultiply.set(100,170,0,0,0,0,'x');
Calculator.BtnDivide.set(100,140,0,0,0,0,'/');
Calculator.BtnSubtract.set(100,110,0,0,0,0,'-');
Calculator.BtnAdd.set(100,80,0,0,0,0,'+');

Calculator.BtnEquals.set(129,170,0,0,0,0,'=');
Calculator.BtnPI.set(129,140,0,0,0,0,'PI');
Calculator.BtnErase.set(129,110,0,0,0,0,'<-');
Calculator.BtnClear.set(129,80,0,0,0,0,'C');


In addition to setting up our button we also need to set up our number label.

Code: Javascript
/* styling the number label */

Calculator.LblNumber.T.className = 'lbl_number';
Calculator.LblNumber.set('div',145,18,5,30);


Woo, it's time to build the calculator :)

Code: Javascript

Manager.build_element(Calculator);


Oh, right we also need to set up the css stuff, so save and close this file for now.

Going with style


Create a file called tutorial_18.css and copy over the css styles from the window tutorial as well as add those styles that we require for our calculator to look pretty.

Code: CSS
/*
* Window styles
*/


.btn_close_1 { background-image: url('btn_close_0.gif'); }
.btn_close_2 { background-image: url('btn_close_1.gif'); }
.btn_minimize_1 { background-image: url('btn_minimize_0.gif'); }
.btn_minimize_2 { background-image: url('btn_minimize_1.gif'); }
.btn_maximize_1 { background-image: url('btn_maximize_0.gif'); }
.btn_maximize_2 { background-image: url('btn_maximize_1.gif'); }

.win_header {
background-color: #838383;
}

.win_caption {
color: #fff;
font: bold 10px Verdana, Arial, Helvetica;
}

.win {
background-color: #d9d9d9;
border: 1px #838383 solid;
}

.win_skin {
border: 1px #fff solid;
}

.win_shad {
border: 2px #838383 solid;
}

/*
* calculator specific
*/


.btn_calc_1 {
background-image: url('btn_calc_0.gif');
font: bold 11px Arial, Helvetica;
text-align: center;
padding-top: 5px;
color: #5f5f5f;
}

.btn_calc_2 {
background-image: url('btn_calc_1.gif');
font: bold 11px Arial, Helvetica;
padding-top: 5px;
text-align: center;
color: #000;
}

.lbl_number {
background-color: #f2f2f2;
border: 1px #838383 solid;
font: bold 10px Verdana, Arial, Helvetica;
color: #838383;
text-align: right;
padding-right: 2px;
padding-top: 5px;
}


It should explain itself which classes are for what. We already know what the window specific stuff is for if you have done the window tutorial :D. btn_calc_1 and 2 are the classes for the buttons on our calculator. The lbl_number class the css class our number label uses.

Putting it all together


We're almost done but since we have our stuff in separate files we need to have our page file (tutorial_18_example.html for example) include them all. We include the vegui.js and the calulcator.class.js file in the HTML headers. It is important that the calculator.class.js is included after the vegui.js file is as it depends on it.

Code: HTML
<head>
<style type="text/javascript" src="vegui.js"></script>
<style type="text/javascript" src="calculator.class.js"></script>
</head>


We also include our css file in the HTML headers using the link tag.

Code: HTML

<link rel="stylesheet" href="tutorial_18.css" type="text/css" />


The tutorial_18.js file needs to be included as well, although not in the head tag as it will work with the body element. So we include this after the body tag.

Code: HTML
<body>
<script type="text/javascript" src="tutorial_18.js"></script>


This can still be buggy sometime producing xxx NOT DEFINED errors which happen when the mouse is moved over vegUI elements before the page is built. Theyre not breaking anything, but theyre unpretty in the console, in order to avoid them you can put the stuff in the tutorial_18.js inside a function and have the onload event of the page call that function, to assure the vegUI stuff is not built before the page is done loading.

That's it, try to load your page, and if everything went right you should have a calculator to play around with, just as in the live example.

Having fun with clones


Since the calculator is a vegui element just as any other element, you can clone it as well, and you will have two fully functional calulcators on your page, useful ? in this case not really but fun either ways.

Code: Javascript
/* cloning a second calculator because it's fun */

secondCalc = Manager.get_clone(Calculator);
secondCalc.set_win('Cloned Calc');

Manager.build_element(Calculator);
Manager.build_element(secondCalc);
secondCalc.move(300);


I wrote this tutorial in one huge session, so there is a chance i might have forgotten something, let me know if something is unclear :)

Related Posts


Your Comment

name:*
email-address:

Are you human?



Comments to this post: (4)

RSS Feed for comments RSS Feed for comments
wake
on 04 May, 2007 - 15:44:47

WTF you have to clone all the subbuttons of the calc in the app??!!??

Report this comment
vegu
on 05 May, 2007 - 04:27:52

Yes, for educational reasons, i know it does not do much in this example, since youre not saving any work as you need to move and set the label of each of the buttons anyways.

But cloning elements saves a lot of time and work once you deal with more complex elements.

Report this comment
wake
on 07 May, 2007 - 08:20:12

yes, but why the prototype doesn't clone recursively?

Report this comment
vegu
on 07 May, 2007 - 09:39:12

ahh, i see - good point. Ill look into that

Report this comment