IERG4210 (Spring 2021)

User Interface Design II - JavaScript

Sherman Chow

Recall

  • Client-side Languages for User Interface (UI) Design
    • Structure and Content - Hypertext Markup Language (HTML)
    • Presentation - Cascading Style Sheet (CSS)
    • Behavior - Javascript (JS)
  • Covered HTML and CSS in the last lecture; Javascript today!

  • Recent advances in Javascript shifts the paradigm of web programming.
    Rich web applications are nowadays heavy in client-side code.

Agenda

  • The Javascript language itself
    • Basics, Variables
    • Functions, Object-oriented Programming
    • Arrays, Objects
    • Looping over Arrays and Objects with for and while
    • String Concatenation with Array
  • Data Object Model (DOM)
  • Events

JavaScript

Javascript Basics (1/2)

  • Governs the page behavior, to make it interactive
  • Inclusion Methods (Similar to that of CSS):
    • External JS file:
    <script type="text/javascript" src="code.js"></script>
    • Embedded JS code:
      <script type="text/javascript">//<![CDATA[
      // do something here in Javascript
      // ]]></script>
    • Inline JS (say, for an onClick event):
          <input type="button" onclick="alert('Hello! Don't do this!')"
          value="Click Me!" />
  • <script> tags block rendering of subsequent content
    • Put them inside <body> instead of <head> whenever possible
    • (as opposed to CSS)

Detour (to Security)

  • External JS file, Embedded JS code, Inline JS, which one is preferred?
    • (recall: the history of "style" of an HTML file in Lecture 2)
  • Another reason: Content Security Policy (CSP)
    • only execute scripts loaded in source files
      (received from those allowlist-ed domains)
    • ignoring all other scripts
      (including inline scripts and event-handling HTML attributes)
    • an added layer of security
    • helps to detect and mitigate certain types of attacks
    • notably, Cross Site Scripting (XSS) and data injection (stay tuned)
    • You can configure your web server to turn it on.
    • You can also configure it within the <meta> element.
    • https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
  • (If you had taken IERG4130: ) Think about Buffer Overflow attack that overflows the buffer with "data" to be put to the code segment
  • Another somewhat related example: SQL injection attack (later lecture)

Javascript Basics (2/2)

  • An Object-Oriented Scripting Language
    • Interpreted Language - Just-In-Time (JIT) Compilation at browsers
    • Dynamic Typing - Variable types (number/string/boolean/null/undefined) are generally dynamic (Pros and Cons)
    • Syntax - Similar to C, Java
  • You're expected to master in C/C++/Java taught in other courses
  • How to try it out?
    • write an html file
    • console of modern browsers' developer tools

Variables (1/2)

  • Dynamic Typing - The type changes with its assigned value

    var foo = 1;     // (typeof foo) becomes 'number'
    foo = "hello";   // (typeof foo) becomes 'string'
  • Declaration is optional but highly recommended

  • Javascript uses Function Scoping (C uses block-level scoping)
    • Can be very confusing if you get used to block-level scoping!
    • Declaring in a function with var - local to that function
    • Declaring without var - global variable, i.e., under window
    var foo = 1;            // global variable - under window
    window.foo = 1;         // equiv. to the above
    window['foo'] = 1;      // equiv. to the above
    function a() {
      bar = 2; foo = 2;     // global variables
      var yo = 1;           // local variable to function a()
    }

Variables (2/2)

  • More examples on Function Scoping (test/exam material?):

    var foo = 1;
    function a(){
      var bar = 2, foo = 2;
      foo2 = 3;
      return foo;
    }
    
    a() == 2;                  // true
    foo == 1;                  // true
    foo2 == 3;                 // true
    foo2 === '3'               // false: the type-checking part failed
    typeof bar == 'undefined'  // true 
  • === will check if the LHS and RHS are of the same type and value
    (or address for arrays and objects)

Functions

  • Function Declaration Approaches:

    function add(param1, param2) { return param1 + param2; }
    
    var add = function(param1, param2) { return param1 + param2; }
    
    window.add = function(param1, param2) { return param1 + param2; }
  • According to function scoping, the first two approaches can become local, while the last one is declaring a global function.

  • Anonymous function are useful for event listeners:

    function(param1) // I have no name 
    { /* do something here, to be discussed later */ }

Basic Object-Oriented Javascript:

  • Javascript has no syntax like class. It's function. :)

    var Person = function(name, sex){
      this.name = name || 'Unnamed';
      this.gender = (sex && sex == 'F') ? 'F' : 'M';
    };
    Person.prototype.setName = function(name) {return this.name=name};
    Person.prototype.getName = function() {return this.name};
    Person.prototype.getGender = function() {return this.gender};
  • prototype is the interface to add methods to every instance

  • To initialize a new instance and call the methods:

    var p1 = new Person('Peter', 'M'),
        p2 = new Person('Niki', 'F'),
        p3 = new Person();
    p1.getGender() == p3.getGender();            // true
    p3.getName() == 'Unnamed';                   // true
    p3.getName=function() {alert('overridden')}; // overriding method

Variable/Function Names can collide!

  • It is especially problematic under dynamic typing.
  • Trivial Solution: Make the names sufficiently long to avoid collision.
  • Click here to see an example
    function pib_popupPdf(pdfUrl ,windowName, winProperties, query) {
        dcsMultiTrack('DCS.dcsqry',"?fbc=" + query);
        var popWin;
        if(winProperties != null && winProperties.length>0){   
            popWin = window.open(pdfUrl,'popupPdf',winProperties);      
        } else {   
            popWin = window.open(pdfUrl,'popupPdf','');     
        }
        popWin.focus();         
    }

Namespace in Javascript (Advanced Concept)

  • Good Solution: Leverage Function Scoping
  • Group all your things in a single namespace (i.e., variable)
    (function(){
      var myLib = window.myLib = (window.myLib || {});  // global
      var a, b, c;                     // private variables
      var calcSubTotal = function() {  // private function
        // calculate subtotal
      };
      myLib.checkOut = function() {    // public function
        // go to the checkout page
      };
      myLib.addToCart = function(id, quantity) {
        // store it in cookies/localStorage first
        calcSubTotal();
        // display it in the shopping list
      };
    })();
    
    myLib.calcSubTotal();// undefined! as it's a private function
    myLib.addToCart();   // OK!
  • Reading: J. Resig, "Pro Javascript Techniques",
    Chap. 2 "Object-Oriented Javascript"

Arrays

  • Dynamically-sized: Auto extend to have more elements
  • Use as a Stack: methods available: push(), pop()
  • Some useful methods: join(), split(), shift(), indexOf(), etc.

    var x = new Array(), // empty array (note: Array could be overridden)
        y = [],          // empty array (no way to over-ride/-load [])
        z = ['Happy', 'New', 'Year', 2021];
    
      x != y       // true - although both are of empty content
      z.push('!'); // z is ['Happy', 'New', 'Year', 2021, '!']
      z.join(' '); // returns "Happy New Year 2021 !"
      z.indexOf('Year'); // returns 2 - i.e., zero-indexed
      // try z.join(' ').indexOf('year'); by yourself
      // following code returns ['JS', 'is', 'fun']
      // since String is an array of character
      "JS is fun".split(' '); 

Looping over an Array (1/2)

  • for, forEach, map, reduce, filter, ...
  • Given:
    var z = ['Happy', 'New', 'Year', 2021];
  • for loop in the traditional way:
    for (var i = 0; i < z.length; i++) {
      // do something with z[i], 
      // can use break and continue as in C
    }
  • If you like while better:
    var i = 0, length = z.length;
    while(i < length) {
      // do something with z[i],
      // can use break and continue as in C
      i++;
    }

Looping over an Array (2/2)

  • Generally, the fastest way to for-loop over an array

    for (var i = 0, value; value = z[i]; i++) {
      // do something with value
    }
  • New approach to loop (Limited Browser Support):

    z.map(function(value, index){
      // do something with value
    })
    // need "polyfill" for old browsers
    
    
  • If you've already included the jQuery library:

    $(z).each(function(index, value){
      // do something with value
      // return false to break
    })

Objects

  • Dynamically-sized: Auto extend to have more elements
  • Key-Value pairs: Referenced with the key, like a hash table

    var x = new Object(), // empty object
        y = {},           // empty object
        z = {"name":"Niki",
             "today":function(){return new Date().toDateString();}};
    
      x != y;     // true - although both are of empty content
      z.age = 6;  // {"name":"Niki","today":func...,"age":6}
                  // the above order may be browser-dependent
      z.age == z['age']; // true - can reference like array
      z.today();    // returns "Fri Jan 29 2021" for example
  • Looping over an Object

    for (var key in z) {
    // z[key] gives the value, can use break and continue as in C
    }

String Concatenation

  • String Concatenation: + is also overloaded with plus

    var w = 'Hello', x = 'World!', y = 1, z = 2021;
    w+' '+x == 'Hello World!'       // all return true
    w+x+y+z == 'HelloWorld!12021'    
    y+z+w+x == '2022HelloWorld!'    // confusing! exam material?
    w+x+(y+z) == 'HelloWorld!2022' 
  • Joining an Array is Faster: very often you will concat string

    for (var i = 0, data = []; i < 5; i++)      // fast
      data.push(i);
    data.join(' ') == '0 1 2 3 4'   // true
    
    for (var i = 0, data = ''; i < 5; i++)      // slow
      data += i + ' ';
    data == '0 1 2 3 4 '            // true, note the last space

Javascript Debugging

  • Using Development Tools
  • Console Tab: to test out your code
  • Script Tab: to debug your code line-by-line
    • Set breakpoint, watch variable, ...
  • Demo - incl/02-debug.html

Javascript w/ Data Object Model (DOM)

DOM Basics

  • Browsers parse an html file and build a tree-like data structure for it
  • Every <tag> corresponds to a Node Object, including CSS, JavsScript
    Image Source: https://www.w3schools.com/js/js_htmldom.asp

Referencing Elements (1/3)

  • Traditional Approach - getElementById() and getElementsByTagName()
    <ul id="header">
      <li>Hello</li>
      <li>World</li>
    </ul>

    <script type="text/javascript">
    var ul = document.getElementById('header');
    var li = ul.getElementsByTagName('li');
    // note that we used two commands above
    li[0].style.color = '#F00';
    li[1].style.color = '#0F0';
    </script>

Referencing Elements (2/3)

  • Modern Approach - Use the CSS selectors (e.g., li:last-child) with querySelector() and querySelectorAll()
    <ul id="header">
      <li>Hello</li>
      <li>World</li>
    </ul>

    <script type="text/javascript">
    var li = document.querySelectorAll('#header li');
    li[0].style.color = '#F00';
    li[1].style.color = '#0F0';
    //re-color the second <li> to #00F
    document.querySelector(
      '#header li:last-child').style.color = '#00F';
    </script>

Referencing Elements (3/3)

  • DOM Navigation (seldomly used nowadays) -
    .parentNode, .childNodes, .nextSibling, etc.
Image Source: J. Resig, "Pro Javascript Techniques", p.90

Referencing Special Elements

  • Some popular shorthands:
    • document.head for <head>
    • document.body for <body>
  • Referencing forms (next week):
    • document.forms[n] for the n-th child <form>
  • Referencing links:
  • Referencing frames (has some security implication, stay tuned):
    • document.frames[n] for the n-th child <frame> and <iframe>
    • Inside a frame
      • parent refers to the immediate parent window
      • top refers to the highest parent window that its URL is reflected in the browser location bar

Common DOM Methods (1/3)

  • Changing Content / Adding New Elements
      // assuming 'el' is an HTML DOM element
    el.innerHTML = 'Your Current Time: ' + (new Date().toString());
  • Cross-Site Scripting (XSS) Attack:
        // What if the string is untrusted (i.e., from the "user")??
      el.innerHTML = 'something <img onerror="alert(\'DANGER\')" />'
  • How to prevent malicious code injection? (more later)
      //the "/&/g" thing is an example of "regular expression"
      el.innerHTML = 'something <img onerror="alert(\'DANGER\')" />'
     .replace(/&/g,'&amp;').replace(/</g,'&lt;')
     .replace(/>/g,'&gt;');
  • To wrap the above code into a reusable function
     String.prototype.escapeHTML = function(){
     return this.replace(/&/g,'&amp;').replace(/</g,'&lt;')
            .replace(/>/g,'&gt;');}
     el.innerHTML = userInput.escapeHTML(); //usable thereafter

Common DOM Methods (2/3)

  • Adding New Elements (DOM-based)
    // To dynamically load a javascript file if needed
    var script = document.createElement('script');
    script.src = "dynamically-loaded.js";
    script.type = "text/javascript";
    // to add the script file as last child of document.body
    document.body.appendChild(script);
    // or, to add as the first child of document.body
    document.body.insertBefore(script, document.body.firstChild)
    • So, a few lines of code (LOC) can introduce an external file that has thousand LOC.
    • Exercise: How to extend an <ul> list? (Recall: use <li>)
  • Removing Elements (DOM-based)
    document.body.removeChild(script);
    //to remove every child of element el
    function(el){while(el.firstChild){el.removeChild(el.firstChild)}

Common DOM Methods (3/3)

  • Changing Style Attribute (not recommended, as explained)
    el.style.color = '#F00';
  • Changing Class Attribute (preferred)
    - to re-style an element and its childs if any
    
    // Assuming newClass has been defined elsewhere (i.e., the CSS file)
    el.className = 'newClass';
    // Modern browsers support the classLits below
    el.classList = 'newClass 2';
    
  • Changing the Current Location - apply to the current window/frame
     window.location.replace('test2.html'); // redirect to test2.html
     window.history.go(-1);                 // back

Javascript Events

Events

  • An element generates events that reflect its current status,

  • which can be registered with event listening callback functions that respond accordingly.
<p>Hello, Click Me!</p>

<script type="text/javascript">
// assign a function to onclick handler
document.querySelector('p').onclick = function(e){
  // display two simple popup dialogs
  alert('You clicked hello!');
  alert(e.target.innerHTML);
}
</script>

About Events

  • Asynchronous - Events are fired out of order
  • Non-threaded - Events get queued and fired one at a time
  • Some common types:
    • Mouse: click, mouseover, mouseout, dragstart*, drop*
    • Keyboard: keydown, keypress, keyup
    • Touchscreen: touchstart*, touchmove*, touchend*
    • Form/Input/Select: submit, change, focus, blur
    • Un/Loading: load, beforeunload, error, readystatechange
    • Timer: setTimeout(), setTimeInterval()
    • * denotes HTML5 new events

More on Event Types: https://www.w3schools.com/TAGS/ref_eventattributes.asp

Event Phases (W3C Model)

  • Event propagates over the hierarchical chain of an element, going through the capturing, target, and bubbling phases.

Image Source: J. Resig, "Pro Javascript Techniques", p.114, 2007

Event Phases (W3C Model) (cont.)

  • Consider the target element <a href="/">Home</a> is clicked:
    • Event listeners with capturing order: <body>, <div>, <ul>, <li>, <a>
    • Event listeners with bubbling order: <a>, <li>, <ul>, <div>, <body>

    • (Events in IE 8 or lower only bubbles, and cannot be captured)

Event Listeners at Bubbling Phase

<p id="el_p"> <em> 
  <a href="test1.html" id="el_a">Click Me!</a> </em> </p>
<script type="text/javascript">
var clickMe = function(e){
  e = e || window.event;
  // IE-specifc way of passing the event
  alert('e.target.id:'+e.target.id +', this.id:'+ this.id);},

el_p = document.getElementById('el_p'),
el_a = document.getElementById('el_a');
el_p.onclick = clickMe;
el_a.onclick = clickMe;

// Expected Results:
// First alert: e.target.id: el_a, this.id: el_a
// Second alert: e.target.id: el_a, this.id: el_p
</script>
  • e.target always refers to the target
  • while this refers to the one handling the event

Event Listeners at Capturing Phase

<p id="el_p"> <em>
    <a href="test1.html" id="el_a">Click Me!</a> </em> </p>
<script type="text/javascript">
var clickMe = function(e){
  alert('e.target.id:'+e.target.id +', this.id:'+ this.id);},

el_p = document.getElementById('el_p'),
el_a = document.getElementById('el_a');
el_p.addEventListener("click", clickMe, true);
el_a.addEventListener("click", clickMe, true);

// Expected Results:
//  First alert: e.target.id: el_a, this.id: el_p
// Second alert: e.target.id: el_a, this.id: el_a
</script>
  • The last argument of addEventListener turns capturing order on
  • In event CAPTURING order: root ancestor first
  • so <p> now handles the event before <a>

Prevent Default Action: preventDefault()

<p id="el_p"> <em>
    <a href="test1.html" id="el_a">Click Me!</a> </em> </p>
<script type="text/javascript">
var clickMe = function(e){
  e = e || window.event;
  e.preventDefault();   // for W3C standard
  return false;},       // for IE 8 or below

el_p = document.getElementById('el_p'),
el_a = document.getElementById('el_a');
el_a.onclick = clickMe;

// Expected Results:
//   No page navigation when clicked
</script>
  • The default action, page navigation to test1.html, is prevented.
  • This is important to stop a form submission (i.e., stopping submit event) if it is not properly validated!!

Stop Propagation: stopPropagation()

<p id="el_p"> <em>
    <a href="test1.html" id="el_a">Click Me!</a> </em> </p>
<script type="text/javascript">
var clickMe = function(e){
  e = e || window.event;
  alert(this.id);
  e.stopPropagation();   // for W3C standard
  e.cancelBubble = true; }, // for IE 8 or below
el_p = document.getElementById('el_p'),
el_a = document.getElementById('el_a');
el_a.onclick = clickMe;  // <a> first as bubbling
el_p.onclick = clickMe;
// Result: One alert appears and displays el_a,
//         then page navigation occurs
</script>
  • Event propagation stopped at el_a, so el_p will not receive the event
  • Note: <iframe>/<frame> will implicitly block event from propagating
  • Note: We can avoid this even for overlapping clickable elements, even if each of which is not an ancestor of the others.

Binding Event Listeners

  • Traditional Approach (Cross-browser support, Bubbling)
    el.onclick = function(e) {
     e = e || window.event; // IE passes the event in global window
     alert(e.target); // e.target is a reference to target element
    }
  • W3C Standard (Supported in Firefox, WebKit, IE 9, etc.)
        el.addEventListener("click", function(e) {
          alert(e.target);
        }, false); // false for bubbling, true for capturing
  • IE 8 or below provides attachEvent()

Binding Event Listeners - Traditional or W3C?

Which is preferred, traditional or W3C? Pros and Cons?

  • Traditional is universally supported across browsers, yet suffers from the overriding problem:
el.onclick = function(e) {/* given that you did something with el */};
// BAD colleage can "append" the following code, e.g., in a .js file (library) you load
el.onclick = function(e) {/* which will override your code */};
// SMART colleage can do complex things leveraging *function scoping*:
(function(){    // Concept of Closure, 02-reading OO JS, p.27-30
  var _onclick = el.onclick; //_onlick now is a local variable
  el.onclick = function(e){
    // execute your handler first if it exists
    _onclick  // AND logic: the second predicate only matters if the first one is true
      && _onclick.call(this, e);  // preserve the event object
    // .call assigns its first argument to be the "this" or it is lost (becomes "window")
    /* can now do his things */
  }   })();  // execute the annonymous function itself
  • W3C event handlers supports event capturing and cannot be overriden, yet it is not supported by IE non-standard compilant browsers

More in the reading - J. Resig, "Pro Javascript Techniques", p.123, 2007

Reading Materials: Javascript Reference