Client-Side Processing

Peter Wood

Client-side processing

Client-side scripting

JavaScript

Document Object Model (DOM)

Document methods and properties

Events

Calling a function

Defining a function

User input

Comments on previous script

Event handlers

  • the value of an onclick attribute is an event handler
  • rather than embedding the event handler code in the HTML, it is preferable to
    • bind the onclick event to the button, and
    • associate a handler function with the event
    in Javascript code separate from the HTML
  • now the code for the button (which we need to be able to identify) is:
    <button type="button" id="hello-button">Click to produce message</button>
    
  • the code for binding onclick to the button and associating an event handler function is:
    document.getElementById("hello-button").onclick = helloAgain;
    
  • note that helloAgain is simply the name of the function to be called

Event handlers example

  • one problem is that the element with id hello-button is only available after the page has loaded
  • so we need to delay binding the onclick event to the button, as follows:
    window.onload = function() {
                      document.getElementById("hello-button").onclick = helloAgain; }
    
  • this binds the onload event to the window object
  • it also associates an anonymous function with the event
  • when button is clicked, the event handler calls the user-defined function helloAgain
  • this time the empty li element has id="hello-again"
  • function helloAgain is defined as follows:
    function helloAgain() {
      // Write "Hello again world!" in the element identified by "hello-again"
      document.getElementById("hello-again").innerHTML = "Hello again world!";
    }
    

Functions and form fields

  • the above was generated by the following:
    <form>
      <label>Enter a word:</label>
      <input type="text" id="myWord" />
      <input type="button" value="Translate"
            onClick="myResult.value=myTranslate(myWord.value)" />
      <input type="text" id="myResult" />
    </form>
    
  • the form element indicates an HTML form
  • an HTML form is usually used for submitting information to a server, but not here
  • the input element (with type="text") creates a single-line textbox
  • the input element (with type="button") creates a button with the given value displayed on it
  • the onClick attribute value includes a call to the user-defined function myTranslate (next slide)
  • myWord and myResult refer to the identified input elements of the form
  • value refers to the contents of the identified input elements

Defining the function myTranslate

  • function myTranslate is defined (in client.js) as follows:
    function myTranslate(word) {
       if (word === "hello")
          return("buongiorno");
       else if (word === "goodbye")
          return("arrivederci");
       else
          return("unknown");
    }
    
  • word contains the word the user entered
  • JavaScript has two equality operators: == and ===
  • == tests if two values are equal (possibly after type coercion)
  • === tests if both the types and values are equal, so is considered safer
  • e.g., (1 == true) is true, while (1 === true) is false

Alternative solutions

  • we could use an anonymous function instead:
    <form>
      <label>Enter a word:</label>
      <input type="text" id="word" />
      <input type="button" value="Translate"
            onClick="result.value=function (w) {
            if (w === 'hello')
               return('buongiorno');
            else if (w === 'goodbye')
               return('arrivederci');
            else
               return('unknown');
         }(word.value)" />
      <input type="text" id="result" />
    </form>
    
  • an anonymous function taking one parameter w is defined
  • and immediately invoked with argument word.value
  • we could even do away with the function by using the ternary ? operator:
    result.value = (word.value==='hello') ? 'buongiorno' :
          ((word.value==='goodbye') ? 'arrivederci' :'unknown')
    

Adding elements

  • the button is defined as follows:
    <button type="button" onclick="addElement()">
      Add li element
    </button>
    
  • the ul element on this slide is identified by id="target1"
  • function addElement is defined as follows (in client.js):
    function addElement() {
      var elem = document.getElementById("target1");
      var node = document.createElement("li");
      var text = document.createTextNode("Hello");
      node.appendChild(text);
      elem.appendChild(node);
    }
    
    which appends a new li element to the identified ul element
  • createElement is a method which creates an element with the given name
  • createTextNode is a method which creates a text node with the given value
  • appendChild is a method of a node; it appends the given node to the list of the node's children

Deleting elements

  • the button is defined as follows:
    <button type="button" onclick="deleteElement()">
      Delete ul element
    </button>
    
  • the ul element on this slide is identified by id="target2"
  • function deleteElement is defined as follows (in client.js):
    function deleteElement() {
      var elem = document.getElementById("target2");
      elem.parentNode.removeChild(elem);
    }
    
    which deletes the identified ul element
  • removeChild is a method of a node; it removes the given node from the list of the node's children

Using jQuery

  • jQuery is a popular JavaScript library which simplifies DOM operations
  • the jQuery file jquery-1.10.2.min.js is referenced by these pages
  • instead of the call to deleteElement, we could use
    onclick="$('#target2').remove()"
    
  • jQuery defines the function named $
    • which can take a CSS selector as an argument
    • and returns the collection of elements selected
  • the remove method deletes the elements on which it is invoked

Using jQuery to add elements

  • instead of the call to addElement, we could use
    onclick="$('#target1').append($('<li />', {'text':'Hello'}))"
    
  • the append method adds a new last child to elements on which it is invoked
  • the function $ creates a new element when passed a string representing an empty element as first argument
  • the second argument is an object comprising property-value pairs:
    • the text property is interpreted as the textual contents of the element
    • other properties are interpreted as attribute names

RSS Example

  • recall the RSS fragment whose structure is as follows
    <rss>
      <channel>
        <title> ... </title>
          ...
        <item>
          <title> ... </title>
          <description> ... </description>
          <link> ... </link>
          <pubDate> ... </pubDate>
        </item>
          ...
        <item>
          <title> ... </title>
          <description> ... </description>
          <link> ... </link>
          <pubDate> ... </pubDate>
        </item>
      <channel>
    </rss>
    

DOM and XML

  • JavaScript can use DOM objects, properties and methods to read and navigate through an XML document like rss-fragment.xml
  • uses the XML parser built into the browser to construct a DOM tree
  • the following:
  • was generated by the following script
    <script type="text/javascript">
      document.write("Document element is: <em>",
               getXML("rss-fragment.xml").documentElement.nodeName,
               "</em>");
    </script>
    
  • calls user-defined getXML function (on next slide) to read rss-fragment.xml
  • documentElement is a property of a document; it returns the document (root) element of the document
  • nodeName is a property of a node; it returns the name of the node

Function to load an XML file (using jQuery)

function getXML(myUrl) {
  var xhr = $.ajax({
              url:      myUrl,
              datatype: "xml",
              async:    false
              });
  return xhr.responseXML;
}
  • this uses the ajax method of jQuery
  • AJAX allows you to retrieve an XML file from the same domain as the loaded page
  • the method takes an object as argument:
    • the first property is the URL
    • the second property is the type of the data retrieved (XML, JSON, etc)
    • the third property specifies whether the script executes asynchronously (true) with the request or not (false)
  • the method returns an XMLHttpRequest object (see later)
  • the DOM document is made available using the responseXML property

Function to load an XML file (using DOM)

  • using standard DOM, the getXML function might be defined as follows:
    function getXML(url) {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", url, false);
      xhr.send(null);
      return xhr.responseXML;
    }
    
  • first create an XMLHttpRequest object (built-in to browsers)
  • the open method sets up an HTTP request (see later):
    • the first argument is an HTTP method (GET in this case)
    • the second argument is the URL of the XML document
    • the third argument specifies whether the script executes asynchronously (true) with the request or not (false)
  • the send method sends the HTTP request
  • note there is no error checking in the above code; it should work on recent versions of Firefox, Chrome, IE and Safari

Navigating XML

  • each DOM node object has a number of properties:
    • nodeName (as seen previously)
    • firstChild
    • nextSibling
    • parentNode
    • childNodes
    which return, respectively, the first child, next sibling, parent and all children of the node
  • these properties can be used to navigate around a DOM document, starting from the documentElement

Finding elements by name

  • say we want to output the value of all title elements in the RSS fragment as table rows
  • using a script inside a table element gives:
  • the script could use nested for loops along with the childNodes property of a node
  • a simpler way uses the DOM getElementsByTagName method (next slide)
  • we can also use jQuery (slide after next)

Finding elements by name (standard DOM)

  • we can use the following script:
    var xmlDoc = getXML("rss-fragment.xml");
    var titleElements = xmlDoc.getElementsByTagName("title");
    for ( i = 0; i < titleElements.length; i++ )
       document.write("<tr><td>",
          titleElements[i].firstChild.nodeValue, "</td></tr>");
    
  • getElementsByTagName returns a collection of nodes
  • length is a property of a collection; it returns the number of items in the collection
  • the i'th item in a collection can be retrieved using item(i) as a method call or using array-like indexing
  • nodeValue is a property of a node; it returns the text value of the node if it is a text node, otherwise it returns null
  • an element with textual contents is represented by an element node having a single text node as a child

Finding elements by name (jQuery)

  • using jQuery, this could be done as follows:
    var xmlDoc = getXML("rss-fragment.xml");
    $(xmlDoc).find("title")
             .each(function() {
                    document.write("<tr><td>",
                                   $(this).text(),
                                   "</td></tr>");
               });
    
  • the jQuery $ function can be passed a DOM document
  • find searches through descendants of each of a set of elements and returns a jQuery object containing a collection of elements matched by the selector
  • each iterates over a set of elements; the function it takes as argument is called for each element
  • this returns the element on which the function is called
  • text returns the textual contents of an element

Applying a stylesheet

  • we can transform a document using a stylesheet in the browser as follows:
    var xmlDoc = getXML("rss-fragment.xml");
    var stylesheet = getXML("rss-headlines.xsl");
    if (typeof (XSLTProcessor) != "undefined") {
      var processor = new XSLTProcessor();
      processor.importStylesheet(stylesheet);
      var result = processor.transformToFragment(xmlDoc, document);
      document.getElementById("target3").appendChild(result);
    } else
      window.alert("Your browser does not support the XSLTProcessor object");
    
  • which produces
  • target3 is the value of id attribute of the list item above
  • rss-headlines.xsl is here
  • note that the stylesheet does not output html tags

Applying a stylesheet (comments)

  • the XSLTProcessor object is supported in Firefox, Chrome, Safari, but not IE
  • importStylesheet is used to specify the stylesheet for the XSLT processor to use
  • transformToFragment applies the transformation specified by the imported stylesheet to the document (node) given as first argument; the second argument is the document that will "own" the resulting fragment
  • transformToDocument also exists and takes a single argument
  • these produce DOM document fragments, hence the need to use appendChild
  • we could use GetXmlStringFromXmlDoc to produce text and use innerHTML instead

Modifying a stylesheet using jQuery

  • we can modify a stylesheet programmatically
  • say we want the value of description elements rather than title elements
    • load the stylesheet
    • find the value-of element in the stylesheet
    • set the value of its select attribute to description (overwriting title)
  • this produces
  • code is on the next slide

Modifying a stylesheet (source code)

  var xmlDoc = getXML("rss-fragment.xml");
  var stylesheet = getXML("rss-headlines.xsl");
  $(stylesheet).find("xsl\\:value-of, value-of")
               .first()
               .attr("select","description");
  if (typeof (XSLTProcessor) != "undefined") {
    ...
    document.getElementById("target4")....
  } else {
    window.alert("Your browser ...");
  }
  • we need to escape the : in xsl:value-of using \\
  • Firefox works with namespace prefixes while Chrome and Safari do not, hence the multiple selector
  • first method returns a jQuery object comprising the first item in a collection
  • attr method with two arguments sets the value of the attribute given by the first argument to be that given by the second
    • with one argument, it returns the value of the attribute
  • target4 is the value of the id attribute of the list item on the previous slide
  • using standard DOM rather than jQuery is rather more complicated (because of namespace prefixes in the stylesheet)

Exercises

  1. Implement the following functions:
    1. Function celsius returns the Celsius equivalent of a Fahrenheit temperature using the calculation C= 5.0/9.0 * (F-32)
    2. Function fahrenheit returns the Fahrenheit equivalent of a Celsius temperature using the calculation F = 9.0/5.0 * C + 32
  2. Use these functions to write a script that enables the user to enter either a Fahrenheit temperature and display the Celsius equivalent or enter a Celsius temperature and display the Fahrenheit equivalent. Your HTML document should contain two buttons, one to initiate the conversion from Fahrenheit to Celsius and one to initiate the conversion from Celsius to Fahrenheit.
  3. As suggested on slide 26, write a script to retrieve the values of all title elements from rss-fragment.xml using nested for loops.

Links to more information

There are many books devoted to Javascript and/or jQuery. DOM is covered in Chapter 7 of [Moller and Schwartzbach] and Chapter 8 of [Jacobs]. See above for a book on jQuery.