<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>John's Technology Blog</title>
<link>http://www.asp.johnavis.com/</link>
<description>My blog including my Classic ASP tips and tricks, sample scripts, and applications, plus JavaScript; and my experiences with buying and selling on eBay and using PayPal.</description>
<item>
<title>Classic ASP Master Pages</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=456</link>
<description>When developing websites most of them have common elements like headers, footers and navigational menus that are common throughout the site. In Classic ASP we mostly use multiple server side includes to accomplish this. I like ASP.NET master pages and this page details the technique I use to achieve something similar in Classic ASP.&lt;br&gt;&lt;br&gt;My technique involves creating a master page ASP file which has all the common elements. Where ever I want content to be added I add a call to a sub routine.&lt;br&gt;&lt;br&gt;Then in my content pages when I want the master page content to be added I include it at that point. I then create a sub routine for each content area and put my content inside these. You can have as many content sub routines as required but they must exist in every content page that uses that master page.&lt;br&gt;&lt;br&gt;The following example shows a master page and content page with two content areas: one inside the head tag (called HeadPlaceHolder in this example) so I can easily add title and meta tags, and add JavaScript and CSS within the head tag if required; and one inside the body tag between the header and footer (called ContentPlaceHolder in this example).&lt;br&gt;&lt;br&gt;Any ASP variables that are needed by more than one content area should be created outside of the sub routines.&lt;br&gt;&lt;br&gt;&lt;b&gt;masperpage.asp&lt;/b&gt;&lt;br&gt;&lt;br&gt;&amp;lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.0 Transitional//EN&quot; &quot;http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;html xmlns=&quot;http://www.w3.org/1999/xhtml&quot;&amp;gt;&lt;br&gt;&amp;lt;head&amp;gt;&lt;br&gt;	&amp;lt;% Call HeadPlaceHolder() %&amp;gt;&lt;br&gt;	&amp;lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;stylesheet.css&quot; /&amp;gt;&lt;br&gt;	&amp;lt;script language=&quot;javascript&quot; type=&quot;text/javascript&quot; src=&quot;/java.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;br&gt;&amp;lt;/head&amp;gt;&lt;br&gt;&amp;lt;body&amp;gt;&lt;br&gt;		&amp;lt;div id=&quot;header&quot;&amp;gt;&lt;br&gt;			Logo etc etc&lt;br&gt;		&amp;lt;/div&amp;gt;&lt;br&gt;		&amp;lt;div id=&quot;content&quot;&amp;gt;&lt;br&gt;			&amp;lt;% Call ContentPlaceHolder() %&amp;gt;&lt;br&gt;		&amp;lt;/div&amp;gt;&lt;br&gt;		&amp;lt;div id=&quot;footer&quot;&amp;gt;&lt;br&gt;			Copyright etc etc&lt;br&gt;		&amp;lt;/div&amp;gt;&lt;br&gt;&amp;lt;/body&amp;gt;&lt;br&gt;&amp;lt;/html&amp;gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;content-page.asp&lt;/b&gt;&lt;br&gt;&lt;br&gt;&amp;lt;%&lt;br&gt;'Any ASP variables that are needed by multiple contenbt areas should be declared&lt;br&gt;'outside of the sub routines&lt;br&gt;'It's a good practice to keep your ASP script at the beginning of your pages anyway&lt;br&gt;%&amp;gt;&lt;br&gt;&amp;lt;!--#include virtual=&quot;masterpage.asp&quot;--&amp;gt;&lt;br&gt;&amp;lt;% Sub HeadPlaceHolder() %&amp;gt;&lt;br&gt;&lt;br&gt;	&amp;lt;title&amp;gt;My page title&amp;lt;/title&amp;gt;&lt;br&gt;	&amp;lt;meta content=&quot;My meta description&quot; name=&quot;description&quot; /&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;% End Sub %&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;% Sub ContentPlaceHolder() %&amp;gt;&lt;br&gt;&lt;br&gt;	&amp;lt;p&amp;gt;My page content goes here&amp;lt;/p&amp;gt;&lt;br&gt;&lt;br&gt;&amp;lt;% End Sub %&amp;gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=456&amp;comments=on#comments</comments>
<pubDate>2010-03-27T12:00:00+10:00</pubDate>
</item>
<item>
<title>CAPCTHAs and alternatives</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=449</link>
<description>CAPTCHAs and their alternatives&lt;br&gt;&lt;br&gt;The basic purpose of a CAPTCHA (Completely Automatic Public Turing Test to Tell Computers and Humans Apart) is to prevent robots from submitting web forms. By displaying an image that cannot be &#8220;read&#8221; by a computer, only a human can submit the form successfully.&lt;br&gt;&lt;br&gt;Most of the time CAPTCHAs are an irritation to legitimate users, and can also cause issues with accessibility unless an alternative audible version of the text or numbers in the CAPTCHA image is also available.&lt;br&gt;&lt;br&gt;My technique for CAPTCHA in Classic ASP is an image only system which works like this:&lt;br&gt;&lt;br&gt;When the form is displayed two random numbers are generated. One is used as the number that is generated into an image. The other random number is used as the name of a session variable which contains the first number. This second number is stored as a hidden form field (it doesn't matter that this second number can be easily read). I use a random session variable name so as not to cause problems if the user has multiple forms open, this way each should submit without any conflict. On form submit the script gets the hidden field value and then the value of this session variable and compares this with the user's input. If they don't match the user (or robot) is redirected back to the form and two new random numbers are generated so the process repeats.&lt;br&gt;&lt;br&gt;A more simple but less effective system that prevents some robots but avoids irritation to legitimate users and accessibility problems is to use a system similar to the CAPTCHA system described above where a random number is generated and added as a hidden form field and also stored in a session variable. On form post the hidden form field value is compared with the session variable value and succeeds only if they match. This means that the form can only be submitted if the user or robot visited the web form page first and posts the random number. This prevents robots that just simulate a form post to a URL from succeeding but doesn't stop the robot if they are willing to retrieve the web form page each time they post a form.&lt;br&gt;&lt;br&gt;An improvement to this system would be to use a JavaScript script to add the value to the hidden field. Again, this would be fairly easy for a robot to simulate but would mean that the robot would have to be written specifically for the site.&lt;br&gt;&lt;br&gt;Creating an image or audible CAPTCHA is not possible for some websites, for example in Classic ASP it usually requires a third party server component (although it can be done without one). There are other alternatives.&lt;br&gt;&lt;br&gt;* Instead of creating a random number you could generate a random mathematical question, for example:&lt;br&gt;&lt;br&gt;What is five plus twenty-three?&lt;br&gt;&lt;br&gt;* You could create a database of random questions and answers, for example:&lt;br&gt;&lt;br&gt;Which is not a colour? Blue, green, apple, orange or black?&lt;br&gt;&lt;br&gt;* You could show several photos of different objects and ask the visitor to pick which picture contains a certain object.&lt;br&gt;&lt;br&gt;All of these techniques are easy to produce using a system similar to what I described near the beginning of this article.&lt;br&gt;&lt;br&gt;* You could create a textbox and set it to display:none in CSS. A robot is likely to fill this field so you can void any form submissions where this textbox is not empty.&lt;br&gt;&lt;br&gt;* Dynamically change the names of form fields and store their new names in a session variable.</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=449&amp;comments=on#comments</comments>
<pubDate>2009-12-09T12:00:00+10:00</pubDate>
</item>
<item>
<title>Obtaining Image Properties in ASP Without a Component</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=448</link>
<description>You don't need a third party server component to determine the height and width of an image file in Classic ASP. The LoadPicture command will load an image into and object and you can then access the height and width, such as in this example:&lt;br&gt;&lt;br&gt;&lt;pre&gt;Dim objImage&lt;br&gt;Set objImage = LoadPicture(Server.MapPath(&quot;image.ext&quot;)) &lt;br&gt;intWidth = objImage.Width&lt;br&gt;intHeight = objImage.Height&lt;/pre&gt;&lt;br&gt;4GuysFromRoll.com has an interesting article on resizing images without a component - see &lt;a href=http://www.4guysfromrolla.com/webtech/050300-1.shtml&gt;http://www.4guysfromrolla.com/webtech/050300-1.shtml&lt;/a&gt;.&lt;br&gt;&lt;br&gt;The following link contains some other code that can access image properties as well as modify GIF images: &lt;a href=http://www.u229.no/stuff/&gt;http://www.u229.no/stuff/&lt;/a&gt;.&lt;br&gt;&lt;br&gt;Lastly the following link is for a component-less CAPTCHA genertor which generates a BMP image from ASP.It may help you understand how to create and modify BMP files. See &lt;a href=http://www.tipstricks.org/&gt;http://www.tipstricks.org/&lt;/a&gt;.</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=448&amp;comments=on#comments</comments>
<pubDate>2009-12-09T12:00:00+10:00</pubDate>
</item>
<item>
<title>Classic ASP Title Case</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=416</link>
<description>Need a function that capitalises the first letter of each word, as used in titles?
&lt;p&gt;
Unlike some title case functions, this one doesn't just look for spaces, but capitilises the first letter after each non-alpha character which means that hyphenated words and words after numbers are considered new words.
&lt;p&gt;
&lt;textarea cols=40 rows=10 style=&quot;width:95%&quot;&gt;Function TitleCase(byVal strValue)
       Dim intChat, intLastChar, blnUCase
       strValue = LCase(strValue)
       For intChar = 1 To Len(strValue)
              blnUCase = False
              If intChar = 1 Then
                     blnUCase = True
              Else
                     intLastChar = Asc(Mid(strValue, intChar - 1, 1))
                     If Not ((intLastChar &gt;= 97 And intLastChar &lt;= 122) Or (intLastChar &gt;= 65 And intLastChar &lt;= 90)) Then
                           blnUCase = True
                     End If
              End If
              If blnUCase Then
                     strValue = Left(strValue, intChar - 1) &amp; UCase(Mid(strValue, intChar, 1)) &amp; Mid(strValue, intChar + 1)
              End If 
       Next
       TitleCase = strValue
End Function&lt;/textarea&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=416&amp;comments=on#comments</comments>
<pubDate>2008-07-29T12:00:00+10:00</pubDate>
</item>
<item>
<title>SQL Injection Protection - b.js</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=407</link>
<description>There is a automated SQL injection attack doing the rounds at the moments which injects some html (&amp;lt;script src=http://www.domain.com/b.js&amp;gt;&amp;lt;/script&amp;gt;) into certain fields in all the tables in a database.
&lt;p&gt;
If you have been attacked don't feel bad as an Internet search of &quot;b.js&quot; reveals tens of thousands of hacked sites.
&lt;p&gt;
The attack cleverly appends a series of SQL commands onto  your querystrings and if your code is unprotected, and you don't use Access databases, the commands may be passed on to your SQL server and the damage done.
&lt;p&gt;
Considering the damage that could be done by this sort of attack, I guess we are lucky that they chose only to append their little JavaScript.
&lt;p&gt;
However, this attack could render your website as &quot;unsafe&quot; in search engine results.
&lt;p&gt;
&lt;strong&gt;Reversing the Damage&lt;/strong&gt;
&lt;p&gt;
We are also extremely fortunate that the changes can be easily reversed with a few changes of the attackers original SQL commands.
&lt;p&gt;
Simply execute the following to clean up the damage. If you have been attacked multiple times (ie. you have multiple script blocks appended to your SQL data) then you will need to execute the following script for each attack.
&lt;p&gt;
&lt;textarea cols=5 rows=10 style=&quot;width:95%&quot;&gt;DECLARE @T varchar(255), @C varchar(255);
DECLARE Table_Cursor CURSOR FOR
SELECT a.name, b.name
FROM sysobjects a, syscolumns b
WHERE a.id = b.id AND a.xtype = 'u' AND
(b.xtype = 99 OR
b.xtype = 35 OR
b.xtype = 231 OR
b.xtype = 167);
OPEN Table_Cursor;
FETCH NEXT FROM Table_Cursor INTO @T, @C;
WHILE (@@FETCH_STATUS = 0) BEGIN
  EXEC(
    'update ['+@T+'] set ['+@C+'] = left(
            convert(varchar(8000), ['+@C+']),
            len(convert(varchar(8000), ['+@C+'])) - 6 -
            patindex(''%tpircs&lt;%'',
                      reverse(convert(varchar(8000), ['+@C+'])))
            )
      where ['+@C+'] like ''%&lt;script%&lt;/script&gt;'''
      );
  FETCH NEXT FROM Table_Cursor INTO @T, @C;
END;
CLOSE Table_Cursor;
DEALLOCATE Table_Cursor;&lt;/textarea&gt;
&lt;p&gt;
&lt;strong&gt;Protecting against attacks&lt;/strong&gt;
&lt;p&gt;
There are many methods out there to protect against this sort of attack.
&lt;p&gt;
The best method is to ensure that you protect every value that you pass to SQL. Strings should have a function to replace single quotes with two single quotes. Numbers should have a function that forces them to a numeric value. 
&lt;p&gt;
The following function is a simple method which removes multiple inline SQL commands. If you issue multiple inline SQL commands in one go then obviously it will not be suitable but for everyone else it should stop any attack. This will not protect against all types of attacks however.
&lt;p&gt;
Parse your SQL command strings with the following syntax:
&lt;p&gt;
&lt;i&gt;SQLCheck(sql-string-here)&lt;/i&gt;
&lt;p&gt;
&lt;textarea cols=5 rows=10 style=&quot;width:95%&quot;&gt;Function SQLCheck(strCommand)
	Dim intChar
	Dim blnQuotes
	SQLCheck = strCommand
	blnQuotes = False
	For intChar = 1 To Len(strCommand)
		If Mid(strCommand, intChar, 1) = &quot;'&quot; Then
			If Not blnQuotes Then
				blnQuotes = True
			Else
				If intChar &lt; Len(strCommand) Then
					If Mid(strCommand, intChar + 1, 1) = &quot;'&quot; Then
						intChar = intChar + 1
					Else
						blnQuotes = False
					End If
				End If

			End If
		ElseIf Mid(strCommand, intChar, 1) = &quot;;&quot; Then
			If Not blnQuotes Then
				SQLCheck = Left(strCommand, intChar - 1)
				Exit For
			End If
		End If
	Next
End Function&lt;/textarea&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=407&amp;comments=on#comments</comments>
<pubDate>2008-06-30T12:00:00+10:00</pubDate>
</item>
<item>
<title>VBScript: Suggested Prefixes for Indicating the Data Type of a Variable</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=387</link>
<description>&lt;table&gt; &lt;tr&gt; &lt;th&gt; &lt;p&gt;Data Type&lt;/p&gt; &lt;/th&gt; &lt;th&gt; &lt;p&gt;Prefix&lt;/p&gt; &lt;/th&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;ADO command&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;cmd&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;ADO connection&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;cnn&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;ADO field&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;fld&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;ADO parameter&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;prm&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;ADO recordset&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;rst&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Boolean&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;bln&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Byte&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;byt&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Collection object&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;col&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Currency&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;cur&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Date-time&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;dtm&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Double&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;dbl&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Error&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;err&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Integer&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;int&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Long&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;lng&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Object&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;obj&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Single&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;sng&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;String&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;str&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;User-defined type&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;udt&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;tr&gt; &lt;td&gt; &lt;p&gt;Variant&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;td&gt; &lt;p&gt;vnt&lt;br /&gt; &lt;/p&gt; &lt;/td&gt; &lt;/tr&gt; &lt;/table&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=387&amp;comments=on#comments</comments>
<pubDate>2008-05-03T12:00:00+10:00</pubDate>
</item>
<item>
<title>Handling Boolean Fields in MS-SQL and MS-Access</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=385</link>
<description>If you have migrated from Microsoft Access to SQL Server or MySql then you have probably encountered the differences with boolean values.
&lt;p&gt;
In Microsoft Access you can use true or false in queries, for example:
&lt;p&gt;
&lt;pre&gt;SELECT * FROM tablename WHERE booleanfield=TRUE
SELECT * FROM tablename WHERE booleanfield=FALSE&lt;/pre&gt;
SQL Server/MySql require a different approach:
&lt;p&gt;
&lt;pre&gt;SELECT * FROM tablename WHERE booleanfield=1
SELECT * FROM tablename WHERE booleanfield=0&lt;/pre&gt;
The Microsoft Access query will not work in SQL Server/MySql and the SQL Server/MySql query will not work in Access as it treats true as -1 not 1.
&lt;p&gt;
For a cross-platform solution you can use the following:
&lt;p&gt;
&lt;pre&gt;SELECT * FROM tablename WHERE booleanfield&lt;&gt;0
SELECT * FROM tablename WHERE booleanfield=0&lt;/pre&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=385&amp;comments=on#comments</comments>
<pubDate>2008-05-03T12:00:00+10:00</pubDate>
</item>
<item>
<title>VBScript Class to Send Mail With CDOSYS</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=350</link>
<description>This is my first attempt at a VBScript class.

&lt;p&gt;It allows full control of the CDO Message object.

&lt;p&gt;You can create a new instance of the class with the following:

&lt;pre&gt;Dim clsSendMail
Set clsSendMail = New SendMail&lt;/pre&gt;

&lt;p&gt;Then add the various properties of the email:

&lt;pre&gt;clsSendMail.SendTo = &quot;test@test.com&quot;
clsSendMail.From = &quot;test@test.com&quot;
clsSendMail.Subject = &quot;Test Message&quot;&lt;/pre&gt;

&lt;p&gt;Next you choose what type of mail you are going to send: plain text, HTML or from a URL or local file.

&lt;p&gt;&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Property/Method&lt;/th&gt;
&lt;th&gt;Syntax&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain Text&lt;/td&gt;
&lt;td&gt;TextBody&lt;/td&gt;
&lt;td&gt;clsSendMail.TextBody = &quot;message here&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTML&lt;/td&gt;
&lt;td&gt;HTMLBody&lt;/td&gt;
&lt;td&gt;clsSendMail.HTMLBody = &quot;&amp;lt;b&amp;gt;html&amp;lt;/b&amp;gt; message here&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URL&lt;/td&gt;
&lt;td&gt;CreateMHTMLBody&lt;/td&gt;
&lt;td&gt;Call clsSendMail.CreateMHTMLBody &quot;http://www.url.com/pagename.htm&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local File&lt;/td&gt;
&lt;td&gt;CreateMHTMLBody&lt;/td&gt;
&lt;td&gt;Call clsSendMail.CreateMHTMLBody &quot;file://c:/mydocuments/test.htm&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;To add an attachment use the following:

&lt;pre&gt;Call clsSendMail.AddAttachment &quot;c:\mydocuments\test.txt&quot;&lt;/pre&gt;

&lt;p&gt;To embed files in your message use:

&lt;pre&gt;clsSendMail.AddRelatedBodyPart &quot;/older/imagefile.gif&quot;, &quot;image1.gif&quot;&lt;/pre&gt;

&lt;p&gt;This can then be included in your HTMLBody by referencing the second parameter as the CID, for example:

&lt;pre&gt;&amp;lt;img src=&quot;cid:image1.gif&quot;&amp;gt;&lt;/pre&gt;

&lt;p&gt;You can also set a SMTP server and port if neccessary:

&lt;pre&gt;Call clsSendMail.SMTPServer &quot;mail.test.com&quot;, 25&lt;/pre&gt;


&lt;p&gt;&lt;b&gt;SendMail Class&lt;/b&gt;

&lt;form&gt;&lt;textarea cols=&quot;50&quot; rows=&quot;20&quot; style=&quot;width:95%&quot;&gt;Class SendMail

	Private objCDOMessage
	Private objCDOBodyPart

	Private strTo
	Private strFrom
	Private strCC
	Private strBCC
	Private strSubject
	Private strTextBody
	Private strHTMLBody

	Private Sub Class_Initialize()
		Set objCDOMessage = CreateObject(&quot;CDO.Message&quot;)
	End Sub

	Private Sub Class_Terminate()
		Set objCDOMessage = Nothing
	End Sub

	Public Property Let SendTo(strParam1)
		strTo = strParam1
	End Property

	Public Property Let From(strParam1)
		strFrom = strParam1
	End Property

	Public Property Let CC(strParam1)
		strCC = strParam1
	End Property

	Public Property Let BCC(strParam1)
		strBCC = strParam1
	End Property

	Public Property Let Subject(strParam1)
		strSubject = strParam1
	End Property

	Public Property Let TextBody(strParam1)
		strTextBody = strParam1
	End Property

	Public Property Let HTMLBody(strParam1)
		strHTMLBody = strParam1
	End Property

	Public Sub CreateMHTMLBody(strParam1)
		objCDOMessage.CreateMHTMLBody strParam1
	End Sub

	Public Sub AddAttachment(strParam1)
		objCDOMessage.AddAttachment strParam1
	End Sub

	Public Sub SMTPServer(strParam1, intParam2)
		objCDOMessage.Configuration.Fields.Item(&quot;http://schemas.microsoft.com/cdo/configuration/sendusing&quot;) = 2
		objCDOMessage.Configuration.Fields.Item(&quot;http://schemas.microsoft.com/cdo/configuration/smtpserver&quot;) = strParam1
		objCDOMessage.Configuration.Fields.Item(&quot;http://schemas.microsoft.com/cdo/configuration/smtpserverport&quot;) = intParam2
		objCDOMessage.Configuration.Fields.Update
	End Sub

	Public Sub AddRelatedBodyPart(strParam1, strParam2)
		Set objCDOBodyPart = objCDOMessage.AddRelatedBodyPart(Server.MapPath(strParam1), strParam2, 1)
		objCDOBodyPart.Fields.Item(&quot;urn:schemas:mailheader:Content-ID&quot;) = &quot;&lt;&quot; &amp; strParam2 &amp; &quot;&gt;&quot;
		objCDOBodyPart.Fields.Update
	End Sub

	Public Sub Send()
		If strTo &lt;&gt; &quot;&quot; Then objCDOMessage.To = strTo
		If strFrom &lt;&gt; &quot;&quot; Then objCDOMessage.From = strFrom
		If strCC &lt;&gt; &quot;&quot; Then objCDOMessage.CC = strCC
		If strBCC &lt;&gt; &quot;&quot; Then objCDOMessage.BCC = strBCC
		If strSubject &lt;&gt; &quot;&quot; Then objCDOMessage.Subject = strSubject
		If strTextBody &lt;&gt; &quot;&quot; Then objCDOMessage.TextBody = strTextBody
		If strHTMLBody &lt;&gt; &quot;&quot; Then objCDOMessage.HTMLBody = strHTMLBody
		objCDOMessage.Send
	End Sub
End Class&lt;/textarea&gt;&lt;/form&gt;
</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=350&amp;comments=on#comments</comments>
<pubDate>2008-02-16T12:00:00+10:00</pubDate>
</item>
<item>
<title>Embed Images in CDOSYS messages</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=349</link>
<description>When sending HTML email messages using CDOSYS you can embed images as per the following sample:

&lt;form&gt;&lt;textarea cols=&quot;10&quot; rows=&quot;10&quot; style=&quot;width:95%;&quot;&gt;Const CdoReferenceTypeName = 1
Dim objCDO, objBP
Set objCDO = Server.CreateObject(&quot;CDO.Message&quot;)
objCDO.MimeFormatted = True
objCDO.To = &quot;to@domain.com&quot;
objCDO.From = &quot;from@domain.com&quot;
objCDO.Subject = &quot;Subject&quot;
objCDO.HTMLBody = &quot;&lt;html&gt;This image is embedded &lt;img src=&quot;&quot;cid:image.gif&quot;&quot;&gt;&lt;/html&gt;&quot;
Set objBP = objCDO.AddRelatedBodyPart(Server.MapPath(&quot;image.gif&quot;), &quot;image.gif&quot;, CdoReferenceTypeName)
objBP.Fields.Item(&quot;urn:schemas:mailheader:Content-ID&quot;) = &quot;&lt;image.gif&gt;&quot;
objBP.Fields.Update
objCDO.Send&lt;/textarea&gt;&lt;/form&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=349&amp;comments=on#comments</comments>
<pubDate>2008-02-16T12:00:00+10:00</pubDate>
</item>
<item>
<title>Thumbnails Using ASPImage</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=291</link>
<description>This script creates thumbnails for images using Server Object's ASPImage (see &lt;a href=&quot;http://www.serverobjects.com/&quot;&gt;www.serverobjects.com&lt;/a&gt;).
&lt;p&gt;
You specify the image path and filename and the height and width required and this script will return the resized image.
&lt;p&gt;
The original aspect ratio of the image will allways be retained and whitespace will be added if neccessary to ensure that the dimensions are as requested.
If there is any whitespace then you can choose to have the image centred or left/right aligned or top/bottom aligned.
&lt;p&gt;
Call the script using the following syntax:
&lt;p&gt;
&lt;b&gt;&amp;lt;img src=&quot;thumbnail.asp?src=image.jpg&amp;align=center&amp;valign=center&amp;height=100&amp;width=75&quot;&amp;gt;&lt;/b&gt;
&lt;p&gt;
Where &lt;i&gt;src&lt;/i&gt; is the path and filename (URL Encoded),
&lt;i&gt;align&lt;/i&gt; is the horizontal alignment (left, center or right, default is left),
&lt;i&gt;valign&lt;/i&gt; is the vertical alignment (top, center or bottom, default is top),
&lt;i&gt;height&lt;/i&gt; is the height in pixels, and
&lt;i&gt;width&lt;/i&gt; is the width in pixels.
&lt;p&gt;
&lt;b&gt;thumbnail.asp&lt;/b&gt;
&lt;p&gt;
&lt;form&gt;&lt;textarea name=&quot;script&quot; cols=&quot;50&quot; rows=&quot;15&quot; style=&quot;width:95%&quot;&gt;
&lt;%
'Get image filename
strImage = Request(&quot;src&quot;)
'Get request characteristics
If IsNumeric(Request(&quot;width&quot;)) Then
	intWidth = CInt(Request(&quot;width&quot;))
Else
	intWidth = 0
End If
If intWidth = 0 Then intWidth = 100 'default size is 100x100
If IsNumeric(Request(&quot;height&quot;)) Then
	intHeight = CInt(Request(&quot;height&quot;))
Else
	intHeight= 0
End If
If intHeight= 0 Then intHeight = intWidth
strAlign = LCase(Request(&quot;align&quot;))
If strAlign = &quot;&quot; Then strAlign = &quot;left&quot; 'default align is left
strVAlign = LCase(Request(&quot;valign&quot;))
If strVAlign = &quot;&quot; Then strVAlign = &quot;top&quot; 'default align is top
Set Image = Server.CreateObject(&quot;AspImage.Image&quot;)
Image.PadSize = 0
Image.LoadImage(Server.MapPath(strImage))
Image.ImageFormat = 1
Response.ContentType = &quot;image/jpeg&quot;
'Set your image quality here
Image.JPEGQuality = 30
intXSize = Image.MaxX
intYSize = Image.MaxY
If (intWidth / intXSize) &gt; (intHeight / intYSize) Then
	intXSize = Int( (intHeight / Image.MaxY) * Image.MaxX )
	intYSize = intHeight
	Image.ResizeR intXSize, intYSize
	If strAlign = &quot;center&quot; Then
		intPadding = Int((intWidth - Image.MaxX) / 2)
		Image.CropImage -(intPadding), 1, Image.MaxX + intPadding, Image.MaxY
	ElseIf strAlign = &quot;right&quot; Then
		intPadding = Int(intWidth - Image.MaxX)
		Image.CropImage -(intPadding), 1, Image.MaxX + intPadding, Image.MaxY
	End If
	Image.MaxX = intWidth
Else
	intXSize = intWidth
	intYSize = (intWidth / Image.MaxX) * Image.MaxY
	Image.ResizeR intXSize, intYSize
	If strVAlign = &quot;center&quot; Then
		intPadding = Int((intHeight - Image.MaxY) / 2)
		Image.CropImage 1, -(intPadding), Image.MaxX, Image.MaxY + intPadding
	ElseIf strVAlign = &quot;bottom&quot; Then
		intPadding = Int(intHeight - Image.MaxY)
		Image.CropImage 1, -(intPadding), Image.MaxX, Image.MaxY + intPadding
	End If
	Image.MaxY = intHeight
End If
Response.BinaryWrite Image.Image
set Image = nothing
%&gt;&lt;/textarea&gt;&lt;/form&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=291&amp;comments=on#comments</comments>
<pubDate>2007-10-25T12:00:00+10:00</pubDate>
</item>
<item>
<title>Converting Numbers To Shorter/Easier to Convey Strings</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=285</link>
<description>These functions convert a number into a shorter string containing both text and numbers.&lt;p&gt;Possible uses include where you may have a reference number that you don't want to look as if it is sequential and easy to pick, and/or if you want a refernce number that people could quote easier (because it is shorter).&lt;p&gt;Basically, it converts the number into a Base-34 number (similar to hexadecimal which is Base-16, or binary which is Base-2. Decimal is Base-10).&lt;p&gt;An advantage to this script, however, is that it doesn't use the letter 'I' or the letter 'O' so there can never been any confusion between them and the numbers one and zero.&lt;p&gt;To call the script simply use &lt;i&gt;ToBase34(your-number)&lt;/i&gt; to convert to the string, then &lt;i&gt;FromBase34(converted-string)&lt;/i&gt; to convert back to decimal.&lt;p&gt;&lt;form name=&quot;script&quot;&gt;&lt;textarea cols=&quot;50&quot; rows=&quot;15&quot; style=&quot;width:95%;&quot;&gt;Function ToBase34(lngDec)
	Dim strCharset
	Dim intRem
	Dim lngQuot
	Dim intDen
	Dim lngOut
	strCharset = &quot;0123456789ABCDEFGHJKLMNPQRSTUVWXYZ&quot;
	lngQuot = lngDec
	Do
		intRem = lngQuot - (Int(lngQuot / 34) * 34)
		lngQuot = Int(lngQuot / 34)
		strOut = strOut &amp; Mid(strCharset, intRem + 1, 1)
	Loop Until lngQuot = 0
	ToBase34 = StrReverse(strOut)
End Function

Function FromBase34(strBase34)
	Dim strCharset
	Dim strChar
	Dim intChar
	Dim lngOut
	strCharset = &quot;0123456789ABCDEFGHJKLMNPQRSTUVWXYZ&quot;
	strInput = StrReverse(strBase34)
	For intChar = 0 To Len(strInput) - 1
		strChar = Mid(strInput, intChar + 1, 1)
		lngOut = lngOut + ((InStr(strCharset, strChar) - 1) * (34 ^ intChar))
	Next
	FromBase34 = lngOut
End Function&lt;/textarea&gt;&lt;/form&gt;&lt;p&gt;&lt;a href=&quot;/blog/uploads/att285_sample.asp&quot;&gt;Show sample&lt;/a&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=285&amp;comments=on#comments</comments>
<pubDate>2007-10-18T12:00:00+10:00</pubDate>
</item>
<item>
<title>Determining a vistor's country or location</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=279</link>
<description>There are several ways to determine a vistor's country through ASP.&lt;br&gt;&lt;br&gt;The most reliable method is to look up the visitor's IP address and determine the country from that but that requires a large IP address database or call to another web site and there is usually a cost associated with these methods (31-Jan-08 update: see &lt;a href=http://www.ourline.com/asp/findip.asp&gt;http://www.ourline.com/asp/findip.asp&lt;/a&gt; for a free script and database).&lt;br&gt;&lt;br&gt;A simpler (and free) method is to try and determine the location based on the visitor's language settings.&lt;br&gt;&lt;br&gt;The server variable 'HTTP_ACCEPT_LANGUAGE' will return a 2 or 5 character string with the visitor's language setting (assuming they have selected it correctly). You can then use this string to lookup what country(s) that language setting is associated with.&lt;br&gt;&lt;br&gt;Whilst hardly a perfect solution, this method works quite reliably for determining major countries.&lt;br&gt;&lt;br&gt;Here is a sample script that attempts to return a 2-digit country code based on this variable.&lt;br&gt;&lt;br&gt;&lt;form&gt;&lt;textarea cols=&quot;50&quot; name=&quot;script&quot; rows=&quot;5&quot; style=&quot;width:95%&quot;&gt;strCountryCode = Request.ServerVariables(&quot;HTTP_ACCEPT_LANGUAGE&quot;)
If Len(strCountryCode) = 5 Then
	strCountryCode = Right(strCountryCode, 2)
End If&lt;/textarea&gt;&lt;/form&gt;&lt;a href=&quot;/blog/uploads/att279_sample.asp&quot;&gt;Run Sample&lt;/a&gt;&lt;br&gt;&lt;br&gt;A better implementation of this method would be to have a lookup database which lists each of the possible language settings and looks up the most likely country.&lt;br&gt;&lt;br&gt;The complete list of possible languages:&lt;br&gt;&lt;br&gt;Afrikaans (af)&lt;br&gt;Albanian (sq)&lt;br&gt;Basque (eu)&lt;br&gt;Bulgarian (bg)&lt;br&gt;Byelorussian (be)&lt;br&gt;Catalan (ca)&lt;br&gt;Chinese (zh)&lt;br&gt;Chinese/China (zh-cn)&lt;br&gt;Chinese/Taiwan (zh-tw)&lt;br&gt;Chinese/Hong Kong (zh-hk)&lt;br&gt;Chinese/singapore (zh-sg)&lt;br&gt;Croatian (hr)&lt;br&gt;Czech (cs)&lt;br&gt;Danish (da)&lt;br&gt;Dutch (nl)&lt;br&gt;Dutch/Belgium (nl-be)&lt;br&gt;English (en)&lt;br&gt;English/United Kingdom (en-gb)&lt;br&gt;English/United Satates (en-us)&lt;br&gt;English/Australian (en-au)&lt;br&gt;English/Canada (en-ca)&lt;br&gt;English/New Zealand (en-nz)&lt;br&gt;English/Ireland (en-ie)&lt;br&gt;English/South Africa (en-za)&lt;br&gt;English/Jamaica (en-jm)&lt;br&gt;English/Belize (en-bz)&lt;br&gt;English/Trinidad (en-tt)&lt;br&gt;Estonian (et)&lt;br&gt;Faeroese (fo)&lt;br&gt;Farsi (fa)&lt;br&gt;Finnish (fi)&lt;br&gt;French (fr)&lt;br&gt;French/Belgium (fr-be)&lt;br&gt;French/France (fr-fr)&lt;br&gt;French/Switzerland (fr-ch)&lt;br&gt;French/Canada (fr-ca)&lt;br&gt;French/Luxembourg (fr-lu)&lt;br&gt;Gaelic (gd)&lt;br&gt;Galician (gl)&lt;br&gt;German (de)&lt;br&gt;German/Austria (de-at)&lt;br&gt;German/Germany (de-de)&lt;br&gt;German/Switzerland (de-ch)&lt;br&gt;German/Luxembourg (de-lu)&lt;br&gt;German/Liechtenstein (de-li)&lt;br&gt;Greek (el)&lt;br&gt;Hindi (hi)&lt;br&gt;Hungarian (hu)&lt;br&gt;Icelandic (is)&lt;br&gt;Indonesian (id or in)&lt;br&gt;Irish (ga)&lt;br&gt;Italian (it)&lt;br&gt;Italian/ Switzerland (it-ch)&lt;br&gt;Japanese (ja)&lt;br&gt;Korean (ko)&lt;br&gt;Latvian (lv)&lt;br&gt;Lithuanian (lt)&lt;br&gt;Macedonian (mk)&lt;br&gt;Malaysian (ms)&lt;br&gt;Maltese (mt)&lt;br&gt;Norwegian (no)&lt;br&gt;Polish (pl)&lt;br&gt;Portuguese (pt)&lt;br&gt;Portuguese/Brazil (pt-br)&lt;br&gt;Rhaeto-Romanic (rm)&lt;br&gt;Romanian (ro)&lt;br&gt;Romanian/Moldavia (ro-mo)&lt;br&gt;Russian (ru)&lt;br&gt;Russian /Moldavia (ru-mo)&lt;br&gt;Scots Gaelic (gd)&lt;br&gt;Serbian (sr)&lt;br&gt;Slovack (sk)&lt;br&gt;Slovenian (sl)&lt;br&gt;Sorbian (sb)&lt;br&gt;Spanish (es or es-do)&lt;br&gt;Spanish/Argentina (es-ar)&lt;br&gt;Spanish/Colombia (es-co)&lt;br&gt;Spanish/Mexico (es-mx)&lt;br&gt;Spanish/Spain (es-es)&lt;br&gt;Spanish/Guatemala (es-gt)&lt;br&gt;Spanish/Costa Rica (es-cr)&lt;br&gt;Spanish/Panama (es-pa)&lt;br&gt;Spanish/Venezuela (es-ve)&lt;br&gt;Spanish/Peru (es-pe)&lt;br&gt;Spanish/Ecuador (es-ec)&lt;br&gt;Spanish/Chile (es-cl)&lt;br&gt;Spanish/Uruguay (es-uy)&lt;br&gt;Spanish/Paraguay (es-py)&lt;br&gt;Spanish/Bolivia (es-bo)&lt;br&gt;Spanish/El salvador (es-sv)&lt;br&gt;Spanish/Honduras (es-hn)&lt;br&gt;Spanish/Nicaragua (es-ni)&lt;br&gt;Spanish/Puerto Rico (es-pr)&lt;br&gt;Sutu (sx)&lt;br&gt;Swedish (sv)&lt;br&gt;Swedish/Findland (sv-fi)&lt;br&gt;Thai (ts)&lt;br&gt;Tswana (tn)&lt;br&gt;Turkish (tr)&lt;br&gt;Ukrainian (uk)&lt;br&gt;Urdu (ur)&lt;br&gt;Vietnamese (vi)&lt;br&gt;Xshosa (xh)&lt;br&gt;Yiddish (ji)&lt;br&gt;Zulu (zu)</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=279&amp;comments=on#comments</comments>
<pubDate>2007-10-09T12:00:00+10:00</pubDate>
</item>
<item>
<title>Nice Page Number Navigation for Recordsets</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=271</link>
<description>The following script creates links to navigate through your database table recordset.&lt;p&gt;It is a solution to displaying navigation links where the number of pages may be quite large and there may not be enough room to display all page numbers.&lt;p&gt;Sometimes it can be frustrating for users if there is only a previous and next link but they want to get to a page half way through of 50 pages.&lt;p&gt;It creates links to the surrounding pages as well as the first or last page, as well as previous and next links.&lt;p&gt;A few examples:&lt;p&gt;1 2 3 4 5 6 7 8 9 10 11 ... 100 Next&lt;p&gt;Previous 1 ... 6 7 8 9 10 11 12 13 14 15 16 ... 100 Next&lt;p&gt;Previous 1 ... 90 91 92 93 94 95 96 97 98 99 100&lt;p&gt;The following variables and objects are used and may need to be changed for your application.&lt;p&gt;'lngPage' is the current page number and this is passed in the URL parameter 'page'.&lt;br&gt;'rsTable' is the recordset.&lt;br&gt;'page.asp' should be changed to the correct script name and any additional parameters added to the querystring.&lt;p&gt;
&lt;form&gt;&lt;textarea cols=50 name=&quot;script&quot; rows=25 style=&quot;width:95%;&quot;&gt;If lngPage &gt; 1 Then Response.Write(&quot;&lt;a href=&quot;&quot;page.asp?page=&quot; &amp; (lngPage - 1) &amp; &quot;&quot;&quot;&gt;Previous&lt;/a&gt; &quot;)
If lngPage &gt; 5 Then
	lngStart = lngPage - 5
	If lngPage &lt; rsTable.PageCount - 5 Then
		lngEnd = lngPage + 5
	Else
		lngEnd = rsTable.PageCount
	End If
	If lngEnd - lngStart &lt; 11 And lngStart &gt; 1 Then
		lngStart = lngEnd - 10
		If lngStart &lt; 1 Then lngStart = 1
	End If
Else
	lngStart = 1
	If lngPage &lt; rsTable.PageCount - 5 Then
		lngEnd = lngPage + 5
	Else
		lngEnd = rsTable.PageCount
	End If
	If lngEnd - lngStart &lt; 11 And lngEnd &lt;&gt; rsTable.PageCount Then
		lngEnd = lngStart + 10
		If lngEnd &gt; rsTable.PageCount Then lngEnd = rsTable.PageCount
	End If
End If
If lngStart &gt; 1 Then
	Response.Write(&quot;&lt;a href=&quot;&quot;page.asp?page=1&quot;&quot;&gt;1&lt;/a&gt; &quot;)
	If lngStart &gt; 2 Then Response.Write(&quot;... &quot;)
End If
For lngCounter = lngStart To lngEnd
	If lngCounter = lngPage Then
		Response.Write(lngCounter &amp; &quot; &quot;)
	Else
		Response.Write(&quot;&lt;a href=&quot;&quot;page.asp?page=&quot; &amp; lngCounter &amp; &quot;&quot;&quot;&gt;&quot; &amp; lngCounter &amp; &quot;&lt;/a&gt; &quot;)
	End if
Next
If lngEnd &lt; rsTable.PageCount Then
	If lngEnd &lt; rsTable.PageCount - 1 Then Response.Write(&quot;... &quot;)
	Response.Write(&quot;&lt;a href=&quot;&quot;page.asp?page=&quot; &amp; rsTable.PageCount &amp; &quot;&quot;&quot;&gt;&quot; &amp; rsTable.PageCount &amp; &quot;&lt;/a&gt; &quot;)
End If
If lngPage &lt; rsTable.PageCount Then Response.Write(&quot;&lt;a href=&quot;&quot;page.asp?page=&quot; &amp; (lngPage + 1) &amp; &quot;&quot;&quot;&gt;Next&lt;/a&gt; &quot;)&lt;/textarea&gt;&lt;/form&gt;&lt;a href=&quot;/blog/uploads/att271_sample.asp&quot;&gt;Show Sample&lt;/a&gt;</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=271&amp;comments=on#comments</comments>
<pubDate>2007-10-03T12:00:00+10:00</pubDate>
</item>
<item>
<title>Complex Keyword Searches</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=206</link>
<description>This article is about how you can generate complex SQL queries for searching database columns for keywords.&lt;br&gt;&lt;br&gt;The examples assume that your keywords are in a variable called &lt;i&gt;strKeywords&lt;/i&gt;.&lt;br&gt;&lt;br&gt;First we need to remove multiple spaces.&lt;br&gt;&lt;br&gt;&lt;pre&gt;Do While InStr(strKeywords, &quot;  &quot;) &gt; 0&lt;br&gt;	strKeywords = Replace(strKeywords, &quot;  &quot;, &quot; &quot;)&lt;br&gt;Loop&lt;/pre&gt;The next step is to filter out any invalid characters. Only letters and numbers, spaces, quotes and hyphens are accepted.&lt;br&gt;&lt;br&gt;&lt;pre&gt;strKeywords = LCase(strKeywords)&lt;br&gt;For lngChar = 1 To Len(strKeywords)&lt;br&gt;	If Not (Asc(Mid(strKeywords, lngChar, 1)) = 34 Or _&lt;br&gt;	  Asc(Mid(strKeywords, lngChar, 1)) = 45 Or _&lt;br&gt;	  (Asc(Mid(strKeywords, lngChar, 1)) &gt;= 48 And _&lt;br&gt;	  Asc(Mid(strKeywords, lngChar, 1)) &lt;= 57) Or _&lt;br&gt;	  (Asc(Mid(strKeywords, lngChar, 1)) &gt;= 97 And _&lt;br&gt;	  Asc(Mid(strKeywords, lngChar, 1)) &lt;= 122) Or _&lt;br&gt;	  Asc(Mid(strKeywords, lngChar, 1)) = 32 Or _&lt;br&gt;	  Asc(Mid(strKeywords, lngChar, 1)) = 46) Then&lt;br&gt;		strKeywords = Left(strKeywords, lngChar - 1) &amp; _&lt;br&gt;		  &quot; &quot; &amp; Mid(strKeywords, lngChar + 1)&lt;br&gt;	End If&lt;br&gt;Next&lt;/pre&gt;This next procedure is optional and can be used to lookup phrases in a user dictionary and change them. For example, if you wanted searches for the word &quot;Landrover&quot; to also find the words &quot;Land&quot; and &quot;Rover&quot; you could do this with this procedure. A database table is required for the dictionary called &quot;Dictionary&quot;, with the columns (text) &quot;Lookup&quot; and &quot;Replace&quot; which contain the phrase to find and the phrase to replace with.&lt;br&gt;&lt;br&gt;&lt;pre&gt;strKeywords = &quot; &quot; &amp; strKeywords &amp; &quot; &quot;&lt;br&gt;Set rsDictionary = Server.CreateObject(&quot;ADODB.Recordset&quot;)&lt;br&gt;rsDictionary.Open &quot;SELECT * FROM Dictionary&quot;, &lt;i&gt;database object&lt;/i&gt;&lt;br&gt;Do Until rsDictionary.Eof&lt;br&gt;	strLookup = &quot; &quot; &amp; rsDictionary(&quot;Lookup&quot;) &amp; &quot; &quot;&lt;br&gt;	If InStr(strKeywords, strLookup) &gt; 0 Then&lt;br&gt;		strReplace = rsDictionary(&quot;Replace&quot;)&lt;br&gt;		strReplace = &quot; &quot; &amp; strReplace &amp; &quot; &quot;&lt;br&gt;		strKeywords = Replace(strKeywords, strLookup, _&lt;br&gt;		  strReplace)&lt;br&gt;	End If&lt;br&gt;	rsDictionary.MoveNext&lt;br&gt;Loop&lt;br&gt;rsDictionary.Close&lt;br&gt;Set rsDictionary = Nothing&lt;br&gt;strKeywords = Trim(Mid(strKeywords, 2))&lt;/pre&gt;This next procedure makes sure that phrases that are surrounded by quotation marks are treated as one and must match exactly.&lt;br&gt;&lt;br&gt;&lt;pre&gt;intStart = InStr(strKeywords, &quot;&quot;&quot;&quot;)&lt;br&gt;Do While intStart &gt; 0&lt;br&gt;	If intStart = Len(strKeywords) Then Exit Do&lt;br&gt;	intEnd = InStr(intStart + 1, strKeywords, &quot;&quot;&quot;&quot;)&lt;br&gt;	If intEnd = 0 Then Exit Do&lt;br&gt;	For intCount = intStart To intEnd&lt;br&gt;		If Mid(strKeywords, intCount, 1) = &quot; &quot; Then&lt;br&gt;			strKeywords = Left(strKeywords, intCount - 1) &amp; _&lt;br&gt;			  &quot;+&quot; &amp; Mid(strKeywords, intCount + 1)&lt;br&gt;		End If&lt;br&gt;	Next&lt;br&gt;	If intEnd = Len(strKeywords) Then Exit Do&lt;br&gt;	intStart = InStr(intEnd + 1, strKeywords, &quot;&quot;&quot;&quot;)&lt;br&gt;Loop&lt;br&gt;strKeywords = Replace(strKeywords, &quot;&quot;&quot;&quot;, &quot;&quot;)&lt;/pre&gt;Now that all the manipulation of the keywords has been done we can create an array containing all the keywords of phrases if surrounded by quotation marks.&lt;br&gt;&lt;br&gt;&lt;pre&gt;intWords = 0&lt;br&gt;intStart = 1&lt;br&gt;intEnd = 1&lt;br&gt;ReDim strWords(0)&lt;br&gt;Do While Len(strKeywords) &gt;= intEnd&lt;br&gt;	If intStart &gt; 1 Then intStart = InStr(intEnd, strKeywords, &quot; &quot;, 0)&lt;br&gt;	If intStart = 0 Then Exit Do&lt;br&gt;	intEnd = InStr(intStart + 1, strKeywords, &quot; &quot;, 0)&lt;br&gt;	If intEnd = 0 Then intEnd = Len(strKeywords) + 1&lt;br&gt;	intWords = intWords + 1&lt;br&gt;	redim preserve strWords(intWords)&lt;br&gt;	strWords(intWords) = Replace(LTrim(RTrim(Mid(strKeywords + &quot; &quot;, _&lt;br&gt;	  intStart, intEnd - intStart))), &quot;+&quot;, &quot; &quot;)&lt;br&gt;	If intStart = 1 Then intStart = 2&lt;br&gt;Loop&lt;/pre&gt;Finally we can generate a SQL query to search for the keywords of phrases in one or more database columns.&lt;br&gt;&lt;br&gt;&lt;pre&gt;strQuery = &quot;&quot;&lt;br&gt;If intWords &lt;&gt; 0 Then&lt;br&gt;	If strQuery &lt;&gt; &quot;&quot; Then strQuery = strQuery &amp; &quot; AND&quot;&lt;br&gt;	strQuery = strQuery &amp; &quot; (&quot;&lt;br&gt;	For intCounter = 1 To intWords&lt;/pre&gt;If you wish to be able to do searches based on finding ALL of the keywords or ANY of the keywords set the variable &lt;i&gt;strAndOr&lt;/i&gt; accordingly on the following line (to AND for all, or OR for any).&lt;br&gt;&lt;br&gt;&lt;pre&gt;		If intCounter &gt; 1 And strQuery &lt;&gt; &quot;&quot; Then strQuery = _&lt;br&gt;		  strQuery &amp; &quot; &quot; &amp; strAndOr&lt;/pre&gt;The next line will vary depending on how many columns you want to search in. For one column it would be as follows:&lt;br&gt;&lt;br&gt;&lt;pre&gt;		strQuery = strQuery &amp; &quot; (&lt;i&gt;fieldname&lt;/i&gt; LIKE '%&quot; &amp; _&lt;br&gt;		  strWords(intCounter) &amp; &quot;%')&quot;&lt;/pre&gt;But for multiple columns it would be:&lt;br&gt;&lt;br&gt;&lt;pre&gt;		strQuery = strQuery &amp; &quot; (&lt;i&gt;fieldname1&lt;/i&gt; LIKE '%&quot; &amp; _&lt;br&gt;		  strWords(intCounter) &amp; &quot;%' OR &lt;i&gt;fieldname2&lt;/i&gt; LIKE '%&quot; &amp; _&lt;br&gt;		  strWords(intCounter) &amp; &quot;%' OR &lt;i&gt;fieldname3&lt;/i&gt; LIKE '%&quot; &amp; _&lt;br&gt;		  strWords(intCounter) &amp; &quot;%')&quot;&lt;/pre&gt;Then we finish off our query.&lt;br&gt;&lt;br&gt;&lt;pre&gt;	Next&lt;br&gt;	strQuery = strQuery &amp; &quot;)&quot;&lt;br&gt;End If&lt;br&gt;strQuery = &quot;SELECT * FROM &lt;i&gt;tablename&lt;/i&gt;&quot;&lt;br&gt;If strQuery &lt;&gt; &quot;&quot; Then strQuery = strQuery &amp; &quot; WHERE&quot; &amp; strQuery&lt;/pre&gt;Obviously you can add more search criteria and &lt;i&gt;ORDER BY&lt;/i&gt; clauses as neccessary.</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=206&amp;comments=on#comments</comments>
<pubDate>2007-08-07T12:00:00+10:00</pubDate>
</item>
<item>
<title>ASP Live Help</title>
<link>http://www.asp.johnavis.com/blog/default.asp?id=204</link>
<description>I developed this application for an on-line store. The code is quite messy because it took me lots of attempts to get right but it now operates almost perfectly.&lt;br&gt;&lt;br&gt;Live Help is a messenger type application written mostly in ASP but with lots of JavaScript. It uses the ASP Application object to store information.&lt;br&gt;&lt;br&gt;&lt;center&gt;&lt;a href=&quot;/blog/uploads/att204_livehelp.jpg&quot; target=&quot;_blank&quot;&gt;&lt;img alt=&quot;Click for full size Live Help screenshot&quot; border=&quot;0&quot; src=&quot;/blog/uploads/img204_livehelp_thumb.jpg&quot;&gt;&lt;/a&gt;&lt;/center&gt;&lt;br&gt;An operator (or multiple operators) can sign in then bring up a messenger type window which displays the status of conversations and requests. They enter their name which is shown to the other party during conversations.&lt;br&gt;&lt;br&gt;Someone using the web site can see the status of Live Help - either available or not available. If available they can click the link to request a conversation with an operator.&lt;br&gt;&lt;br&gt;To initiate the conversation they enter their name which is shown to the operator.&lt;br&gt;&lt;br&gt;The operator(s) will hear a ringing sound to indicate an incoming conversation request. They can either accept the request, ignore it or decline it. If the request is ignored it will eventually expire and (or if cancelled, will occur immediately) the user will be prompted to try again later or leave a messge (which is delivered via normal email).&lt;br&gt;&lt;br&gt;If the request is accepted a two-way conversation can take place in a messenger style environoment. The system displays messages from both the operator and user including the time of last message received and plays a sound to indicate a message received. The conversation can be ended by either party at any time. It will also display a message if the other party is typing a message.&lt;br&gt;&lt;br&gt;Download the sample and give it a try on any ASP server.&lt;br&gt;&lt;br&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;&lt;br&gt;Unzip the file into your root web folder. All files will then be unzipped into a foler called 'livehelp'. It will not operate correctly if in any other folder without modification.&lt;br&gt;&lt;br&gt;The system will now work as is for demonstration/test purposes. If you wish to integrate into a web site then changes are neccessary.&lt;br&gt;&lt;br&gt;'request.asp' contains an email address where any messages are sent if a user cannot contact Live Help. This should be changed to an email address of your choice. It may also require changes if CDOSYS is not available or if you need to specify a mail server.&lt;br&gt;&lt;br&gt;'Default.asp' and 'user.asp' are required only for demonstration/testing and should be removed prior to proper use in your own web site. They do contain functions and procedures which will need to be used in your own scripts and pages.&lt;br&gt;&lt;br&gt;You will need to incorporate the JavaScript functions and operator link contained in 'default.asp' into your own administration or web site back end. The operator pages should only be able to be accessed by authorised persons so you will need to implement your own security. At present these scripts check for a session variable 'admin' being true.&lt;br&gt;&lt;br&gt;In any web page where you wish for users to be able to use Live Help from you will ned to include the JavaScript function and VB Script Sub contained within 'user.asp'. Then simply call 'LiveHelpStatus' where you would like the status image/link to be displayed.&lt;br&gt;&lt;br&gt;&lt;strong&gt;Demonstration&lt;/strong&gt;&lt;br&gt;&lt;br&gt;Simply unzip the file in the root of your web or localhost web folder.&lt;br&gt;&lt;br&gt;Open the 'default.asp' page.&lt;br&gt;&lt;br&gt;Click sign in as operator and enter a name for yourself.&lt;br&gt;&lt;br&gt;Then you can sign in as a user either on the same or another computer and enter a name for yourself to request a conversation.&lt;br&gt;&lt;br&gt;You should hear a ringing noise and in your operator messenger window you can accept the request.&lt;br&gt;&lt;br&gt;Conversation windows will then pop-up and you can converse as required.&lt;br&gt;&lt;br&gt;&lt;a href=&quot;/blog/uploads/att204_livehelp.zip&quot;&gt;Download Live Help&lt;/a&gt; (updated 16-Jul-2008)&lt;br&gt;&lt;br&gt;License: Freeware&lt;p&gt;&lt;b&gt;Updates&lt;/b&gt;&lt;p&gt;16-Nov-2007: There has been reports of the chat application not scrolling down automatically to show messages. 'chat.asp' has been updated which seems to have solved the problem.&lt;p&gt;20-Nov-2007: I have found the issue causing problems with Firefox. Download the following file for the fix. Unfortunately in Firefox it doesn't display &quot;...is typing a message&quot; or play sounds but at least now it works.&lt;p&gt;16-Jan-2008: I have fixed a few bugs including the non-functioning decline chat function. If you click the download Live Help link above you will receive the latest version of all parts of Live Help. Changes are to request.asp, getusers,asp, messenger.asp.&lt;p&gt;16-Jul-2008: I was advised that LiveHelp wasn't working with Safari so I have had to make some modifications for it to work. Click the download link above for the latest version.</description>
<comments>http://www.asp.johnavis.com/blog/default.asp?id=204&amp;comments=on#comments</comments>
<pubDate>2007-08-06T12:00:00+10:00</pubDate>
</item>
</channel>
</rss>
