Tuesday, December 14, 2010

Calendar PopUp

There has been quite a number of Javascript Calendar PopUp available in the internet. Many of them are very professionally made. However, I seems to have trouble using them. They either do not work out-of-the-box or needs a few settings here and there to function properly.

What I wanted is to have a very simple utility that allows me to choose a date in a limited format. I do not want to have too many settings. My habit is always to make life easy for programmers by not bothering them with too much options in my library.

The following is my version of a CalendarPopUp.

var $cP=function(myid,query2,query3){
// must set the id value of the date input element to work properly
// call the funcstion and pass the id value and type (MDY,DMY or YMD). query3 is not user definable
//remember to set the date input element to read only
var months=['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
if (typeof(query3) != "undefined"){
var odate=query3.split("-")
var curdate=new Date(odate[0],odate[1],odate[2],0,0,0,0)
}
else
var curdate=new Date()
var curmth=curdate.getMonth()+1
var curyr=curdate.getFullYear()
var x,y
fdm=new Date(curdate.getFullYear(),curdate.getMonth(),1,0,0,0,0)
if(fdm.getMonth() !=0)
ldm=fdm.getFullYear()+"-"+(fdm.getMonth()-1)+"-"+fdm.getDate()
else
ldm=(fdm.getFullYear()-1)+"-11-"+fdm.getDate()
ndm=fdm.getFullYear()+"-"+(fdm.getMonth()+1)+"-"+fdm.getDate()
fdm.setDate(fdm.getDay()*-1)
var $winP=window.open("","cP","toolbar=0,location=0,drectories=0,menubar=0,scrollbars=0,resizable=0,width=250,height=200,status=0",true)
obj=$winP.document
obj.write('<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">')
obj.write('<html><head><title>Calendar</title>\n');
obj.write('<style type=\'text\/css\'>\n')
obj.write('table {width:100%;border-width: 3px;border-style:solid;border-spacing:0;border-collapse:collapse;}\n')
obj.write('td,th {font-size:12px;margin:0px;border-width: 2px 2px 2px 2px;border-style:solid;border-spacing:0;border-collapse:collapse;text-align:center;cursor:hand}\n')
obj.write('td:hover {background-color:yellow;font-weight:bold;cursor:pointer}\n')
obj.write('.hv:hover {background-color:yellow;font-weight:bold;cursor:pointer}\n')
obj.write('.igray {background-color:#BDBDBD}\n')
obj.write('.iwhite {background-color:white}\n')
obj.write('p {width:100%;text-align:center;margin:0}\n')
obj.write('<\/style>\n')
obj.write('</head><body>');
obj.write('<table>');
obj.write('<tr><th class=\'hv\' onclick=window.opener.$cP(\"'+myid+'\",\"'+query2+'\",\"'+ldm+'\")>&lt;</th><th colspan=5>'+months[curdate.getMonth()]+" "+ curdate.getFullYear()+'</th><th class=\'hv\' onclick=window.opener.$cP(\"'+myid+'\",\"'+query2+'\",\"'+ndm+'\")>&gt;</th></tr>')
obj.write('<tr><th>S</th><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th></tr>')
var myhtml=""
fdm.setDate(fdm.getDate()+1)
for (x=1;x<=5;x++){
myhtml +="<tr>"
for(y=1;y<=7;y++){
newmth=fdm.getMonth()+1
newyr=fdm.getFullYear()
if (curmth != newmth || curyr != newyr)
cal='igray'
else
cal='iwhite'
switch (query2){
case "MDY":
sdm=(fdm.getMonth()+1)+"/"+fdm.getDate()+"/"+fdm.getFullYear()
break
case "DMY":
sdm=fdm.getDate()+"/"+(fdm.getMonth()+1)+"/"+fdm.getFullYear()
break
default:
sdm=fdm.getFullYear()+"-"+(fdm.getMonth()+1)+"-"+fdm.getDate()
}
myhtml +="<td class='"+cal+"' onclick='window.opener.document.getElementById(\""+myid+"\").value=\""+sdm+"\";self.close()'>"+fdm.getDate()+"</td>\n"
fdm.setDate(fdm.getDate()+1)
}
myhtml +="</tr>"
if (x==5 && fdm.getDate() > 29)
x -=1
}
obj.write(myhtml)
obj.write('</table>');
obj.write('<p><a href="javascript:self.close()">close</a></p>');
obj.write('</body></html>');
obj.close();
obj.focus();

}

There is no preset available. The function is complete by itself. Just call the function and pass the necessary parameter and that is all.

In your form you would set the date input field as below example

<input type="text" name="mydate" id="mydate" value="" readonly onclick="$cP(this.id,'YMD')">

When user clicks on the input, the calendar will popup for user to choose a date.

User can navigate by month to choose different months using the "<" and ">" on both sides of the month indicator.

The second parameter of the $cP function has three formats. "YMD" displays "YYYY-MM-DD" format. "DMY" displays "DD/MM/YYYY" format. Finally, "MDY" displays "MM/DD/YYYY" format.

I do not want to add more options beside the three that I always use. It is up to individuals to add their own options. Adding it is very simple. Just go to the place where the switch(query2) is and add another case option.

The above is tested on IE7. Although I could not make the size of the popup less than that which IE7 limited. It worked just fine. Even the css hover worked after I set the DTD to "strict".

On Firefox, the window features seems to have a mind of its own. It does not allow me to hide a number of features which I turned off. However, this is firefox's preferred setting and since it does not hamper the functionality, it is acceptable despite having a bit too much unnecessary things.

On Chrome, only the address line is still visible.

I did not have other browsers installed thus does not test those.


Monday, December 13, 2010

Form Validation update

Previous post on the same subject saw a list of functions. Below is a modified update.

var errMsg="";
var $vF= function(qtype,query,query2,startw,endw){
//use $vF() without param to echo errMsg and clears it for the next form validation.
var tempMsg="";
var varRegEx=/.+/;
var testvalue="";
if (startw==null)
startw=""
if (endw==null)
endw=""
var regExAry={
"Empty":" must not be empty\n",
"SqlOK":" must not be empty or contains tab, enter, quotation marks, ampersand or semicolon character\n",
"Email":" is not valid Email\n",
"URL":" is not valid URL\n",
"Number":" is not valid Number\n",
"Alpha":" must not be empty and is Alphabet and space only\n",
"Integer":" is not valid Integer\n",
"Range":" is not between " +startw+ " and "+ endw+"\n",
"Zip":" is not valid Post Code\n",
"Positive":" is not positive number\n",
"Begin":" must start with "+startw+" and is "+endw+" digits\n",
"Date":" is not valid Date (DMY, MDY, or YMD)\n",
"YMD":" is not valid Date (DMY, MDY, or YMD)\n",
"Phone":" is not a valid phone number\n",
"Pwd":" must start with an Alphabet followed by at least one each of upper case, lower case, number and special characters. Length must be "+startw + " to "+endw+" characters\n",
"Radio":" must have a selecstion\n",
"Select": " must have a selection\n",
"CheckBox":" must be checked\n",
"CheckBoxList":" must have at least one item checked\n",
"AgteB":" must be greater than or equal "+endw+"\n",
"AeqB":" must be same as "+endw+"\n",
"Fixed":" must be exactly "+startw+" characters\n",
"Maxlen":" must not exceed "+startw+" characters\n",
"Name":" must be proper name with caps on every first character of word\n"
}
if (qtype!="Radio" && qtype !="Select" && typeof(qtype) != "undefined")
testvalue=query.value
switch (qtype){
case "Empty":
varRegEx=/.+/
break
case "SqlOK":
if (query.nodeName != "TEXTAREA")
varRegEx=/[\0\f\n\r\t\v'"&;]/
else
varRegEx=/[\0'"&;]/
break
case "Email":
varRegEx = /^([a-z0-9]+)([_\-.][a-z0-9]+)*@+([a-z0-9]+)([_\-.][a-z0-9]+)*[.]+[a-z]{2,4}$/i;
break
case "URL":
varRegEx = /^(HTTPS?|FTP):\/\/[a-z0-9_-]+(\.[a-z0-9_-]+)*\.[a-z]{2,4}(\/.+)*\/?$/i;
break
case "Alpha":
varRegEx = /(^[a-z ]+$)/i;
break
case "Number":
varRegEx = /(^[+-]?[0-9]+\.[0-9]{1,}$)|(^[+-]?[0-9]+$)/;
break
case "Integer":
varRegEx = /^[+-]?[0-9]+$/;
break
case "Range":
var locRegEx = /^[0-9]+$/;
var localtest=locRegEx.test(testvalue)
if (localtest){
testvalue=testvalue*1
if (testvalue < startw || testvalue > endw)
testvalue=""
}
else
testvalue="true"
break
case "AgteB":
// B is actually less than A so no need a "Less Than" test.
var locRegEx = /^[0-9]+$/;
var swv=startw.value*1
var localtest=locRegEx.test(testvalue)
if (localtest){
testvalue=testvalue*1
if (testvalue < swv)
testvalue=""
}
else
testvalue="true"
break
case "AeqB":
if (testvalue != startw.value)
testvalue=""
break
case "Zip":
varRegEx = /^[0-9]{6}$/;
break
case "Fixed":
varRegEx = new RegExp("^(.|\n){"+startw+"}$")
break
case "Maxlen":
varRegEx = new RegExp("^(.|\n){1,"+startw+"}$")
break
case "Positive":
varRegEx = /(^[+]?[0-9]+\.[0-9]+$)|(^[+]?[0-9]+$)/;
break
case "Begin":
startw +=""
var myrlen=endw-startw.length
startw = startw.replace(/\\/g,"\\\\")
startw = startw.replace(/\//g,"\/")
startw = startw.replace(/\-/g,"\-")
varRegEx = new RegExp("(^"+startw+"[0-9]{"+myrlen+"}$)","");
break
case "Date":
varRegEx = /^((0?[1-9]|1[0-2])[\/-](0?[1-9]|(1[0-9]|2[0-9])|3[01])[\/-][12][0-9]{3})|((0?[1-9]|[12][0-9]|3[01])[\/-](0?[1-9]|1[0-2])[\/-][12][0-9]{3})|([12][0-9]{3}[\/-](0?[1-9]|1[0-2])[\/-](0?[1-9]|[12][0-9]|3[01]))$/;
break
case "YMD":
varRegEx = /^[12][0-9]{3}[\/-](0?[1-9]|1[0-2])[\/-](0?[1-9]|[12][0-9]|3[01])$/;
break
case "Phone":
varRegEx = /^(\+?65)?[689]{1}[0-9]{7}$/
break
case "Pwd":
varRegEx = new RegExp("(?=^.{"+startw+","+endw+"}$)[a-zA-Z]+(?=.*[A-Z])(?=.*[a-z])(?=.*\\d)(?=.*[^a-zA-Z0-9])")
break
case "Name":
varRegEx = /^[A-Z][a-z]+( [A-Z][a-z.]+)*( [A-Z]+)*$/
break
case "Radio":
for (radb=0;radb<query.length;radb++){
if (query[radb].checked == true)
testvalue="true"
}
break
case "Select":
if (query.selectedIndex != 0)
testvalue="true"
break
case "CheckBox":
if (query.checked == false)
testvalue=""
break
case "CheckBox":
if (query.checked == false)
testvalue=""
break
case "CheckBoxList":
var qform=query.form
var x
var qcnt=0
for (x=0;x<qform.elements.length;x++){
if (qform.elements[x].name == query.name){
if(qform.elements[x].checked == true)
qcnt=1
}
}
if (qcnt==0)
testvalue=""
break
default:
if(errMsg !="")
alert(errMsg)
errMsg=""
return 0
}
if ((varRegEx.test(testvalue)== false && qtype !="SqlOK") ||(qtype=="SqlOK" && varRegEx.test(testvalue)==true))
errMsg +=query2+regExAry[qtype]
return 0
};

The use of the above is quite straightforward. There is no setting required. Just perform the validation straight away. There is only a caveat that you should be aware of . The errMsg string will keep concatenating every time you run the function until you run the function without parameters.

Basically you should do the following.

obj=document,formname
$vF("Empty",obj.StartDate,"Registration Date")
$vF("Email",obj.Email,"Email")
$vF()

The function has 3-5 parameters. Most of the functions only uses 3. Javascript can accept null input parameters. The first parameter tells the function what test you are performing on the next parameter which is the form input element name to be tested. The third parameter is the user friendly name for the input element.

Fourth and fifth parameter is used for start/stop, max/min number for range test. It is also used to define a second input element name and its friendly name respectively. Some of the fourth parameter contains a number and there is no fifth parameter. This is usually used for testing for a fixed length of input.

You need not perform the test only once per input element. More test can be performed on the same input. For example, you perform a test on an input to ensure that it is a integer number and then perform another test on the same element to ensure that the number is exactly 6 digits.

At times you may want to perform condtional validation test. That is to say the input may not have entry but if it has an entry then the validation is performed, then you have to enclose the function with a condition test to ensure that the validation is bypassed if there is no input. The reason being all validation requires input to have a value (except the empty test).

$vF() will output the failed validation messages. At the same time it clears the errMsg content so that you could perform new set of tests.


Friday, December 10, 2010

Validating checkbox with same name

Normally validating checkbox with the same name is pretty simple. However, to submit to PHP you would want to use the name like xx[]. This then posts a problem in form validation as we cannot use document.form.xx[][1].checked to check if the element is checked.

It turned out that it is quite simple. Just treat the name as name and use the method below

document.form["xx[]"][1].checked


Wednesday, December 08, 2010

Javascript Regex

The following regex is valid.

myreg=/\w{2,3}/

However, if you use it to test "abcd", it still return true unless you test it with "a".

It turns out that you must code it as

myreg=/^\w{2,3}$/

just to get it return the right answer.

I have a headache with "^" and "$". I really don't know whether to use it or not. It seems like not every regex I used have to include them.

The trick is try without them first. If fail, then adds them in.

Thursday, December 02, 2010

Creating Regex with literal backslash

Backslash in Regex has special meanings. So how do I create a Regex expression that contains a literal backslash.

It is quite simple. Just use "\\" instead of "\" in the Regex.

Now come the problem. I need to define a string variable and pass it to a function to create a Regex in the function.

For example, I need to create a string "A\" . The string can be defined as myvar="A\\" in javascript. Up till now it is fine. Now if I set

myRegex=new RegExp("^"+myvar,"")

in the function and test it, the script crashes.

It turned out that I must define the string as myvar="A\\\\" so that the RegExp can see the string as "A\\" and parse it as a literal "A\".

So much trouble to just create a backslash in Regex.

Wednesday, December 01, 2010

Javascript Form Validation

I used to do a script for every input to check their validity even to just check if it is blank.

The following is an easier and more streamline way of doing the same thing.

function isEmpty(query,query2){
if (query.value=="")
return query2+" must not be empty\n"
else
return ""
}
function isEmail(query,query2){
var emailRegEx = /^([a-z0-9][a-z0-9_.-]*[a-z0-9])+@+([a-z0-9][a-z0-9-]*[.])+[a-z0-9]{2,4}$/i;
if (emailRegEx.test(query.value)== false)
return query2+ " is not valid Email\n"
else
return ""
}
function isNumber(query,query2){
var numRegEx = /(^[+-]?[0-9]+\.[0-9]{1,}$)|(^[+-]?[0-9]+$)/;
if (numRegEx.test(query.value)== false )
return query2+ " is not valid Number\n"
else
return ""
}
function isInteger(query,query2){
var numRegEx = /^[+-]?[0-9]+$/;
if (numRegEx.test(query.value)== false)
return query2+ " is not valid Integer\n"
else
return ""
}
function isPlusNumber(query,query2){
var numRegEx = /(^[+]?[0-9]+\.[0-9]+$)|(^[+]?[0-9]+$)/;
if (numRegEx.test(query.value)== false)
return query2+ " is not Positive Number\n"
else
return ""
}
function isRadio(query,query2){
var testrad=false
for (radb=0;radb<query.length;radb++){
if (query[radb].checked == true)
testrad=true
}
if (testrad)
return ""
else
return query2 +" no selection\n"
}
function isSelect(query,query2){
// provided selected item 0 =not valid selection.
if (query.selectedIndex == 0)
return query2 +" no selection\n"
else
return ""
}
function isCheckBox(query,query2){
if (query.checked==true)
return ""
else
return query2+" must be checked\n"
}
function isPhone(query,query2){
// for Singapore only
var numRegEx = /(^[689]{1}[0-9]{7}$)|(^\+65[689]{1}[0-9]{7}$)/;
if (numRegEx.test(query.value)== false )
return query2+ " is not valid Phone Number\n"
else
return ""
}
function chksmt(){
obj=document.noname
ecode=""
ecode +=isEmpty(obj.Adr,"Address")
ecode +=isPlusNumber(obj.Pcode,"Postal Code")
ecode +=isInteger(obj.Cno,"Contact No")
ecode +=isEmail(obj.Email,"Email")
if (ecode !=""){
alert(ecode)
return 0
}
obj.submit()
}

Javascript Regex with numbers

Javascript Regex is a powerful tool. However, there is something you must take note when doing validation with numbers.

if your regex is /[0-9]+/ and your intention is to ensure that the input is just numbers. You would be surprised that when you enter ".1" or "1." it still passes.

I am not sure what happened but isNaN also behaves this way.

To ensure that only numbers is valid you would have to set the regex as /^[0-9]+$/