Developer Center

Java tutorial

This guide was written to help people with no Java experience get into the more advanced functions offered via the skin interface, for example the ability to write beanshell scripts (interpreted Java). This guide was very kindly written by Laura Seabrook and was previously posted to the forum. It is now presented here as a permanent reference.

This guide is broken down into four sections as follows:

  • You can run Java code samples interactively in the "jAlbum console window" available from the help menu.

  • Part 1. Literals, Variables and Expressions

    I'm writing this for people who, like me, aren't that familiar with Java as a programming language, but would like to use some of it to customise their jAlbum skins. I'm no expert -- in fact until I downloaded jAlbum I hadn't even looked at ANY Java coding. This is not a full tutorial because I don't know that much, but hopefully it will help you get started. The following has been culled from various books explaining Java, and from other topics in the forums, and repeats information from the help pages as well.

    Scriptlets

    The first thing to realise is that you can include Beanshell scripts directly on the htt pages. Beanshell is just a "shell" under which Java codes can be written and evaluated directly. To write such code, you need to place it like so...

    <%
    	CODE GOES HERE;
    %>

    or

    <% CODE GOES HERE %> 

    ...in the location on the page that you want it to be evaluated. Within the section between the "<%" and "%>", you can place Java statements. Basically you put one statement per line, and end it with a ";", like...

    userVar1 = 45;
    uservar2 = userVar1 + 6;

    If you DON'T put the semicolon at the end of a the statement, you'll probably get an error message when you try to generate an album -- the message will vary depending upon what the next statement is. It's also possible to place code in "blocks" delimited by curly brackets, e.g.

    {
          LINE ONE OF CODE;
          LINE TWO OF CODE;
          LINE THREE OF CODE;
    }

    This comes in handy for IF statements (See PART 2 for details) because then a single IF can activate several lines of code. You can create a label by placing a colon after the label's name, e.g.

    section_one:
    section_two: STATEMENT;

    ...these can be handy for later documentation, and also breaking out of loops. You can also comment out bits of code in two ways:

    // All code on this line is ignored
    /* This is a block of comments that get ignored by Java until it is closed. */

    The other important scriptlet to know is the EVAL function. Anything written like...

    <%= EXPRESSION %>

    ...will return whatever value is indicated in the EXPRESSION listed. This might be a simple variable, like "userVar1", or something more complex like "filename.lastIndexOf('.')". This is important because if you're going to create and manipulate variables in a script, you need a way of putting the results into HTML codes.

    Literals

    Java uses literals and variables to assign and indicate values. A Literal has a fixed value, whereas variables can be changed. There are four types of literals.

    Boolean

    A boolean literal is either true or false, and is used to test/assign a result. e.g.

       fileTest = false;
    Characters & Strings

    A character literal is a single printable character and is indicated by putting single quotes around the character. e.g

       'A'         // The "A" character
       '?'         // The "?" character
       '\u0041'    // The "A" character
       '\u0022'    // The double quote character

    ...the last two lines show "Unicode" characters. The value (in hex) after the "\u" is a special code for the character. You will need to look up a java reference for a full list of codes. If you use "\" instead of "\u", you can insert an octal (base 8) character as well, for example...

    
       '\101'      // The "A" character (again)
       '\060'      // The "0" character (zero)
    
        

    It is also possible to use a "special character escape sequence" which is a special code after the "\" character. The codes are:

       '\\' backslash // Not to be confused with the comments code
       '\"'        double quote
       '\''        single quote
       '\b'        backspace
       '\f'        Form feed
       '\n'        newline
       '\r'        carriage return
       '\t'        horizontal tab

    Strings are consist of zero or more characters surrounded by double quotes. Note in the examples below how escape codes can be used in a string:

       ""                            // Gives an empty string of no characters
       "This is a test string"       // Has a value of: This is a test string
       "This is a \"test\" string"   // Has a value of: This is a "test" string
       "The value was \060"          // Has a value of: The value was 0
    Numbers

    Java supports floating point, integer and byte literals (and variables). There are different types of floating point literals (and variables) but what concerns us here is how to represent them. A floating point number can have an exponent (shown by using "E" or "e") and a type suffix ("F" or "f" for single precision, "D" or "d" for double precision):

       6.5E+3 (or 6.5E3)    // 65000.0 double precision
       7D                   // 7.0 double precision
       .01f                 // .01 single precision

    Integers can be shown as a series of digits, followed by an optional suffix ("L" or "l" for long integer types). a prefix of "0x" indicates that the number is hex based (digits 0-9, and letters A-F), and a prefix of "0" indicates that it's octal based (digits 0-8). e.g.

       76       // Integer
       76      // Long Integer
       0x4a     // Hexidecimal number
       057     // Octal number that is a long integer

    There is also a short integer type and a byte type, but in practice there's no difference in setting them than with a regular integer (see variables).

    Null

    A null literal represents no value and is simply listed as null. There is a difference between 0 (zero), "" (no string value) and null. The first two are actual values, whereas null isn't, You can use null in testing to see if a value has been set, e.g.

       if (newTitle != null) title = newTitle;   // Assign newTitle to title, if it value

    Variables

    Simple variables (anything not an array or object) have values assigned to them, or re-assigned as needs to be. The variety of variable types matches the type of literals. A variable has to be assigned a value otherwise it has a "null" or "void" value. Assignments are made in the form of...

        TYPE  VARIABLE [, VARIABLE];
    or [TYPE] VARIABLE = [VARIABLE = ] VALUE;

    ...where TYPE is the type of variable to be created (this is optional, hence the brackets), VARIABLE is the name of the variable to be assigned (e.g. userVar1), and VALUE is either a literal or an expression to be evaluated (e.g. userVar2+1 or "default"). All the following assign variables:

       boolean test1, fileExists  // Sets boolean variables test1 and fileExists
       userVar = "fred"     // assigns "fred" to a string variable called userVar
       byte   thumbX = thumbY = 0 // assigns 0 to byte variables thumbX and thumbY

    If you don't explicitly give the variable a type, it will be assumed to of a type that matches the value assigned to it. e.g.

       userVar = 44      // integer number
       userVar = 44.5    // floating point number
       userVar = 44D     // double precision floating point number
       userVar = 'E'     // character
       userVar = "test"  // string
       userVar = true    // boolean

    ...however, you can also assign the type with a prefix. e.g.

       boolean limit   = (n>5)             // true if n is greater than 5, otherwise false
       byte    posXRay = -33               // a number between -128 to 127
       char    tabChar = '\t'              // assigns the TAB character to a character variable
       double  energy  = mass*velocity     // a value between +/- 4.9 times 10^324 power
       float   yPos    = 2.0345            // a value between +/- 1.4 times 10^45 power
       int     timeSec = distX-16000       // a value between +/- 2,147,483,647
       long    tempSol = 44.56             // a value between +/- 9 times 10^18 power
       short   userVar = 1533              // a value between +/- 32,767
       String  userVar = preFix + sufFix   // a concatenated value of strings preFix & sufFix

    Normally you wouldn't be using more than three types of variables in jAlbum: boolean, integer and String. The other types still exist though. The main different in the types of number variables is how much space each occupies. The short variable only takes up two bytes of storage, whereas a double takes up eight.

    Expressions

    Assignments are actually a form of expression, where values can be manipulated and checked. Basically expressions will be either numeric, logical, character or string based. Expressions have OPERATORS and OPERANDS. The OPERAND is a value or variable, and the OPERATOR is a symbol that tells you what to do with the OPERANDS. e.g. in...

       userVar1 * 44

    ...userVar1 and 44 are the OPERANDS and the "*" symbol (for multiplication) is the OPERATOR.

    Maths

    Basic maths is supported in Java as per the following functions:

       testA * testB  // result is testA times TestB
       testA / testB  // result is testA divided by TestB
       testA - testB  // result is testA minus TestB
       testA + testB  // result is testA plus TestB
       testA % testB  // result is remainder of testA divided by TestB
    
       e.g.  20%6 = 2, 20%7 = 6, and 20%10 = 0

    The "=" in assignment determines how the value is assigned to the variable, and there are variants to "=". The following examples show some of these:

       userVar1 += userVar2    // equivalent of userVar1 = userVar1 + userVar2
       userVar1 -= userVar2    // equivalent of userVar1 = userVar1 - userVar2
       userVar1 /= userVar2    // equivalent of userVar1 = userVar1 / userVar2
       userVar1 *= userVar2    // equivalent of userVar1 = userVar1 * userVar2
       userVar1 ^= userVar2    // equivalent of userVar1 = userVar1 ^ userVar2
       userVar1 %= userVar2    // equivalent of userVar1 = userVar1 % userVar2

    You can also increase or decrease a variable by 1 using the following:

       userVar1 ++;            // equivalent to userVar1 = userVar1 + 1
       userVar1 --;            // equivalent to userVar1 = userVar1 - 1

    If you want more complex mathematic, you have to call a Maths method to do it. Some methods are...

       Math.abs(uVar3)         // returns the absolute value of uVar3
                                  e.g. Math.abs(-4)     = 4
       Math.max(uVar1, uVar2)  // returns the maximum of the two values
                                  e.g. Math.max(3, -1)  = 3
       Math.min(uVar1, uVar2)  // returns the minimum of the two values
                                  e.g. Math.min(3, -1)  = -1
       Math.pow(uVar1, uVar2)  // raises uVar1 to the power of uVar2
                                  e.g. Math.pow(2,3)    = 2*2*2 = 8
       Math.sqrt(uVar3)        // equals the square root of uVar3
                                  e.g. Math.sqrt(9)     = 3
       Math.round(uVar3)       // results in a rounding of uVar3 to the nearest integer
                                  e.g. Math.round(5.321)= 5
       Math.random()           // equals a double precision number between 0 and 1. e.g.
                                  int result = (int) (Math.random()*uVar3)+1
                                  ...will result in a random integer between 1 and uVar3

    There are other methods too, such as "acos", but it's unlikely that you'd need them to create an album.

    Parenthesis

    You can enclose parts or a whole of an expression in parenthesis, and this will affect the order in which the OPERATORs affect the OPERANDS, otherwise everything gets resolved more or less from left to right, though multiplication (*) and division (/) have a higher precedence than addition (+) and subtraction (-). e.g.

        8 + 3  * 2    // result = 14
       (8 + 3) * 2    // result = 22

    Parenthesis are also used to specify an expression for testing, like...

       (userTest > 5) // result is true if userTest is over 5, otherwise is false

    See Part 2-- Decision Structures for more detail.

    Characters

    Characters can be manipulated using Char methods, like...

       cVar = Character.toLowerCase("A") // character cVar is set to "a"
       cVar = Character.toUpperCase("a") // character cVar is set to "A"
    Strings

    String variables can be concatenated, like...

       userVar = "123" + "ABC"   // results in a value of "123ABC"

    They can also be manipulated by using methods:

       sVar = String.toLowerCase("A Test") // character sVar is set to "a test"
       sVar = String.toUpperCase("A Test") // character sVar is set to "A TEST"

    A more extensive list of methods you can use is given in Part 3 -- Methods List.

    Conditional Operator

    This is an operator which can set up alternative results. The form is:

       (CONDITION ? EXPRESSION1 : EXPRESSION2)

    Two examples are...

        xPos1 = (xPos>xLimit ? 0 : xPos )

    ...sets xPos1 to xPos, unless it's over xLimit in which case, set it to 0 (zero), and...

       strVar = (strVal.equals("default") ? "red" : "green")

    ...sets strVar to "red" if StrVal equals "default", otherwise it is set to "green".

  • Decision Structures

    Control Structures are used to control the flow of activity in a script. There are several useful statements you can use to create such structures, like IF, FOR and DO. Many use boolean conditions to determine actions. This part explains briefly how each work.

    Boolean conditions

    You can test whether a condition is either true of false, and use that condition in statements. Basically a conditional test is placed within parenthesis:

       (userTest > 5) // result is true if userTest is over 5, otherwise is false
    Maths Testing

    When testing numeric variables or literals, the following "operators" can be used in the condition's expression:

       uVar1 == uVar2    // true if uVar1 is equal to uVar2
       uVar1 != uVar2    // true if uVar1 is NOT equal to uVar2
       uVar1 <  uVar2    // true if uVar1 is less than uVar2
       uVar1 <= uVar2    // true if uVar1 is less than or equal to uVar2
       uVar1 >  uVar2    // true if uVar1 is more than uVar2
       uVar1 >= uVar2    // true if uVar1 is more than or equal to uVar2
    Character Testing

    Character variables can be tested using the following methods:

       Character.isDigit(cVar)           // true if cVar is a digit
       Character.isLetter(cVar)          // true if cVar is a letter
       Character.isLetterorDigit(cVar)   // true if cVar is a letter or digit
       Character.isLowerCase(cVar)       // true if cVar is in lower case
       Character.isUpperCase(cVar)       // true if cVar is in upper case
       Character.isSpace(cVar)           // true if cVar is the space character " "

    and

       cVar.equals(VALUE)                // true if cVar equals VALUE

    Note that the isDigit and isLetter methods are based on unicode, not ASCII values, so that code values in different values are equally valid, is formatted in unicode.

    String Testing

    String variables can be tested with the following methods (assume that strVar is set to "aValue.txt"):

       strVar.equals(VALUE)             // true if strVar is the EXACTLY the same as VALUE
                                           e.g. VALUE = "aValue.txt"
       strVar.equalsIgnoreCase(VALUE)   // true if strVar is the same as VALUE
                                           e.g. VALUE = "aVaLue.TXT" or VALUE = "AVALUE.txt"
       strVar.endsWith(VALUE)           // true if strVar ends with a string equal to VALUE
                                           e.g. VALUE = ".txt"
       strVar.startsWith(VALUE)         // true if strVar starts with a string equal to VALUE
                                           e.g. VALUE = "AVa"

    It is also possible to use methods such as "compareTo", "indexOf" and "lastIndexOF", but these produce a numeric or string result, not a boolean one (see Part Three -- Methods List for details). You can also try using "==" to compare strings, but the results are not reliable, because this test DOESN'T actually test the value of each string, but where the string variable points to.

    Boolean Operators

    Several conditions can be tested using the "&&", "||" and "!" operators, and placed within further parenthesis. e.g.

        ((userTest > 5) && (limitVal =0)) // true if userTest is over 5 AND limitVal is 0
        ((userTest > 5) || (limitVal =0)) // true if userTest is over 5 OR limitVal is 0
        (userTest == 5)                   // true if userTest is equal to 5
       !(userTest == 5)                   // true if userTest is NOT equal to 5
        (userTest != 5)                   // true if userTest is NOT equal to 5
       !(userTest != 5)                   // true if userTest is equal to 5!
       !(strVal.equals("default")         // true if strVal is NOT set to "default"

    If

    The if statement sets up a conditional block of code that gets used (IF) its conditions are met, with an optional block of code (ELSE). The formats are:

       if (CONDITION1) CODEBLOCK; // if CONDITION1 is true, CODEBLOCK1 is executed
       if (CONDITION1)            // same as above, only nicer to look at
          CODEBLOCK;
    
       if (CONDITION1)            // same as above
          CODEBLOCK;
       else
          CODEBLOCKz;             // if CONDITION1 is false, then CODEBLOCKz is executed
    
       if (CONDITION1)            // same as above
          CODEBLOCK1;
       else if (CONDITION2)       // if CONDITION2 is true, CODEBLOCK2 is executed
          CODEBLOCK2;
      [else if (CONDITIONx)       // if CONDITIONx is true, CODEBLOCKx is executed
          CODEBLOCKx;]
       else
          CODEBLOCKz;             // if none of the above conditions is true
    
                                     CODEBLOCKz is executed.

    Each CODEBLOCK can be either a single bit of code, or a series of coding statements surrounds by "{" and "}", e.g.

       if (xPos>xLimit) {   // Only do the first block of code if xPos > xLimit
          xPos=0;           // Reset xPos
          yPos++;           // Add 1 to yPos
       } else {
          x++;              // Add 1 to xPos
          cCount++;         // Add 1 to cCount
       }

    Switch & Case

    The SWITCH statement provides a way in which to test a value against a range of possibilities, as provided for by the CASE statements. Its form is:

       switch (EXPRESSION) {
          case CONSTANT1:
             CODEBLOCK1;
             [break;]
         [case CONSTANTx:
             CODEBLOCKx;]
         [default:
             CODEBLOCKz;]
       }

    The values for EXPRESSION should be either an integer, character or string. The value in EXPRESSION is checked against the value given in CONSTANT1, and if it matches, CODEBLOCK1 is executed. If not, extra cases (if present) are checked in the same way and their code is executed if the value of EXPRESSION equals their CONSTANT values. Finally, if none of the above was matched, CODEBLOCKz in the optional "default" condition is executed instead. Finally, a break statement (see below) can be used to prevent further processing of the SWITCH code. An example of a SWITCH block is...

       switch (styleName) {             // use String value from styleName variable
          case "brown":
             backgroundCol="brown";     // if styleName="brown", set backgroundCol to "brown"
          case "grey":
             backgroundCol="silver";    // if styleName="grey", set backgroundCol to "silver"
             foregroundCol="black";     // also set foregroundCol to "black"
          default:
             backgroundCol="brown";     // if neither of the above is true, set it to "white"
       }

    You can "bunch" the CASE lines together to provide for multiple instances. e.g.

       switch (styleName) {             // use String value from styleName variable
          case "brown":
          case "pink":
          case "red":
             backgroundCol="brown";     // set if styleName is "brown", "pink" or "red"
          case "grey":
          case "monotone":
             backgroundCol="silver";    // set if styleName is "grey", or "monotone"
             foregroundCol="black";
          default:
             backgroundCol="brown";     // if neither of the above is true, set it to "white"
       }

    While

    Like IF, a WHILE statement will execute a block of code if a condition is true. Unlike IF, it may repeat itself. After the block is executed, the condition is checked again, and if still true, the block is executed once again. This continues until either the condition becomes false, or a BREAK is used to stop the loop. the format is...

       while (CONDITION) {
          CODEBLOCK;
       }

    An example of this might be:

       while ((xPos>0) && (xPos>xLimit)) { // true if 0 < xPos < xLimit
          xStr= xStr+":"+toString(xPos);   // adds "value" to xStr string variable
          xPos=Math.random()*100)-50;      // xPos = +/- 50
       }

    Do while

    This is similar to the WHILE statement, but the code block gets executed at least once, before the condition is checked, and any repeat executions are made. the format is...

       do {
          CODEBLOCK;
       } while (CONDITION);

    An example of this (based on the previous WHILE example) might be:

       do (
          xPos=Math.random()*100)-50;      // xPos = +/- 50
          xStr= xStr+":"+toString(xPos);   // adds "value" to xStr string variable
       while ((xPos>0) && (xPos>xLimit))   // true if 0 < xPos < xLimit
       }

    ...which will always add the string version of xPos to xStr at least once (even if that value is OUTSIDE the limits set by the WHILE) at least once.

    For

    This statement runs a loop that will execute a code block a number of times, based on different values. The form is...

       for (INITILIZER; CONDITION; INCREMENT) {
          CODEBLOCK;
       }

    An example of this is...

    <% // Produce links to all other pages, cooler than just next, previous links
      for (int i=0; i<files.length; i++) {
        Map vars = (Map)fileVariables.get(files[i]);
        if (i+1 != imageNum) out.print("<a href=\"" + vars.get("currentPage") + "\">");
        else out.print("<b>");
        out.print(" " + (i+1));
        if (i+1 != imageNum) out.print("</a>");
        else out.print("</b>");
      }
    %>

    ...which is taken from the Help Pages. Here i is set to zero (pointing to the first character of an object) and incremented until all the files listed in the object are processed.

    Break

    This is used to "break out of" a SWITCH, DO, WHILE or FOR statement block. There are two formats:

       break;
       break LABEL;

    The first format breaks the "innermost" loop (or SWITCH) that is running. If you don't insert a break into the code block, the following CASE conditions are still checked for after that CASE block is executed (and if you have changed the CONDITION value in that block, might get executed as well). The second format breaks any loops until the level at the label is found. e.g.

       major_loop: for (int i=0; i<20; i++) {
          for (int j=0; j<20; j++){
             if (test=6)
                break major_loop;          // breaks both FOR loops if test = 6
             test=Math.random()*6)+1;      // test = between 1 to 6
             result= i*j;
          }
       }
     
  • Methods

    A method is a way of calling a special function which can produce a result from an input value. The following is a list if methods that can be used to modify and extract information from variables, and is divided into sections based upon the type of result you get (some of these are repeated in the other parts). This list is NOT complete, but refers to methods you would be most likely to use with jAlbum.

    In the examples, "strVar", "cVar", and "numVar" are all sample variables, whereas "Character.", "String." and "Math." are literals. Anything called VALUE is a numeric value; INT is an integer number, CHAR is a character and STRING is a string value. Also, position 0 (zero) in a string variable is always the FIRST character of that variable.

    Boolean

       Character.isDigit(CHAR)          // true if CHAR is a digit
       Character.isLetter(CHAR)         // true if CHAR is a letter
       Character.isLetterorDigit(CHAR)  // true if CHAR is a letter or digit
       Character.isLowerCase(CHAR)      // true if CHAR is in lower case
       Character.isUpperCase(CHAR)      // true if CHAR is in upper case
       Character.isSpace(CHAR)          // true if CHAR is the space character " "
       strVar.equals(STRING)            // true if strVar is the EXACTLY the same as STRING
                                           including the case of each character
       strVar.equalsIgnoreCase(STRING)  // true if strVar is the same as STRING, but ignores
                                           upper and lower case settings in the string
       strVar.endsWith(STRING)          // true if strVar ends with a string equal to STRING
       strVar.startsWith(STRING)        // true if strVar starts with a string equal to STRING

    Numeric

       Math.abs(VALUE)         // returns the absolute value of VALUE
       Math.max(VALUE1, VALUE2)// returns the maximum of the two values
       Math.min(VALUE1, VALUE) // returns the minimum of the two values
       Math.pow(VALUE1, VALUE2)// raises VALUE1 to the power of VALUE2
       Math.sqrt(VALUE)        // equals the square root of VALUE
       Math.round(VALUE)       // results in a rounding of VALUE to the nearest integer
       Math.random()           // equals a double precision number between 0 and 1. e.g.
       compareTo(STRING1, STRING2)   // compares two strings and gives either -1, 0, or +1,
                                        based the alphabetical comparison of those strings.
       strVar.indexOf(CHAR)          // finds FIRST position of character CHAR in strVar.
                                        returns -1 if not found
       strVar.indexOf(CHAR, INT)     // finds FIRST position of character CHAR in strVar
                                        starting at position INT. returns -1 if not found
       strVar.lastIndexOf(CHAR)      // same as indexOf above, but finds LAST position
       strVar.lastIndexOf(CHAR, INT) // in strVar of CHAR, starting from INT, if included
       strVar.indexOf(STRING)        // finds FIRST position of string STRING in strVar.
                                        returns -1 if not found
       strVar.indexOf(STRING, INT)   // finds FIRST position of string STRING in strVar
                                        starting at position INT. returns -1 if not found
       strVar.lastIndexOf(STRING)      // same as indexOf above, but finds LAST position
       strVar.lastIndexOf(STRING, INT) // in strVar of STRING, starting from INT, if included
       strVar.length()                 // results in length of string
    Parsing
       parseByte(strVal)   // Converts strVal to a Byte
       parseInt(strVal)    // Converts strVal to an Integer
       parseLong(strVal)   // Converts strVal to a Long Floating Number

    If you don't do this, you might get error messages when you try and do maths with a String! Sometimes it almost works, but "userVar1 + uservar2" is different when both values are Strings than when they're numbers (i.e. 4 + 5 = 9, but "4" + "5" = "45"!)

    Character

       Character.toLowerCase(CHAR)  // lowercase value of CHAR
       Character.toUpperCase(CHAR)  // uppercase value of CHAR
       strVar.charAt(INT)           // returns a character value from position INT in strVar

    String

       String.toLowerCase(STRING)     // lowercase value of STRING
       String.toUpperCase(STRING)     // uppercase value of STRING
       String.valueOF(EXPRESSION)       // returns the string value of VALUE,
                                           where EXPRESSION is other than a string
                                           itself (e.g. boolean or numeric).
       strVar.replace(STRING1,STRING2)  // replaces any occurrences of STRING1
                                           in strVar with STRING2
       strVar.substring(INT1)           // returns a substring of strVar that starts at
                                           position VALUE1 and ends at the end of
                                           the string variable
       strVar.substring(INT1,INT2)      // returns a substring of strVar that starts at
                                           position VALUE1 and ends at position VALUE2
       strVar.trim()                    // removes all the whitespace from strVar

    All user variables in jAlbum (the ones set on the Advanced tab) are all String variables. That means if you want to use them for maths, they have to be converted. The methods for this are:

  • Interfacing with jAlbum

    This part of coding basics will take you through an two examples of modifying an existing skin, and will also show you how to add and use user variables, to make those additions more flexible. In each case, we'll use a copy of the "standard" skin that comes with jAlbum. DON'T edit this skin directly. Instead, copy the whole directory/folder it's in and edit the copies therein.

    Example one (setting up a standard width table) will be included with the next post, and example two (using javascript filters to highlight thumbs) will be in the following post, so please don't reply until both are posted.

    Original code

    In both examples, we'll be playing around with the following code in index.htt:

       <TABLE>
       <ja:rowiterator>
          <TR>
          <ja:coliterator>
             <TD WIDTH="$maxThumbWidth" VALIGN="bottom">
                <A HREF="$closeupPath">
                   <ja:if exists="iconPath">
                      <!-- No frames around icons like folders and movie files -->
                      <IMG SRC="$iconPath" WIDTH="$thumbWidth" HEIGHT="$thumbHeight"
                       BORDER=0><BR>
                   </ja:if>
                   <ja:else>
                      <IMG CLASS="image" SRC="$thumbPath" WIDTH="$thumbWidth"
                       HEIGHT="$thumbHeight" BORDER=0><BR>
                   </ja:else>
                   <SMALL>$label</SMALL>
                </A>
             </TD>
          </ja:coliterator>
          </TR>
       </ja:rowiterator>
       </TABLE>

    Example one - Tables

    First, copy the "standard" folder to "exTable". We'll be editing the index.htt file in that folder.

    Looking at the source code we find <TABLE> on the first line. This is ok, but has a couple of restrictions. Because the <TABLE> code has no attributes, its width and other settings get put to a "default", and depend upon the values set in jAlbum for variables "rows" and "cols" (max rows and columns on the main screen).

    Temporarily we'll add a border="1" attribute so we can see the effect of our changes, so the tag is now...

       <TABLE border="1">

    ...run jAlbum with the above change and you'll see where the cells are in the table.

    What we want however, is a table that has a standard or constant width. Maybe the whole table will in a cell of a two column table (and the other cell with explanatory text). We'll "hard code" that width, as say...

       <TABLE border="1" width="600">

    ...which should then give us a table 600 pixels wide. Do another "make album" in jAlbum and see the difference.

    This works fine, but suppose we wanted different width tables for different collections. In this case, perhaps a user variable would be better, then we could change it without having to re-code "index.htt" all the time.

    The first thing we do is go to the advanced page of jAlbum and add a user variable. We'll call this variable "tableWidth" and initially give it a value of "580". Once you've done that, save the project as "exTable.jap". User variables either get saved with project files, otherwise you have to re-enter them when you're using jAlbum.

    Now we need to insert that value into the code. The above tag now looks like:

       <TABLE border="1" width="$tableWidth">

    ...doing another "Make Album" will show a change in the width of the table, because the value of "tableWidth" was 580, less than the 600 we first coded (we can always change that later).

    Is this coding "safe"? What if we don't have a value set for "tableWidth" or no "tableWidth" at all? In the first case the line of code gets translated as...

       <TABLE border="1" width="">

    ...and in the second case...

       <TABLE border="1" width="$tableWidth">

    As each has a non-numeric value, the attribute is ignored and we have what we started with!

    What is we want to control a bit more of that table? Apart from "border" and "width" there are also "cellspacing" and "cellpading" which set how many pixels apart the cells are, and how many extra pixels each cell has as a margin. We'll repeat the above by adding a variable for each: cellSpacing (value of 10) & cellPadding (value of 5). Enter these variables on the advanced page and save the project again. Now change the line to:

       <TABLE border="1" width="$tableWidth" cellspacing="$cellSpacing"
        cellpadding="$cellPadding">

    ...do another "Make Album" and see the difference.

    At the moment, all the cells in the table are left justified. We may want to space out the cells so that the thumbs are more evenly distributed. Now we could just hard code an align="center" into:

       <TD WIDTH="$maxThumbWidth" VALIGN="bottom">

    ...so that it looks like...

       <TD WIDTH="$maxThumbWidth" VALIGN="bottom" align="center">

    ...that would center the thumbs but it also centers any text in the cell as well. Maybe we're planning on having a longer description there. We can set the width of a cell as equal to the table width divided by the number of columns, or "tableWidth/cols" (we just have to be careful not to set the max Thumb Width wider than this). We then change the table tags to...

       <TABLE border="1" cellspacing="0" cellpadding="0">

    and change line defining the cell to

       <TD WIDTH="<%=tableWidth/cols%>" VALIGN="bottom">

    Save these changes and do another "make album". Whoa! All of a sudden we have a "scripting error" of

       Sourced file:
       <Inline eval of: tableWidth/cols; >
       : Operator: '"/"' inappropriate for objects
       : at Line: 1
       : in file: <Inline eval of: tableWidth/cols; > : ;

    What does this mean? It means that while system variables are always appropriately set to type, all user variables are created as Strings. Therefore, the value in "tableWidth" has to be converted to a numeric value before we can do maths on it. Change the line to...

       <TD WIDTH="<%=Integer.parseInt(tableWidth)/cols%>" VALIGN="bottom">

    ...and do another "make album". Each cell is now evenly spaced. As a last change, we change the code to be:

       <TD WIDTH="<%=Integer.parseInt(tableWidth)/cols%>"
        HEIGHT="<%=Integer.parseInt(tableWidth)/cols%>" VALIGN="bottom">

    This will make each cell into a square (provided that the thumb height isn't larger than the height we've defined). Do another "make album" and see.

    But wait a minute, it seems a bit wasteful to duplicate the same code twice doesn't it. Maybe we can rewrite the code so that the value is only calculated once. We can do this by inserting a scriptlet above the line, as...

       <%
         cellSize=Integer.parseInt(tableWidth)/cols;
       %>
       <TD WIDTH="<%=cellSize%>" HEIGHT="<%=cellSize%>" VALIGN="bottom">

    This assigns the value to the variable "cellSize" which is then used twice in the line below. The advantage of this is that we can now use that variable elsewhere in index.htt if we want to. The "<%=cellSize%>" is used instead of $cellSize because it gives the latest value.

    There can be many different ways of doing things with jAlbum. The simple example above shows how easy it can be to change and modify existing code.

    Example two - Filters

    First, save a copy of the "standard" skin to a folder called "exFilter". We'll edit the files in that folder

    In this example, we're going to add a "highlighting function" to the thumbs generated in index.htt. Instead of using a jAlbum function to alter the thumbs manually, we will use a set of javascript functions instead. The functions used are based on those found in pages 871-884 of the book "Javascript Bible" (ISBN 0-7645-3342-8) and examples found under "Beispiel: 8". You can find additional examples of using style based javascript filters at http://www.xentrik.net/css/filters.php, http://www.dynamicdrive.com/dynamicindex4/image5.htm and http://www.dynamicdrive.com/dynamicindex4/highlight2.htm.

    First of all we decide that we only want image thumbs and not the folder image to be highlighted. Secondly the javascript used might not be that portable. That is, it may only work on Windows computers under Internet Explorer, and on which javascript is enabled. That can always be fixed or changed later -- the main thing is to get it working on our own computer at least!

    The first thing we need to do is make a test that the filters work. Go to the examples shown under "Beispiel: 8". Do they work on your computer under your browser? If not, then you may need to use a different version of filters found on the other pages. The rest of this example assumes that they DO work.

    The following line of code is the one that displays an thumb image on the page:

       <IMG CLASS="image" SRC="$thumbPath" WIDTH="$thumbWidth" HEIGHT="$thumbHeight" BORDER=0><BR>

    We can insert the style elements anywhere in that line of code, but it will help if we break the IMG tag into three lines like so:

       <IMG CLASS="image" SRC="$thumbPath" WIDTH="$thumbWidth" HEIGHT="$thumbHeight"
       <!-- FILTER CODE GOES ON THIS LINE -->
       BORDER=0><BR>

    The IMG tag is still interpreted the same, but the spacing make it easier to see where the code is placed (and the <!-- --> is HTML code indicating a non-executable comment). Now we'll insert the filter code, so it now reads:

       <IMG CLASS="image" SRC="$thumbPath" WIDTH="$thumbWidth" HEIGHT="$thumbHeight"
       id="a1" style="filter: Gray()" onmouseout="style.filter='gray()'"
       onmouseover="style.filter=''" BORDER=0><BR>

    Now go do a "make album" in jAlbum and test the highlighting. As you can see, the thumbnails initially appear as a gray image, but when you place the mouse over them, they spring back to colour (assuming they were colour to begin with)! You'll also notice that although I included the "id" attribute, I DIDN'T include the equivalent in the stylesheet, even though this was done on the sample page.

    OK, that works, but what if we want to either...
          a) select what sort of highlight to use, or
          b) make the highlighting type dependent upon the style used?

    Multiple Filters

    The first thing we need to do is set up a user variable to select the type of hi-lighting. On the advanced page add a variable called hiType and give it a value of "gray". Now save these settings in a project called "exFilters.jap".

    To change the filter used, we'll use the SWITCH statement in a scriptlet. The scriptlet should be inserted somewhere in the index.htt file ABOVE the IMG tag. The scriptlet will read:

    <%
       switch (hiType) {
        case "invert":
          filterCode ="filter: Invert()\" onMouseOut=\"style.filter = \'Invert()\'\"
                                          onMouseOver=\"style.filter=\'none\'";
          break;
        case "xray":
        case "x-ray":
          filterCode ="filter: Xray()\"   onMouseOut=\"style.filter = \'Xray()\'\"
                                          onMouseOver=\"style.filter=\'none\'";
          break;
        case "gray":
        case "grey":
       default:
          filterCode ="filter: Gray()\" onMouseOut=\"style.filter=\'Gray()\'\"
                                          onMouseOver=\"style.filter=\'none\'";
       }
    %>

    What the code does is to assign a string value to the "filterCode" variable, based on what value the user variable "hiType" is set to. If none of the cases match, it uses the code under default. Note how I've allowed for variant spelling. Also note that the \" and \' are java codes for " and '. To make this work, we need to change the IMG code to:

       <IMG CLASS="image" SRC="$thumbPath" WIDTH="$thumbWidth" HEIGHT="$thumbHeight"
       id="a1" style="<%= filterCode%>"
       BORDER=0><BR>

    Now change hiType to "x-ray", do a "make album" and see the result.

    Style Filters

    We can adapt the above code to work with styles as well.

    In our "exFilters" skin (a copy of "standard", remember we have the following styles: 3D.css; AskUser.css; Black.css; Blue.css; Khaki.css; Milkyblue.css; Plain.css; and White.css. Suppose we want the gray filter used for all styles except "Milkyblue.css" and "Blue.css", where we want to use the x-ray filter, and "Black.css", where we want to use invert. All we have to do is change to SWITCH statement to read:

    <%
       switch (style) {
        case "Black.css":
          filterCode ="filter: Invert()\" onMouseOut=\"style.filter = \'Invert()\'\"
                                          onMouseOver=\"style.filter=\'none\'";
          break;
        case "Milkyblue.css":
        case "Blue.css":
          filterCode ="filter: Xray()\"   onMouseOut=\"style.filter = \'Xray()\'\"
                                          onMouseOver=\"style.filter=\'none\'";
          break;
       default:
          filterCode ="filter: Gray()\" onMouseOut=\"style.filter=\'Gray()\'\"
                                        onMouseOver=\"style.filter=\'none\'";
       }
    %>

    In this version, the system variable "style" (which matches the name of the stylesheet used) is used instead of "hiType", which is now ignored. There's no need to alter the IMG tag, because the same string variable is set. Change the code, then select one of the styles listed above, and do a "make album" to see the results.

    Further Developement

    There's still more you can do with this code. You could use other filters. I will post a list of these in another topic. You could also remove parts of it from index.htt and place it in another folder and read that code using INCLUDE. You could also put the statements used with filterCode into separate files and INCLUDE them instead. These techniques are covered in other postings and if you do a search in the forum you should find them.