CICS 515 a Internet Programming Week 4 Mike Feeley JavaScript - - PowerPoint PPT Presentation
CICS 515 a Internet Programming Week 4 Mike Feeley JavaScript - - PowerPoint PPT Presentation
CICS 515 a Internet Programming Week 4 Mike Feeley JavaScript continued ... Validators JavaScript to check validity of user input report errors to user without going to server so that user can fix them quickly and easily
JavaScript continued ...
Validators
JavaScript to check validity of user input
- report errors to user without going to server
- so that user can fix them quickly and easily
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/validator/foo.html
- lets say that first field can only have value ‘zero’ and second ‘one’
- connect JavaScript event handler to input’s onblur event (or maybe onchange)
<html> <head> </head> <body onload='whenLoaded()'> <form onsubmit='javascript: return this.isSubmitOkay ()'> <table> <tr> <td><input type='text' size='20' onblur='validate()'></td> </tr> <tr> <td><input type='text' size='20' onblur='validate()'></td> </tr> </table> <input type='submit' value='submit'> </form> </body> </html>
giving error feedback to user
- immediate responsiveness, don’t wait for submit to indicate errors
- “modal” (fix error before continuing) or not (allow multiple inputs before fixing errors)
- clearly indicate fields in error and what is wrong
- consistent across fields, forms and pages
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/foo.html pick standard for assigning field IDs
- x_eFlag is the element that is highlighted when there’s an error in input x
- x_eMsg is the element where error message is displayed
use style sheet for highlighting
<td id='i0_eFlag'><input id='i0' type='text' size='20' onblur='validate()'></td> <td id='i0_eMsg' class='ErrorMessage'></td> <td id='i1_eFlag'><input id='i1' type='text' size='20' onblur='validate()'></td> <td id='i1_eMsg' class='ErrorMessage'></td> *.InputError { background-color: red; } *.InputOkay { background-color: white; }
JavaScript to set/clear an error on a field but, we’ll do it in object-oriented fashion
- extend the DOM Input object to add these new properties
var ERROR_FLAG_SUFFIX = '_eFlag'; var ERROR_MSG_SUFFIX = '_eMsg'; function setError () { document.getElementById(this.eFlagID()).className = INPUT_ERROR_CLASS; document.getElementById(this.eMsgID()).innerHTML = this.errMsg; } function clearError () { document.getElementById(this.eFlagID()).className = INPUT_OKAY_CLASS; document.getElementById(this.eMsgID()).innerHTML = ''; }
setError () highlights field and sets its error message clearError () unhilights field and clears its error message validate () calld by onblur event to check field’s validity isValid () returns true if and only if current value of field is valid errMsg error message to display if field is in error hasError true if and only if current value of field is invalid
extending the Input prototype in JavaScript set isValid and errMsg for inputs in page’s onload hander
HTMLInputElement.prototype.setError = function () { this.hasError = true; document.getElementById(this.eFlagID()).className = INPUT_ERROR_CLASS; document.getElementById(this.eMsgID()).innerHTML = this.errMsg; this.form.disableSubmit (true); } HTMLInputElement.prototype.clearError = function () { this.hasError = false; document.getElementById(this.eFlagID()).className = INPUT_OKAY_CLASS; document.getElementById(this.eMsgID()).innerHTML = ''; if (this.form.isSubmitOkay()) this.form.disableSubmit (false); } HTMLInputElement.prototype.validate = function () { if (this.isValid ()) this.clearError (); else this.setError (); } function whenLoaded () { document.getElementById('i0').isValid = function () { return this.value=='zero'; }; document.getElementById('i0').errMsg = 'must be "zero"'; document.getElementById('i1').isValid = function () { return this.value=='one'; }; document.getElementById('i1').errMsg = 'must be "one"'; }
disable submitting if there is an error
HTMLFormElement.prototype.disableSubmit = function (disable) { for (var i=0; i<this.elements.length; i++) if (this.elements[i].type=='submit') this.elements[i].disabled=disable; } HTMLFormElement.prototype.isSubmitOkay = function () { for (var i=0; i<this.elements.length; i++) if (this.elements[i].hasError) return false; return true; } <body onload='whenLoaded()'> <form onsubmit='javascript: return this.isSubmitOkay ()'>
Putting it all together
<html> <head> <link href='Project.css' rel='stylesheet' type='text/css'> <script src='ValidateInput.js' language='JavaScript' ></script> <script type='text/JavaScript'> function whenLoaded () { document.getElementById('i0').isValid = function () { return this.value=='zero'; }; document.getElementById('i0').errMsg = 'must be "zero"'; document.getElementById('i1').isValid = function () { return this.value=='one'; }; document.getElementById('i1').errMsg = 'must be "one"'; } </script> </head> <body onload='whenLoaded()'> <form onsubmit='javascript: return this.isSubmitOkay ()'> <table> <tr> <td id='i0_eFlag'><input id='i0' type='text' size='20'
- nblur='validate()'></td>
<td id='i0_eMsg' class='ErrorMessage'></td> </tr> <tr> <td id='i1_eFlag'><input id='i1' type='text' size='20'
- nblur='validate()'></td>
<td id='i1_eMsg' class='ErrorMessage'></td> </tr> </table> <input type='submit' value='submit'> </form> </body> </html>
var INPUT_ERROR_CLASS = 'InputError'; var INPUT_OKAY_CLASS = 'InputOkay'; var ERROR_FLAG_SUFFIX = '_eFlag'; var ERROR_MSG_SUFFIX = '_eMsg'; HTMLFormElement.prototype.disableSubmit = function (disable) { for (var i=0; i<this.elements.length; i++) if (this.elements[i].type=='submit') this.elements[i].disabled=disable; } HTMLFormElement.prototype.isSubmitOkay = function () { for (var i=0; i<this.elements.length; i++) if (this.elements[i].hasError) return false; return true; } HTMLInputElement.prototype.eFlagID = function () { return this.id+ERROR_FLAG_SUFFIX; } HTMLInputElement.prototype.eMsgID = function () { return this.id+ERROR_MSG_SUFFIX; } HTMLInputElement.prototype.setError = function () { this.hasError = true; document.getElementById(this.eFlagID()).className = INPUT_ERROR_CLASS; document.getElementById(this.eMsgID()).innerHTML = this.errMsg; this.form.disableSubmit (true); } HTMLInputElement.prototype.clearError = function () { this.hasError = false; document.getElementById(this.eFlagID()).className = INPUT_OKAY_CLASS; document.getElementById(this.eMsgID()).innerHTML = ''; if (this.form.isSubmitOkay()) this.form.disableSubmit (false); } HTMLInputElement.prototype.validate = function () { if (this.isValid ()) this.clearError (); else this.setError (); }
Combining JavaScript with PHP and MySQL
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/enterStudents6.php
add country to this form
- assume country must be country in North America
- not as a drop-down box, why?
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/enterStudents7.php
Your turn ...
Checking for valid country
code
var isValidCountry = function () { return (!this.value || (this.value=='Canada' || this.value=='United States' || this.value=='Mexico')); }; var errMsgCountry = 'country in North America (property capitalized)'; echo "<script type='text/JavaScript'>\n"; echo "function loadCountryValidators () {\n"; for ($i=0; $i<mysql_numrows($rows); $i++) { echo "document.getElementById('country$i').isValid = isValidCountry;\n"; echo "document.getElementById('country$i').errMsg = errMsgCountry;\n"; } echo "}\n</script>\n"; echo "<td><table><tr><td id='country${i}_eFlag'> <input id='country$i' name='country[]' type='text' size='30’ value='$country' "; echo "onchange='checkBox($i)' onblur='validate()'></td></tr> <tr><td id='country${i}_eMsg' class='ErrorMessage'> </td></tr> </table>\n";
separation of concerns is important
- style elements in the style file
- so that site can define a consistent look and feel and that it can be easily changed
- form validation defined at the class level
- so that every input field is validated in a consistent manner with minimal form-specific work
- error flagging and message at least semi-independent from style and HTML
but what we have so far is not really good enough
- the problem is that HTML still encodes some formatting (table, rows, cells etc.)
- no connection to underlying data types in database
- validation and formatting should be properties of the data’s schema
- but how ...
Modular design in HTML / JavaScript
XML
extensible markup language
- adds structure to a text document
- HTML is an example
- XML allows creation of new element tags and attributes
uses
- describe database-stored values once extracted for exchange between databases
- as a sort of database itself inside of a file (stuff like configuration parameters etc.)
- as data transport between web servers and clients
how it is different from relational databases like SQL
- any RDB table can be described in XML
- key difference is that XML is recursive (allows nesting), while RDB is not
XML
<student> <sid>10</sid> <name>First Student</name> </student> <student> <sid>10</sid> <name>Second Student</name> </student> <blogEntry> <author>Feeley</author> <text>Blah Blah</text> <blogEntry> <author>Feeley</author> <text>More blather</text> </blogEntry> </blogEntry>
XML syntax
element
- an element is delimited by opening and closing tags
- <foo> ... </foo> or <foo/>
attribute
- an element can have attributes
- <foo attribute=’blah’>
inner text
- an element can have text enclosed by opening and closing tags
- this text becomes the element’s nodeValue
nested subelements
- an element can have other XML elements encodes by opening and closing tags
- thus creating a tree of elements for the document
- element types can be recursive, as we have seen, but element values are not
syntax checking is strict
- tags are case sensitive, closing and proper nesting is required
XML document tree
all XML documents must have a root element
- earlier examples were not complete, they need roots like <students> and <blogEntrys>
- I know that the plural of blogEntry is blogEntries, but you’ll see why I’ve chosen this style later
elements form tree text and attributes are child nodes of their element
<students> <student> <sid>10</sid> <name>First Student</name> </student> <student> <sid>20</sid> <name>Second Student</name> </student> </students> <blogEntrys> <blogEntry> <author>Feeley</author> <text>Blah Blah</text> <blogEntry> <author>Feeley</author> <text>More blather</text> </blogEntry> </blogEntry> </blogEntrys>
An XML example
xml data description
<?xml version='1.0'?> <?xml-stylesheet type='text/xsl' href='students.xsl'?> <students> <student> <sid>10</sid> <name>First Student</name> <birthCountry>Canada</birthCountry> </student> <student> <sid>20</sid> <name>Second Student</name> <birthCountry>United States</birthCountry> </student> </students>
XML Namespaces
a set of tag definitions is called a namespace XML documents can include tags from multiple namespaces
- e.g., HTML is a XML document whose namespace is defined by www.w3.org
XML elements can specify a namespace using xmlns attribute documents with multiple namespaces use qualified tag names
- xmlns attribute picks a qualifier for namespace
- then tags for that namespace are proceeded by qualifier colon
folks have tried to devise namespaces for everything
- this stuff is best ignored whenever possible
<html xmlns="http://www.w3.org/1999/xhtml"> <xsl:stylesheet version='1.0' xmlns:xsl = 'http://www.w3.org/1999/XSL/Transform'> <xsl:template match = '/'> ...
XML style sheet (converts XML to HTML)
- defined by an XML namespace called XSL/Transform
- http://ws.cs.ubc.ca/~feeley/cics515/code/week4/students0/students.xsl
<?xml version='1.0'?> <xsl:stylesheet version='1.0' xmlns:xsl = 'http://www.w3.org/1999/XSL/Transform'> <xsl:template match = '/'> <html> <body> <table border='1'> <xsl:for-each select = 'students/student'> <tr> <td><xsl:value-of select='sid'/></td> <td><xsl:value-of select='name'/></td> <td><xsl:value-of select='country'/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
Web browsers can display XML
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/students0/students.xsl
<?xml version='1.0'?> <?xml-stylesheet type='text/xsl' href='students.xsl'?> <students xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='students.xsd'> <student> <sid>10</sid> <name>First Student</name> <country>Canada</country> </student> <student> <sid>20</sid> <name>Second Student</name> <country>United States</country> </student> </students>
defines XML document type
- documents permitted syntax of XML documents of a user-defined type
- can be checked
schemas are optional
- only really needed when transferring data between realms
- within a single realm usually not necessary, though some form of documentation is
DTD
- fairly easy to read, but not in XML and do not support namespaces
XML Schema
<!ELEMENT blogEntrys (blogEntry*)> <!ELEMENT blogEntry (name, entry+)> <!ELEMENT entry (date, author, text, entry+)> <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE blogs SYSTEM 'blog.dtd'> <blogs>
<?xml version='1.0' encoding='utf-8'?> <xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'> <xsd:element name='blogEntry'> <xsd:complexType> <xsd:sequence> <xsd:element name='date'/> <xsd:element name='author' type='xsd:string'/> <xsd:element name='text'/> <xsd:element ref='blogEntry' minOccurs='0' maxOccurs='unbounded'/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name='blogEntries'> <xsd:complexType> <xsd:sequence minOccurs='0' maxOccurs='unbounded'> <xsd:element name='blogEntry'> <xsd:complexType> <xsd:sequence> <xsd:element name='name' type='xsd:string'/> <xsd:element ref='entry' minOccurs='0' maxOccurs='unbounded'/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
XSD in XML format and impossible to read
name schema in document http://www.w3.org/2001/03/webdata/xsv
- if your xml and xsd files are have Internet-accessible URLs, select “show warnings”
- otherwise you can upload your files to this web site have it will check them from there
http://www.ltg.ed.ac.uk/~ht/xsv-status.html
- downloadable versions (easy for windows)
Validating XML to a Schema
<?xml version='1.0' encoding='utf-8'?> <blogEntrys xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='blog.xsd'> <blogEntry> <id></id> <date></date> <author>Mike</author> ...
AJAX
Asynchronous JavaScript with XML
server / client exchange
- only data
- formatted in XML
JavaScript
- loads XML from server
- uses DOM model for access XML
- displays data using “Dynamic HTML”, creating HTML elements on the fly
- sends updates back in similar way
the old way
- client/server exchange was page at a time
- server formatted data (e.g., into tables)
- identity / type of data elements not know to JavaScript
Talking to the server
create httpRequest object
- IE and result of the world do it differently
function newXMLHttpRequest () { var xmlHttp; if (window.XMLHttpRequest) { // code for Mozilla, etc. xmlHttp=new XMLHttpRequest() } else if (window.ActiveXObject) { // code for IE xmlHttp=new ActiveXObject("Microsoft.XMLHTTP") } else throw 'Your browser does not support AJAX'; ...
add method for getting some XML from server asynchonously
- system calls method assigned to onreadystatechange four times
- as processing of request proceeds: loading, loaded, interactive and complete (=4)
- we call method provided in whenComplete when requests completes
- call whenError if there is an error
function newXMLHttpRequest () { ... xmlHttp.whenStateChanges = function () { if (this.readyState==4) { if (this.status==200 && this.responseXML!=null) { if (this.whenComplete) this.whenComplete (this.responseXML); } else { if (this.whenError) this.whenError (); } } xmlHttp.loadXMLDoc = function (url, whenComplete, whenError) { this.whenComplete = whenComplete; this.whenError = whenError; this.onreadystatechange=this.whenStateChanges; this.open("GET",url,true); this.send(null); }; }
making a call to the server
- specify the URL (an xml file for now) and the “callback” method
function showStudentTable (xml) { // here when XML request completes } function xmlLoadError () { alert ('error loading xml'); } var xmlHttp = newXMLHttpRequest (); xmlHttp.loadXMLDoc ('students.xml',showStudentTable,xmlLoadError);
showing the data in a table (with expand/collapse feature)
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/students1/showStudents0.html
function showStudentTable (xml) { var students = xml.getElementsByTagName('student'); document.write ('<table>'); for (i=0; i<students.length; i++) { document.write ('<tr>'); student = students[i].childNodes; for (j=0; j<student.length; j++) if (student[j].nodeType!=3) { if (student[j].nodeName=='sid') { document.write ('<td onclick="toggleExpand(',i,')">?</td>'); document.write ('<td>'+student[j].firstChild.nodeValue+'</td>'); } else { document.write ('<td name="t',i,'">'+ student[j].firstChild.nodeValue+'</td>'); } } document.write('</tr>'); } document.write ('</table>'); }
create a new document element object
- element = document.createElement (<element-name>)
add element somewhere using appendChild() method
- document.body.appendChild (element);
- anotherElement.appendChild (element);
working with DOM tables
- table = document.createElement (‘table’);
- document.body.appendChild (table);
- row = table.insertRow (rowNum);
- cell = row.insertCell (cellNum);
- cell.appendChild (element);
- cell.innerHTML = “blah blah”;
Creating web page with DOM objects
use helper function to read XML createStudentTable using DOM
Improved example
function getValueByNodeName (nodeList, aNodeName) { for (var i=0; i<nodeList.length; i++) if (nodeList[i].nodeName==aNodeName) return nodeList[i].firstChild? nodeList[i].firstChild.nodeValue : ''; } function createStudentTable (xml) { var students = xml.getElementsByTagName('student'); var table = document.createElement('table'); document.body.appendChild(table); for (var rowNum=0; rowNum<students.length; rowNum++) buildStudentRow (table.insertRow(table.rows.length), students[rowNum].childNodes); }
build student table using DOM
function buildStudentRow (row, student) { row.id = 'row'+row.rowIndex; var cell = row.insertCell (0); var expandButton = document.createElement('a'); expandButton.href = 'javascript: toggleVisibility ("'+row.id+'")'; expandButton.innerHTML = '>'; expandButton.className = 'expandButton'; cell.appendChild (expandButton); var cell = row.insertCell (1); innerTable = document.createElement ('table'); cell.appendChild (innerTable); row.summaryRow = innerTable.insertRow (0); row.summaryRow.style.display = 'block'; row.summaryRow.insertCell(0).innerHTML = getValueByNodeName (student,'sid'); row.summaryRow.insertCell(1).innerHTML = 'summary'; row.detailRow = innerTable.insertRow (1); row.detailRow.style.display = 'none'; row.detailRow.insertCell(0).innerHTML = getValueByNodeName (student,'sid'); row.detailRow.insertCell(1).innerHTML = getValueByNodeName (student,'name'); row.detailRow.insertCell(2).innerHTML = getValueByNodeName (student,'country'); }
toggleVisibility some style
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/students1/showStudents1.html
function toggleVisibility (rowId) { row = document.getElementById (rowId); if (row.summaryRow.style.display == 'none') { row.summaryRow.style.display = ''; row.detailRow.style.display = 'none'; row.cells[0].firstChild.innerHTML = '>'; } else { row.summaryRow.style.display = 'none'; row.detailRow.style.display = ''; row.cells[0].firstChild.innerHTML = 'v'; } }
<style type='text/css'> a.StudentTable_ExpandButton { color: blue; text-decoration: none; font-family: arial; font-weight: bold; } *.StudentTable td { vertical-align: top; } *.StudentTable_Summary { margin-top: -4; color: grey; vertical-align: bottom; font-size: 14; font-style: italic; } *.StudentTable_Detail { background: #cccccc; vertical-align: top; } </style>
http://ws.cs.ubc.ca/~feeley/cics515/code/week4/students2/showStudents.html
Creating XML from MySQL at server
<?php include 'dbconfig.php'; include 'opendb.php'; header ('Content-Type: text/xml'); $rows = mysql_query ("SELECT sid, name, birthCountry FROM student ORDER BY sid"); if ($rows) { $xml_output .= "<?xml version='1.0'?>\n"; $xml_output .= "<students>\n"; while ($row = mysql_fetch_assoc ($rows)) { $xml_output .= "<student>\n"; $xml_output .= "<sid>".$row['sid']."</sid>\n"; $xml_output .= "<name>".$row['name']."</name>\n"; $xml_output .= "<country>".$row['birthCountry']."</country>\n"; $xml_output .= "</student>\n"; } $xml_output .= "</students>\n"; } mysql_close (); echo $xml_output; ?> xmlHttp.loadXMLDoc ('selectAllStudents.php',createStudentTable,xmlLoadError);