Tuesday, 23 December 2008

Using Connect By to Build Data Tree in Oracle

This example uses the connect by select clause to return data in a tree structure.

Our example has one table, which links to itself, parentId = treeTestId of the parent record.

CREATE TABLE treeTest
( treeTestId NUMBER(3,0),
parentId NUMBER(3,0),
description VARCHAR2(20) );

Now some test data

INSERT INTO treeTest VALUES ( 1, NULL, 'Animal');
INSERT INTO treeTest VALUES ( 2, NULL, 'Vegtable');
INSERT INTO treeTest VALUES ( 3, NULL, 'Mineral');
INSERT INTO treeTest VALUES ( 4, 1, 'Elephant');
INSERT INTO treeTest VALUES ( 5, 4, 'African');
INSERT INTO treeTest VALUES ( 6, 4, 'Indian');
INSERT INTO treeTest VALUES ( 7, 1, 'Giraffe');
INSERT INTO treeTest VALUES ( 8, 1, 'Rhino');
INSERT INTO treeTest VALUES ( 9, 8, 'Black');
INSERT INTO treeTest VALUES ( 10, 8, 'White');
INSERT INTO treeTest VALUES ( 11, 2, 'Potato');
INSERT INTO treeTest VALUES ( 12, 11, 'New');
INSERT INTO treeTest VALUES ( 13, 11, 'King Edward');
INSERT INTO treeTest VALUES ( 14, 2, 'Carrott');
INSERT INTO treeTest VALUES ( 15, 3, 'Gold');
INSERT INTO treeTest VALUES ( 16, 3, 'Silver');
INSERT INTO treeTest VALUES ( 17, 3, 'Lead');

And a select statement to return the data. The level pseudo column tells us which branch level the data belongs to.

SELECT treeTestId,
description,
parentId,
level
FROM treeTest
CONNECT BY PRIOR treeTestId = parentId
START WITH parentId IS NULL;

And another select statement, with the data tabbed in depending on its level.

SELECT LPAD(' ',(level-1)*2)||description
FROM treeTest
CONNECT BY PRIOR treeTestId = parentId
START WITH parentId IS NULL;


Done.

Friday, 21 November 2008

Compare SQL Server and Oracle Numeric Data Types

Number data types aren't so easy to directly compare. Oracle has one main data type, NUMBER, which is equivilent to SqlServer NUMERIC.













SQL Server Oracle
NUMERIC / DECIMAL Maximum precision of 38 digits NUMBER Maximum precision of 38 digits


SqlServer also has number data types that are sized in bytes not precision, which have no direct equivalent in Oracle.












Data TypeMinMaxBytesNear Oracle Equivalent
BIGINT-9,223,372,036,854,775,8089,223,372,036,854,775,8078BINARY_DOUBLE
INT-2,147,483,6482,147,483,6474BINARY_FLOAT
SMALLINT-32,76832,7672NUMBER(5,0)
TINYINT02551NUMBER(3,0)
FLOAT-1.79E+308 to -2.23E-308, 0 and 2.23E-308 to 1.79E+3084 or 8BINARY_DOUBLE
REAL-3.40E+38 to -1.18E-38, 0 and 1.18E-38 to 3.40E+384BINARY_FLOAT
MONEY-922,337,203,685,477.5808922,337,203,685,477.58078NUMBER(19,4)
SMALLMONEY-214,748.3648214,748.36474NUMBER(10,4)


Oracle also has data types FLOAT, DECIMAL and INTEGER, however these are ANSI synonyms which refer back to the NUMBER data type with specific precisions.

See also: Compare Lob Data Types, Compare Character Data Types

Thursday, 20 November 2008

Compare SQL Server and Oracle LOB Data Types

Oracle and SQL Server data types do not always match exactly. This table compares SQL Server 2008 and Oracle 11g LOB data types.































SQL Server Oracle
VARCHAR(MAX) Variable length non-Unicode character data, up to 2,147,483,645 characters. CLOB Variable length non-Unicode character data, that can hold be up 8 terabytes of character data.
NVARCHAR(MAX) Variable length Unicode character data, up to 1,073,741,822 characters. NCLOB Variable length non-Unicode character data, that can hold be up 8 terabytes of character data. The maximum character length is dependent on the number of bytes of the national character set.
VARBINARY(MAX) Stores the binary objects in the database. Up to 2Gb in size. BLOB Stores unstructured binary data in the database, up to 8 terabytes in size.
VARBINARY(MAX) FILESTREAM Stores the large objects outside of the database, in the NTFS file system. Size limited only by the volume size of the file system. BFILE Stores unstructured binary data, up to 8 terabytes, in operating-system files outside the database



In SQL Server VARBINARY(MAX), VARCHAR(MAX) and NVARCHAR(MAX) data types store the data in the record, unless the data is over 8Kb. When it exceeds 8Kb its behaviour changes and it stores a LOB locator in the record and holds the data in a LOB elsewhere. This locator value and the location is all handled behind the scenes and is invisible to the user.
The Oracle CLOB data type can be defined to be stored within the record or have a LOB locator, along with other storage characteristics, when the object is created.

See also: Compare Character Data Types, Compare Numeric Data Types

Tuesday, 18 November 2008

Compare SQL Server and Oracle Character Data Types

Oracle and SQL Server data types do not always match exactly. This table compares SQL Server 2008 and Oracle 11g character data types.











































SQL Server Oracle
CHAR Fixed length non-Unicode character data. Can be specified up to 8000 characters CHAR Fixed length non-Unicode character data. Can be specified up to 4000 characters
NCHAR Fixed length Unicode character data. Can be specified up to 4000 characters NCHAR Fixed length Unicode character data. Maximum of 2000 bytes, the maximum character length is dependent on the number of bytes of the national character set.
VARCHAR Variable length non-Unicode character data. Can be specified up to 8,000 characters. VARCHAR2 Variable length non-Unicode character data. Can be specified up to 4,000 characters. The VARCHAR data type is synonymous with the VARCHAR2 data type.
VARCHAR(MAX) Variable length non-Unicode character data, up to 2,147,483,645 characters. CLOB Variable length non-Unicode character data, that can hold be up 8 terabytes of character data.
NVARCHAR Variable length Unicode character data. Can be specified up to 4,000 characters. NVARCHAR2 Variable length Unicode character data. Maximum of 4000 bytes, the maximum character length is dependent on the number of bytes of the national character set.
NVARCHAR(MAX) Variable length Unicode character data, up to 1,073,741,822 characters. NCLOB Variable length non-Unicode character data, that can hold be up 8 terabytes of character data. The maximum character length is dependent on the number of bytes of the national character set.



"N” Data Types

For both SQL Server and Oracle, each character data type has a corresponding “N” data type, which use multi-byte character sets, allowing a larger number of different characters. This is useful for non-alphabetic languages.
This means that the size in bytes is no longer equal to the length in characters, so the maximum length is often less.
In Oracle the database character set controls the character set of CHAR, VARCHAR2 and CLOB data types. The national character set controls the character set of NCHAR, NVARCHAR2 and NCLOB data types.

SQL Server TEXT and NTEXT Data Types

SQL Server includes TEXT and NTEXT data types which may be removed in a future version. Developers should use VARCHAR(MAX) or NVARCHAR(MAX) instead.
By default the TEXT and NTEXT data types store a LOB locator in the record, and hold the actual text value in a LOB elsewhere.

LOB Data Types

The SQL Server VARCHAR(MAX) and NVARCHAR(MAX) data types store the text value in the record, unless the text is over 8,000 bytes, as a normal VARCHAR would. When it exceeds 8000 bytes its behaviour changes and it stores a LOB locator in the record and holds the text in a LOB elsewhere, like the older TEXT data type.
The Oracle CLOB data type can be defined to be stored within the record or have a LOB locator, along with other storage characteristics, when the object is created.

LONG Data Type

Oracle includes a LONG data type, which has been included for backward compatibility since Oracle 8, when the CLOB data type was introduced. The CLOB data type should always be used instead of a LONG.

See also: Compare Lob Data Types, Compare Numeric Data Types

Monday, 17 November 2008

CSS Common Font Families

As web users use different operating systems with different fonts installed, the font specified for a web page may not be installed, so the page may not always look as it was intended.
The CSS font-family property allows you to specify multiple fonts. The browser will try to use the first font in the list. If it is not installed it will use the next and so on.
The following lists try to replace fonts with similar fonts on Windows, Apple and Unix, or on older versions of the operating systems which may still be in use and may not have the newer fonts.

Sans Serif
Simplier fonts used mainly for on-screen text

Arial, Helvetica, sans-serif
'Arial Black', Gadget, sans-serif
Tahoma, Geneva, Arial, sans-serif
'Trebuchet MS', Helvetica, Arial, sans-serif
Verdana, Geneva, sans-serif

Serif
Roman fonts used mainly for headlines and printed material.

Georgia, Utopia, Palatino, 'Palatino Linotype', serif
Times New Roman, Times, serif

Monospace
Fixed width fonts, used mainly for computer code.

'Courier New', Courier, monospace
Lucida Console, Monaco, 'Courier New', monospace


Done.

Friday, 7 November 2008

Fade Images Even When You Don't Have Access To PhotoShop

This is a little piece of cobbled together code, that I created when I didn't have access to any decent image editing software. I wanted to fade an image to use as background, hard with just Paint!
This is a rough and ready solution.

It uses HTML and CSS opacity property, which I can then screen grab and crop.

Edit the property values as appropriate.

<html>
<head>
<style>

div {
position: absolute;
top: 0px;
left: 0px;
width: 1000px;
height: 1000px;
opacity: 0.5;
filter: alpha(opacity=50);
background: white;
}


</style>
</head>

<body>

<img src="MyImage.jpg"/>
<div>
</div>

</body>


</html>

Faded.

Monday, 27 October 2008

Prevent the Browser Scrollbar Causing Centred Content Jumping

With current trends in website design with fixed width content, leaning towards centred, rather than left aligned, content jump is being seen more often.

This is when the content grows past the bottom of the window, cauing the browser scrollbar to appear, which causes the centre position of the page to move, which in turn causes the content to quickly jump a few pixels to the left. How annoying, especially when you have spent ages getting anything else looking exactly right.

This is not a problem in IE, as the scrollbar is always displayed.

Good news is we can work around this by forcing all browsers to always display the scrollbar, like IE does. When the content is contained within the page the scrollbar will be visible, but disabled.

Simply add the following CSS to your pages and the jumping stops.

html {
overflow-y: scroll;
}

Done..

Tuesday, 14 October 2008

Java Output Formatted Date String Using SimpleDateFormat

This is another quickie simple example, as it took me ages to find what exactly what I was looking for. :-)

Using Java, I wanted to output the current date as a string, in a particular format. However (with limited knowledge) looking at the obvious starting point of java.util.Date, I couldn't find anything other than the default output.

I now know that this is because java.util.Date doesn't do this, you need to use another class, such as java.text.SimpleDateFormat.

So to output the date as year month day EG "2008-10-14"...
...
SimpleDateFormat formatNow = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("=Now=> "+formatNow.format(new Date()));
...

See here for all the format options.

Done.

Monday, 13 October 2008

Javascript to Change the Cursor Style

Changing the cursor with Javascript is pretty easy.

document.body.style.cursor = 'wait';

Changes the cursor to the wait/busy style.

document.body.style.cursor = 'default';

Changes the cursor back to the pointer.

There are several different styles you can use, however if your aiming to be compatable with older browsers you may want to do some testing.

The styles are:
  • auto
  • crosshair
  • default
  • pointer
  • help
  • move
  • text
  • wait

And the resize styles:
  • e-resize
  • w-resize
  • n-resize
  • s-resize
  • ne-resize
  • nw-resize
  • se-resize
  • sw-resize
Cursor Styled.

Thursday, 9 October 2008

Javascript Function That Returns A Select Label

This is a little example function to return the label of an html select form element, which is simple but not as immediately obvious as getting the value.

<html>

<head>
<script>
function getSelectLabel(obj)
{
return obj.options[obj.selectedIndex].text;
}
</script>
</head>

<body>

<h1>Get Select Label</h1>

<form name="testForm">

<select name="testSelect">
<option value="r">Red</option>
<option value="g">Green</option>
<option value="b">Blue</option>
</select>

<br/>
<br/>

<input type="button" onclick="alert(getSelectLabel(document.testForm.testSelect));" value="Get Label"/>
<input type="button" onclick="alert(document.testForm.testSelect.value);" value="Get Value"/>

</form>

</body>

</html>

I've also added a second button to display the value for reference.
Done...

Monday, 22 September 2008

Javascript: Read Selected Value of a Radio Group

When you first approach reading the value of a radio group input, you may be fooled into assuming it would be similar to reading a text input, IE "document.myForm.myInput.value", however radio groups are not so straight forward.

To get the value you need to loop through the radio group elements to find which one is checked, then get the value for that element.

This example contains the getRadioValue function which can be added to any page, just pass the object reference of the radio group, as the parameter.

It's also an example of how to add a radio to your form.

<html>

<head>
<script>
function getRadioValue(radioObj)
{
var radioValue = 'undefined';
for (i=0; i<radioObj.length; i++)
{
if(radioObj[i].checked==true)
{
radioValue = radioObj[i].value;
break;
}
}
return radioValue;
}
</script>
</head>

<body>

<h1>Get Radio Group Value</h1>

<form name="myForm">

<input type="radio" name="myRadioGroup" value="R">Red</input> <br/>
<input type="radio" name="myRadioGroup" value="G">Green</input> <br/>
<input type="radio" name="myRadioGroup" value="B">Blue</input> <br/>
<br/>
<input type="button" onclick="alert(getRadioValue(document.myForm.myRadioGroup));" value="Test"/>

</form>

</body>

</html>

Value Got.

Wednesday, 17 September 2008

Confirm Browser Window Close with Javascript onBeforeUnload

If you have a web page that if closed prematurly could lose users work or data, then you may want to add a prompt to confirm the user wishes to close window. Rather than the default browser behaviour of just closing or opening a new page over your page in the same window..

To to this you can add an onbeforeunload Javascript function. Please note that this doesn't work with some older browsers.

This is a basic example that should display a message when the window or tab is closed.

<html>
<head>

<script type="text/javascript">

window.onbeforeunload = function() {
return "Are you sure you wish to close this page?";
}

</script>

</head>
<body>
<h1>On Before Unload Test</h1>
</body>
</html>

This should help prevent any lost work.

Done.

Friday, 12 September 2008

Oracle DBA Script for Checking Tablespace Used/Free Space

This select statement returns tablespace information including total size, used Mb, free Mb and the number of data files used by the tablespace.

SELECT dt.tablespace_name,
dt.contents,
COUNT(DISTINCT ddf.file_id) dataFileCount,
DECODE(SUM(ddf.maxBytes),0,SUM(ddf.bytes)/(1024*1024),SUM(ddf.maxBytes)/(1024*1024)) MBTotal,
DECODE(SUM(ddf.maxBytes),0,(SUM(ddf.bytes)/(1024*1024))-(ROUND(SUM(dfs.bytes)/(1024*1024))),SUM(ddf.bytes)/(1024*1024)) MBUsed,
DECODE(SUM(ddf.maxBytes),0,ROUND(SUM(dfs.bytes)/(1024*1024)),(SUM(ddf.maxBytes)-SUM(ddf.bytes))/(1024*1024)) MBFree
FROM sys.dba_tablespaces dt,
sys.dba_data_files ddf,
( SELECT file_id,
SUM(bytes) bytes
FROM sys.dba_free_space
GROUP BY file_id ) dfs
WHERE dt.tablespace_name = ddf.tablespace_name
AND ddf.file_id = dfs.file_id(+)
GROUP BY dt.tablespace_name, dt.contents
ORDER BY 1;

Simple, but useful

Thursday, 11 September 2008

JavaScript getElementsByClass Function and Example

Why is there no standard function in Javascript for getting elements by class? Who knows, however it's not the end of the World, as it's not too difficult to write one, or copy the one I've written here.

With this function an array of elements is returned which are of the class specified. There are two further optional parameters which enable you to filter the array by tag name or only those contained within a specified parent element.

function getElementsByClass(getClass,tag,node)
{

// Set optional defaults
if (tag == null)
tag = '*';
if (node == null)
node = document;

// Load constants
const allElements2 = document.getElementsByTagName('*');
const allElements = node.getElementsByTagName(tag);
const elements = new Array();
const pattern = new RegExp("(^|\\s)"+getClass+"(\\s|$)");

// Loop allElements
var e = 0;
for (var i=0; i<allElements.length; i++)
{
if (pattern.test(allElements[i].className) ) {
elements[e] = allElements[i];
e++;
}
}

// Return elemnts array
return elements;

}

Parameters:
  • getClass: the specified class to select. (Called getClass rather than class due to reserved words in IE)
  • tag: Optional parameter to filter the returned elements by tag.
  • node: Optional parameter to filter returned elements to child elements of this parent.

Use this test page to test the function and syntax for calling it.

<html>

<head>
<script>

function getElementsByClass(getClass,tag,node)
{

// Set optional defaults
if (tag == null)
tag = '*';
if (node == null)
node = document;

// Load constants
const allElements2 = document.getElementsByTagName('*');
const allElements = node.getElementsByTagName(tag);
const elements = new Array();
const pattern = new RegExp("(^|\\s)"+getClass+"(\\s|$)");

// Loop allElements
var e = 0;
for (var i=0; i<allElements.length; i++)
{
if (pattern.test(allElements[i].className) ) {
elements[e] = allElements[i];
e++;
}
}

// Return elemnts array
return elements;

}

function alertIdForClass(c,n,t)
{
var e;
if(n)
e = getElementsByClass(c,t,document.getElementById(n));
else
e = getElementsByClass(c,t);
for(var i=0; i<e.length; i++)
alert(e[i].id);
}

</script>
</head>

<body>

<h1>Get Element By Class Test</h1>

<div id="d1" class="c1">
<div id="d2" class="c1">
<span id="s1" class="c1"></span>
<span id="s2" class="c2"></span>
<span id="s3" class="c2"></span>
</div>
</div>

<pre>
&lt;div id="d1" class="c1">
&lt;div id="d2" class="c1">
&lt;span id="s1" class="c1">&lt;/span>
&lt;span id="s2" class="c2">&lt;/span>
&lt;span id="s3" class="c2">&lt;/span>
&lt;/div>
&lt;/div>
</pre>

<input type="button" value="Class 'c1'" onClick="alertIdForClass('c1');"/><br/><br/>
<input type="button" value="Class 'c1' within Element 'd1'" onClick="alertIdForClass('c1','d1');"/><br/><br/>
<input type="button" value="Class 'c1' within Element 'd1' With 'div' Tag" onClick="alertIdForClass('c1','d1','div');"/><br/><br/>
<input type="button" value="Class 'c2'" onClick="alertIdForClass('c2');"/><br/><br/>

</body>

</html>

ElementsByClass...Got

Thursday, 4 September 2008

Java Unchecked Cast Warning

An annoyance with Java that I had resently was with the unchecked cast warning. I had placed a String List in the HttpSession, but when I ready it back I got the warning "warning: [unchecked] unchecked cast"

I knew no error would occur, as I was setting the attribute in the HttpSession in the same servlet and I didn't want loads of warnings to build up, as this can hide real warnings I need to action.

So, my original code looked like

private void foo(HttpServletRequest request) {
HttpSession session = request.getSession(true);
List myItems = (ArrayList)session.getAttribute("myItems");
for (String myItem : myItems) {
...

I found that to stop the error appearing you can suppress the warning, by adding the SupressWarnings command anywhere before the line that gives the warning.

private void foo(HttpServletRequest request) {
HttpSession session = request.getSession(true);
@SuppressWarnings("unchecked")
List myItems = (ArrayList)session.getAttribute("myItems");
for (String myItem : myItems) {
...

Putting it just before the warning line, allows unexpected warnings before to still appear. But what about afterwards? In my case it wasn't necessary as the function had no more casts, but

private void foo(HttpServletRequest request) {
HttpSession session = request.getSession(true);
List myItems = null;
{
@SuppressWarnings("unchecked")
List uncheckedList = (ArrayList)session.getAttribute("myItems");
myItems = uncheckedList;
}
for (String myItem : myItems) {
...

Placing the SuppressWarnings in brackets keeps the suppression inside, so any unexpected warnings afterwards will still appear.

I think it depends on your code as to if you need to go this far or just leave it as I did, with SuppressWarnings just before the warning line, or even place it at the top of the package.

Where is best... You Decide.

Sunday, 31 August 2008

Googlebot, fdfdkll.html and Configuring 404 Page Not Found with PHP

After checking the logs for a website, I noticed Googlebot trying to access fdfdkll.html, which definately does not exist.

I assumed Googlebot hadn't gone mad so I did some investigations and found this is Googlebot trying to establish how the site handles invalid urls.

Thinking from the search engine perspective, this is important, as if a default page is served with a status of 200, page found, the invalid url would be listed.

This is exactly what I had done. Ooops. I had set htaccess to display the homepage if the requested page didn't exist. The site is small so a specifc error page is not really needed.

It is most likely this would effect the page rank so I really needed to change the return status to 404 if the page has been reached due to an invalid url.

The homepage is PHP, so I made the following changes:

Initial .htaccess extract

ErrorDocument 404 /home.php

I changed this to

ErrorDocument 404 /home.php?error=true

And added the following code to the start of the PHP homepage.

if($_GET['error']=='true')
header("HTTP/1.0 404 Not Found");

The header command needs to come before any output is written to the page.

So now if the homepage is accessed because the url in invalid, the error parameter is set and the status returned is 404, otherwise it is 200 as normal. From the user perspective, there is no difference, the homepage is displayed as normal.

fdfdkll.html ...Done.

Thursday, 28 August 2008

PHP: Read File of Name Value Pair Parameters into an Array

This is a simple code snippet, that can be dropped into any code, to read name value pair parameters from a file into an array, so that the parameter name is the array key.

Example params.txt file extract:
height=100
width=50

PHP code snippet:
$param = array();
$paramsFile = fopen('params.txt','r');
while(!feof($paramsFile))
{
$buffer = fgets($paramsFile);
list($name,$value) = split('=',trim($buffer));
$param[$name] = $value;
}
fclose ($paramsFile);

The array can then be referenced as:
echo $param['height'];

To use the tab character as the seperator instead of (=) equals, change the list line to:
list($name,$value) = split("\t",trim($buffer));

BlogEntry=Done

Tuesday, 19 August 2008

Java Node to String Conversion

When trying to convert a org.w3c.dom.Node to a string, I initial tried using toString(). I assumed toString() would give me text content of the XML contained in the node but it was not going to be that easy today, it gave me some kind of pointer gobbledeguck back.

OK then, with some investigation, I found that toString() shouldn't really be relied upon, so I have written the following method using StringWriter and Transform methods.

import org.w3c.dom.Node;

import java.io.StringWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


private String nodeToString(Node node) {
StringWriter sw = new StringWriter();
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.transform(new DOMSource(node), new StreamResult(sw));
} catch (TransformerException te) {
System.out.println("nodeToString Transformer Exception");
}
return sw.toString();
}

Node to String...Converted.

Monday, 18 August 2008

JSP Printing Escape Characters.

During some recent developing in Java/JSP, using the c taglib, I had an issue when I wanted to print characters that JSP interprited as escape characters.

To output an attribute stored in the request I was simply using

{$myAttribute}

This worked well until the escape character came into play.

After a bit of research, I found some instructions to change the code as follows.

<c:out escapeXml='false' value='$myAttribute'/>

This now outputs the attribute exactly as it was stored.

Done.

Thursday, 14 August 2008

Javascript Simple Image Slideshow; Take 2

This Javascript image slideshow expands on the previous version, See Here.
As in the previous version, the user navigates through the slideshow using previous and next buttons, however the previous version required images to be numbered in sequence, image1.jpg image2.jpg etc, this version allows any filename. It also makes use of the image preload code, See Here, so that the images are loaded with the page, so there is no waiting for them to during the slideshow.

<html>

<head>

<script type="text/javascript">

var imageFiles=[];
var imageIndex=0;

function imageSwitch(i,d)
{
imageIndex += d;
if(imageIndex >= imageFiles.length){imageIndex=0;}
else if(imageIndex < 0){imageIndex=imageFiles.length-1}
i.src = imageFiles[imageIndex].src;
}

function imagePreload()
{
for(i=0; i<arguments.length; i++)
{
imageFiles[imageFiles.length]=new Image();
imageFiles[imageFiles.length-1].src=arguments[i];
}
}

imagePreload('your.jpg','list.jpg','of.jpg','images.jpg','goes.jpg','here.jpg');


</script>

</head>

<body>

<h1>Switch Image Source 2</h1>

<input type="button" value="Prev" onClick="imageSwitch(document.getElementById('myImg'),-1);"/>
<input type="button" value="Next" onClick="imageSwitch(document.getElementById('myImg'),1);"/>
<br/><br/>
<img id="myImg" src="image0.jpg"/>

</body>

</html>

Much better version...Done.

Wednesday, 13 August 2008

PHP Copyright Year Trick

I found this snippet on a forum recently, it's so simple and obvious I wish I'd thought of it!

A lot of web pages have a copyright notice in the footer of the page with the year included. For server side scripts, instead of just writing the year in, you can replace it with code to dynamically write the year, so it's always up to date.

For PHP:

&copy;<?php print date("Y"); ?> My Company Ltd

You can use the same idea for other server side code, such as Java or Perl.

How Simple?

Tuesday, 12 August 2008

Generating Java Map List and Displaying with JSP

This example is to demonstrate recent investigations into outputting xml data to the page using a Java HTTPServlet and JSP with a Map List.

We want to output the id, description and size for each widget from the xml, to a form in html.
The id is stored in its own element but the description and size are name value pair properties.

<?xml version="1.0" encoding="UTF-8"?>
<widgetNode>
<widget>
<id>101</id>
<properties>
<property>
<name>description</name>
<value>Thingy Bolt</value>
</property>
<property>
<name>size</name>
<value>27</value>
</property>
</widget>
<widget>
<id>102</id>
<properties>
<property>
<name>description</name>
<value>Wotsit Nut</value>
</property>
<property>
<name>size</name>
<value>11</value>
</property>
</widget>
</widgetNode>


The Java extract here loops through each widget node, adding the values we want to Map, then adds that Map to a List.
The Map List is then added, as an attribute, to the request.

(Although unintended, this is also an example for XPath, which took a little investigation too)

...
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
...

private void processWidgetXml(HttpServletRequest request, String xml) {
List<Map<String,String>> widgets = new ArrayList<Map<String,String>>();
String expression = "/widgets/widget";
try {
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList)xPath.evaluate(expression, new InputSource(new StringReader(xml)),XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
Map<String,String> widgetMap = new HashMap<String,String>();
widgetMap.put("id", (String)xPath.evaluate("id", node));
widgetMap.put("description", (String)xPath.evaluate("properties/property[name='description']/value", node));
widgetMap.put("size", (String)xPath.evaluate("properties/property[name='size']/value", node));
widgets.add(widgetMap);
}
request.setAttribute("widgets", widgets);
} catch (XPathExpressionException e) {
System.err.println("XPath expression invalid: "+e.getMessage());
e.printStackTrace();
}
}

Coming to the JSP, the following extract highlights the syntax for the Map List. It loops through all of the widgets, creating a form, with unique name, for each widget.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
<% int lc = 0; %>
<c:forEach var="widget" items="${widgets}">
<form name="myWidget<%= lc %>" action="/myActionPage" method="post">
<input type="hidden" name="widgetId" value="${widget['id']}"/>
<input type="submit" value="Select"/>
${widget['description']} : ${widget['size']}
</form>
<% lc++; %>
</c:forEach>


Done... (finally)

Monday, 11 August 2008

Javascript to Pre Load Images

Normal behaviour for images on web pages is to load them when they are required. This can sometimes create an undesirable delay whilst the user navigates around the page. Not good, and annoying for the user. To get around this problem you can load the images you know you'll use as the page loads, giving the user a smoother experience.

The following Javascript loads images into an array. This forces the images to be downloaded, so that when the page requires them the browser will automatically get them from the cache. Add this code the head section.

<script type="text/javascript">

function preloadImages()
{
var images=[];
for(i=0; i<arguments.length; i++)
{
images[images.length]=new Image();
images[images.length-1].src=arguments[i];
}
}

preloadImages('your.jpg','list.jpg','of.jpg','images.jpg','goes.jpg','here.jpg');

</script>

Obviously (I hope), change the list of images passed in the call to the preloadImages function, to your own list of images.

Image Preload... Done.

Example: Preloaded images used for a slideshow

Friday, 8 August 2008

Javascript Simple Image Slideshow

This is a simple web page using HTML and Javascript. It displays an image with Next and Previous buttons.

When one of the buttons is pressed, the image is switched with the next or previous image.

As this is a simple example, the filenames for the images are expected to be the same but with incremental numbering. EG. image0.jpg, image1.jpg ... imageN.jpg.

<html>

<head>

<script type="text/javascript">
var img = 0; // Current image

function switchImage(i,d)
{
const imgs = 5; // Total number of images
img+=d;
if(img>=imgs){img=0;}
else if(img<0){img=imgs-1}
i.src = "image"+img+".jpg";
}
</script>

</head>

<body>

<h1>Switch Image Source</h1>

<input type="button" value="Prev" onClick="switchImage(document.getElementById('myImg'),-1);"/>
<input type="button" value="Next" onClick="switchImage(document.getElementById('myImg'),1);"/>
<br/><br/>
<img id="myImg" src="image0.jpg"/>

</body>

</html>

Change the imgs constant value to the total number of images you have in you slideshow.

Done.

See also: Progressed version preloading images and using any filename

Thursday, 7 August 2008

OOBasic Export Macro Part 2: CSV

Expanding on the last blog entry, See here, using a macro to create tab delimited text files, we can modify the code easily to create csv files instead.

Replace the following two lines in the code

FileProperties(2).Value = "9,0,ANSI,1"
sExt = ".txt"

With these

FileProperties(2).Value = "44,34,ANSI"
sExt = ".csv"

This then creates a comma seperated file with .csv extention for each tab within the spreadsheet.

CSV macro... Done

Wednesday, 6 August 2008

OpenOffice Basic Macro to Create Tab Delimited Text Files

This piece of code has proven most useful today...

It's a OpenOffice Basic macro that creates a tab delimited text file for each sheet in the document. Each sheet tab name is used to name the file.

Sub CreateText

  '----------------------------------------------------------------------
  ' Save document

  Dim document As Object
  Dim dispatcher As Object
  document = ThisComponent.CurrentController.Frame
  dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
  dispatcher.executeDispatch(document, ".uno:Save", "", 0, Array())

  '----------------------------------------------------------------------
  ' Export to text files

  Dim oDoc As Object ' The current Document
  Dim oSheets As Object ' A collection of all sheets
  Dim oSheet As Object ' One specific Sheet
  Dim sNewFilename As String ' New Filename
  Dim sExt As String ' The new extension to be applied
  Dim sURL As String ' Existing URL of file
  Dim sCurrDir As String ' Current Directory
  Dim oSaveSheet As Object
  Dim FileProperties(2) As New com.sun.star.beans.PropertyValue

  oDoc = thisComponent
  oSaveSheet = oDoc.CurrentController.getActiveSheet()
  oSheets = oDoc.Sheets()
  sURL = ConvertFromURL(oDoc.getLocation())
  sCurrDir = Mid(sURL,1,Len(sURL)-Len(Dir(sURL)))

  FileProperties(0).Name = "Overwrite"
  FileProperties(0).Value = True
  FileProperties(1).Name = "FilterName"
  FileProperties(1).Value = "Text - txt - csv (StarCalc)"
  FileProperties(2).Name = "FilterOptions"
  FileProperties(2).Value = "9,0,ANSI,1"
  sExt = ".txt"

  For i = 0 to (oSheets.getCount()-1)
    oSheet = oSheets.getByIndex(i)
    sNewFilename = oSheet.getName() & sExt
    oDoc.CurrentController.setActiveSheet(oSheet)
    oDoc.storeToURL(ConvertToURL(sCurrDir & sNewFilename),FileProperties())
  Next i

  oDoc.CurrentController.setActiveSheet(oSaveSheet) ' restores the original view

  MsgBox("Text Files Created")

End Sub


OOBasic Text File Export...Done

Friday, 1 August 2008

VBScript to Delete Files Older Than 14 Days

The VBScript can be run from the Windows command line or added to a windows script. It accepts a start directory as a parameter and searches through the directory and subdirectories deleting files older than 14 days.

(You can change the 14 in the code to any number of days)

Save this code in a file called DeleteOldFiles.vbs

Sub processFolder(ByRef folder, ByVal path)
  For Each file In folder.Files
    If DateDiff("d",file.datelastmodified,Date) > 14 Then
      Wscript.Echo "Deleting: "&path&"/"&file.Name
      file.Delete
    End If
  Next
  For Each subfolder In folder.SubFolders
    processFolder subfolder, path&"/"&folder.Name
  Next
End Sub
processFolder CreateObject("Scripting.FileSystemObject").GetFolder(WScript.Arguments(0)), WScript.Arguments(0)

To run the script from the command line issue the following statement, changing c:\temp to your starting directory:

cscript.exe DeleteOldFiles.vbs c:\temp


Or for the current directory

cscript.exe DeleteOldFiles.vbs .


Files Deleted Successfully.

Thursday, 31 July 2008

Enable an HTML Form to Submit to Multiple Locations Using Javascript

This is a simple solution to allow a single html form to submit to multiple locations.

<form name="myform" method="post" action="noscriptPage.php">
...
<input type="submit" name="submit" value="Update"
onclick="document.myform.action='updatePage.php';"/>
<input type="submit" name="submit" value="Submit"
onclick="document.myform.action='submitPage.php';"/>
</form>

Add the multiple submit buttons to the form, each with a Javascript onclick clause to change the forms action property, to the desired action page.

Easy.

Optionally you can also cater for users with Javascript disabled, by adding a default action to the form, to either go to a message page, forward page that can determine the button pressed and forward.

Wednesday, 30 July 2008

Oracle Forms: Jar Icon Images

Oracle web forms uses image files for icons, by default forms will get each image individually, wasting time and bandwidth.

If you add the images to a jar file they are compressed and are loaded at startup, so each form wouldn't need to load the images each time. It also seems like a neater solution.

This code is a windows command script for creating the icon jar file.
The three parameters need to be set for each system.
  • oh = Oracle home
  • id = Icon directory
  • jf = Jar filename
@ECHO OFF

TITLE Jar Icons

SET oh="C:\Oracle\DS10"
SET id="C:\MyApp\icons"
SET jf="C:\MyApp\icons.jar"

CD /D %id%

%oh%\jdk\bin\jar -cvf %jf% *.*

TITLE Jar Icons Complete

PAUSE

You then need to place the jar file on to your web server, so the web server can access them via a URL.
If you placed the jar file in the forms/java directory you wouldn't need a path, however mixing application and forms files isn't best practice.
The URL path to the jar file needs to be added to the archive parameter in forms/server/formsweb.cfg file, so for example:

archive=frmall.jar,myIconPath/icons.jar

or in the archive_jini parameter, if you use JInitiator

archive_jini=frmall_jinit.jar,myIconPath/icons.jar

You also need to set the imagebase parameter

imagebase=codebase

You can then reference the icons by name inside the form and they should then be displayed.

Quicker, Faster, Neater Forms Icons.. Done.

Tuesday, 29 July 2008

Oracle Forms: Web.Show_Document without Toolbars

When using web deployed Oracle Forms, it was really annoying that we just wanted to display a report or popup page in the broswer without the toolbars and buttons. They weren't necessary and they cluttered up the page. The web.show_document command didn't have an option to remove them so we came up with a little routine to get around the problem and make things a little nicer.

First we added a hidden iframe, to the page forms was running on and gave it the name "hiddenframe".

<iframe name="hiddenframe" style="display:none"></iframe>

We then created a new html page, "newWindow.html".

<html>
  <body>
    <h1>Open Page in a New Window</h1>
    <script type="text/javascript">
      var thisUrl = document.location.toString();
      var newPage = thisUrl.substring(thisUrl.indexOf("?",0)+1);
      var openWin = window.open(newPage,"_blank","resizable=yes,scrollbars=yes");
      openWin.focus();
    </script>
  </body>
</html>

This page is always called into "hiddenframe" with the report page we want to display passed as a parameter. Javascript in the "newWindow.html" page then opens a new window without toolbars and buttons and calls the desired page.

So if I wanted to open a page called myReport.html in a new window without toolbars and buttons I can call web.show_document as:

WEB.SHOW_DOCUMENT('http://myserver/newWindow.html?http://myserver/pls/myReport.html', 'hiddenframe');

The user needs popup blockers turned off for this server but this shouldn't be an issue for business applications of this type.

This is not restricted to Oracle Forms, it's so simple it can be used for any web application.
You can use this test page to test the newWindow.html page. Enter the url to open in the input box.

<html>
  <body>
    <h1>New Window Test</h1>
    URL: <input id="page" type="text" value="http://www.google.com/"/>
    <input type="button" value="Open" onclick="window.open('newWindow.html?'+document.getElementById('page').value,'hiddenframe')"/>
    <iframe name="hiddenframe" style="display:none"></iframe>
  </body>
</html>

Done...

Monday, 28 July 2008

Get URL Parameters Using Javascript

Most URL parameters are read and processed by server side code, however you can access them easily by using Javascript. Add the function from this example to your page and call it, passing it the parameter name you want to read.

<html>
<head>
<script>
function getParam(name)
{
var start=location.search.indexOf("?"+name+"=");
if (start<0)
start=location.search.indexOf("&"+name+"=");
if (start<0)
return '';
start += name.length+2;
var end=location.search.indexOf("&",start)-1;
if (end<0)
end=location.search.length;
var result=location.search.substring(start,end);
var result='';
for(var i=start;i<=end;i++)
{
var c=location.search.charAt(i);
result=result+(c=='+'?' ':c);
}
return unescape(result);
}
</script>
</head>
<body>
<script>
document.write(getParam("test1")+"<br>");
document.write(getParam("test2")+"<br>");
</script>
</body>
</html>

So to test the code call it up in your browser and add the following to the end of the URL in the address bar.

?test1=Hello&test2=World

The test page will then display the parameter values

....Done.

Thursday, 24 July 2008

Java Session Cookies v HttpSession

Having written some Java Servlet code, which stored a string in a session cookie, I then looked into how HttpSession would compare and found I can get the same results with much simpler code.

My original session cookie code:

private String mySessionValue;

private String getMySessionValue(HttpServletRequest request) {
if(mySessionValue == null) {
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for(int i=0; i < cookies.length; i++) {
Cookie c = cookies[i];
if (c.getName().equals("mySessionValue")) {
mySessionValue = c.getValue();
break;
}
}
}
}
return mySessionValue;
}

private void setMySessionValue(HttpServletRequest request, HttpServletResponse response, String newCookieValue) {
mySessionValue = newCookieValue;
Cookie cookie = new Cookie("mySessionValue", newCookieValue);
response.addCookie(cookie);
}

I had several issues with the cookie version,
  • I had to loop through the cookies to find my cookie
  • As the cookie value could be referenced after it was set, I had to store it locally too, as the actual cookie would not be updated until the responce is sent.
With HttpSession the code is much much less and no need to store the value locally:

private String getMySessionValue(HttpServletRequest request) {
HttpSession session = request.getSession(true);
return (String)session.getAttribute("mySessionValue");
}

private void setMySessionValue(HttpServletRequest request, String newSessionValue) {
HttpSession session = request.getSession(true);
session.setAttribute("mySessionValue", newSessionValue);
}

As well as much less code one main advantage to HttpSession is that it handles Objects not just Strings, so arrays can be stored much easier.

HttpSession wins I think.

Wednesday, 23 July 2008

Daily Notes

Something I have found very useful over the last year is a notepad script I wrote, that creates me a new text file for each day I use it. Whether it be for little code snippets, notes taken whilst on the phone, interesting websites or whatever, the "Day Notes" shortcut on my taskbar is used alot.

I know there are many free note taking programs available but I like to keep things simple and they don't get much simplier than text files. So:

1) Create a directory where you want to keep your day note files. EG: C:\DayNotes

2) Create a text file called "DayNotes.cmd", edit it with notepad and paste in the following code and save.

@ECHO OFF

SET DAYNOTEHOME=C:\DayNotes

REM ------------------------
REM -- Set today variable

FOR /f "tokens=1,2,3 delims=/" %%A in ('echo %DATE%') DO SET TODAY=%%C-%%B-%%A.txt

IF NOT EXIST "%DAYNOTEHOME%\%TODAY%" (
ECHO %DATE% >> "%DAYNOTEHOME%\%TODAY%"
ECHO ========== >> "%DAYNOTEHOME%\%TODAY%"
)

START NOTEPAD.EXE "%DAYNOTEHOME%\%TODAY%"

3) If you want, create a shortcut and place it on your taskbar. Set the Run property to Minimized, so you don't get a command window flash up.

When you run the script it creates a text file in your day notes directory, EG: 2008-07-23.txt, if it doesn't already exist, so you can easily refer back to notes made that day.

If I can't remember when I had a particular telephone call or wrote some notes, out comes the windows search, which finds it quite quickly.


Daily Notes Script...Done.

Tuesday, 22 July 2008

Wrap Oracle PLSQL Routine

This Windows command script uses Oracle's wrap utility to wrap the contents of an entire directory. The wrapped files are placed in a sub directory is created called "Wrapped", which is created if it doesn't exist.

Copy the following code into a file called "WrapDirectory.cmd".

@ECHO OFF

TITLE Wrap Directory

SET /p src="Enter Source Directory...: "
SET ora=E:\Oracle\DB10

IF NOT EXIST %src%\Wrapped MKDIR %src%\Wrapped

TITLE Wrapping Directory

FOR %%F IN (%src%\*.sql) DO CALL %ora%\Bin\WRAP INAME=%%F ONAME=%%~dpF\Wrapped\%%~nF.plb >> %%~dpF\Wrapped\wrap.log

TITLE Wrapped Directory

PAUSE

The Oracle home directory needs to be configured to your own home, before it can be run. It can also be easily modified to wrap files other than ".sql" which it currently does.

This has been been written and tested against Oracle 10g.

PLSQL Files... Wrapped.

Monday, 21 July 2008

Consider Printing When Designing Web Pages

When designing a web site you should consider the media with which it is going to be viewed. Currently the most common is on the screen through the web browser but what is often not considered is, what does this look like when printed?

Some websites have links to printable pages but this is only necessary if you want to accommodate browsers from the dark ages. CSS2 allows 9 different media types: aural, braille, embossed, handheld, print, projection, screen, tty and tv, so the same web paged can be viewed in a style suitable for the media without the need for separate pages.

To get the best screen and print layouts for the same page, you need to add separate style sheets for screen and printing:

<link rel="stylesheet" type="text/css" href="screen.css" media="screen">
<link rel="stylesheet" type="text/css" href="print.css" media="print">

Or if you want to import:

<style type="text/css" media="print">
@import "print.css";
</style>

Or, you can specify a style sheet for multiple media:

<style type="text/css" media="screen, print">
  @import "print.css";
</style>

You can also specify within the style sheet by wrapping a style section:
<style type="text/css">
@media print {
#menu { display: none; }
}
</style>

As the screen is interactive and a print out is not, you need to consider which parts of the page should be included when printed. Sections such as menus, adverts and links are of no use on a printed page, as the user can not click on them, so you can hide them by using the display:none css property.

Also consider that the page maybe printed in black, so change highlights using colours to bold, italics or underlined.

The font may also need to be changed. Serif fonts, such as Times Roman or Garamond, are generally easier to read when printed, where as sans serif, such as Arial or Helvetica, would be used on the screen as they are simpler. The text size should be in points as pixels can sometimes give strange results.

Keep images, especially backgrounds to a minimum, the user would not thank you for using up all of their ink on a pretty background.

Friday, 18 July 2008

Backup USB Memory Stick Files

This is a little short Windows command script to enable you to backup your USB memory stick to your PC's hard drive. (Although I say USB Memory stick, it can backup any directory). You'll need to create a directory to place the script in, the backups are then placed in dated sub directories, so you can keep them as archives if you wish.

1) Decide where you want to keep your backups and create a directory.

2) Create a file called "USBbackup.cmd"

3) Edit the file using Notepad, copying the following text into the file.

@echo off
title USB BackUp
cd /d %~dp0
set /p BACKUPDIR="Enter drive letter to be backed up..: "

REM ====
REM Check if backup source is drive or directory

if exist "%BACKUPDIR%:\." ( set BACKUPDIR=%BACKUPDIR%:
) else ( if not exist %BACKUPDIR%\. goto :error )

REM ====
REM Create sub directory based on date.

for /f "tokens=1,2,3 delims=/" %%A in ('echo %DATE%') do set TARGET=Backup-%%C%%B%%A
for /f "tokens=1,2,3 delims=:." %%A in ('echo %TIME%') do set TARGET=%TARGET%-%%A%%B%%C

REM ====
REM Check source and target

echo Backing up from drive "%BACKUPDIR%" to "%TARGET%"
pause

REM ====
REM Create target directory and backup files

title Backing up files
if not exist ".\%TARGET%\." mkdir .\%TARGET%
XCOPY /E "%BACKUPDIR%\*.*" ".\%TARGET%\"

REM ====
REM Finalise

title Usb Backup Complete
goto :end
:error
title Usb Backup Error
echo Unable to locate drive "%BACKUPDIR%"
:end
pause

4) Save the file and run it by double clicking on it.

5) When prompt, enter the drive letter or directory to be backed up.


USB Stick backup... Done.

Thursday, 17 July 2008

Some Amusing Javascript

Found this little Javascript feature in a forum.
Call up any web page and then paste the following code into the address bar.

javascript: document.body.contentEditable = 'true'; document.designMode = 'on'; void 0

You can then have a bit of fun editing the page.
Weird!

Wednesday, 16 July 2008

Create a Windows Show Desktop Icon

After installing Windows 2003 Server, I found that it doesn't have a show desktop shortcut on the taskbar, which was annoying as I found this useful on XP. So I spent a few minutes to create one with these few steps.

1) Create a file "Show Desktop.scf" in the windows folder.

2) Edit the new file in notepad, adding the following lines:

[Shell]
Command=2
IconFile=explorer.exe,3
[Taskbar]
Command=ToggleDesktop

3) Create a shortcut and add it to the taskbar.

Show Desktop icon, created.

Tuesday, 15 July 2008

Check If Your Hosted Website Has Access To PHP

When I built my hosted website I didn't intend to use PHP, so later on when I wanted to try out some PHP, I wasn't sure if I had access to use it and if I did which version of PHP it was.
I had to do a lot of searching to find out how to check if I had PHP or not, but when I found a solution I realised that it was probably because it is very simple. But these things are only simple if you know, so..

1) Create a file on your website called phpinfo.php (Can be called anything but should end with .php)
2) The file should contain one line:

<?php phpinfo(); ?>

3) Call the file through your web browser. If you have PHP it should come back with the version details.

Done...

Monday, 14 July 2008

Javascript Random Numbers

OK then, to generate a random number in javascript you need to use

Math.random()

which produces a number between 0 and 1.

I began by multipling the random number by the maxmium value to give a random number in a range. So for a number between 0 and 4,

Math.round(Math.random()*4)

However this will not give you a true result for 0 or 4, as the rounding for 0 and 4 will have half the possible candidate random numbers.
So another possibility is to use floor instead of round and use the range instead of the max value.

Math.floor(Math.random()*5)

This is floored in principle if the random number returns 1, which is a 1 in a gazillion billion chance. (Haven't actually proved 1 is a possible return value, but assuming it is) So to be clinically correct we can mod the result to always give a value answer.

Math.floor(Math.random()*5)%5

And an example to prove the spread of random numbers:

<html><body>
<script>
var a = new Array(0,0,0,0,0);
for(i=0;i<1000;i++)
{
a[Math.floor(Math.random()*5)%5] += 1;
}
document.write(a+"<br/>");
document.write("Total: "+(a[0]+a[1]+a[2]+a[3]+a[4]));
</script>
</body></html>

Javascript Random Numbers ... Done.

Friday, 11 July 2008

Welcome

This blog is my jotter for making notes for niffty software development routines I create or find, things of interest I discover on the web or general stuff that may I may want to remember for later, or just anything I fancy writing :-)

If you're not me and are here searching for something, I hope this has been useful or somehow interesting.